Skip to content

Commit 5bb9b2f

Browse files
Redesign Gant charts and fix issues (#11058)
* Redesign Gant charts and fix issues Signed-off-by: Artem Savchenko <armisav@gmail.com> * Fix svelte check Signed-off-by: Artem Savchenko <armisav@gmail.com> * Fix review comments Signed-off-by: Artem Savchenko <armisav@gmail.com> * Add missing translations Signed-off-by: Artem Savchenko <armisav@gmail.com> --------- Signed-off-by: Artem Savchenko <armisav@gmail.com>
1 parent d10b0a1 commit 5bb9b2f

42 files changed

Lines changed: 1182 additions & 357 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎foundations/utils/packages/platform-rig/bin/do-svelte-check.js‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ const { readFileSync, existsSync, mkdirSync, createWriteStream } = require('fs')
33
const { spawn } = require('child_process')
44

55
function parseSvelteCheckLog(logContent) {
6-
const lines = logContent.split('\n')
6+
// Human output includes ANSI colors, even when redirected to a log file.
7+
// Strip them before looking for "Error:" so failed checks report the errors.
8+
const lines = logContent.replace(/\x1b\[[0-9;]*m/g, '').split('\n')
79
const errors = []
810
let currentError = null
911

‎packages/gantt/src/__tests__/time-scale.test.ts‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,29 @@ describe('time-scale — secondary label (year/month supra row)', () => {
141141
void DAY
142142
})
143143
})
144+
145+
describe('time-scale locale', () => {
146+
const origin = Date.UTC(2026, 0, 1)
147+
148+
it.each(['en', 'ru', 'de', 'ja'])('formats day and month labels using %s', (locale) => {
149+
const expected = new Intl.DateTimeFormat(locale, { month: 'short', timeZone: 'UTC' }).format(origin)
150+
expect(createTimeScale('day', origin, undefined, { locale }).ticks([origin, origin])[0].secondaryLabel).toBe(
151+
expected
152+
)
153+
expect(createTimeScale('month', origin, undefined, { locale }).ticks([origin, origin])[0].label).toBe(expected)
154+
})
155+
156+
it('accepts translated week and quarter labels without changing geometry', () => {
157+
const labels = {
158+
locale: 'ru',
159+
weekLabel: (week: number) => `Нед. ${week}`,
160+
quarterLabel: (quarter: number) => `Кв. ${quarter}`
161+
}
162+
const week = createTimeScale('week', origin, 14, labels)
163+
const first = week.ticks([origin, origin + 7 * DAY_MS])[0]
164+
expect(first.label).toBe('Нед. 2')
165+
expect(first.date).toBe(Date.UTC(2026, 0, 5))
166+
expect(week.toX(first.date)).toBe(56)
167+
expect(createTimeScale('quarter', origin, undefined, labels).ticks([origin, origin])[0].label).toBe('Кв. 1')
168+
})
169+
})

‎packages/gantt/src/__tests__/toolbar-overflow.test.ts‎

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const ALL: ToolbarTier[] = [...TOOLBAR_TIERS]
1616
// One easy-to-reason-about width per tier.
1717
const W: Record<ToolbarTier, number> = {
1818
group: 100,
19+
color: 80,
1920
nav: 200,
2021
date: 150,
2122
zoom: 120,
@@ -39,24 +40,31 @@ describe('computeToolbarOverflow', () => {
3940
})
4041

4142
it('keeps everything inline when it fits exactly', () => {
42-
// 100+200+150+120+60+90 = 720, plus five gaps = 740
43-
const r = computeToolbarOverflow(ALL, W, 740, GAP, MORE)
43+
// 100+80+200+150+120+60+90 = 800, plus six gaps = 824
44+
const r = computeToolbarOverflow(ALL, W, 824, GAP, MORE)
4445
expect(r.hidden).toEqual([])
4546
expect(r.visible).toEqual(ALL)
4647
})
4748

4849
it('collapses the least important tier first', () => {
49-
const r = computeToolbarOverflow(ALL, W, 739, GAP, MORE)
50+
const r = computeToolbarOverflow(ALL, W, 823, GAP, MORE)
5051
expect(r.hidden).toEqual(['savedview'])
51-
expect(r.visible).toEqual(['group', 'nav', 'date', 'zoom', 'undo'])
52+
expect(r.visible).toEqual(['group', 'color', 'nav', 'date', 'zoom', 'undo'])
5253
})
5354

5455
it('collapses further tiers in TOOLBAR_COLLAPSE_ORDER as space shrinks', () => {
55-
expect(computeToolbarOverflow(ALL, W, 600, GAP, MORE).hidden).toEqual(['date', 'savedview'])
56-
expect(computeToolbarOverflow(ALL, W, 450, GAP, MORE).hidden).toEqual(['group', 'date', 'savedview'])
57-
expect(computeToolbarOverflow(ALL, W, 400, GAP, MORE).hidden).toEqual(['group', 'date', 'undo', 'savedview'])
56+
expect(computeToolbarOverflow(ALL, W, 600, GAP, MORE).hidden).toEqual(['color', 'date', 'savedview'])
57+
expect(computeToolbarOverflow(ALL, W, 450, GAP, MORE).hidden).toEqual(['group', 'color', 'date', 'savedview'])
58+
expect(computeToolbarOverflow(ALL, W, 400, GAP, MORE).hidden).toEqual([
59+
'group',
60+
'color',
61+
'date',
62+
'undo',
63+
'savedview'
64+
])
5865
expect(computeToolbarOverflow(ALL, W, 300, GAP, MORE).hidden).toEqual([
5966
'group',
67+
'color',
6068
'date',
6169
'zoom',
6270
'undo',
@@ -71,7 +79,7 @@ describe('computeToolbarOverflow', () => {
7179
})
7280

7381
it('never drops a tier — visible + hidden is always the input set', () => {
74-
for (const available of [-1, 0, 30, 120, 300, 500, 740, 2000]) {
82+
for (const available of [-1, 0, 30, 120, 300, 500, 824, 2000]) {
7583
const r = computeToolbarOverflow(ALL, W, available, GAP, MORE)
7684
expect([...r.visible, ...r.hidden].sort()).toEqual([...ALL].sort())
7785
}
@@ -83,6 +91,13 @@ describe('computeToolbarOverflow', () => {
8391
expect(r.hidden).toEqual(ALL.filter((t) => r.hidden.includes(t)))
8492
})
8593

94+
it('keeps grouping inline when only one of the two view controls fits', () => {
95+
const present: ToolbarTier[] = ['group', 'color', 'nav', 'zoom', 'undo']
96+
const r = computeToolbarOverflow(present, W, 510, GAP, 0)
97+
expect(r.visible).toEqual(['group', 'nav', 'zoom', 'undo'])
98+
expect(r.hidden).toEqual(['color'])
99+
})
100+
86101
it('ignores tiers that are not present', () => {
87102
const present: ToolbarTier[] = ['nav', 'zoom']
88103
// nav(200) + zoom(120) + gap = 324 > 240; nav alone plus the "…" trigger

‎packages/gantt/src/time-scale.ts‎

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,22 @@ export interface TimeScale {
4545
ticks: (range: [number, number]) => Tick[]
4646
}
4747

48-
export function createTimeScale (zoom: ZoomLevel, origin: number, pxPerDayOverride?: number): TimeScale {
48+
/** Presentation settings supplied by the host application's active language. */
49+
export interface TimeScaleLocale {
50+
locale?: string
51+
weekLabel?: (week: number) => string
52+
quarterLabel?: (quarter: number) => string
53+
}
54+
55+
export function createTimeScale (
56+
zoom: ZoomLevel,
57+
origin: number,
58+
pxPerDayOverride?: number,
59+
labels: TimeScaleLocale = {}
60+
): TimeScale {
61+
const locale = labels.locale ?? 'en'
62+
const weekLabel = labels.weekLabel ?? ((week: number) => `W${week}`)
63+
const quarterLabel = labels.quarterLabel ?? ((quarter: number) => `Q${quarter}`)
4964
const pxPerDay = pxPerDayOverride ?? PX_PER_DAY[zoom]
5065
const originSnapped = snapToUtcMidnight(origin)
5166

@@ -71,7 +86,7 @@ export function createTimeScale (zoom: ZoomLevel, origin: number, pxPerDayOverri
7186
const m = d.getUTCMonth()
7287
let secondary: string | undefined
7388
if (first || m !== lastMonth) {
74-
secondary = d.toLocaleString(undefined, { month: 'short', timeZone: 'UTC' })
89+
secondary = d.toLocaleString(locale, { month: 'short', timeZone: 'UTC' })
7590
lastMonth = m
7691
first = false
7792
}
@@ -106,7 +121,7 @@ export function createTimeScale (zoom: ZoomLevel, origin: number, pxPerDayOverri
106121
}
107122
result.push({
108123
date: cursor,
109-
label: `W${isoWeekNumber(c)}`,
124+
label: weekLabel(isoWeekNumber(c)),
110125
level: isFirstWeekOfMonth ? 'major' : 'minor',
111126
secondaryLabel: secondary
112127
})
@@ -135,7 +150,7 @@ export function createTimeScale (zoom: ZoomLevel, origin: number, pxPerDayOverri
135150
}
136151
result.push({
137152
date: cursor,
138-
label: c.toLocaleString(undefined, { month: 'short', timeZone: 'UTC' }),
153+
label: c.toLocaleString(locale, { month: 'short', timeZone: 'UTC' }),
139154
level: c.getUTCMonth() === 0 ? 'major' : 'minor',
140155
secondaryLabel: secondary
141156
})
@@ -170,7 +185,7 @@ export function createTimeScale (zoom: ZoomLevel, origin: number, pxPerDayOverri
170185
}
171186
result.push({
172187
date: cursor,
173-
label: `Q${qNum}`,
188+
label: quarterLabel(qNum),
174189
level: qNum === 1 ? 'major' : 'minor',
175190
secondaryLabel: secondary
176191
})

‎packages/gantt/src/toolbar-overflow.ts‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
* that collapses as a whole; splitting e.g. the date-navigation into single
2929
* buttons would leave a half-usable control group behind.
3030
*/
31-
export const TOOLBAR_TIERS = ['group', 'nav', 'date', 'zoom', 'undo', 'savedview'] as const
31+
export const TOOLBAR_TIERS = ['group', 'color', 'nav', 'date', 'zoom', 'undo', 'savedview'] as const
3232
export type ToolbarTier = (typeof TOOLBAR_TIERS)[number]
3333

3434
/**
@@ -37,12 +37,21 @@ export type ToolbarTier = (typeof TOOLBAR_TIERS)[number]
3737
* - `savedview` is a passive "modified" indicator plus its update button; the
3838
* same action is reachable from the More-actions menu.
3939
* - `date` (jump-to-date) duplicates what panning and the nav buttons do.
40-
* - `group` (group-by / colour-by) is view configuration, not navigation.
40+
* - `color` is cosmetic; keep `group` visible longer because it changes the
41+
* layout of the issue list and chart.
4142
* - `undo` matters while editing, but the keyboard shortcuts stay live.
4243
* - `zoom` and `nav` are the two controls a Gantt is unusable without, so
4344
* they are the last to go.
4445
*/
45-
export const TOOLBAR_COLLAPSE_ORDER: readonly ToolbarTier[] = ['savedview', 'date', 'group', 'undo', 'zoom', 'nav']
46+
export const TOOLBAR_COLLAPSE_ORDER: readonly ToolbarTier[] = [
47+
'savedview',
48+
'date',
49+
'color',
50+
'group',
51+
'undo',
52+
'zoom',
53+
'nav'
54+
]
4655

4756
/**
4857
* Width assumed for a tier that has never been measured. Deliberately

‎plugins/tracker-assets/assets/icons.svg‎

Lines changed: 2 additions & 4 deletions
Loading

‎plugins/tracker-assets/lang/cs.json‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,19 @@
315315
"SearchEmptyTitle": "Pro „{query}\" nebyly nalezeny žádné úkoly",
316316
"SearchEmptyActiveFilters": "Aktivní filtry: {filters}",
317317
"SearchEmptyClearFilters": "Hledat bez filtrů",
318-
"SearchEmptyAllProjects": "Hledat ve všech projektech"
318+
"SearchEmptyAllProjects": "Hledat ve všech projektech",
319+
"GanttWeekLabel": "T{week}",
320+
"GanttQuarterLabel": "Q{quarter}",
321+
"GanttWithoutDates": "{count} bez dat",
322+
"GanttDates": "Data",
323+
"GanttZoomLabel": "Přiblížení",
324+
"GanttSetDates": "Nastavit data",
325+
"GanttNoScheduledTasks": "Žádné naplánované úkoly",
326+
"GanttTodayAlreadyVisible": "Dnešek je již zobrazen",
327+
"GanttVisibleRange": "Rozsah",
328+
"GanttNotScheduled": "Nenaplánováno",
329+
"GanttMissingStartDates": "{count} úkolů nemá datum zahájení.",
330+
"GanttSetDatesHint": "Nastavte u úkolu datum zahájení a termín dokončení, aby se zobrazil v časové ose."
319331
},
320332
"status": {}
321333
}

‎plugins/tracker-assets/lang/de.json‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,8 @@
358358
"GanttJumpToDate": "Zu Datum springen",
359359
"GanttZoomDay": "Tag",
360360
"GanttZoomWeek": "Woche",
361+
"GanttWeekLabel": "KW{week}",
362+
"GanttQuarterLabel": "Q{quarter}",
361363
"GanttZoomMonth": "Monat",
362364
"GanttZoomQuarter": "Quartal",
363365
"GanttZoomCustom": "Benutzerdefiniert",
@@ -563,7 +565,16 @@
563565
"SearchEmptyTitle": "Keine Treffer für \"{query}\"",
564566
"SearchEmptyActiveFilters": "Aktive Filter: {filters}",
565567
"SearchEmptyClearFilters": "Suche ohne Filter",
566-
"SearchEmptyAllProjects": "In allen Projekten suchen"
568+
"SearchEmptyAllProjects": "In allen Projekten suchen",
569+
"GanttWithoutDates": "{count} ohne Daten",
570+
"GanttDates": "Termine",
571+
"GanttSetDates": "Daten festlegen",
572+
"GanttNoScheduledTasks": "Keine geplanten Aufgaben",
573+
"GanttTodayAlreadyVisible": "Heute ist bereits sichtbar",
574+
"GanttVisibleRange": "Zeitraum",
575+
"GanttNotScheduled": "Nicht geplant",
576+
"GanttMissingStartDates": "Für {count} Aufgaben ist kein Startdatum festgelegt.",
577+
"GanttSetDatesHint": "Legen Sie für ein Issue Start- und Fälligkeitsdatum fest, damit es auf der Zeitachse erscheint."
567578
},
568579
"status": {}
569580
}

‎plugins/tracker-assets/lang/en.json‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,8 @@
344344
"GanttJumpToDate": "Jump to date",
345345
"GanttZoomDay": "Day",
346346
"GanttZoomWeek": "Week",
347+
"GanttWeekLabel": "W{week}",
348+
"GanttQuarterLabel": "Q{quarter}",
347349
"GanttZoomMonth": "Month",
348350
"GanttZoomQuarter": "Quarter",
349351
"GanttZoomCustom": "Custom",
@@ -563,7 +565,16 @@
563565
"SearchEmptyTitle": "No issues found for \"{query}\"",
564566
"SearchEmptyActiveFilters": "Active filters: {filters}",
565567
"SearchEmptyClearFilters": "Search without filters",
566-
"SearchEmptyAllProjects": "Search in all projects"
568+
"SearchEmptyAllProjects": "Search in all projects",
569+
"GanttWithoutDates": "{count} without dates",
570+
"GanttDates": "Dates",
571+
"GanttSetDates": "Set dates",
572+
"GanttNoScheduledTasks": "No scheduled tasks",
573+
"GanttTodayAlreadyVisible": "Today is already in view",
574+
"GanttVisibleRange": "Range",
575+
"GanttNotScheduled": "Not scheduled",
576+
"GanttMissingStartDates": "{count} issues have no start date.",
577+
"GanttSetDatesHint": "Set start and due dates in an issue to show its timeline."
567578
},
568579
"status": {}
569580
}

‎plugins/tracker-assets/lang/es.json‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,19 @@
308308
"SearchEmptyTitle": "No se encontraron tareas para \"{query}\"",
309309
"SearchEmptyActiveFilters": "Filtros activos: {filters}",
310310
"SearchEmptyClearFilters": "Buscar sin filtros",
311-
"SearchEmptyAllProjects": "Buscar en todos los proyectos"
311+
"SearchEmptyAllProjects": "Buscar en todos los proyectos",
312+
"GanttWeekLabel": "S{week}",
313+
"GanttQuarterLabel": "T{quarter}",
314+
"GanttWithoutDates": "{count} sin fechas",
315+
"GanttDates": "Fechas",
316+
"GanttZoomLabel": "Zoom",
317+
"GanttSetDates": "Establecer fechas",
318+
"GanttNoScheduledTasks": "No hay tareas programadas",
319+
"GanttTodayAlreadyVisible": "Hoy ya está visible",
320+
"GanttVisibleRange": "Intervalo",
321+
"GanttNotScheduled": "Sin programar",
322+
"GanttMissingStartDates": "{count} tareas no tienen fecha de inicio.",
323+
"GanttSetDatesHint": "Establece las fechas de inicio y vencimiento de una tarea para mostrarla en la línea de tiempo."
312324
},
313325
"status": {}
314326
}

0 commit comments

Comments
 (0)