ai-design-system 0.1.47 → 0.1.49
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/components/ai-elements/code-block.tsx +2 -4
- package/components/ai-elements/prompt-input.tsx +7 -12
- package/components/ai-elements/queue.tsx +2 -2
- package/components/blocks/DataTable/DataTable.tsx +2 -2
- package/components/blocks/index.ts +0 -11
- package/components/composites/AppHeader/AppHeader.tsx +5 -1
- package/components/composites/AppHeader/interfaces.ts +1 -1
- package/components/composites/ApprovalCard/ApprovalCard.tsx +11 -11
- package/components/composites/CommentBox/CommentBox.tsx +3 -3
- package/components/{blocks → composites}/DashboardHeader/DashboardHeader.tsx +2 -0
- package/components/composites/DataTable/DragHandleCell.tsx +2 -2
- package/components/composites/DataTable/EnhancedDataTable.behaviors.stories.tsx +1 -1
- package/components/composites/DataTable/EnhancedDataTable.tsx +20 -9
- package/components/composites/DataTable/InlineEditCell.tsx +0 -2
- package/components/composites/DataTable/RowDetailDrawer.tsx +2 -3
- package/components/composites/DataTable/StatusCell.tsx +4 -4
- package/components/composites/DataTable/index.ts +0 -2
- package/components/composites/DocumentTabBar/DocumentTabBar.tsx +5 -4
- package/components/composites/EmptyState/EmptyState.stories.tsx +26 -0
- package/components/composites/EmptyState/EmptyState.tsx +38 -0
- package/components/composites/EmptyState/index.ts +1 -0
- package/components/composites/ProjectSwitcher/ProjectSwitcher.tsx +105 -0
- package/components/composites/ProjectSwitcher/index.ts +1 -0
- package/components/{blocks → composites}/PromptInput/PromptInput.stories.tsx +1 -1
- package/components/composites/StateNode/StateNode.tsx +5 -5
- package/components/composites/TransitionNode/TransitionNode.tsx +4 -4
- package/components/composites/TriggerNode/TriggerNode.tsx +2 -2
- package/components/composites/WorkflowToolbar/WorkflowToolbar.tsx +3 -4
- package/components/composites/index.ts +24 -0
- package/components/features/DashboardFeature/DashboardFeature.behaviors.stories.tsx +4 -0
- package/components/features/DashboardFeature/DashboardFeature.mocks.ts +1 -1
- package/components/features/DashboardFeature/DashboardFeature.stories.tsx +21 -0
- package/components/features/DashboardFeature/DashboardFeature.tsx +95 -24
- package/components/features/DashboardFeature/useDashboardFeature.d.ts +21 -0
- package/components/features/DashboardFeature/useDashboardFeature.mock.ts +39 -30
- package/components/features/PageLayout/PageLayout.stories.tsx +33 -10
- package/components/features/PageLayout/PageLayout.tsx +1 -1
- package/components/features/PageLayout/useDashboardIntegration.mock.ts +108 -0
- package/components/features/RefinementPanel/RefinementPanel.tsx +1 -1
- package/components/features/WorkflowBuilder/WorkflowBuilder.tsx +3 -6
- package/components/index.ts +4 -3
- package/components/primitives/Icon/Icon.tsx +48 -1
- package/dist/index.cjs +731 -228
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +28 -0
- package/dist/index.d.ts +123 -4
- package/dist/index.js +730 -230
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- /package/components/{blocks → composites}/DashboardChart/DashboardChart.tsx +0 -0
- /package/components/{blocks → composites}/DashboardChart/index.ts +0 -0
- /package/components/{blocks → composites}/DashboardHeader/index.ts +0 -0
- /package/components/{blocks → composites}/LayoutProvider/LayoutProvider.tsx +0 -0
- /package/components/{blocks → composites}/LayoutProvider/index.ts +0 -0
- /package/components/{blocks → composites}/PromptInput/PromptInput.tsx +0 -0
- /package/components/{blocks → composites}/PromptInput/index.ts +0 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { Icon } from "@/components/primitives/Icon"
|
|
3
|
+
|
|
4
|
+
import { Button } from "@/components/primitives/Button"
|
|
5
|
+
import {
|
|
6
|
+
Command,
|
|
7
|
+
CommandEmpty,
|
|
8
|
+
CommandGroup,
|
|
9
|
+
CommandInput,
|
|
10
|
+
CommandItem,
|
|
11
|
+
CommandList,
|
|
12
|
+
CommandSeparator,
|
|
13
|
+
} from "@/components/primitives/Command"
|
|
14
|
+
import {
|
|
15
|
+
Popover,
|
|
16
|
+
PopoverContent,
|
|
17
|
+
PopoverTrigger,
|
|
18
|
+
} from "@/components/primitives/Popover"
|
|
19
|
+
|
|
20
|
+
export interface Project {
|
|
21
|
+
id: string
|
|
22
|
+
name: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ProjectSwitcherProps {
|
|
26
|
+
projects: Project[]
|
|
27
|
+
selectedProjectId?: string | null
|
|
28
|
+
onSelectProject: (id: string) => void
|
|
29
|
+
onCreateProject: () => void
|
|
30
|
+
className?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const ProjectSwitcher = React.memo<ProjectSwitcherProps>(
|
|
34
|
+
({ projects, selectedProjectId, onSelectProject, onCreateProject, className }) => {
|
|
35
|
+
const [open, setOpen] = React.useState(false)
|
|
36
|
+
|
|
37
|
+
const selectedProject = projects.find((p) => p.id === selectedProjectId)
|
|
38
|
+
const displayLabel = selectedProject ? selectedProject.name : "Select Project..."
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
42
|
+
<PopoverTrigger asChild>
|
|
43
|
+
<Button
|
|
44
|
+
variant="ghost"
|
|
45
|
+
role="combobox"
|
|
46
|
+
aria-expanded={open}
|
|
47
|
+
className={`w-[240px] justify-between font-medium ${className || ""}`}
|
|
48
|
+
>
|
|
49
|
+
{displayLabel}
|
|
50
|
+
<Icon name="chevrons-up-down" size="sm" className="ml-2 shrink-0 opacity-50" />
|
|
51
|
+
</Button>
|
|
52
|
+
</PopoverTrigger>
|
|
53
|
+
<PopoverContent className="w-[240px] p-0" align="start">
|
|
54
|
+
<Command>
|
|
55
|
+
<CommandInput placeholder="Find Project..." />
|
|
56
|
+
<CommandList className="max-h-[300px]">
|
|
57
|
+
<CommandEmpty>
|
|
58
|
+
<div className="flex flex-col items-center justify-center p-6 text-center text-sm text-muted-foreground">
|
|
59
|
+
No projects, yet!
|
|
60
|
+
</div>
|
|
61
|
+
</CommandEmpty>
|
|
62
|
+
{projects.length > 0 && (
|
|
63
|
+
<CommandGroup heading="All Projects">
|
|
64
|
+
{projects.map((project) => (
|
|
65
|
+
<CommandItem
|
|
66
|
+
key={project.id}
|
|
67
|
+
value={project.name}
|
|
68
|
+
onSelect={() => {
|
|
69
|
+
onSelectProject(project.id)
|
|
70
|
+
setOpen(false)
|
|
71
|
+
}}
|
|
72
|
+
>
|
|
73
|
+
<Icon name="check"
|
|
74
|
+
className={`mr-2 h-4 w-4 ${
|
|
75
|
+
selectedProjectId === project.id ? "opacity-100" : "opacity-0"
|
|
76
|
+
}`}
|
|
77
|
+
/>
|
|
78
|
+
{project.name}
|
|
79
|
+
</CommandItem>
|
|
80
|
+
))}
|
|
81
|
+
</CommandGroup>
|
|
82
|
+
)}
|
|
83
|
+
</CommandList>
|
|
84
|
+
<CommandSeparator />
|
|
85
|
+
<div className="p-1">
|
|
86
|
+
<Button
|
|
87
|
+
variant="ghost"
|
|
88
|
+
className="w-full justify-start text-sm font-normal"
|
|
89
|
+
onClick={() => {
|
|
90
|
+
setOpen(false)
|
|
91
|
+
onCreateProject()
|
|
92
|
+
}}
|
|
93
|
+
>
|
|
94
|
+
<Icon name="plus" size="sm" className="mr-2" />
|
|
95
|
+
Create App
|
|
96
|
+
</Button>
|
|
97
|
+
</div>
|
|
98
|
+
</Command>
|
|
99
|
+
</PopoverContent>
|
|
100
|
+
</Popover>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
ProjectSwitcher.displayName = "ProjectSwitcher"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./ProjectSwitcher"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import type { NodeProps } from "@xyflow/react";
|
|
4
|
-
import { Check, EyeOff, XCircle, Zap } from "lucide-react";
|
|
5
4
|
import { memo } from "react";
|
|
5
|
+
import { Icon } from "@/components/primitives/Icon";
|
|
6
6
|
import {
|
|
7
7
|
Node,
|
|
8
8
|
NodeDescription,
|
|
@@ -45,10 +45,10 @@ const StatusBadge = ({
|
|
|
45
45
|
)}
|
|
46
46
|
>
|
|
47
47
|
{status === "success" && (
|
|
48
|
-
<
|
|
48
|
+
<Icon name="check" className="size-3.5 text-white" strokeWidth={2.5} />
|
|
49
49
|
)}
|
|
50
50
|
{status === "error" && (
|
|
51
|
-
<
|
|
51
|
+
<Icon name="circle-x" className="size-3.5 text-white" strokeWidth={2.5} />
|
|
52
52
|
)}
|
|
53
53
|
</div>
|
|
54
54
|
);
|
|
@@ -79,7 +79,7 @@ export const StateNode = memo(({ data, selected, id }: StateNodeProps) => {
|
|
|
79
79
|
{/* Disabled badge in top left */}
|
|
80
80
|
{isDisabled && (
|
|
81
81
|
<div className="absolute top-1 left-1 rounded-full bg-gray-500/50 p-0.5">
|
|
82
|
-
<
|
|
82
|
+
<Icon name="eye-off" size="xs" className="text-white" />
|
|
83
83
|
</div>
|
|
84
84
|
)}
|
|
85
85
|
|
|
@@ -87,7 +87,7 @@ export const StateNode = memo(({ data, selected, id }: StateNodeProps) => {
|
|
|
87
87
|
<StatusBadge status={status} />
|
|
88
88
|
|
|
89
89
|
<div className="flex h-full w-full items-center justify-center gap-1.5 px-3 py-2">
|
|
90
|
-
<
|
|
90
|
+
<Icon name="zap" size="xs" className="shrink-0 text-primary" strokeWidth={1.5} />
|
|
91
91
|
<div className="min-w-0 flex-1 text-center">
|
|
92
92
|
<NodeTitle className="line-clamp-2 text-center text-xs font-medium leading-tight" title={displayTitle}>
|
|
93
93
|
{displayTitle}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import type { NodeProps } from "@xyflow/react";
|
|
4
|
-
import { Check, GitBranch, XCircle } from "lucide-react";
|
|
5
4
|
import { memo } from "react";
|
|
5
|
+
import { Icon } from "@/components/primitives/Icon";
|
|
6
6
|
import {
|
|
7
7
|
Node,
|
|
8
8
|
NodeDescription,
|
|
@@ -70,16 +70,16 @@ export const TransitionNode = memo(
|
|
|
70
70
|
)}
|
|
71
71
|
>
|
|
72
72
|
{status === "success" && (
|
|
73
|
-
<
|
|
73
|
+
<Icon name="check" size="xs" className="text-white" strokeWidth={2.5} />
|
|
74
74
|
)}
|
|
75
75
|
{status === "error" && (
|
|
76
|
-
<
|
|
76
|
+
<Icon name="circle-x" size="xs" className="text-white" strokeWidth={2.5} />
|
|
77
77
|
)}
|
|
78
78
|
</div>
|
|
79
79
|
)}
|
|
80
80
|
|
|
81
81
|
<div className="flex h-full w-full items-center justify-center gap-1.5 px-3 py-2">
|
|
82
|
-
<
|
|
82
|
+
<Icon name="git-branch" size="xs" className="shrink-0 text-muted-foreground" strokeWidth={1.5} />
|
|
83
83
|
<div className="min-w-0 flex-1 text-center">
|
|
84
84
|
<NodeTitle className="line-clamp-2 text-center text-xs font-medium leading-tight" title={displayTitle}>
|
|
85
85
|
{displayTitle}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import type { NodeProps } from "@xyflow/react";
|
|
4
|
-
import { Play } from "lucide-react";
|
|
5
4
|
import { memo } from "react";
|
|
5
|
+
import { Icon } from "@/components/primitives/Icon";
|
|
6
6
|
import {
|
|
7
7
|
Node,
|
|
8
8
|
NodeDescription,
|
|
@@ -43,7 +43,7 @@ export const TriggerNode = memo(({ data, selected, id }: TriggerNodeProps) => {
|
|
|
43
43
|
status={status}
|
|
44
44
|
>
|
|
45
45
|
<div className="flex h-full w-full items-center justify-center gap-1.5 px-3 py-2">
|
|
46
|
-
<
|
|
46
|
+
<Icon name="play" size="xs" className="shrink-0 text-purple-600 dark:text-purple-400" strokeWidth={1.5} />
|
|
47
47
|
<div className="min-w-0 flex-1 text-center">
|
|
48
48
|
<NodeTitle className="line-clamp-2 text-center text-xs font-medium leading-tight" title={displayTitle}>
|
|
49
49
|
{displayTitle}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { Check, ChevronDown, Loader2 } from "lucide-react";
|
|
4
3
|
import { Button } from "@/components/primitives/Button";
|
|
5
4
|
import { ButtonGroup } from "@/components/primitives/ButtonGroup";
|
|
6
5
|
import {
|
|
@@ -48,7 +47,7 @@ export function WorkflowToolbarActions({
|
|
|
48
47
|
variant="secondary"
|
|
49
48
|
>
|
|
50
49
|
{action.loading ? (
|
|
51
|
-
<
|
|
50
|
+
<Icon name="loader-2" size="sm" className="animate-spin" />
|
|
52
51
|
) : typeof action.icon === 'string' ? (
|
|
53
52
|
<Icon name={action.icon} size="sm" />
|
|
54
53
|
) : (
|
|
@@ -133,7 +132,7 @@ export function WorkflowToolbar({
|
|
|
133
132
|
variant="secondary"
|
|
134
133
|
>
|
|
135
134
|
{currentVersion?.label ?? "Version"}
|
|
136
|
-
<
|
|
135
|
+
<Icon name="chevron-down" size="xs" className="ml-1 opacity-50" />
|
|
137
136
|
</Button>
|
|
138
137
|
</DropdownMenuTrigger>
|
|
139
138
|
<DropdownMenuContent align="start">
|
|
@@ -144,7 +143,7 @@ export function WorkflowToolbar({
|
|
|
144
143
|
onClick={() => onVersionSelect?.(v.id)}
|
|
145
144
|
>
|
|
146
145
|
{v.label}
|
|
147
|
-
{v.id === currentVersionId && <
|
|
146
|
+
{v.id === currentVersionId && <Icon name="check" size="sm" className="ml-4" />}
|
|
148
147
|
</DropdownMenuItem>
|
|
149
148
|
))}
|
|
150
149
|
</DropdownMenuContent>
|
|
@@ -158,3 +158,27 @@ export type { InboxListItem, InboxListProps } from './InboxList'
|
|
|
158
158
|
// ModeSwitcher Composite
|
|
159
159
|
export { ModeSwitcher } from './ModeSwitcher'
|
|
160
160
|
export type { Mode, ModeSwitcherProps } from './ModeSwitcher'
|
|
161
|
+
|
|
162
|
+
// EmptyState Composite
|
|
163
|
+
export { EmptyState } from './EmptyState'
|
|
164
|
+
export type { EmptyStateProps } from './EmptyState'
|
|
165
|
+
|
|
166
|
+
// ProjectSwitcher Composite
|
|
167
|
+
export { ProjectSwitcher } from './ProjectSwitcher'
|
|
168
|
+
export type { ProjectSwitcherProps, Project } from './ProjectSwitcher'
|
|
169
|
+
|
|
170
|
+
// DashboardChart Composite
|
|
171
|
+
export { DashboardChart } from './DashboardChart'
|
|
172
|
+
export type { DashboardChartProps } from './DashboardChart'
|
|
173
|
+
|
|
174
|
+
// DashboardHeader Composite
|
|
175
|
+
export { DashboardHeader } from './DashboardHeader'
|
|
176
|
+
export type { DashboardHeaderProps } from './DashboardHeader'
|
|
177
|
+
|
|
178
|
+
// LayoutProvider Composite
|
|
179
|
+
export { LayoutProvider } from './LayoutProvider'
|
|
180
|
+
export type { LayoutProviderProps } from './LayoutProvider'
|
|
181
|
+
|
|
182
|
+
// PromptInput Composite
|
|
183
|
+
export { PromptInput } from './PromptInput'
|
|
184
|
+
export type { PromptInputBlockProps } from './PromptInput'
|
|
@@ -4,8 +4,10 @@ import { Toaster } from "sonner"
|
|
|
4
4
|
|
|
5
5
|
import { DashboardFeature } from "./DashboardFeature"
|
|
6
6
|
import {
|
|
7
|
+
dashboardCreateFields,
|
|
7
8
|
dashboardKpis,
|
|
8
9
|
dashboardRows,
|
|
10
|
+
dashboardTableSchema,
|
|
9
11
|
visitorsSeries,
|
|
10
12
|
} from "./DashboardFeature.mocks"
|
|
11
13
|
|
|
@@ -34,6 +36,8 @@ const args = {
|
|
|
34
36
|
kpis: dashboardKpis,
|
|
35
37
|
rows: dashboardRows,
|
|
36
38
|
visitorsSeries,
|
|
39
|
+
tableSchema: dashboardTableSchema,
|
|
40
|
+
createFields: dashboardCreateFields,
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
const interactionSpy = fn()
|
|
@@ -61,3 +61,24 @@ export const WithStateManagement: Story = {
|
|
|
61
61
|
)
|
|
62
62
|
},
|
|
63
63
|
}
|
|
64
|
+
|
|
65
|
+
export const EmptyState: Story = {
|
|
66
|
+
args: {
|
|
67
|
+
kpis: [],
|
|
68
|
+
rows: [],
|
|
69
|
+
tableSchema: dashboardTableSchema,
|
|
70
|
+
visitorsSeries: [],
|
|
71
|
+
createFields: [
|
|
72
|
+
{ name: "name", label: "App Name", type: "text", required: true },
|
|
73
|
+
{ name: "description", label: "Description", type: "text" },
|
|
74
|
+
],
|
|
75
|
+
emptyState: {
|
|
76
|
+
title: "Create your first App",
|
|
77
|
+
description: "Get started by creating a new app to build workflows and manage tasks.",
|
|
78
|
+
actionLabel: "Create App",
|
|
79
|
+
},
|
|
80
|
+
quickCreateFields: [
|
|
81
|
+
{ name: "name", label: "App Name", type: "text", required: true },
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from "react"
|
|
2
|
-
|
|
3
|
-
import { DashboardChart } from "@/components/
|
|
2
|
+
import { EmptyState } from "@/components/composites/EmptyState"
|
|
3
|
+
import { DashboardChart } from "@/components/composites/DashboardChart"
|
|
4
4
|
import { DashboardMetrics } from "@/components/blocks/DashboardMetrics"
|
|
5
5
|
import { DataTable } from "@/components/blocks/DataTable"
|
|
6
6
|
import { type DashboardRow } from "@/components/composites/DataTable"
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import type { DynamicTableSchema } from "ui-schema-contracts"
|
|
14
14
|
|
|
15
15
|
import type {
|
|
16
|
+
DashboardApp,
|
|
16
17
|
DashboardFeatureActionHandlers,
|
|
17
18
|
DashboardKpi,
|
|
18
19
|
DashboardSeriesPoint,
|
|
@@ -27,6 +28,25 @@ export interface DashboardFeatureProps {
|
|
|
27
28
|
createEntityName?: string
|
|
28
29
|
createFields: FormReportsFieldDefinition[]
|
|
29
30
|
createButtonLabel?: string
|
|
31
|
+
apps?: DashboardApp[]
|
|
32
|
+
currentAppId?: string
|
|
33
|
+
quickCreateEntityName?: string
|
|
34
|
+
quickCreateFields?: FormReportsFieldDefinition[]
|
|
35
|
+
quickCreateButtonLabel?: string
|
|
36
|
+
/**
|
|
37
|
+
* Optional empty state configuration. When provided and rows is empty, this state is shown instead of the table.
|
|
38
|
+
*/
|
|
39
|
+
emptyState?: {
|
|
40
|
+
title: string
|
|
41
|
+
description: string
|
|
42
|
+
actionLabel: string
|
|
43
|
+
}
|
|
44
|
+
/** Controlled state for external creation drawer open */
|
|
45
|
+
createDrawerOpen?: boolean
|
|
46
|
+
onOpenCreateDrawerChange?: (open: boolean) => void
|
|
47
|
+
/**
|
|
48
|
+
* Additional CSS classes
|
|
49
|
+
*/
|
|
30
50
|
className?: string
|
|
31
51
|
}
|
|
32
52
|
|
|
@@ -47,17 +67,41 @@ export const DashboardFeature = React.memo<DashboardFeatureProps>(
|
|
|
47
67
|
createEntityName = "Section",
|
|
48
68
|
createFields,
|
|
49
69
|
createButtonLabel = "Create",
|
|
70
|
+
apps,
|
|
71
|
+
currentAppId,
|
|
72
|
+
quickCreateEntityName = "App",
|
|
73
|
+
quickCreateFields,
|
|
74
|
+
quickCreateButtonLabel = "Quick Create",
|
|
75
|
+
emptyState,
|
|
76
|
+
createDrawerOpen: externalDrawerOpen,
|
|
77
|
+
onOpenCreateDrawerChange: externalOnDrawerOpenChange,
|
|
50
78
|
className,
|
|
51
79
|
}) => {
|
|
52
|
-
const [
|
|
53
|
-
const
|
|
80
|
+
const [internalDrawerOpen, setInternalDrawerOpen] = React.useState(false)
|
|
81
|
+
const drawerOpen = externalDrawerOpen !== undefined ? externalDrawerOpen : internalDrawerOpen
|
|
82
|
+
const setDrawerOpen = externalOnDrawerOpenChange || setInternalDrawerOpen
|
|
83
|
+
|
|
84
|
+
const [drawerMode, setDrawerMode] = React.useState<'table' | 'quick-create' | null>(null)
|
|
85
|
+
|
|
86
|
+
const activeFields = drawerMode === 'quick-create' && quickCreateFields ? quickCreateFields : createFields
|
|
87
|
+
const activeEntityName = drawerMode === 'quick-create' ? quickCreateEntityName : createEntityName
|
|
88
|
+
const activeButtonLabel = drawerMode === 'quick-create' ? quickCreateButtonLabel : createButtonLabel
|
|
89
|
+
|
|
90
|
+
const [values, setValues] = React.useState<FormReportsValues>(() => buildInitialValues(activeFields))
|
|
54
91
|
|
|
55
92
|
React.useEffect(() => {
|
|
56
|
-
setValues(buildInitialValues(
|
|
57
|
-
}, [
|
|
93
|
+
setValues(buildInitialValues(activeFields))
|
|
94
|
+
}, [activeFields])
|
|
58
95
|
|
|
59
96
|
const openCreateDrawer = React.useCallback(() => {
|
|
60
97
|
actionHandlers?.table?.onCreateClick?.()
|
|
98
|
+
setDrawerMode('table')
|
|
99
|
+
setDrawerOpen(true)
|
|
100
|
+
actionHandlers?.onCreateDrawerOpenChange?.(true)
|
|
101
|
+
}, [actionHandlers])
|
|
102
|
+
|
|
103
|
+
const openQuickCreateDrawer = React.useCallback(() => {
|
|
104
|
+
setDrawerMode('quick-create')
|
|
61
105
|
setDrawerOpen(true)
|
|
62
106
|
actionHandlers?.onCreateDrawerOpenChange?.(true)
|
|
63
107
|
}, [actionHandlers])
|
|
@@ -65,6 +109,9 @@ export const DashboardFeature = React.memo<DashboardFeatureProps>(
|
|
|
65
109
|
const handleDrawerOpenChange = React.useCallback(
|
|
66
110
|
(open: boolean) => {
|
|
67
111
|
setDrawerOpen(open)
|
|
112
|
+
if (!open) {
|
|
113
|
+
setTimeout(() => setDrawerMode(null), 300)
|
|
114
|
+
}
|
|
68
115
|
actionHandlers?.onCreateDrawerOpenChange?.(open)
|
|
69
116
|
},
|
|
70
117
|
[actionHandlers]
|
|
@@ -87,37 +134,59 @@ export const DashboardFeature = React.memo<DashboardFeatureProps>(
|
|
|
87
134
|
|
|
88
135
|
const handleCreateSubmit = React.useCallback(
|
|
89
136
|
async (nextValues: FormReportsValues) => {
|
|
90
|
-
|
|
137
|
+
if (drawerMode === 'quick-create') {
|
|
138
|
+
await Promise.resolve(actionHandlers?.onQuickCreateSubmit?.(nextValues))
|
|
139
|
+
} else {
|
|
140
|
+
await Promise.resolve(actionHandlers?.onCreateSubmit?.(nextValues))
|
|
141
|
+
}
|
|
91
142
|
setDrawerOpen(false)
|
|
143
|
+
setTimeout(() => setDrawerMode(null), 300)
|
|
92
144
|
actionHandlers?.onCreateDrawerOpenChange?.(false)
|
|
93
145
|
setValues(buildInitialValues(createFields))
|
|
94
146
|
},
|
|
95
|
-
[actionHandlers, createFields]
|
|
147
|
+
[actionHandlers, createFields, drawerMode]
|
|
96
148
|
)
|
|
97
149
|
|
|
98
150
|
return (
|
|
99
151
|
<div className={`flex flex-1 flex-col gap-4 py-4 md:gap-6 md:py-6 ${className ?? ""}`}>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
152
|
+
{rows.length === 0 && emptyState ? (
|
|
153
|
+
<div className="flex flex-1 items-center justify-center min-h-[500px]">
|
|
154
|
+
<EmptyState
|
|
155
|
+
title={emptyState.title}
|
|
156
|
+
description={emptyState.description}
|
|
157
|
+
actionLabel={emptyState.actionLabel}
|
|
158
|
+
onAction={quickCreateFields ? openQuickCreateDrawer : openCreateDrawer}
|
|
159
|
+
/>
|
|
160
|
+
</div>
|
|
161
|
+
) : (
|
|
162
|
+
<>
|
|
163
|
+
{rows.length > 0 && (
|
|
164
|
+
<>
|
|
165
|
+
<DashboardMetrics items={kpis} />
|
|
166
|
+
<DashboardChart
|
|
167
|
+
series={visitorsSeries}
|
|
168
|
+
onTimeRangeChange={actionHandlers?.onChartTimeRangeChange}
|
|
169
|
+
/>
|
|
170
|
+
</>
|
|
171
|
+
)}
|
|
172
|
+
<DataTable
|
|
173
|
+
rows={rows}
|
|
174
|
+
tableSchema={tableSchema}
|
|
175
|
+
handlers={actionHandlers?.table}
|
|
176
|
+
onCreateClick={openCreateDrawer}
|
|
177
|
+
createButtonLabel={createButtonLabel}
|
|
178
|
+
/>
|
|
179
|
+
</>
|
|
180
|
+
)}
|
|
112
181
|
|
|
113
182
|
<FormReportsDrawerForm
|
|
114
183
|
open={drawerOpen}
|
|
115
184
|
onOpenChange={handleDrawerOpenChange}
|
|
116
|
-
title={`Create ${
|
|
117
|
-
description={`Enter the details for your new ${
|
|
118
|
-
fields={
|
|
185
|
+
title={`Create ${activeEntityName}`}
|
|
186
|
+
description={`Enter the details for your new ${activeEntityName.toLowerCase()}.`}
|
|
187
|
+
fields={activeFields}
|
|
119
188
|
values={values}
|
|
120
|
-
submitLabel={
|
|
189
|
+
submitLabel={activeButtonLabel}
|
|
121
190
|
onFieldChange={handleFieldChange}
|
|
122
191
|
onFieldBlur={handleFieldBlur}
|
|
123
192
|
onSubmit={handleCreateSubmit}
|
|
@@ -129,3 +198,5 @@ export const DashboardFeature = React.memo<DashboardFeatureProps>(
|
|
|
129
198
|
)
|
|
130
199
|
|
|
131
200
|
DashboardFeature.displayName = "DashboardFeature"
|
|
201
|
+
|
|
202
|
+
|
|
@@ -30,6 +30,11 @@ export interface DashboardSeriesPoint {
|
|
|
30
30
|
|
|
31
31
|
export type DashboardChartTimeRange = "90d" | "30d" | "7d"
|
|
32
32
|
|
|
33
|
+
export interface DashboardApp {
|
|
34
|
+
id: string
|
|
35
|
+
name: string
|
|
36
|
+
}
|
|
37
|
+
|
|
33
38
|
/**
|
|
34
39
|
* Consumer callbacks for DashboardFeature interactions.
|
|
35
40
|
* Use this contract to wire feature UI actions to app-specific logic.
|
|
@@ -47,8 +52,12 @@ export interface DashboardFeatureActionHandlers {
|
|
|
47
52
|
onCreateFieldBlur?: (name: string, value: FormReportsValue, values: FormReportsValues) => void
|
|
48
53
|
/** Called when create drawer form is submitted. */
|
|
49
54
|
onCreateSubmit?: (values: FormReportsValues) => void | Promise<void>
|
|
55
|
+
/** Called when quick create drawer form is submitted. */
|
|
56
|
+
onQuickCreateSubmit?: (values: FormReportsValues) => void | Promise<void>
|
|
50
57
|
/** Called when create drawer cancel is clicked. */
|
|
51
58
|
onCreateCancel?: () => void
|
|
59
|
+
/** Called when a different app is selected in the app switcher. */
|
|
60
|
+
onAppChange?: (appId: string) => void
|
|
52
61
|
}
|
|
53
62
|
|
|
54
63
|
export interface UseDashboardFeatureReturn {
|
|
@@ -59,7 +68,19 @@ export interface UseDashboardFeatureReturn {
|
|
|
59
68
|
createEntityName?: string
|
|
60
69
|
createFields: FormReportsFieldDefinition[]
|
|
61
70
|
createButtonLabel?: string
|
|
71
|
+
apps?: DashboardApp[]
|
|
72
|
+
currentAppId?: string
|
|
73
|
+
quickCreateEntityName?: string
|
|
74
|
+
quickCreateFields?: FormReportsFieldDefinition[]
|
|
75
|
+
quickCreateButtonLabel?: string
|
|
62
76
|
actionHandlers?: DashboardFeatureActionHandlers
|
|
77
|
+
emptyState?: {
|
|
78
|
+
title: string
|
|
79
|
+
description: string
|
|
80
|
+
actionLabel: string
|
|
81
|
+
}
|
|
82
|
+
createDrawerOpen?: boolean
|
|
83
|
+
onOpenCreateDrawerChange?: (open: boolean) => void
|
|
63
84
|
}
|
|
64
85
|
|
|
65
86
|
export function useDashboardFeature(): UseDashboardFeatureReturn
|
|
@@ -11,45 +11,54 @@ import {
|
|
|
11
11
|
} from "./DashboardFeature.mocks"
|
|
12
12
|
import type { UseDashboardFeatureReturn } from "./useDashboardFeature.d"
|
|
13
13
|
|
|
14
|
-
export
|
|
15
|
-
const
|
|
16
|
-
() => dashboardRows.map((row) => ({ ...row })),
|
|
17
|
-
[]
|
|
18
|
-
)
|
|
14
|
+
export const useDashboardFeatureMock = (): UseDashboardFeatureReturn => {
|
|
15
|
+
const [rows, setRows] = useState<DashboardRow[]>([])
|
|
19
16
|
|
|
20
|
-
const
|
|
17
|
+
const handleCreateSubmit = async (values: FormReportsValues) => {
|
|
18
|
+
const nextId = rows.reduce((max, row) => Math.max(max, Number(row.id)), 0) + 1
|
|
19
|
+
const payload = {
|
|
20
|
+
name: values.name || `App ${nextId}`,
|
|
21
|
+
description: values.description || "",
|
|
22
|
+
tenant_id: "mock-tenant-id",
|
|
23
|
+
}
|
|
24
|
+
console.log("[Mock] Create submitted with simulated BFF payload:", payload)
|
|
25
|
+
|
|
26
|
+
await new Promise(resolve => setTimeout(resolve, 500))
|
|
27
|
+
setRows(prev => {
|
|
28
|
+
const newRow: DashboardRow = {
|
|
29
|
+
id: nextId,
|
|
30
|
+
header: payload.name,
|
|
31
|
+
type: "App",
|
|
32
|
+
status: "Active",
|
|
33
|
+
target: "0",
|
|
34
|
+
limit: "0",
|
|
35
|
+
reviewer: "Unassigned",
|
|
36
|
+
}
|
|
37
|
+
return [newRow, ...prev]
|
|
38
|
+
})
|
|
39
|
+
}
|
|
21
40
|
|
|
22
41
|
return {
|
|
42
|
+
kpis: dashboardKpis,
|
|
23
43
|
rows,
|
|
24
44
|
tableSchema: dashboardTableSchema,
|
|
25
|
-
kpis: dashboardKpis,
|
|
26
45
|
visitorsSeries,
|
|
27
|
-
createFields:
|
|
46
|
+
createFields: [
|
|
47
|
+
{ name: "name", label: "App Name", type: "text", required: true },
|
|
48
|
+
{ name: "description", label: "Description", type: "text" },
|
|
49
|
+
],
|
|
50
|
+
emptyState: {
|
|
51
|
+
title: "Create your first App",
|
|
52
|
+
description: "Get started by creating a new app to build workflows and manage tasks.",
|
|
53
|
+
actionLabel: "Create App",
|
|
54
|
+
},
|
|
28
55
|
actionHandlers: {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const nextId = prev.reduce((max, row) => Math.max(max, Number(row.id)), 0) + 1
|
|
32
|
-
const nextRow: DashboardRow = {
|
|
33
|
-
id: nextId,
|
|
34
|
-
header: String(values.slug || `section-${nextId}`),
|
|
35
|
-
type: String(values.type || "narrative"),
|
|
36
|
-
status: values.enabled ? "Done" : "Not Started",
|
|
37
|
-
target: "0",
|
|
38
|
-
limit: "0",
|
|
39
|
-
reviewer: "Assign reviewer",
|
|
40
|
-
}
|
|
41
|
-
return [nextRow, ...prev]
|
|
42
|
-
})
|
|
43
|
-
},
|
|
56
|
+
onChartTimeRangeChange: (range) => console.log("[Mock] Chart range changed:", range),
|
|
57
|
+
onCreateSubmit: handleCreateSubmit,
|
|
44
58
|
table: {
|
|
45
59
|
onDeleteRow: (row) => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
onCopyRow: (row) => {
|
|
49
|
-
setRows((prev) => {
|
|
50
|
-
const nextId = prev.reduce((max, item) => Math.max(max, Number(item.id)), 0) + 1
|
|
51
|
-
return [{ ...row, id: nextId, header: `${String(row.header ?? "Section")} Copy` }, ...prev]
|
|
52
|
-
})
|
|
60
|
+
console.log("[Mock] Delete:", row.id)
|
|
61
|
+
setRows(prev => prev.filter(r => String(r.id) !== String(row.id)))
|
|
53
62
|
},
|
|
54
63
|
},
|
|
55
64
|
},
|