|
15 | 15 | import SubFilterBar from './SubFilterBar.svelte'; |
16 | 16 | import Select from '../../components/Select.svelte'; |
17 | 17 | import ItemDetail from '../items/ItemDetail.svelte'; |
18 | | - import { Settings, ChevronLeft, ChevronRight, Diamond, ChevronDown, Circle, GitBranch } from '@lucide/svelte'; |
| 18 | + import RoadmapItemPreview from './RoadmapItemPreview.svelte'; |
| 19 | + import { Settings, ChevronLeft, ChevronRight, Diamond, ChevronDown, Circle, GitBranch, CalendarClock } from '@lucide/svelte'; |
19 | 20 | import { getVisibleColor } from '../../utils/colorUtils.js'; |
20 | 21 | import { itemTypeIconMap } from '../../utils/icons.js'; |
21 | 22 | import { SYSTEM_FIELDS } from '../../stores/fieldConfig.js'; |
|
65 | 66 | let boardConfig = $state(null); |
66 | 67 | let boardConfigId = $state(null); |
67 | 68 | let roadmapConfig = $state({ start_field_id: 'due_date', end_field_id: '', dependency_link_type_id: null }); |
| 69 | + let roadmapCardFields = $derived(boardConfig?.card_fields || []); |
68 | 70 | let linkTypes = $state([]); |
69 | 71 | let customFields = $state([]); |
70 | 72 | let screenFields = $state([]); |
|
412 | 414 | return !!(start || end); |
413 | 415 | } |
414 | 416 |
|
| 417 | + let unscheduledItemCount = $derived( |
| 418 | + collectionStore.loading ? 0 : collectionStore.items.filter((item) => !itemHasDate(item)).length, |
| 419 | + ); |
| 420 | +
|
415 | 421 | // Auto-expand root items with children on first load |
416 | 422 | $effect(() => { |
417 | 423 | if (!collectionStore.loading && collectionStore.items.length > 0) { |
|
1045 | 1051 |
|
1046 | 1052 | </div> |
1047 | 1053 |
|
| 1054 | + {#if roadmapConfig.start_field_id && unscheduledItemCount > 0} |
| 1055 | + <div |
| 1056 | + class="mb-4 flex items-center gap-2 rounded-md px-3 py-2 text-xs" |
| 1057 | + style="background-color: var(--ds-background-neutral-hovered); color: var(--ds-text-subtle);" |
| 1058 | + data-testid="roadmap-unscheduled-hint" |
| 1059 | + > |
| 1060 | + <CalendarClock class="h-4 w-4 shrink-0" /> |
| 1061 | + <span>{unscheduledItemCount} {unscheduledItemCount === 1 ? 'item needs dates' : 'items need dates'} — click a timeline row to schedule it.</span> |
| 1062 | + </div> |
| 1063 | + {/if} |
| 1064 | +
|
1048 | 1065 |
|
1049 | 1066 | <!-- No config state --> |
1050 | 1067 | {#if !roadmapConfig.start_field_id} |
|
1246 | 1263 |
|
1247 | 1264 | {#if roadmapItem.isMilestone && !roadmapConfig.end_field_id} |
1248 | 1265 | <!-- Diamond milestone (no end field configured, can't expand) --> |
1249 | | - <div |
1250 | | - data-testid="roadmap-bar-{item.id}" |
1251 | | - data-start-date={roadmapItem.startDate || ''} |
1252 | | - data-end-date={roadmapItem.endDate || ''} |
1253 | | - class="absolute flex items-center justify-center" |
1254 | | - style="left: {barLeftPx + barWidthPx / 2 - 8}px; top: {(ROW_HEIGHT - 16) / 2}px; width: 16px; height: 16px; transform: rotate(45deg); background-color: {visibleColor}; border-radius: 2px; z-index: 10; cursor: grab;" |
1255 | | - role="button" |
1256 | | - tabindex="0" |
| 1266 | + <RoadmapItemPreview |
| 1267 | + item={roadmapItem} |
| 1268 | + workspace={workspace} |
| 1269 | + itemTypes={itemTypes} |
| 1270 | + cardFields={roadmapCardFields} |
| 1271 | + priorities={workspaceDataStore.priorities} |
| 1272 | + statuses={statuses} |
| 1273 | + iterations={workspaceDataStore.iterations} |
| 1274 | + projects={workspaceDataStore.projects} |
| 1275 | + labels={workspaceDataStore.labels} |
| 1276 | + customFieldDefinitions={customFields} |
| 1277 | + users={workspaceDataStore.users} |
1257 | 1278 | onpointerdown={(e) => onBarPointerDown(e, roadmapItem, 'move')} |
1258 | | - ></div> |
| 1279 | + onopen={openItem} |
| 1280 | + > |
| 1281 | + {#snippet children()} |
| 1282 | + <div |
| 1283 | + data-testid="roadmap-bar-{item.id}" |
| 1284 | + data-start-date={roadmapItem.startDate || ''} |
| 1285 | + data-end-date={roadmapItem.endDate || ''} |
| 1286 | + class="absolute flex items-center justify-center" |
| 1287 | + style="left: {barLeftPx + barWidthPx / 2 - 8}px; top: {(ROW_HEIGHT - 16) / 2}px; width: 16px; height: 16px; transform: rotate(45deg); background-color: {visibleColor}; border-radius: 2px; z-index: 10; cursor: grab;" |
| 1288 | + role="button" |
| 1289 | + tabindex="0" |
| 1290 | + ></div> |
| 1291 | + {/snippet} |
| 1292 | + </RoadmapItemPreview> |
1259 | 1293 | {:else} |
1260 | 1294 | <!-- Range bar OR expandable milestone (thin bar with resize handles) --> |
1261 | | - <div |
1262 | | - data-testid="roadmap-bar-{item.id}" |
1263 | | - data-start-date={roadmapItem.startDate || ''} |
1264 | | - data-end-date={roadmapItem.endDate || ''} |
1265 | | - class="absolute flex items-center rounded group/bar" |
1266 | | - style="left: {barLeftPx}px; width: {barWidthPx}px; top: {(ROW_HEIGHT - 24) / 2}px; height: 24px; background-color: {visibleColor}; opacity: 0.85; z-index: 10; cursor: grab;" |
1267 | | - role="button" |
1268 | | - tabindex="0" |
| 1295 | + <RoadmapItemPreview |
| 1296 | + item={roadmapItem} |
| 1297 | + workspace={workspace} |
| 1298 | + itemTypes={itemTypes} |
| 1299 | + cardFields={roadmapCardFields} |
| 1300 | + priorities={workspaceDataStore.priorities} |
| 1301 | + statuses={statuses} |
| 1302 | + iterations={workspaceDataStore.iterations} |
| 1303 | + projects={workspaceDataStore.projects} |
| 1304 | + labels={workspaceDataStore.labels} |
| 1305 | + customFieldDefinitions={customFields} |
| 1306 | + users={workspaceDataStore.users} |
1269 | 1307 | onpointerdown={(e) => onBarPointerDown(e, roadmapItem, 'move')} |
| 1308 | + onopen={openItem} |
1270 | 1309 | > |
| 1310 | + {#snippet children()} |
| 1311 | + <div |
| 1312 | + data-testid="roadmap-bar-{item.id}" |
| 1313 | + data-start-date={roadmapItem.startDate || ''} |
| 1314 | + data-end-date={roadmapItem.endDate || ''} |
| 1315 | + class="absolute flex items-center rounded group/bar" |
| 1316 | + style="left: {barLeftPx}px; width: {barWidthPx}px; top: {(ROW_HEIGHT - 24) / 2}px; height: 24px; background-color: {visibleColor}; opacity: 0.85; z-index: 10; cursor: grab;" |
| 1317 | + role="button" |
| 1318 | + tabindex="0" |
| 1319 | + > |
1271 | 1320 | <div |
1272 | 1321 | class="absolute left-0 top-0 bottom-0 w-2 cursor-col-resize opacity-0 group-hover/bar:opacity-100 rounded-l" |
1273 | 1322 | style="background-color: rgba(0,0,0,0.2);" |
|
1289 | 1338 | tabindex="-1" |
1290 | 1339 | onpointerdown={(e) => { e.stopPropagation(); onBarPointerDown(e, roadmapItem, 'resize-right'); }} |
1291 | 1340 | ></div> |
1292 | | - </div> |
| 1341 | + </div> |
| 1342 | + {/snippet} |
| 1343 | + </RoadmapItemPreview> |
1293 | 1344 | {/if} |
1294 | 1345 | {:else if schedulePreview?.itemId === item.id} |
1295 | 1346 | {@const previewColor = getVisibleColor(statuses.find(s => s.id === item.status_id)?.color || '#6b7280')} |
|
0 commit comments