@startsimpli/ui 0.1.7 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,19 @@
1
+ export { GanttChart } from './GanttChart'
2
+ export type {
3
+ TimelineItem,
4
+ TimelineDependency,
5
+ GanttTask,
6
+ GanttChartProps,
7
+ GanttFilterState,
8
+ GanttViewMode,
9
+ HealthStatus,
10
+ } from './types'
11
+ export {
12
+ calculateExpectedProgress,
13
+ calculateHealthStatus,
14
+ getHealthColor,
15
+ } from './lib/progress'
16
+ export {
17
+ parseDateRangeFromTitle,
18
+ getHierarchyLevel,
19
+ } from './lib/dates'
@@ -0,0 +1,61 @@
1
+ import { startOfYear, endOfYear, startOfMonth, endOfMonth, addYears } from 'date-fns'
2
+
3
+ const MONTHS: Record<string, number> = {
4
+ JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5,
5
+ JUL: 6, AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11,
6
+ }
7
+
8
+ export function parseDateRangeFromTitle(
9
+ title: string,
10
+ referenceYear: number = new Date().getFullYear()
11
+ ): { start: Date; end: Date } | null {
12
+ const match = title.match(/^\[([^\]]+)\]/)
13
+ if (!match) return null
14
+
15
+ const tag = match[1].toUpperCase()
16
+ const baseDate = new Date(referenceYear, 0, 1)
17
+
18
+ const yearMatch = tag.match(/^(\d+)YR$/)
19
+ if (yearMatch) {
20
+ const years = parseInt(yearMatch[1])
21
+ return { start: startOfYear(baseDate), end: endOfYear(addYears(baseDate, years - 1)) }
22
+ }
23
+
24
+ if (tag === 'H1') return { start: new Date(referenceYear, 0, 1), end: new Date(referenceYear, 5, 30) }
25
+ if (tag === 'H2') return { start: new Date(referenceYear, 6, 1), end: new Date(referenceYear, 11, 31) }
26
+
27
+ const quarterMatch = tag.match(/^Q([1-4])$/)
28
+ if (quarterMatch) {
29
+ const q = parseInt(quarterMatch[1])
30
+ const startMonth = (q - 1) * 3
31
+ return { start: new Date(referenceYear, startMonth, 1), end: endOfMonth(new Date(referenceYear, startMonth + 2, 1)) }
32
+ }
33
+
34
+ if (tag in MONTHS) {
35
+ const month = MONTHS[tag]
36
+ return { start: startOfMonth(new Date(referenceYear, month, 1)), end: endOfMonth(new Date(referenceYear, month, 1)) }
37
+ }
38
+
39
+ const weekMatch = tag.match(/^W(\d+)$/)
40
+ if (weekMatch) {
41
+ const week = parseInt(weekMatch[1])
42
+ const start = new Date(referenceYear, 0, 1 + (week - 1) * 7)
43
+ const end = new Date(start)
44
+ end.setDate(end.getDate() + 6)
45
+ return { start, end }
46
+ }
47
+
48
+ return null
49
+ }
50
+
51
+ export function getHierarchyLevel(title: string): number {
52
+ const match = title.match(/^\[([^\]]+)\]/)
53
+ if (!match) return 0
54
+ const tag = match[1].toUpperCase()
55
+ if (/^\d+YR$/.test(tag)) return 100 + parseInt(tag)
56
+ if (/^H[12]$/.test(tag)) return 50
57
+ if (/^Q[1-4]$/.test(tag)) return 40
58
+ if (tag in MONTHS) return 30
59
+ if (/^W\d+$/.test(tag)) return 20
60
+ return 10
61
+ }
@@ -0,0 +1,39 @@
1
+ import type { HealthStatus } from '../types'
2
+
3
+ export interface HealthStatusResult {
4
+ status: HealthStatus
5
+ reason?: string
6
+ }
7
+
8
+ export function calculateExpectedProgress(
9
+ startDate: Date,
10
+ endDate: Date,
11
+ currentDate: Date = new Date()
12
+ ): number {
13
+ if (currentDate <= startDate) return 0
14
+ if (currentDate >= endDate) return 100
15
+ const totalDuration = endDate.getTime() - startDate.getTime()
16
+ const elapsed = currentDate.getTime() - startDate.getTime()
17
+ return Math.round((elapsed / totalDuration) * 100)
18
+ }
19
+
20
+ export function calculateHealthStatus(
21
+ progress: number,
22
+ expectedProgress: number,
23
+ hasBlockers: boolean = false
24
+ ): HealthStatusResult {
25
+ if (hasBlockers) return { status: 'blocked', reason: 'Has active blockers' }
26
+ if (progress === 0 && expectedProgress > 0) return { status: 'not_started', reason: 'Work has not started' }
27
+ if (progress - expectedProgress < -10) return { status: 'at_risk', reason: 'Behind schedule' }
28
+ return { status: 'on_track' }
29
+ }
30
+
31
+ export function getHealthColor(status: HealthStatus): string {
32
+ const colors: Record<HealthStatus, string> = {
33
+ on_track: '#22c55e',
34
+ at_risk: '#eab308',
35
+ blocked: '#ef4444',
36
+ not_started: '#9ca3af',
37
+ }
38
+ return colors[status]
39
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Generic timeline item interface.
3
+ * Consumers map their domain models (Goals, Beads, etc.) to this shape.
4
+ */
5
+ export interface TimelineItem {
6
+ id: string
7
+ title: string
8
+ description?: string
9
+ status: string
10
+ category?: string
11
+ progress?: number // 0-100
12
+ start_date?: string | null
13
+ end_date?: string | null
14
+ created_at?: string
15
+ children?: TimelineItem[]
16
+ }
17
+
18
+ /**
19
+ * Dependency between two timeline items.
20
+ */
21
+ export interface TimelineDependency {
22
+ from_id: string
23
+ to_id: string
24
+ type?: string
25
+ }
26
+
27
+ export type HealthStatus = 'on_track' | 'at_risk' | 'blocked' | 'not_started'
28
+
29
+ export interface GanttTask {
30
+ item: TimelineItem
31
+ start: Date
32
+ end: Date
33
+ progress: number
34
+ timeProgress: number
35
+ depth: number
36
+ hasChildren: boolean
37
+ parentId: string | null
38
+ healthStatus: HealthStatus
39
+ }
40
+
41
+ export interface GanttFilterState {
42
+ search: string
43
+ statuses: string[]
44
+ categories: string[]
45
+ dateRange: { start: Date | null; end: Date | null }
46
+ }
47
+
48
+ export type GanttViewMode = 'timeline' | 'board' | 'list'
49
+
50
+ export interface GanttChartProps {
51
+ /** Items to display on the timeline */
52
+ items: TimelineItem[]
53
+ /** Optional dependencies between items */
54
+ dependencies?: TimelineDependency[]
55
+ /** Callback when an item is clicked */
56
+ onItemClick?: (item: TimelineItem) => void
57
+ /** Callback when dates are changed via drag */
58
+ onDateChange?: (id: string, startDate: Date, endDate: Date) => void
59
+ /** Callback when an item is edited via the detail modal. Return updated item or void. */
60
+ onItemEdit?: (id: string, changes: Partial<TimelineItem>) => void
61
+ /** Callback when item status changes (e.g. board drag-and-drop) */
62
+ onStatusChange?: (id: string, newStatus: string) => void
63
+ /** Map a status string to a progress value (0-100) */
64
+ statusToProgress?: (status: string) => number
65
+ /** Map a category string to a bar color */
66
+ categoryColors?: Record<string, string>
67
+ /** Whether items have hierarchy via children[] arrays. Default: true */
68
+ hierarchical?: boolean
69
+ /** Initial zoom index (0-5). Default: 3 */
70
+ initialZoom?: number
71
+ /** Custom CSS class for the wrapper */
72
+ className?: string
73
+ /** Info column header label. Default: 'Item' */
74
+ infoColumnLabel?: string
75
+ /** Info column width in px. Default: 320 */
76
+ infoColumnWidth?: number
77
+ /** Show category badge in info column. Default: true */
78
+ showCategory?: boolean
79
+ /** Show status badge in info column. Default: true */
80
+ showStatus?: boolean
81
+ /** Show filter bar above the gantt. Default: false */
82
+ showFilterBar?: boolean
83
+ /** Show fullscreen toggle button. Default: false */
84
+ showFullscreen?: boolean
85
+ /** Show view mode switcher (timeline/board/list). Default: false */
86
+ showViewSwitcher?: boolean
87
+ /** Initial view mode. Default: 'timeline' */
88
+ initialViewMode?: GanttViewMode
89
+ /** Available statuses for the board view columns. If not provided, extracted from items. */
90
+ boardStatuses?: string[]
91
+ /** localStorage key for persisting collapse state. If set, collapse state is saved/restored. */
92
+ persistCollapseKey?: string
93
+ }
@@ -1,3 +1,3 @@
1
- import type { Config } from 'tailwindcss'
2
- declare const config: Config
3
- export default config
1
+ import type { Config } from 'tailwindcss';
2
+ declare const config: Config;
3
+ export default config;