@agile-vibe-coding/avc 0.1.0 β 0.2.3
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.
- package/README.md +2 -0
- package/cli/agent-loader.js +21 -0
- package/cli/agents/agent-selector.md +129 -0
- package/cli/agents/architecture-recommender.md +418 -0
- package/cli/agents/database-deep-dive.md +470 -0
- package/cli/agents/database-recommender.md +634 -0
- package/cli/agents/doc-distributor.md +176 -0
- package/cli/agents/documentation-updater.md +203 -0
- package/cli/agents/epic-story-decomposer.md +280 -0
- package/cli/agents/feature-context-generator.md +91 -0
- package/cli/agents/gap-checker-epic.md +52 -0
- package/cli/agents/impact-checker-story.md +51 -0
- package/cli/agents/migration-guide-generator.md +305 -0
- package/cli/agents/mission-scope-generator.md +79 -0
- package/cli/agents/mission-scope-validator.md +112 -0
- package/cli/agents/project-context-extractor.md +107 -0
- package/cli/agents/project-documentation-creator.json +226 -0
- package/cli/agents/project-documentation-creator.md +595 -0
- package/cli/agents/question-prefiller.md +269 -0
- package/cli/agents/refiner-epic.md +39 -0
- package/cli/agents/refiner-story.md +42 -0
- package/cli/agents/solver-epic-api.json +15 -0
- package/cli/agents/solver-epic-api.md +39 -0
- package/cli/agents/solver-epic-backend.json +15 -0
- package/cli/agents/solver-epic-backend.md +39 -0
- package/cli/agents/solver-epic-cloud.json +15 -0
- package/cli/agents/solver-epic-cloud.md +39 -0
- package/cli/agents/solver-epic-data.json +15 -0
- package/cli/agents/solver-epic-data.md +39 -0
- package/cli/agents/solver-epic-database.json +15 -0
- package/cli/agents/solver-epic-database.md +39 -0
- package/cli/agents/solver-epic-developer.json +15 -0
- package/cli/agents/solver-epic-developer.md +39 -0
- package/cli/agents/solver-epic-devops.json +15 -0
- package/cli/agents/solver-epic-devops.md +39 -0
- package/cli/agents/solver-epic-frontend.json +15 -0
- package/cli/agents/solver-epic-frontend.md +39 -0
- package/cli/agents/solver-epic-mobile.json +15 -0
- package/cli/agents/solver-epic-mobile.md +39 -0
- package/cli/agents/solver-epic-qa.json +15 -0
- package/cli/agents/solver-epic-qa.md +39 -0
- package/cli/agents/solver-epic-security.json +15 -0
- package/cli/agents/solver-epic-security.md +39 -0
- package/cli/agents/solver-epic-solution-architect.json +15 -0
- package/cli/agents/solver-epic-solution-architect.md +39 -0
- package/cli/agents/solver-epic-test-architect.json +15 -0
- package/cli/agents/solver-epic-test-architect.md +39 -0
- package/cli/agents/solver-epic-ui.json +15 -0
- package/cli/agents/solver-epic-ui.md +39 -0
- package/cli/agents/solver-epic-ux.json +15 -0
- package/cli/agents/solver-epic-ux.md +39 -0
- package/cli/agents/solver-story-api.json +15 -0
- package/cli/agents/solver-story-api.md +39 -0
- package/cli/agents/solver-story-backend.json +15 -0
- package/cli/agents/solver-story-backend.md +39 -0
- package/cli/agents/solver-story-cloud.json +15 -0
- package/cli/agents/solver-story-cloud.md +39 -0
- package/cli/agents/solver-story-data.json +15 -0
- package/cli/agents/solver-story-data.md +39 -0
- package/cli/agents/solver-story-database.json +15 -0
- package/cli/agents/solver-story-database.md +39 -0
- package/cli/agents/solver-story-developer.json +15 -0
- package/cli/agents/solver-story-developer.md +39 -0
- package/cli/agents/solver-story-devops.json +15 -0
- package/cli/agents/solver-story-devops.md +39 -0
- package/cli/agents/solver-story-frontend.json +15 -0
- package/cli/agents/solver-story-frontend.md +39 -0
- package/cli/agents/solver-story-mobile.json +15 -0
- package/cli/agents/solver-story-mobile.md +39 -0
- package/cli/agents/solver-story-qa.json +15 -0
- package/cli/agents/solver-story-qa.md +39 -0
- package/cli/agents/solver-story-security.json +15 -0
- package/cli/agents/solver-story-security.md +39 -0
- package/cli/agents/solver-story-solution-architect.json +15 -0
- package/cli/agents/solver-story-solution-architect.md +39 -0
- package/cli/agents/solver-story-test-architect.json +15 -0
- package/cli/agents/solver-story-test-architect.md +39 -0
- package/cli/agents/solver-story-ui.json +15 -0
- package/cli/agents/solver-story-ui.md +39 -0
- package/cli/agents/solver-story-ux.json +15 -0
- package/cli/agents/solver-story-ux.md +39 -0
- package/cli/agents/story-doc-enricher.md +133 -0
- package/cli/agents/suggestion-business-analyst.md +88 -0
- package/cli/agents/suggestion-deployment-architect.md +263 -0
- package/cli/agents/suggestion-product-manager.md +129 -0
- package/cli/agents/suggestion-security-specialist.md +156 -0
- package/cli/agents/suggestion-technical-architect.md +269 -0
- package/cli/agents/suggestion-ux-researcher.md +93 -0
- package/cli/agents/task-subtask-decomposer.md +188 -0
- package/cli/agents/validator-documentation.json +152 -0
- package/cli/agents/validator-documentation.md +453 -0
- package/cli/agents/validator-epic-api.json +93 -0
- package/cli/agents/validator-epic-api.md +137 -0
- package/cli/agents/validator-epic-backend.json +93 -0
- package/cli/agents/validator-epic-backend.md +130 -0
- package/cli/agents/validator-epic-cloud.json +93 -0
- package/cli/agents/validator-epic-cloud.md +137 -0
- package/cli/agents/validator-epic-data.json +93 -0
- package/cli/agents/validator-epic-data.md +130 -0
- package/cli/agents/validator-epic-database.json +93 -0
- package/cli/agents/validator-epic-database.md +137 -0
- package/cli/agents/validator-epic-developer.json +74 -0
- package/cli/agents/validator-epic-developer.md +153 -0
- package/cli/agents/validator-epic-devops.json +74 -0
- package/cli/agents/validator-epic-devops.md +153 -0
- package/cli/agents/validator-epic-frontend.json +74 -0
- package/cli/agents/validator-epic-frontend.md +153 -0
- package/cli/agents/validator-epic-mobile.json +93 -0
- package/cli/agents/validator-epic-mobile.md +130 -0
- package/cli/agents/validator-epic-qa.json +93 -0
- package/cli/agents/validator-epic-qa.md +130 -0
- package/cli/agents/validator-epic-security.json +74 -0
- package/cli/agents/validator-epic-security.md +154 -0
- package/cli/agents/validator-epic-solution-architect.json +74 -0
- package/cli/agents/validator-epic-solution-architect.md +156 -0
- package/cli/agents/validator-epic-test-architect.json +93 -0
- package/cli/agents/validator-epic-test-architect.md +130 -0
- package/cli/agents/validator-epic-ui.json +93 -0
- package/cli/agents/validator-epic-ui.md +130 -0
- package/cli/agents/validator-epic-ux.json +93 -0
- package/cli/agents/validator-epic-ux.md +130 -0
- package/cli/agents/validator-selector.md +211 -0
- package/cli/agents/validator-story-api.json +104 -0
- package/cli/agents/validator-story-api.md +152 -0
- package/cli/agents/validator-story-backend.json +104 -0
- package/cli/agents/validator-story-backend.md +152 -0
- package/cli/agents/validator-story-cloud.json +104 -0
- package/cli/agents/validator-story-cloud.md +152 -0
- package/cli/agents/validator-story-data.json +104 -0
- package/cli/agents/validator-story-data.md +152 -0
- package/cli/agents/validator-story-database.json +104 -0
- package/cli/agents/validator-story-database.md +152 -0
- package/cli/agents/validator-story-developer.json +104 -0
- package/cli/agents/validator-story-developer.md +152 -0
- package/cli/agents/validator-story-devops.json +104 -0
- package/cli/agents/validator-story-devops.md +152 -0
- package/cli/agents/validator-story-frontend.json +104 -0
- package/cli/agents/validator-story-frontend.md +152 -0
- package/cli/agents/validator-story-mobile.json +104 -0
- package/cli/agents/validator-story-mobile.md +152 -0
- package/cli/agents/validator-story-qa.json +104 -0
- package/cli/agents/validator-story-qa.md +152 -0
- package/cli/agents/validator-story-security.json +104 -0
- package/cli/agents/validator-story-security.md +152 -0
- package/cli/agents/validator-story-solution-architect.json +104 -0
- package/cli/agents/validator-story-solution-architect.md +152 -0
- package/cli/agents/validator-story-test-architect.json +104 -0
- package/cli/agents/validator-story-test-architect.md +152 -0
- package/cli/agents/validator-story-ui.json +104 -0
- package/cli/agents/validator-story-ui.md +152 -0
- package/cli/agents/validator-story-ux.json +104 -0
- package/cli/agents/validator-story-ux.md +152 -0
- package/cli/ansi-colors.js +21 -0
- package/cli/build-docs.js +298 -0
- package/cli/ceremony-history.js +369 -0
- package/cli/command-logger.js +245 -0
- package/cli/components/static-output.js +63 -0
- package/cli/console-output-manager.js +94 -0
- package/cli/docs-sync.js +306 -0
- package/cli/epic-story-validator.js +1174 -0
- package/cli/evaluation-prompts.js +1008 -0
- package/cli/execution-context.js +195 -0
- package/cli/generate-summary-table.js +340 -0
- package/cli/index.js +3 -25
- package/cli/init-model-config.js +697 -0
- package/cli/init.js +1765 -100
- package/cli/kanban-server-manager.js +228 -0
- package/cli/llm-claude.js +109 -0
- package/cli/llm-gemini.js +115 -0
- package/cli/llm-mock.js +233 -0
- package/cli/llm-openai.js +233 -0
- package/cli/llm-provider.js +300 -0
- package/cli/llm-token-limits.js +102 -0
- package/cli/llm-verifier.js +454 -0
- package/cli/logger.js +32 -5
- package/cli/message-constants.js +58 -0
- package/cli/message-manager.js +334 -0
- package/cli/message-types.js +96 -0
- package/cli/messaging-api.js +297 -0
- package/cli/model-pricing.js +169 -0
- package/cli/model-query-engine.js +468 -0
- package/cli/model-recommendation-analyzer.js +495 -0
- package/cli/model-selector.js +269 -0
- package/cli/output-buffer.js +107 -0
- package/cli/process-manager.js +332 -0
- package/cli/repl-ink.js +5840 -504
- package/cli/repl-old.js +4 -4
- package/cli/seed-processor.js +792 -0
- package/cli/sprint-planning-processor.js +1813 -0
- package/cli/template-processor.js +2306 -108
- package/cli/templates/project.md +25 -8
- package/cli/templates/vitepress-config.mts.template +34 -0
- package/cli/token-tracker.js +520 -0
- package/cli/tools/generate-story-validators.js +317 -0
- package/cli/tools/generate-validators.js +669 -0
- package/cli/update-checker.js +19 -17
- package/cli/update-notifier.js +4 -4
- package/cli/validation-router.js +605 -0
- package/cli/verification-tracker.js +563 -0
- package/kanban/README.md +386 -0
- package/kanban/client/README.md +205 -0
- package/kanban/client/components.json +20 -0
- package/kanban/client/dist/assets/index-CiD8PS2e.js +306 -0
- package/kanban/client/dist/assets/index-nLh0m82Q.css +1 -0
- package/kanban/client/dist/index.html +16 -0
- package/kanban/client/dist/vite.svg +1 -0
- package/kanban/client/index.html +15 -0
- package/kanban/client/package-lock.json +9442 -0
- package/kanban/client/package.json +44 -0
- package/kanban/client/postcss.config.js +6 -0
- package/kanban/client/public/vite.svg +1 -0
- package/kanban/client/src/App.jsx +622 -0
- package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
- package/kanban/client/src/components/ceremony/AskArchPopup.jsx +416 -0
- package/kanban/client/src/components/ceremony/AskModelPopup.jsx +616 -0
- package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +946 -0
- package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
- package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +619 -0
- package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +704 -0
- package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
- package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +154 -0
- package/kanban/client/src/components/ceremony/steps/DatabaseStep.jsx +202 -0
- package/kanban/client/src/components/ceremony/steps/DeploymentStep.jsx +123 -0
- package/kanban/client/src/components/ceremony/steps/MissionStep.jsx +106 -0
- package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +125 -0
- package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +228 -0
- package/kanban/client/src/components/kanban/CardDetailModal.jsx +559 -0
- package/kanban/client/src/components/kanban/EpicSection.jsx +146 -0
- package/kanban/client/src/components/kanban/FilterToolbar.jsx +222 -0
- package/kanban/client/src/components/kanban/GroupingSelector.jsx +57 -0
- package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
- package/kanban/client/src/components/kanban/KanbanCard.jsx +138 -0
- package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
- package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +789 -0
- package/kanban/client/src/components/layout/LoadingScreen.jsx +82 -0
- package/kanban/client/src/components/process/ProcessMonitorBar.jsx +80 -0
- package/kanban/client/src/components/settings/AgentEditorPopup.jsx +171 -0
- package/kanban/client/src/components/settings/AgentsTab.jsx +353 -0
- package/kanban/client/src/components/settings/ApiKeysTab.jsx +113 -0
- package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +98 -0
- package/kanban/client/src/components/settings/CostThresholdsTab.jsx +94 -0
- package/kanban/client/src/components/settings/ModelPricingTab.jsx +204 -0
- package/kanban/client/src/components/settings/ServersTab.jsx +121 -0
- package/kanban/client/src/components/settings/SettingsModal.jsx +84 -0
- package/kanban/client/src/components/stats/CostModal.jsx +353 -0
- package/kanban/client/src/components/ui/badge.jsx +27 -0
- package/kanban/client/src/components/ui/dialog.jsx +121 -0
- package/kanban/client/src/components/ui/tabs.jsx +85 -0
- package/kanban/client/src/hooks/__tests__/useGrouping.test.js +232 -0
- package/kanban/client/src/hooks/useGrouping.js +118 -0
- package/kanban/client/src/hooks/useWebSocket.js +120 -0
- package/kanban/client/src/lib/__tests__/api.test.js +196 -0
- package/kanban/client/src/lib/__tests__/status-grouping.test.js +94 -0
- package/kanban/client/src/lib/api.js +401 -0
- package/kanban/client/src/lib/status-grouping.js +144 -0
- package/kanban/client/src/lib/utils.js +11 -0
- package/kanban/client/src/main.jsx +10 -0
- package/kanban/client/src/store/__tests__/kanbanStore.test.js +164 -0
- package/kanban/client/src/store/ceremonyStore.js +172 -0
- package/kanban/client/src/store/filterStore.js +201 -0
- package/kanban/client/src/store/kanbanStore.js +115 -0
- package/kanban/client/src/store/processStore.js +65 -0
- package/kanban/client/src/store/sprintPlanningStore.js +33 -0
- package/kanban/client/src/styles/globals.css +59 -0
- package/kanban/client/tailwind.config.js +77 -0
- package/kanban/client/vite.config.js +28 -0
- package/kanban/client/vitest.config.js +28 -0
- package/kanban/dev-start.sh +47 -0
- package/kanban/package.json +12 -0
- package/kanban/server/index.js +516 -0
- package/kanban/server/routes/ceremony.js +305 -0
- package/kanban/server/routes/costs.js +157 -0
- package/kanban/server/routes/processes.js +50 -0
- package/kanban/server/routes/settings.js +303 -0
- package/kanban/server/routes/websocket.js +276 -0
- package/kanban/server/routes/work-items.js +347 -0
- package/kanban/server/services/CeremonyService.js +1190 -0
- package/kanban/server/services/FileSystemScanner.js +95 -0
- package/kanban/server/services/FileWatcher.js +144 -0
- package/kanban/server/services/HierarchyBuilder.js +196 -0
- package/kanban/server/services/ProcessRegistry.js +122 -0
- package/kanban/server/services/WorkItemReader.js +123 -0
- package/kanban/server/services/WorkItemRefineService.js +510 -0
- package/kanban/server/start.js +49 -0
- package/kanban/server/utils/kanban-logger.js +132 -0
- package/kanban/server/utils/markdown.js +91 -0
- package/kanban/server/utils/status-grouping.js +107 -0
- package/kanban/server/workers/sponsor-call-worker.js +84 -0
- package/kanban/server/workers/sprint-planning-worker.js +130 -0
- package/package.json +34 -7
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { motion } from 'framer-motion';
|
|
2
|
+
import { ChevronDown, ChevronRight } from 'lucide-react';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { KanbanColumn } from './KanbanColumn';
|
|
5
|
+
import { COLUMN_ORDER, STATUS_COLUMN_MAPPING } from '../../lib/status-grouping';
|
|
6
|
+
import { getStatusMetadata } from '../../lib/status-grouping';
|
|
7
|
+
import { cn } from '../../lib/utils';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Epic Section Component
|
|
11
|
+
* Displays an epic with its work items grouped by status columns
|
|
12
|
+
*/
|
|
13
|
+
export function EpicSection({ group, columnVisibility, onCardClick }) {
|
|
14
|
+
const [isExpanded, setIsExpanded] = useState(true);
|
|
15
|
+
|
|
16
|
+
const { name, epic, items, columns } = group;
|
|
17
|
+
const isUngrouped = group.type === 'ungrouped';
|
|
18
|
+
|
|
19
|
+
// Calculate epic progress
|
|
20
|
+
const totalItems = items.length;
|
|
21
|
+
const completedItems = items.filter((item) => item.status === 'completed').length;
|
|
22
|
+
const progressPercentage = totalItems > 0 ? (completedItems / totalItems) * 100 : 0;
|
|
23
|
+
|
|
24
|
+
// Get epic status if available
|
|
25
|
+
const epicStatus = epic?.status;
|
|
26
|
+
const epicStatusMeta = epicStatus ? getStatusMetadata(epicStatus) : null;
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className="mb-8">
|
|
30
|
+
{/* Epic Header */}
|
|
31
|
+
<div
|
|
32
|
+
className={cn(
|
|
33
|
+
'mb-4 pb-4 border-b-2',
|
|
34
|
+
isUngrouped ? 'border-slate-300' : 'border-indigo-300'
|
|
35
|
+
)}
|
|
36
|
+
>
|
|
37
|
+
<div className="flex items-start justify-between gap-4">
|
|
38
|
+
<div className="flex items-start gap-3 flex-1 min-w-0">
|
|
39
|
+
{/* Expand/Collapse Toggle */}
|
|
40
|
+
<button
|
|
41
|
+
onClick={() => setIsExpanded(!isExpanded)}
|
|
42
|
+
className="text-slate-600 hover:text-slate-900 transition-colors mt-3 flex-shrink-0"
|
|
43
|
+
>
|
|
44
|
+
{isExpanded ? (
|
|
45
|
+
<ChevronDown className="w-5 h-5" />
|
|
46
|
+
) : (
|
|
47
|
+
<ChevronRight className="w-5 h-5" />
|
|
48
|
+
)}
|
|
49
|
+
</button>
|
|
50
|
+
|
|
51
|
+
{/* Epic Title */}
|
|
52
|
+
{epic && !isUngrouped ? (
|
|
53
|
+
<button
|
|
54
|
+
onClick={() => onCardClick?.(epic)}
|
|
55
|
+
className="flex-1 min-w-0 text-left p-3 border border-indigo-200 rounded-lg hover:border-indigo-400 hover:bg-indigo-50 transition-colors"
|
|
56
|
+
title="View epic details"
|
|
57
|
+
>
|
|
58
|
+
<div className="flex items-start justify-between gap-2">
|
|
59
|
+
<div className="flex-1 min-w-0">
|
|
60
|
+
<div className="flex items-center gap-2 flex-wrap mb-1">
|
|
61
|
+
<span className="text-lg font-bold text-indigo-900">ποΈ {name}</span>
|
|
62
|
+
{epicStatusMeta && (
|
|
63
|
+
<span
|
|
64
|
+
className={cn(
|
|
65
|
+
'px-2 py-0.5 rounded-full text-xs font-medium',
|
|
66
|
+
epicStatusMeta.color === 'green' && 'bg-green-100 text-green-700',
|
|
67
|
+
epicStatusMeta.color === 'blue' && 'bg-blue-100 text-blue-700',
|
|
68
|
+
epicStatusMeta.color === 'yellow' && 'bg-yellow-100 text-yellow-700'
|
|
69
|
+
)}
|
|
70
|
+
>
|
|
71
|
+
{epicStatusMeta.icon} {epicStatusMeta.label}
|
|
72
|
+
</span>
|
|
73
|
+
)}
|
|
74
|
+
</div>
|
|
75
|
+
{epic?.description && (
|
|
76
|
+
<p className="text-sm text-slate-500 line-clamp-2">{epic.description}</p>
|
|
77
|
+
)}
|
|
78
|
+
</div>
|
|
79
|
+
<ChevronRight className="w-4 h-4 text-indigo-400 mt-1 flex-shrink-0" />
|
|
80
|
+
</div>
|
|
81
|
+
</button>
|
|
82
|
+
) : (
|
|
83
|
+
<div className="flex-1 py-2">
|
|
84
|
+
<h2
|
|
85
|
+
className={cn(
|
|
86
|
+
'text-xl font-bold',
|
|
87
|
+
isUngrouped ? 'text-slate-600' : 'text-indigo-900'
|
|
88
|
+
)}
|
|
89
|
+
>
|
|
90
|
+
{isUngrouped ? 'π' : 'ποΈ'} {name}
|
|
91
|
+
</h2>
|
|
92
|
+
</div>
|
|
93
|
+
)}
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
{/* Progress Stats */}
|
|
97
|
+
<div className="text-right flex-shrink-0">
|
|
98
|
+
<div className="text-sm text-slate-600 mb-1">
|
|
99
|
+
{completedItems} / {totalItems} completed
|
|
100
|
+
</div>
|
|
101
|
+
{/* Progress Bar */}
|
|
102
|
+
<div className="w-48 h-2 bg-slate-200 rounded-full overflow-hidden">
|
|
103
|
+
<motion.div
|
|
104
|
+
initial={{ width: 0 }}
|
|
105
|
+
animate={{ width: `${progressPercentage}%` }}
|
|
106
|
+
transition={{ duration: 0.5, ease: 'easeOut' }}
|
|
107
|
+
className={cn(
|
|
108
|
+
'h-full',
|
|
109
|
+
isUngrouped ? 'bg-slate-500' : 'bg-indigo-600'
|
|
110
|
+
)}
|
|
111
|
+
/>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
{/* Columns */}
|
|
118
|
+
{isExpanded && (
|
|
119
|
+
<motion.div
|
|
120
|
+
initial={{ opacity: 0, height: 0 }}
|
|
121
|
+
animate={{ opacity: 1, height: 'auto' }}
|
|
122
|
+
exit={{ opacity: 0, height: 0 }}
|
|
123
|
+
transition={{ duration: 0.3 }}
|
|
124
|
+
className="flex gap-4 overflow-x-auto pb-4"
|
|
125
|
+
>
|
|
126
|
+
{COLUMN_ORDER.filter((column) => columnVisibility[column]).map(
|
|
127
|
+
(columnName) => {
|
|
128
|
+
const statuses = STATUS_COLUMN_MAPPING[columnName];
|
|
129
|
+
const columnItems = columns[columnName] || [];
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<KanbanColumn
|
|
133
|
+
key={`${group.id}-${columnName}`}
|
|
134
|
+
columnName={columnName}
|
|
135
|
+
statuses={statuses}
|
|
136
|
+
workItems={columnItems}
|
|
137
|
+
onCardClick={onCardClick}
|
|
138
|
+
/>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
)}
|
|
142
|
+
</motion.div>
|
|
143
|
+
)}
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { Search, RefreshCw, X, Filter, Eye, EyeOff } from 'lucide-react';
|
|
3
|
+
import { useFilterStore } from '../../store/filterStore';
|
|
4
|
+
import { useKanbanStore } from '../../store/kanbanStore';
|
|
5
|
+
import { GroupingSelector } from './GroupingSelector';
|
|
6
|
+
import { cn } from '../../lib/utils';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Filter Toolbar Component
|
|
10
|
+
* Provides filtering controls for work items in a single compact row
|
|
11
|
+
*/
|
|
12
|
+
export function FilterToolbar() {
|
|
13
|
+
const [searchInput, setSearchInput] = useState('');
|
|
14
|
+
|
|
15
|
+
// Zustand stores
|
|
16
|
+
const {
|
|
17
|
+
typeFilters,
|
|
18
|
+
columnVisibility,
|
|
19
|
+
searchQuery,
|
|
20
|
+
toggleTypeFilter,
|
|
21
|
+
setAllTypeFilters,
|
|
22
|
+
toggleColumnVisibility,
|
|
23
|
+
applyPreset,
|
|
24
|
+
setSearchQuery,
|
|
25
|
+
clearSearch,
|
|
26
|
+
resetFilters,
|
|
27
|
+
} = useFilterStore();
|
|
28
|
+
|
|
29
|
+
const { refresh, loading } = useKanbanStore();
|
|
30
|
+
|
|
31
|
+
// Type filter buttons
|
|
32
|
+
const typeOptions = [
|
|
33
|
+
{ key: 'epic', label: 'Epics', icon: 'ποΈ' },
|
|
34
|
+
{ key: 'story', label: 'Stories', icon: 'π' },
|
|
35
|
+
{ key: 'task', label: 'Tasks', icon: 'βοΈ' },
|
|
36
|
+
{ key: 'subtask', label: 'Subtasks', icon: 'π' },
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
// Column visibility options
|
|
40
|
+
const columnOptions = [
|
|
41
|
+
{ key: 'Backlog', label: 'Backlog' },
|
|
42
|
+
{ key: 'Ready', label: 'Ready' },
|
|
43
|
+
{ key: 'In Progress', label: 'In Progress' },
|
|
44
|
+
{ key: 'Review', label: 'Review' },
|
|
45
|
+
{ key: 'Done', label: 'Done' },
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
// Handle search input change (debounced)
|
|
49
|
+
const handleSearchChange = (e) => {
|
|
50
|
+
const value = e.target.value;
|
|
51
|
+
setSearchInput(value);
|
|
52
|
+
|
|
53
|
+
// Simple debounce
|
|
54
|
+
clearTimeout(window.searchDebounce);
|
|
55
|
+
window.searchDebounce = setTimeout(() => {
|
|
56
|
+
setSearchQuery(value);
|
|
57
|
+
}, 300);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Handle search clear
|
|
61
|
+
const handleClearSearch = () => {
|
|
62
|
+
setSearchInput('');
|
|
63
|
+
clearSearch();
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Check if all type filters are active
|
|
67
|
+
const allTypesActive = Object.values(typeFilters).every((v) => v);
|
|
68
|
+
const anyTypesActive = Object.values(typeFilters).some((v) => v);
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<div className="bg-white border-b border-slate-200 shadow-sm">
|
|
72
|
+
<div className="max-w-full px-4 py-2">
|
|
73
|
+
<div className="flex items-center justify-between gap-3">
|
|
74
|
+
|
|
75
|
+
{/* Left: Type Filters + Group By (single row) */}
|
|
76
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
77
|
+
|
|
78
|
+
{/* Filter icon doubles as toggle-all button */}
|
|
79
|
+
<button
|
|
80
|
+
onClick={() => setAllTypeFilters(!allTypesActive)}
|
|
81
|
+
className={cn(
|
|
82
|
+
'-ml-1.5 p-1.5 rounded-md transition-colors flex-shrink-0',
|
|
83
|
+
allTypesActive
|
|
84
|
+
? 'text-blue-600 hover:bg-blue-50'
|
|
85
|
+
: anyTypesActive
|
|
86
|
+
? 'text-slate-500 hover:bg-slate-100'
|
|
87
|
+
: 'text-slate-400 hover:bg-slate-100'
|
|
88
|
+
)}
|
|
89
|
+
title={allTypesActive ? 'Deselect all types' : 'Select all types'}
|
|
90
|
+
>
|
|
91
|
+
<Filter className="w-4 h-4" />
|
|
92
|
+
</button>
|
|
93
|
+
|
|
94
|
+
{/* Type filter buttons */}
|
|
95
|
+
<div className="flex items-center gap-1">
|
|
96
|
+
{typeOptions.map(({ key, label, icon }) => (
|
|
97
|
+
<button
|
|
98
|
+
key={key}
|
|
99
|
+
onClick={() => toggleTypeFilter(key)}
|
|
100
|
+
className={cn(
|
|
101
|
+
'px-2.5 py-1 rounded-md text-xs font-medium transition-colors',
|
|
102
|
+
'flex items-center gap-1',
|
|
103
|
+
typeFilters[key]
|
|
104
|
+
? 'bg-blue-100 text-blue-700 hover:bg-blue-200'
|
|
105
|
+
: 'bg-slate-100 text-slate-400 hover:bg-slate-200'
|
|
106
|
+
)}
|
|
107
|
+
>
|
|
108
|
+
<span className="text-sm leading-none">{icon}</span>
|
|
109
|
+
<span>{label}</span>
|
|
110
|
+
</button>
|
|
111
|
+
))}
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
{/* Divider */}
|
|
115
|
+
<div className="w-px h-5 bg-slate-200 flex-shrink-0" />
|
|
116
|
+
|
|
117
|
+
{/* Group By inline */}
|
|
118
|
+
<GroupingSelector />
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
{/* Right: Search + Actions */}
|
|
122
|
+
<div className="flex items-center gap-2 flex-shrink-0">
|
|
123
|
+
|
|
124
|
+
{/* Column visibility dropdown */}
|
|
125
|
+
<div className="relative group">
|
|
126
|
+
<button className="px-2.5 py-1 rounded-md text-xs font-medium bg-slate-100 text-slate-700 hover:bg-slate-200 transition-colors flex items-center gap-1.5">
|
|
127
|
+
<Eye className="w-3.5 h-3.5" />
|
|
128
|
+
Columns
|
|
129
|
+
</button>
|
|
130
|
+
|
|
131
|
+
{/* Dropdown menu */}
|
|
132
|
+
<div className="absolute right-0 top-full mt-2 bg-white border border-slate-200 rounded-lg shadow-lg py-2 min-w-[180px] opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all z-10">
|
|
133
|
+
<div className="px-3 py-2 text-xs font-semibold text-slate-500 border-b border-slate-100">
|
|
134
|
+
COLUMN VISIBILITY
|
|
135
|
+
</div>
|
|
136
|
+
{columnOptions.map(({ key, label }) => (
|
|
137
|
+
<button
|
|
138
|
+
key={key}
|
|
139
|
+
onClick={() => toggleColumnVisibility(key)}
|
|
140
|
+
className="w-full px-3 py-2 text-sm text-left hover:bg-slate-50 flex items-center justify-between"
|
|
141
|
+
>
|
|
142
|
+
<span>{label}</span>
|
|
143
|
+
{columnVisibility[key] ? (
|
|
144
|
+
<Eye className="w-4 h-4 text-blue-600" />
|
|
145
|
+
) : (
|
|
146
|
+
<EyeOff className="w-4 h-4 text-slate-400" />
|
|
147
|
+
)}
|
|
148
|
+
</button>
|
|
149
|
+
))}
|
|
150
|
+
<div className="border-t border-slate-100 mt-2 pt-2 px-3">
|
|
151
|
+
<button
|
|
152
|
+
onClick={() => applyPreset('all')}
|
|
153
|
+
className="text-xs text-blue-600 hover:text-blue-700 font-medium"
|
|
154
|
+
>
|
|
155
|
+
Show All
|
|
156
|
+
</button>
|
|
157
|
+
<span className="text-slate-300 mx-2">β’</span>
|
|
158
|
+
<button
|
|
159
|
+
onClick={() => applyPreset('active')}
|
|
160
|
+
className="text-xs text-blue-600 hover:text-blue-700 font-medium"
|
|
161
|
+
>
|
|
162
|
+
Active Work
|
|
163
|
+
</button>
|
|
164
|
+
<span className="text-slate-300 mx-2">β’</span>
|
|
165
|
+
<button
|
|
166
|
+
onClick={() => applyPreset('hide-completed')}
|
|
167
|
+
className="text-xs text-blue-600 hover:text-blue-700 font-medium"
|
|
168
|
+
>
|
|
169
|
+
Hide Done
|
|
170
|
+
</button>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
{/* Search input */}
|
|
176
|
+
<div className="relative">
|
|
177
|
+
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-slate-400" />
|
|
178
|
+
<input
|
|
179
|
+
type="text"
|
|
180
|
+
placeholder="Search..."
|
|
181
|
+
value={searchInput}
|
|
182
|
+
onChange={handleSearchChange}
|
|
183
|
+
className="pl-8 pr-7 py-1 w-44 border border-slate-200 rounded-md text-xs focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
184
|
+
/>
|
|
185
|
+
{searchInput && (
|
|
186
|
+
<button
|
|
187
|
+
onClick={handleClearSearch}
|
|
188
|
+
className="absolute right-2 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600"
|
|
189
|
+
>
|
|
190
|
+
<X className="w-3.5 h-3.5" />
|
|
191
|
+
</button>
|
|
192
|
+
)}
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
{/* Refresh button */}
|
|
196
|
+
<button
|
|
197
|
+
onClick={refresh}
|
|
198
|
+
disabled={loading}
|
|
199
|
+
className={cn(
|
|
200
|
+
'p-1.5 rounded-md transition-colors',
|
|
201
|
+
loading
|
|
202
|
+
? 'bg-slate-100 text-slate-400 cursor-not-allowed'
|
|
203
|
+
: 'bg-slate-100 text-slate-700 hover:bg-slate-200'
|
|
204
|
+
)}
|
|
205
|
+
title="Refresh"
|
|
206
|
+
>
|
|
207
|
+
<RefreshCw className={cn('w-3.5 h-3.5', loading && 'animate-spin')} />
|
|
208
|
+
</button>
|
|
209
|
+
|
|
210
|
+
{/* Reset filters button */}
|
|
211
|
+
<button
|
|
212
|
+
onClick={resetFilters}
|
|
213
|
+
className="px-2.5 py-1 rounded-md text-xs font-medium bg-slate-100 text-slate-700 hover:bg-slate-200 transition-colors"
|
|
214
|
+
>
|
|
215
|
+
Reset
|
|
216
|
+
</button>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { LayoutGrid, Package, Box } from 'lucide-react';
|
|
2
|
+
import { useFilterStore } from '../../store/filterStore';
|
|
3
|
+
import { cn } from '../../lib/utils';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Grouping Selector Component
|
|
7
|
+
* Allows users to change how work items are grouped
|
|
8
|
+
*/
|
|
9
|
+
export function GroupingSelector() {
|
|
10
|
+
const { groupBy, setGroupBy } = useFilterStore();
|
|
11
|
+
|
|
12
|
+
const groupingOptions = [
|
|
13
|
+
{
|
|
14
|
+
value: 'status',
|
|
15
|
+
label: 'Status',
|
|
16
|
+
icon: LayoutGrid,
|
|
17
|
+
description: 'Traditional kanban columns',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
value: 'epic',
|
|
21
|
+
label: 'Epic',
|
|
22
|
+
icon: Package,
|
|
23
|
+
description: 'Hierarchical epic sections',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
value: 'type',
|
|
27
|
+
label: 'Type',
|
|
28
|
+
icon: Box,
|
|
29
|
+
description: 'Separate boards by type',
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className="flex items-center gap-0.5 bg-slate-100 rounded-md p-0.5">
|
|
35
|
+
{groupingOptions.map(({ value, label, icon: Icon, description }) => {
|
|
36
|
+
const isActive = groupBy === value;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<button
|
|
40
|
+
key={value}
|
|
41
|
+
onClick={() => setGroupBy(value)}
|
|
42
|
+
title={description}
|
|
43
|
+
className={cn(
|
|
44
|
+
'flex items-center gap-1.5 px-2.5 py-1 rounded-md text-xs font-medium transition-all',
|
|
45
|
+
isActive
|
|
46
|
+
? 'bg-white text-slate-900 shadow-sm'
|
|
47
|
+
: 'text-slate-600 hover:bg-slate-200'
|
|
48
|
+
)}
|
|
49
|
+
>
|
|
50
|
+
<Icon className="w-3.5 h-3.5" />
|
|
51
|
+
<span>{label}</span>
|
|
52
|
+
</button>
|
|
53
|
+
);
|
|
54
|
+
})}
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { useState, useEffect, useMemo } from 'react';
|
|
2
|
+
import { AnimatePresence, motion } from 'framer-motion';
|
|
3
|
+
import { KanbanColumn } from './KanbanColumn';
|
|
4
|
+
import { EpicSection } from './EpicSection';
|
|
5
|
+
import { useKanbanStore } from '../../store/kanbanStore';
|
|
6
|
+
import { useFilterStore } from '../../store/filterStore';
|
|
7
|
+
import { useGrouping } from '../../hooks/useGrouping';
|
|
8
|
+
import {
|
|
9
|
+
groupItemsByColumn,
|
|
10
|
+
COLUMN_ORDER,
|
|
11
|
+
STATUS_COLUMN_MAPPING,
|
|
12
|
+
} from '../../lib/status-grouping';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Kanban Board Component
|
|
16
|
+
* Main board container with columns and filtering
|
|
17
|
+
*/
|
|
18
|
+
export function KanbanBoard({ onCardClick, onStartProject, projectFilesReady, onEditProjectDoc, onStartSprintPlanning, onOpenSprintPlanningSelection, sponsorCallRunning }) {
|
|
19
|
+
const [selectedItem, setSelectedItem] = useState(null);
|
|
20
|
+
|
|
21
|
+
// Zustand stores
|
|
22
|
+
const { workItems, loading } = useKanbanStore();
|
|
23
|
+
const { typeFilters, columnVisibility, searchQuery, groupBy } = useFilterStore();
|
|
24
|
+
|
|
25
|
+
// Filter work items
|
|
26
|
+
const filteredItems = useMemo(() => {
|
|
27
|
+
// Apply type filters
|
|
28
|
+
let filtered = workItems.filter((item) => typeFilters[item.type]);
|
|
29
|
+
|
|
30
|
+
// Apply search filter
|
|
31
|
+
if (searchQuery.trim()) {
|
|
32
|
+
const query = searchQuery.toLowerCase();
|
|
33
|
+
filtered = filtered.filter(
|
|
34
|
+
(item) =>
|
|
35
|
+
item.name.toLowerCase().includes(query) ||
|
|
36
|
+
item.id.toLowerCase().includes(query) ||
|
|
37
|
+
(item.description && item.description.toLowerCase().includes(query)) ||
|
|
38
|
+
(item.epicName && item.epicName.toLowerCase().includes(query))
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return filtered;
|
|
43
|
+
}, [workItems, typeFilters, searchQuery]);
|
|
44
|
+
|
|
45
|
+
// Group work items based on grouping mode
|
|
46
|
+
const groupedData = useGrouping(filteredItems, groupBy);
|
|
47
|
+
|
|
48
|
+
// Handle card click
|
|
49
|
+
const handleCardClick = (item) => {
|
|
50
|
+
setSelectedItem(item);
|
|
51
|
+
onCardClick?.(item);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
if (loading && workItems.length === 0) {
|
|
55
|
+
return (
|
|
56
|
+
<div className="flex items-center justify-center py-12">
|
|
57
|
+
<div className="text-center">
|
|
58
|
+
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
|
|
59
|
+
<p className="text-slate-600">Loading work items...</p>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (filteredItems.length === 0 && workItems.length > 0) {
|
|
66
|
+
return (
|
|
67
|
+
<div className="flex items-center justify-center py-12">
|
|
68
|
+
<div className="text-center">
|
|
69
|
+
<div className="text-6xl mb-4">π</div>
|
|
70
|
+
<h3 className="text-xl font-semibold text-slate-900 mb-2">
|
|
71
|
+
No items match your filters
|
|
72
|
+
</h3>
|
|
73
|
+
<p className="text-slate-600">
|
|
74
|
+
Try adjusting your filters or search query
|
|
75
|
+
</p>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (workItems.length === 0) {
|
|
82
|
+
return (
|
|
83
|
+
<div className="flex items-center justify-center py-12">
|
|
84
|
+
<div className="text-center">
|
|
85
|
+
{onStartProject ? (
|
|
86
|
+
// Case A: project files missing, ceremony not running
|
|
87
|
+
<>
|
|
88
|
+
<div className="text-6xl mb-4">π</div>
|
|
89
|
+
<h3 className="text-xl font-semibold text-slate-900 mb-2">No Work Items</h3>
|
|
90
|
+
<p className="text-sm text-slate-500 mb-4">Run the Sponsor Call ceremony to set up your project.</p>
|
|
91
|
+
<button
|
|
92
|
+
onClick={onStartProject}
|
|
93
|
+
className="px-5 py-2.5 text-sm font-medium bg-slate-900 text-white rounded-lg hover:bg-slate-700 transition-colors"
|
|
94
|
+
>
|
|
95
|
+
π Start Project
|
|
96
|
+
</button>
|
|
97
|
+
</>
|
|
98
|
+
) : projectFilesReady ? (
|
|
99
|
+
// Case B: files exist, ready for sprint planning (or sprint planning is running)
|
|
100
|
+
<>
|
|
101
|
+
<div className="text-5xl mb-4">π</div>
|
|
102
|
+
<h3 className="text-lg font-semibold text-slate-900 mb-3">Ready for Sprint Planning</h3>
|
|
103
|
+
<p className="text-sm text-slate-500 mb-2 max-w-sm">
|
|
104
|
+
Your project is set up and ready for sprint planning.
|
|
105
|
+
</p>
|
|
106
|
+
<p className="text-sm text-slate-700 font-medium mb-5 max-w-sm">
|
|
107
|
+
Review <code className="bg-slate-100 px-1 rounded text-xs">doc.md</code> before starting β this file is the foundation of every Epic and Story that will be planned.
|
|
108
|
+
</p>
|
|
109
|
+
<div className="flex items-center justify-center gap-3 mb-6">
|
|
110
|
+
<button
|
|
111
|
+
onClick={onEditProjectDoc}
|
|
112
|
+
className="flex items-center gap-2 px-4 py-2 text-sm font-medium border border-slate-200 rounded-lg hover:bg-slate-50 transition-colors text-slate-700"
|
|
113
|
+
>
|
|
114
|
+
π Documentation
|
|
115
|
+
</button>
|
|
116
|
+
</div>
|
|
117
|
+
{onStartSprintPlanning ? (
|
|
118
|
+
<button
|
|
119
|
+
onClick={onStartSprintPlanning}
|
|
120
|
+
className="px-5 py-2.5 text-sm font-medium bg-slate-900 text-white rounded-lg hover:bg-slate-700 transition-colors"
|
|
121
|
+
>
|
|
122
|
+
π Start Sprint Planning
|
|
123
|
+
</button>
|
|
124
|
+
) : onOpenSprintPlanningSelection ? (
|
|
125
|
+
<button
|
|
126
|
+
onClick={onOpenSprintPlanningSelection}
|
|
127
|
+
className="flex items-center gap-2 px-5 py-2.5 text-sm font-medium bg-amber-500 text-white rounded-lg hover:bg-amber-600 transition-colors animate-pulse"
|
|
128
|
+
>
|
|
129
|
+
<span>β </span>
|
|
130
|
+
Sprint planning needs your input
|
|
131
|
+
</button>
|
|
132
|
+
) : (
|
|
133
|
+
<p className="text-xs text-slate-400">Sprint planning is runningβ¦</p>
|
|
134
|
+
)}
|
|
135
|
+
</>
|
|
136
|
+
) : sponsorCallRunning ? (
|
|
137
|
+
// Case C: no project files yet, sponsor call ceremony is running
|
|
138
|
+
<>
|
|
139
|
+
<div className="text-5xl mb-4">π</div>
|
|
140
|
+
<h3 className="text-lg font-semibold text-slate-900 mb-3">Sponsor Call is runningβ¦</h3>
|
|
141
|
+
<p className="text-sm text-slate-500 max-w-sm">
|
|
142
|
+
The Sponsor Call ceremony is generating your project definition. Work items will appear here once it completes.
|
|
143
|
+
</p>
|
|
144
|
+
</>
|
|
145
|
+
) : null}
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Render based on grouping mode
|
|
152
|
+
if (groupedData.mode === 'sections') {
|
|
153
|
+
// Epic or Type grouping - render sections
|
|
154
|
+
return (
|
|
155
|
+
<motion.div
|
|
156
|
+
key={groupBy}
|
|
157
|
+
initial={{ opacity: 0, x: -20 }}
|
|
158
|
+
animate={{ opacity: 1, x: 0 }}
|
|
159
|
+
exit={{ opacity: 0, x: 20 }}
|
|
160
|
+
transition={{ duration: 0.3 }}
|
|
161
|
+
>
|
|
162
|
+
{groupedData.groups.length === 0 ? (
|
|
163
|
+
<div className="text-center py-12">
|
|
164
|
+
<p className="text-slate-600">No groups to display</p>
|
|
165
|
+
</div>
|
|
166
|
+
) : (
|
|
167
|
+
groupedData.groups.map((group, index) => (
|
|
168
|
+
<motion.div
|
|
169
|
+
key={group.id}
|
|
170
|
+
initial={{ opacity: 0, y: 20 }}
|
|
171
|
+
animate={{ opacity: 1, y: 0 }}
|
|
172
|
+
transition={{ duration: 0.3, delay: index * 0.1 }}
|
|
173
|
+
>
|
|
174
|
+
<EpicSection
|
|
175
|
+
group={group}
|
|
176
|
+
columnVisibility={columnVisibility}
|
|
177
|
+
onCardClick={handleCardClick}
|
|
178
|
+
/>
|
|
179
|
+
</motion.div>
|
|
180
|
+
))
|
|
181
|
+
)}
|
|
182
|
+
</motion.div>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Default: Status grouping - render columns
|
|
187
|
+
return (
|
|
188
|
+
<motion.div
|
|
189
|
+
key={groupBy}
|
|
190
|
+
initial={{ opacity: 0, x: -20 }}
|
|
191
|
+
animate={{ opacity: 1, x: 0 }}
|
|
192
|
+
exit={{ opacity: 0, x: 20 }}
|
|
193
|
+
transition={{ duration: 0.3 }}
|
|
194
|
+
className="flex gap-4 overflow-x-auto pb-4"
|
|
195
|
+
>
|
|
196
|
+
<AnimatePresence mode="sync">
|
|
197
|
+
{groupedData.groups
|
|
198
|
+
.filter((group) => columnVisibility[group.name])
|
|
199
|
+
.map((group) => (
|
|
200
|
+
<KanbanColumn
|
|
201
|
+
key={group.id}
|
|
202
|
+
columnName={group.name}
|
|
203
|
+
statuses={STATUS_COLUMN_MAPPING[group.name]}
|
|
204
|
+
workItems={group.items}
|
|
205
|
+
onCardClick={handleCardClick}
|
|
206
|
+
/>
|
|
207
|
+
))}
|
|
208
|
+
</AnimatePresence>
|
|
209
|
+
</motion.div>
|
|
210
|
+
);
|
|
211
|
+
}
|