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.
Files changed (56) hide show
  1. package/components/ai-elements/code-block.tsx +2 -4
  2. package/components/ai-elements/prompt-input.tsx +7 -12
  3. package/components/ai-elements/queue.tsx +2 -2
  4. package/components/blocks/DataTable/DataTable.tsx +2 -2
  5. package/components/blocks/index.ts +0 -11
  6. package/components/composites/AppHeader/AppHeader.tsx +5 -1
  7. package/components/composites/AppHeader/interfaces.ts +1 -1
  8. package/components/composites/ApprovalCard/ApprovalCard.tsx +11 -11
  9. package/components/composites/CommentBox/CommentBox.tsx +3 -3
  10. package/components/{blocks → composites}/DashboardHeader/DashboardHeader.tsx +2 -0
  11. package/components/composites/DataTable/DragHandleCell.tsx +2 -2
  12. package/components/composites/DataTable/EnhancedDataTable.behaviors.stories.tsx +1 -1
  13. package/components/composites/DataTable/EnhancedDataTable.tsx +20 -9
  14. package/components/composites/DataTable/InlineEditCell.tsx +0 -2
  15. package/components/composites/DataTable/RowDetailDrawer.tsx +2 -3
  16. package/components/composites/DataTable/StatusCell.tsx +4 -4
  17. package/components/composites/DataTable/index.ts +0 -2
  18. package/components/composites/DocumentTabBar/DocumentTabBar.tsx +5 -4
  19. package/components/composites/EmptyState/EmptyState.stories.tsx +26 -0
  20. package/components/composites/EmptyState/EmptyState.tsx +38 -0
  21. package/components/composites/EmptyState/index.ts +1 -0
  22. package/components/composites/ProjectSwitcher/ProjectSwitcher.tsx +105 -0
  23. package/components/composites/ProjectSwitcher/index.ts +1 -0
  24. package/components/{blocks → composites}/PromptInput/PromptInput.stories.tsx +1 -1
  25. package/components/composites/StateNode/StateNode.tsx +5 -5
  26. package/components/composites/TransitionNode/TransitionNode.tsx +4 -4
  27. package/components/composites/TriggerNode/TriggerNode.tsx +2 -2
  28. package/components/composites/WorkflowToolbar/WorkflowToolbar.tsx +3 -4
  29. package/components/composites/index.ts +24 -0
  30. package/components/features/DashboardFeature/DashboardFeature.behaviors.stories.tsx +4 -0
  31. package/components/features/DashboardFeature/DashboardFeature.mocks.ts +1 -1
  32. package/components/features/DashboardFeature/DashboardFeature.stories.tsx +21 -0
  33. package/components/features/DashboardFeature/DashboardFeature.tsx +95 -24
  34. package/components/features/DashboardFeature/useDashboardFeature.d.ts +21 -0
  35. package/components/features/DashboardFeature/useDashboardFeature.mock.ts +39 -30
  36. package/components/features/PageLayout/PageLayout.stories.tsx +33 -10
  37. package/components/features/PageLayout/PageLayout.tsx +1 -1
  38. package/components/features/PageLayout/useDashboardIntegration.mock.ts +108 -0
  39. package/components/features/RefinementPanel/RefinementPanel.tsx +1 -1
  40. package/components/features/WorkflowBuilder/WorkflowBuilder.tsx +3 -6
  41. package/components/index.ts +4 -3
  42. package/components/primitives/Icon/Icon.tsx +48 -1
  43. package/dist/index.cjs +731 -228
  44. package/dist/index.cjs.map +1 -1
  45. package/dist/index.css +28 -0
  46. package/dist/index.d.ts +123 -4
  47. package/dist/index.js +730 -230
  48. package/dist/index.js.map +1 -1
  49. package/package.json +1 -1
  50. /package/components/{blocks → composites}/DashboardChart/DashboardChart.tsx +0 -0
  51. /package/components/{blocks → composites}/DashboardChart/index.ts +0 -0
  52. /package/components/{blocks → composites}/DashboardHeader/index.ts +0 -0
  53. /package/components/{blocks → composites}/LayoutProvider/LayoutProvider.tsx +0 -0
  54. /package/components/{blocks → composites}/LayoutProvider/index.ts +0 -0
  55. /package/components/{blocks → composites}/PromptInput/PromptInput.tsx +0 -0
  56. /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"
@@ -3,7 +3,7 @@ import { PromptInput } from "./PromptInput";
3
3
  import { useState } from "react";
4
4
 
5
5
  const meta: Meta<typeof PromptInput> = {
6
- title: "Blocks/PromptInput",
6
+ title: "Composites/PromptInput",
7
7
  component: PromptInput,
8
8
  parameters: {
9
9
  layout: "padded",
@@ -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
- <Check className="size-3.5 text-white" strokeWidth={2.5} />
48
+ <Icon name="check" className="size-3.5 text-white" strokeWidth={2.5} />
49
49
  )}
50
50
  {status === "error" && (
51
- <XCircle className="size-3.5 text-white" strokeWidth={2.5} />
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
- <EyeOff className="size-3 text-white" />
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
- <Zap className="size-3 shrink-0 text-primary" strokeWidth={1.5} />
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
- <Check className="size-3 text-white" strokeWidth={2.5} />
73
+ <Icon name="check" size="xs" className="text-white" strokeWidth={2.5} />
74
74
  )}
75
75
  {status === "error" && (
76
- <XCircle className="size-3 text-white" strokeWidth={2.5} />
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
- <GitBranch className="size-3 shrink-0 text-muted-foreground" strokeWidth={1.5} />
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
- <Play className="size-3 shrink-0 text-purple-600 dark:text-purple-400" strokeWidth={1.5} />
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
- <Loader2 className="size-4 animate-spin" />
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
- <ChevronDown className="ml-1 size-3 opacity-50" />
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 && <Check className="ml-4 size-4" />}
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()
@@ -18,7 +18,7 @@ const dashboardTableSchemaInput = {
18
18
  {
19
19
  key: "header",
20
20
  label: "Header",
21
- renderType: "text",
21
+ renderType: "link",
22
22
  inputType: "text",
23
23
  editable: true,
24
24
  align: "left",
@@ -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/blocks/DashboardChart"
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 [drawerOpen, setDrawerOpen] = React.useState(false)
53
- const [values, setValues] = React.useState<FormReportsValues>(() => buildInitialValues(createFields))
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(createFields))
57
- }, [createFields])
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
- await Promise.resolve(actionHandlers?.onCreateSubmit?.(nextValues))
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
- <DashboardMetrics items={kpis} />
101
- <DashboardChart
102
- series={visitorsSeries}
103
- onTimeRangeChange={actionHandlers?.onChartTimeRangeChange}
104
- />
105
- <DataTable
106
- rows={rows}
107
- tableSchema={tableSchema}
108
- handlers={actionHandlers?.table}
109
- onCreateClick={openCreateDrawer}
110
- createButtonLabel={createButtonLabel}
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 ${createEntityName}`}
117
- description={`Enter the details for your new ${createEntityName.toLowerCase()}.`}
118
- fields={createFields}
185
+ title={`Create ${activeEntityName}`}
186
+ description={`Enter the details for your new ${activeEntityName.toLowerCase()}.`}
187
+ fields={activeFields}
119
188
  values={values}
120
- submitLabel={createButtonLabel}
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 function useDashboardFeatureMock(): UseDashboardFeatureReturn {
15
- const getInitialRows = useCallback<() => DashboardRow[]>(
16
- () => dashboardRows.map((row) => ({ ...row })),
17
- []
18
- )
14
+ export const useDashboardFeatureMock = (): UseDashboardFeatureReturn => {
15
+ const [rows, setRows] = useState<DashboardRow[]>([])
19
16
 
20
- const [rows, setRows] = useState<DashboardRow[]>(getInitialRows)
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: dashboardCreateFields,
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
- onCreateSubmit: (values: FormReportsValues) => {
30
- setRows((prev) => {
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
- setRows((prev) => prev.filter((item) => String(item.id) !== String(row.id)))
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
  },