jbilcke-hf HF staff commited on
Commit
e5c2fd4
·
1 Parent(s): 838a15b

starting to work on the ui

Browse files
next.config.js CHANGED
@@ -1,4 +1,8 @@
1
  /** @type {import('next').NextConfig} */
2
- const nextConfig = {}
 
 
 
 
3
 
4
  module.exports = nextConfig
 
1
  /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ experimental: {
4
+ serverActions: true,
5
+ },
6
+ }
7
 
8
  module.exports = nextConfig
scripts/test.js CHANGED
@@ -2,14 +2,14 @@ const { promises: fs } = require("node:fs")
2
 
3
  const main = async () => {
4
  console.log('generating shot..')
5
- const response = await fetch("http://localhost:3000/api/shot", {
6
  method: "POST",
7
  headers: {
8
  "Accept": "application/json",
9
  "Content-Type": "application/json"
10
  },
11
  body: JSON.stringify({
12
- token: process.env.VS_SECRET_ACCESS_TOKEN,
13
  shotPrompt: "video of a dancing cat"
14
  })
15
  });
 
2
 
3
  const main = async () => {
4
  console.log('generating shot..')
5
+ const response = await fetch(process.env."http://localhost:3000/api/shot", {
6
  method: "POST",
7
  headers: {
8
  "Accept": "application/json",
9
  "Content-Type": "application/json"
10
  },
11
  body: JSON.stringify({
12
+ token: process.env.VC_SECRET_ACCESS_TOKEN,
13
  shotPrompt: "video of a dancing cat"
14
  })
15
  });
src/api/base.ts ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ // note: there is no / at the end in the variable
3
+ // so we have to add it ourselves if needed
4
+ const apiUrl = process.env.VC_VIDEOCHAIN_API_URL
5
+
6
+ export const get = async <T>(path: string = '', defaultValue: T): Promise<T> => {
7
+ try {
8
+ const res = await fetch(`${apiUrl}/${path}`, {
9
+ headers: {
10
+ method: "GET",
11
+ Accept: "application/json",
12
+ Authorization: `Bearer ${process.env.VC_SECRET_ACCESS_TOKEN}`,
13
+ }
14
+ })
15
+ // The return value is *not* serialized
16
+ // You can return Date, Map, Set, etc.
17
+
18
+ // Recommendation: handle errors
19
+ if (res.status !== 200) {
20
+ // This will activate the closest `error.js` Error Boundary
21
+ throw new Error('Failed to fetch data')
22
+ }
23
+
24
+ const data = await res.json()
25
+
26
+ return ((data as T) || defaultValue)
27
+ } catch (err) {
28
+ console.error(err)
29
+ return defaultValue
30
+ }
31
+ }
src/api/index.ts ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import { VideoTask } from "@/app/types"
3
+
4
+ import { get } from "./base"
5
+
6
+ export const getPendingTasks = async () => {
7
+ const tasks = await get<VideoTask[]>("", [])
8
+
9
+ return tasks
10
+ }
11
+
12
+ export const getTask = async (id: string) => {
13
+ const task = await get<VideoTask>(id, null as unknown as VideoTask)
14
+
15
+ return task
16
+ }
src/app/data/data.ts ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ ArrowDownIcon,
3
+ ArrowRightIcon,
4
+ ArrowUpIcon,
5
+ CheckCircledIcon,
6
+ CircleIcon,
7
+ CrossCircledIcon,
8
+ QuestionMarkCircledIcon,
9
+ StopwatchIcon,
10
+ } from "@radix-ui/react-icons"
11
+
12
+ export const labels = [
13
+ {
14
+ value: "bug",
15
+ label: "Bug",
16
+ },
17
+ {
18
+ value: "feature",
19
+ label: "Feature",
20
+ },
21
+ {
22
+ value: "documentation",
23
+ label: "Documentation",
24
+ },
25
+ ]
26
+
27
+ export const statuses = [
28
+ {
29
+ value: "backlog",
30
+ label: "Backlog",
31
+ icon: QuestionMarkCircledIcon,
32
+ },
33
+ {
34
+ value: "todo",
35
+ label: "Todo",
36
+ icon: CircleIcon,
37
+ },
38
+ {
39
+ value: "in progress",
40
+ label: "In Progress",
41
+ icon: StopwatchIcon,
42
+ },
43
+ {
44
+ value: "done",
45
+ label: "Done",
46
+ icon: CheckCircledIcon,
47
+ },
48
+ {
49
+ value: "canceled",
50
+ label: "Canceled",
51
+ icon: CrossCircledIcon,
52
+ },
53
+ ]
54
+
55
+ export const priorities = [
56
+ {
57
+ label: "Low",
58
+ value: "low",
59
+ icon: ArrowDownIcon,
60
+ },
61
+ {
62
+ label: "Medium",
63
+ value: "medium",
64
+ icon: ArrowRightIcon,
65
+ },
66
+ {
67
+ label: "High",
68
+ value: "high",
69
+ icon: ArrowUpIcon,
70
+ },
71
+ ]
src/app/data/mock.json ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": "TASK-8782",
4
+ "title": "You can't compress the program without quantifying the open-source SSD pixel!",
5
+ "status": "in progress",
6
+ "label": "documentation",
7
+ "priority": "medium"
8
+ },
9
+ {
10
+ "id": "TASK-7878",
11
+ "title": "Try to calculate the EXE feed, maybe it will index the multi-byte pixel!",
12
+ "status": "backlog",
13
+ "label": "documentation",
14
+ "priority": "medium"
15
+ }
16
+ ]
src/app/data/schema.ts ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { z } from "zod"
2
+
3
+ // We're keeping a simple non-relational schema here.
4
+ // IRL, you will have a schema for your data models.
5
+ export const taskSchema = z.object({
6
+ id: z.string(),
7
+ title: z.string(),
8
+ status: z.string(),
9
+ label: z.string(),
10
+ priority: z.string(),
11
+ })
12
+
13
+ export type Task = z.infer<typeof taskSchema>
src/app/forlater/page.tsx ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Head from "next/head"
2
+
3
+ import { Timeline } from "@/components/business/timeline"
4
+
5
+ export default function Index() {
6
+ return (
7
+ <div>
8
+ <Head>
9
+ <meta name="viewport" content="width=device-width, initial-scale=0.86, maximum-scale=5.0, minimum-scale=0.86" />
10
+ </Head>
11
+ <main className="h-screen w-full flex bg-gray-700 text-gray-200">
12
+ <div className="flex flex-col">
13
+ <Timeline />
14
+ </div>
15
+ </main>
16
+ </div>
17
+ )
18
+ }
src/app/main.tsx ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { promises as fs } from "fs"
2
+ import path from "path"
3
+ import { Metadata } from "next"
4
+ import Image from "next/image"
5
+ import { z } from "zod"
6
+
7
+ import { taskSchema } from "./data/schema"
8
+ import { UserNav } from "@/components/business/tasks/user-nav"
9
+ import { DataTable } from "@/components/business/tasks/data-table"
10
+ import { columns } from "@/components/business/tasks/columns"
11
+
12
+
13
+ export const metadata: Metadata = {
14
+ title: "Tasks",
15
+ description: "A task and issue tracker build using Tanstack Table.",
16
+ }
17
+
18
+ // Simulate a database read for tasks.
19
+ async function getTasks() {
20
+ const data = await fs.readFile(
21
+ path.join(process.cwd(), "src/app/data/mock.json")
22
+ )
23
+
24
+ const tasks = JSON.parse(data.toString())
25
+
26
+ return z.array(taskSchema).parse(tasks)
27
+ }
28
+
29
+ export default async function Main() {
30
+ const tasks = await getTasks()
31
+
32
+ return (
33
+ <>
34
+ <div className="md:hidden">
35
+ <Image
36
+ src="/examples/tasks-light.png"
37
+ width={1280}
38
+ height={998}
39
+ alt="Playground"
40
+ className="block dark:hidden"
41
+ />
42
+ <Image
43
+ src="/examples/tasks-dark.png"
44
+ width={1280}
45
+ height={998}
46
+ alt="Playground"
47
+ className="hidden dark:block"
48
+ />
49
+ </div>
50
+ <div className="hidden h-full flex-1 flex-col space-y-8 p-8 md:flex">
51
+ <div className="flex items-center justify-between space-y-2">
52
+ <div>
53
+ <h2 className="text-2xl font-bold tracking-tight">Welcome back!</h2>
54
+ <p className="text-muted-foreground">
55
+ Here&apos;s a list of your tasks for this month!
56
+ </p>
57
+ </div>
58
+ <div className="flex items-center space-x-2">
59
+ <UserNav />
60
+ </div>
61
+ </div>
62
+ <DataTable data={tasks} columns={columns} />
63
+ </div>
64
+ </>
65
+ )
66
+ }
src/app/page.tsx CHANGED
@@ -1,6 +1,6 @@
1
  import Head from "next/head"
2
 
3
- import { Timeline } from "@/components/business/timeline"
4
 
5
  export default function Index() {
6
  return (
@@ -10,7 +10,7 @@ export default function Index() {
10
  </Head>
11
  <main className="h-screen w-full flex bg-gray-700 text-gray-200">
12
  <div className="flex flex-col">
13
- <Timeline />
14
  </div>
15
  </main>
16
  </div>
 
1
  import Head from "next/head"
2
 
3
+ import Main from "./main"
4
 
5
  export default function Index() {
6
  return (
 
10
  </Head>
11
  <main className="h-screen w-full flex bg-gray-700 text-gray-200">
12
  <div className="flex flex-col">
13
+ <Main />
14
  </div>
15
  </main>
16
  </div>
src/app/pending/page.tsx ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Head from "next/head"
2
+
3
+ import { getPendingTasks } from "@/api"
4
+
5
+
6
+ export default async function Pending() {
7
+ const tasks = await getPendingTasks()
8
+ console.log(`tasks:`, tasks)
9
+ return (
10
+ <div>
11
+ <Head>
12
+ <meta name="viewport" content="width=device-width, initial-scale=0.86, maximum-scale=5.0, minimum-scale=0.86" />
13
+ </Head>
14
+ <main className="h-screen w-full flex bg-gray-700 text-gray-200">
15
+ <div className="flex flex-col">
16
+ Nb tasks: {tasks.length}
17
+ </div>
18
+ </main>
19
+ </div>
20
+ )
21
+ }
src/app/types.ts CHANGED
@@ -1,63 +1,230 @@
1
- export interface Shot {
2
- shotId: string
3
- index: number
4
- lastGenerationAt: string
5
- videoPrompt: string
6
- audioPrompt: string
7
- duration: number // no more than 3 (we don't have the ressources for it)
8
- fps: number // typically 8, 12, 24
9
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
- export interface Sequence {
12
- sequenceId: string
13
- skip: boolean
14
- lastGenerationAt: string
15
- videoPrompt: string
16
- audioPrompt: string
17
- channel: string
18
- tags: string[]
19
- shots: Shot[]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  }
21
 
22
- export interface Database {
 
 
 
 
 
 
 
23
  version: number
24
- startAtShotId: string
25
- sequences: Sequence[]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  }
27
 
 
28
 
29
- export interface ShotQuery {
30
- token: string
31
- shotPrompt: string
32
- // inputVideo?: string
33
 
34
  // describe the background audio (crowd, birds, wind, sea etc..)
35
- backgroundAudioPrompt?: string
36
 
37
  // describe the foreground audio (cars revving, footsteps, objects breaking, explosion etc)
38
- foregroundAudioPrompt?: string
39
 
40
  // describe the main actor visible in the shot (optional)
41
- actorPrompt?: string
42
 
43
  // describe the main actor voice (man, woman, old, young, amused, annoyed.. etc)
44
- actorVoicePrompt?: string
45
 
46
  // describe the main actor dialogue line
47
- actorDialoguePrompt?: string
 
 
48
 
49
- seed?: number
50
- upscale?: boolean
51
 
52
- duration?: number
53
- steps?: number
54
 
55
- fps?: number // 8, 12, 24, 30, 60
56
 
57
- resolution?: number // 256, 512, 576, 720, 1080
 
 
 
58
  }
59
 
60
- export interface Job {
61
- startedAt: string
62
- query: ShotQuery
63
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export type VideoTransition =
2
+ | 'dissolve'
3
+ | 'bookflip'
4
+ | 'bounce'
5
+ | 'bowtiehorizontal'
6
+ | 'bowtievertical'
7
+ | 'bowtiewithparameter'
8
+ | 'butterflywavescrawler'
9
+ | 'circlecrop'
10
+ | 'colourdistance'
11
+ | 'crazyparametricfun'
12
+ | 'crosszoom'
13
+ | 'directional'
14
+ | 'directionalscaled'
15
+ | 'doomscreentransition'
16
+ | 'dreamy'
17
+ | 'dreamyzoom'
18
+ | 'edgetransition'
19
+ | 'filmburn'
20
+ | 'filmburnglitchdisplace'
21
+ | 'glitchmemories'
22
+ | 'gridflip'
23
+ | 'horizontalclose'
24
+ | 'horizontalopen'
25
+ | 'invertedpagecurl'
26
+ | 'leftright'
27
+ | 'linearblur'
28
+ | 'mosaic'
29
+ | 'overexposure'
30
+ | 'polkadotscurtain'
31
+ | 'radial'
32
+ | 'rectangle'
33
+ | 'rectanglecrop'
34
+ | 'rolls'
35
+ | 'rotatescalevanish'
36
+ | 'simplezoom'
37
+ | 'simplezoomout'
38
+ | 'slides'
39
+ | 'staticfade'
40
+ | 'stereoviewer'
41
+ | 'swirl'
42
+ | 'tvstatic'
43
+ | 'topbottom'
44
+ | 'verticalclose'
45
+ | 'verticalopen'
46
+ | 'waterdrop'
47
+ | 'waterdropzoomincircles'
48
+ | 'zoomleftwipe'
49
+ | 'zoomrigthwipe'
50
+ | 'angular'
51
+ | 'burn'
52
+ | 'cannabisleaf'
53
+ | 'circle'
54
+ | 'circleopen'
55
+ | 'colorphase'
56
+ | 'coordfromin'
57
+ | 'crosshatch'
58
+ | 'crosswarp'
59
+ | 'cube'
60
+ | 'directionaleasing'
61
+ | 'directionalwarp'
62
+ | 'directionalwipe'
63
+ | 'displacement'
64
+ | 'doorway'
65
+ | 'fade'
66
+ | 'fadecolor'
67
+ | 'fadegrayscale'
68
+ | 'flyeye'
69
+ | 'heart'
70
+ | 'hexagonalize'
71
+ | 'kaleidoscope'
72
+ | 'luma'
73
+ | 'luminance_melt'
74
+ | 'morph'
75
+ | 'mosaic_transition'
76
+ | 'multiply_blend'
77
+ | 'perlin'
78
+ | 'pinwheel'
79
+ | 'pixelize'
80
+ | 'polar_function'
81
+ | 'powerkaleido'
82
+ | 'randomnoisex'
83
+ | 'randomsquares'
84
+ | 'ripple'
85
+ | 'rotatetransition'
86
+ | 'rotate_scale_fade'
87
+ | 'scalein'
88
+ | 'squareswire'
89
+ | 'squeeze'
90
+ | 'static_wipe'
91
+ | 'swap'
92
+ | 'tangentmotionblur'
93
+ | 'undulatingburnout'
94
+ | 'wind'
95
+ | 'windowblinds'
96
+ | 'windowslice'
97
+ | 'wipedown'
98
+ | 'wipeleft'
99
+ | 'wiperight'
100
+ | 'wipeup'
101
+ | 'x_axistranslation'
102
 
103
+
104
+ export interface VideoShotMeta {
105
+ shotPrompt: string
106
+ // inputVideo?: string
107
+
108
+ // describe the background audio (crowd, birds, wind, sea etc..)
109
+ backgroundAudioPrompt: string
110
+
111
+ // describe the foreground audio (cars revving, footsteps, objects breaking, explosion etc)
112
+ foregroundAudioPrompt: string
113
+
114
+ // describe the main actor visible in the shot (optional)
115
+ actorPrompt: string
116
+
117
+ // describe the main actor voice (man, woman, old, young, amused, annoyed.. etc)
118
+ actorVoicePrompt: string
119
+
120
+ // describe the main actor dialogue line
121
+ actorDialoguePrompt: string
122
+
123
+ seed: number
124
+ noise: boolean // add movie noise
125
+
126
+ durationMs: number // in milliseconds
127
+ steps: number
128
+
129
+ fps: number // 8, 12, 24, 30, 60
130
+
131
+ resolution: string // {width}x{height} (256, 512, 576, 720, 1080)
132
+
133
+ introTransition: VideoTransition
134
+ introDurationMs: number // in milliseconds
135
  }
136
 
137
+
138
+ export interface VideoShotData {
139
+ // must be unique
140
+ id: string
141
+
142
+ fileName: string
143
+
144
+ // used to check compatibility
145
  version: number
146
+
147
+ // for internal use
148
+ hasGeneratedPreview: boolean
149
+ hasGeneratedVideo: boolean
150
+ hasUpscaledVideo: boolean
151
+ hasGeneratedBackgroundAudio: boolean
152
+ hasGeneratedForegroundAudio: boolean
153
+ hasGeneratedActor: boolean
154
+ hasInterpolatedVideo: boolean
155
+ hasAddedAudio: boolean
156
+ hasPostProcessedVideo: boolean
157
+ nbCompletedSteps: number
158
+ nbTotalSteps: number
159
+ progressPercent: number
160
+ completedAt: string
161
+ completed: boolean
162
+ error: string
163
  }
164
 
165
+ export type VideoShot = VideoShotMeta & VideoShotData
166
 
167
+ export interface VideoSequenceMeta {
168
+
169
+ // describe the whole movie
170
+ videoPrompt: string
171
 
172
  // describe the background audio (crowd, birds, wind, sea etc..)
173
+ backgroundAudioPrompt: string
174
 
175
  // describe the foreground audio (cars revving, footsteps, objects breaking, explosion etc)
176
+ foregroundAudioPrompt: string
177
 
178
  // describe the main actor visible in the shot (optional)
179
+ actorPrompt: string
180
 
181
  // describe the main actor voice (man, woman, old, young, amused, annoyed.. etc)
182
+ actorVoicePrompt: string
183
 
184
  // describe the main actor dialogue line
185
+ actorDialoguePrompt: string
186
+
187
+ seed: number
188
 
189
+ noise: boolean // add movie noise
 
190
 
191
+ steps: number // between 10 and 50
 
192
 
193
+ fps: number // 8, 12, 24, 30, 60
194
 
195
+ resolution: string // 256, 512, 576, 720, 1080
196
+
197
+ outroTransition: VideoTransition
198
+ outroDurationMs: number
199
  }
200
 
201
+
202
+ export interface VideoSequenceData {
203
+ // must be unique
204
+ id: string
205
+
206
+ fileName: string
207
+
208
+ // used to check compatibility
209
+ version: number
210
+
211
+ hasAssembledVideo: boolean
212
+ nbCompletedShots: number
213
+ nbTotalShots: number
214
+ progressPercent: number
215
+ completedAt: string
216
+ completed: boolean
217
+ error: string
218
+ }
219
+
220
+ export type VideoSequence = VideoSequenceMeta & VideoSequenceData
221
+
222
+ export type VideoSequenceRequest = {
223
+ token: string
224
+ sequence: VideoSequenceMeta
225
+ shots: VideoShotMeta[]
226
+ }
227
+
228
+ export type VideoTask = VideoSequence & {
229
+ shots: VideoShot[]
230
+ }
src/components/business/tasks/columns.tsx ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { ColumnDef } from "@tanstack/react-table"
4
+
5
+ import { Badge } from "@/components/ui/badge"
6
+ import { Checkbox } from "@/components/ui/checkbox"
7
+
8
+ import { labels, priorities, statuses } from "@/app/data/data"
9
+
10
+ import { DataTableColumnHeader } from "./data-table-column-header"
11
+ import { DataTableRowActions } from "./data-table-row-actions"
12
+ import { Task } from "@/app/data/schema"
13
+
14
+ export const columns: ColumnDef<Task>[] = [
15
+ {
16
+ id: "select",
17
+ header: ({ table }) => (
18
+ <Checkbox
19
+ checked={table.getIsAllPageRowsSelected()}
20
+ onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
21
+ aria-label="Select all"
22
+ className="translate-y-[2px]"
23
+ />
24
+ ),
25
+ cell: ({ row }) => (
26
+ <Checkbox
27
+ checked={row.getIsSelected()}
28
+ onCheckedChange={(value) => row.toggleSelected(!!value)}
29
+ aria-label="Select row"
30
+ className="translate-y-[2px]"
31
+ />
32
+ ),
33
+ enableSorting: false,
34
+ enableHiding: false,
35
+ },
36
+ {
37
+ accessorKey: "id",
38
+ header: ({ column }) => (
39
+ <DataTableColumnHeader column={column} title="Task" />
40
+ ),
41
+ cell: ({ row }) => <div className="w-[80px]">{row.getValue("id")}</div>,
42
+ enableSorting: false,
43
+ enableHiding: false,
44
+ },
45
+ {
46
+ accessorKey: "title",
47
+ header: ({ column }) => (
48
+ <DataTableColumnHeader column={column} title="Title" />
49
+ ),
50
+ cell: ({ row }) => {
51
+ const label = labels.find((label) => label.value === row.original.label)
52
+
53
+ return (
54
+ <div className="flex space-x-2">
55
+ {label && <Badge variant="outline">{label.label}</Badge>}
56
+ <span className="max-w-[500px] truncate font-medium">
57
+ {row.getValue("title")}
58
+ </span>
59
+ </div>
60
+ )
61
+ },
62
+ },
63
+ {
64
+ accessorKey: "status",
65
+ header: ({ column }) => (
66
+ <DataTableColumnHeader column={column} title="Status" />
67
+ ),
68
+ cell: ({ row }) => {
69
+ const status = statuses.find(
70
+ (status) => status.value === row.getValue("status")
71
+ )
72
+
73
+ if (!status) {
74
+ return null
75
+ }
76
+
77
+ return (
78
+ <div className="flex w-[100px] items-center">
79
+ {status.icon && (
80
+ <status.icon className="mr-2 h-4 w-4 text-muted-foreground" />
81
+ )}
82
+ <span>{status.label}</span>
83
+ </div>
84
+ )
85
+ },
86
+ filterFn: (row, id, value) => {
87
+ return value.includes(row.getValue(id))
88
+ },
89
+ },
90
+ {
91
+ accessorKey: "priority",
92
+ header: ({ column }) => (
93
+ <DataTableColumnHeader column={column} title="Priority" />
94
+ ),
95
+ cell: ({ row }) => {
96
+ const priority = priorities.find(
97
+ (priority) => priority.value === row.getValue("priority")
98
+ )
99
+
100
+ if (!priority) {
101
+ return null
102
+ }
103
+
104
+ return (
105
+ <div className="flex items-center">
106
+ {priority.icon && (
107
+ <priority.icon className="mr-2 h-4 w-4 text-muted-foreground" />
108
+ )}
109
+ <span>{priority.label}</span>
110
+ </div>
111
+ )
112
+ },
113
+ filterFn: (row, id, value) => {
114
+ return value.includes(row.getValue(id))
115
+ },
116
+ },
117
+ {
118
+ id: "actions",
119
+ cell: ({ row }) => <DataTableRowActions row={row} />,
120
+ },
121
+ ]
src/components/business/tasks/data-table-column-header.tsx ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ ArrowDownIcon,
3
+ ArrowUpIcon,
4
+ CaretSortIcon,
5
+ EyeNoneIcon,
6
+ } from "@radix-ui/react-icons"
7
+ import { Column } from "@tanstack/react-table"
8
+
9
+ import { cn } from "@/lib/utils"
10
+ import { Button } from "@/components/ui/button"
11
+ import {
12
+ DropdownMenu,
13
+ DropdownMenuContent,
14
+ DropdownMenuItem,
15
+ DropdownMenuSeparator,
16
+ DropdownMenuTrigger,
17
+ } from "@/components/ui/dropdown-menu"
18
+
19
+ interface DataTableColumnHeaderProps<TData, TValue>
20
+ extends React.HTMLAttributes<HTMLDivElement> {
21
+ column: Column<TData, TValue>
22
+ title: string
23
+ }
24
+
25
+ export function DataTableColumnHeader<TData, TValue>({
26
+ column,
27
+ title,
28
+ className,
29
+ }: DataTableColumnHeaderProps<TData, TValue>) {
30
+ if (!column.getCanSort()) {
31
+ return <div className={cn(className)}>{title}</div>
32
+ }
33
+
34
+ return (
35
+ <div className={cn("flex items-center space-x-2", className)}>
36
+ <DropdownMenu>
37
+ <DropdownMenuTrigger asChild>
38
+ <Button
39
+ variant="ghost"
40
+ size="sm"
41
+ className="-ml-3 h-8 data-[state=open]:bg-accent"
42
+ >
43
+ <span>{title}</span>
44
+ {column.getIsSorted() === "desc" ? (
45
+ <ArrowDownIcon className="ml-2 h-4 w-4" />
46
+ ) : column.getIsSorted() === "asc" ? (
47
+ <ArrowUpIcon className="ml-2 h-4 w-4" />
48
+ ) : (
49
+ <CaretSortIcon className="ml-2 h-4 w-4" />
50
+ )}
51
+ </Button>
52
+ </DropdownMenuTrigger>
53
+ <DropdownMenuContent align="start">
54
+ <DropdownMenuItem onClick={() => column.toggleSorting(false)}>
55
+ <ArrowUpIcon className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
56
+ Asc
57
+ </DropdownMenuItem>
58
+ <DropdownMenuItem onClick={() => column.toggleSorting(true)}>
59
+ <ArrowDownIcon className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
60
+ Desc
61
+ </DropdownMenuItem>
62
+ <DropdownMenuSeparator />
63
+ <DropdownMenuItem onClick={() => column.toggleVisibility(false)}>
64
+ <EyeNoneIcon className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
65
+ Hide
66
+ </DropdownMenuItem>
67
+ </DropdownMenuContent>
68
+ </DropdownMenu>
69
+ </div>
70
+ )
71
+ }
src/components/business/tasks/data-table-faceted-filter.tsx ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { CheckIcon, PlusCircledIcon } from "@radix-ui/react-icons"
3
+ import { Column } from "@tanstack/react-table"
4
+
5
+ import { cn } from "@/lib/utils"
6
+ import { Badge } from "@/components/ui/badge"
7
+ import { Button } from "@/components/ui/button"
8
+ import {
9
+ Command,
10
+ CommandEmpty,
11
+ CommandGroup,
12
+ CommandInput,
13
+ CommandItem,
14
+ CommandList,
15
+ CommandSeparator,
16
+ } from "@/components/ui/command"
17
+ import {
18
+ Popover,
19
+ PopoverContent,
20
+ PopoverTrigger,
21
+ } from "@/components/ui/popover"
22
+ import { Separator } from "@/components/ui/separator"
23
+
24
+ interface DataTableFacetedFilter<TData, TValue> {
25
+ column?: Column<TData, TValue>
26
+ title?: string
27
+ options: {
28
+ label: string
29
+ value: string
30
+ icon?: React.ComponentType<{ className?: string }>
31
+ }[]
32
+ }
33
+
34
+ export function DataTableFacetedFilter<TData, TValue>({
35
+ column,
36
+ title,
37
+ options,
38
+ }: DataTableFacetedFilter<TData, TValue>) {
39
+ const facets = column?.getFacetedUniqueValues()
40
+ const selectedValues = new Set(column?.getFilterValue() as string[])
41
+
42
+ return (
43
+ <Popover>
44
+ <PopoverTrigger asChild>
45
+ <Button variant="outline" size="sm" className="h-8 border-dashed">
46
+ <PlusCircledIcon className="mr-2 h-4 w-4" />
47
+ {title}
48
+ {selectedValues?.size > 0 && (
49
+ <>
50
+ <Separator orientation="vertical" className="mx-2 h-4" />
51
+ <Badge
52
+ variant="secondary"
53
+ className="rounded-sm px-1 font-normal lg:hidden"
54
+ >
55
+ {selectedValues.size}
56
+ </Badge>
57
+ <div className="hidden space-x-1 lg:flex">
58
+ {selectedValues.size > 2 ? (
59
+ <Badge
60
+ variant="secondary"
61
+ className="rounded-sm px-1 font-normal"
62
+ >
63
+ {selectedValues.size} selected
64
+ </Badge>
65
+ ) : (
66
+ options
67
+ .filter((option) => selectedValues.has(option.value))
68
+ .map((option) => (
69
+ <Badge
70
+ variant="secondary"
71
+ key={option.value}
72
+ className="rounded-sm px-1 font-normal"
73
+ >
74
+ {option.label}
75
+ </Badge>
76
+ ))
77
+ )}
78
+ </div>
79
+ </>
80
+ )}
81
+ </Button>
82
+ </PopoverTrigger>
83
+ <PopoverContent className="w-[200px] p-0" align="start">
84
+ <Command>
85
+ <CommandInput placeholder={title} />
86
+ <CommandList>
87
+ <CommandEmpty>No results found.</CommandEmpty>
88
+ <CommandGroup>
89
+ {options.map((option) => {
90
+ const isSelected = selectedValues.has(option.value)
91
+ return (
92
+ <CommandItem
93
+ key={option.value}
94
+ onSelect={() => {
95
+ if (isSelected) {
96
+ selectedValues.delete(option.value)
97
+ } else {
98
+ selectedValues.add(option.value)
99
+ }
100
+ const filterValues = Array.from(selectedValues)
101
+ column?.setFilterValue(
102
+ filterValues.length ? filterValues : undefined
103
+ )
104
+ }}
105
+ >
106
+ <div
107
+ className={cn(
108
+ "mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
109
+ isSelected
110
+ ? "bg-primary text-primary-foreground"
111
+ : "opacity-50 [&_svg]:invisible"
112
+ )}
113
+ >
114
+ <CheckIcon className={cn("h-4 w-4")} />
115
+ </div>
116
+ {option.icon && (
117
+ <option.icon className="mr-2 h-4 w-4 text-muted-foreground" />
118
+ )}
119
+ <span>{option.label}</span>
120
+ {facets?.get(option.value) && (
121
+ <span className="ml-auto flex h-4 w-4 items-center justify-center font-mono text-xs">
122
+ {facets.get(option.value)}
123
+ </span>
124
+ )}
125
+ </CommandItem>
126
+ )
127
+ })}
128
+ </CommandGroup>
129
+ {selectedValues.size > 0 && (
130
+ <>
131
+ <CommandSeparator />
132
+ <CommandGroup>
133
+ <CommandItem
134
+ onSelect={() => column?.setFilterValue(undefined)}
135
+ className="justify-center text-center"
136
+ >
137
+ Clear filters
138
+ </CommandItem>
139
+ </CommandGroup>
140
+ </>
141
+ )}
142
+ </CommandList>
143
+ </Command>
144
+ </PopoverContent>
145
+ </Popover>
146
+ )
147
+ }
src/components/business/tasks/data-table-pagination.tsx ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ ChevronLeftIcon,
3
+ ChevronRightIcon,
4
+ DoubleArrowLeftIcon,
5
+ DoubleArrowRightIcon,
6
+ } from "@radix-ui/react-icons"
7
+ import { Table } from "@tanstack/react-table"
8
+
9
+ import { Button } from "@/components/ui/button"
10
+ import {
11
+ Select,
12
+ SelectContent,
13
+ SelectItem,
14
+ SelectTrigger,
15
+ SelectValue,
16
+ } from "@/components/ui/select"
17
+
18
+ interface DataTablePaginationProps<TData> {
19
+ table: Table<TData>
20
+ }
21
+
22
+ export function DataTablePagination<TData>({
23
+ table,
24
+ }: DataTablePaginationProps<TData>) {
25
+ return (
26
+ <div className="flex items-center justify-between px-2">
27
+ <div className="flex-1 text-sm text-muted-foreground">
28
+ {table.getFilteredSelectedRowModel().rows.length} of{" "}
29
+ {table.getFilteredRowModel().rows.length} row(s) selected.
30
+ </div>
31
+ <div className="flex items-center space-x-6 lg:space-x-8">
32
+ <div className="flex items-center space-x-2">
33
+ <p className="text-sm font-medium">Rows per page</p>
34
+ <Select
35
+ value={`${table.getState().pagination.pageSize}`}
36
+ onValueChange={(value) => {
37
+ table.setPageSize(Number(value))
38
+ }}
39
+ >
40
+ <SelectTrigger className="h-8 w-[70px]">
41
+ <SelectValue placeholder={table.getState().pagination.pageSize} />
42
+ </SelectTrigger>
43
+ <SelectContent side="top">
44
+ {[10, 20, 30, 40, 50].map((pageSize) => (
45
+ <SelectItem key={pageSize} value={`${pageSize}`}>
46
+ {pageSize}
47
+ </SelectItem>
48
+ ))}
49
+ </SelectContent>
50
+ </Select>
51
+ </div>
52
+ <div className="flex w-[100px] items-center justify-center text-sm font-medium">
53
+ Page {table.getState().pagination.pageIndex + 1} of{" "}
54
+ {table.getPageCount()}
55
+ </div>
56
+ <div className="flex items-center space-x-2">
57
+ <Button
58
+ variant="outline"
59
+ className="hidden h-8 w-8 p-0 lg:flex"
60
+ onClick={() => table.setPageIndex(0)}
61
+ disabled={!table.getCanPreviousPage()}
62
+ >
63
+ <span className="sr-only">Go to first page</span>
64
+ <DoubleArrowLeftIcon className="h-4 w-4" />
65
+ </Button>
66
+ <Button
67
+ variant="outline"
68
+ className="h-8 w-8 p-0"
69
+ onClick={() => table.previousPage()}
70
+ disabled={!table.getCanPreviousPage()}
71
+ >
72
+ <span className="sr-only">Go to previous page</span>
73
+ <ChevronLeftIcon className="h-4 w-4" />
74
+ </Button>
75
+ <Button
76
+ variant="outline"
77
+ className="h-8 w-8 p-0"
78
+ onClick={() => table.nextPage()}
79
+ disabled={!table.getCanNextPage()}
80
+ >
81
+ <span className="sr-only">Go to next page</span>
82
+ <ChevronRightIcon className="h-4 w-4" />
83
+ </Button>
84
+ <Button
85
+ variant="outline"
86
+ className="hidden h-8 w-8 p-0 lg:flex"
87
+ onClick={() => table.setPageIndex(table.getPageCount() - 1)}
88
+ disabled={!table.getCanNextPage()}
89
+ >
90
+ <span className="sr-only">Go to last page</span>
91
+ <DoubleArrowRightIcon className="h-4 w-4" />
92
+ </Button>
93
+ </div>
94
+ </div>
95
+ </div>
96
+ )
97
+ }
src/components/business/tasks/data-table-row-actions.tsx ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { DotsHorizontalIcon } from "@radix-ui/react-icons"
4
+ import { Row } from "@tanstack/react-table"
5
+
6
+ import { Button } from "@/components/ui/button"
7
+ import {
8
+ DropdownMenu,
9
+ DropdownMenuContent,
10
+ DropdownMenuItem,
11
+ DropdownMenuRadioGroup,
12
+ DropdownMenuRadioItem,
13
+ DropdownMenuSeparator,
14
+ DropdownMenuShortcut,
15
+ DropdownMenuSub,
16
+ DropdownMenuSubContent,
17
+ DropdownMenuSubTrigger,
18
+ DropdownMenuTrigger,
19
+ } from "@/components/ui/dropdown-menu"
20
+
21
+ import { labels } from "@/app/data/data"
22
+ import { taskSchema } from "@/app/data/schema"
23
+
24
+ interface DataTableRowActionsProps<TData> {
25
+ row: Row<TData>
26
+ }
27
+
28
+ export function DataTableRowActions<TData>({
29
+ row,
30
+ }: DataTableRowActionsProps<TData>) {
31
+ const task = taskSchema.parse(row.original)
32
+
33
+ return (
34
+ <DropdownMenu>
35
+ <DropdownMenuTrigger asChild>
36
+ <Button
37
+ variant="ghost"
38
+ className="flex h-8 w-8 p-0 data-[state=open]:bg-muted"
39
+ >
40
+ <DotsHorizontalIcon className="h-4 w-4" />
41
+ <span className="sr-only">Open menu</span>
42
+ </Button>
43
+ </DropdownMenuTrigger>
44
+ <DropdownMenuContent align="end" className="w-[160px]">
45
+ <DropdownMenuItem>Edit</DropdownMenuItem>
46
+ <DropdownMenuItem>Make a copy</DropdownMenuItem>
47
+ <DropdownMenuItem>Favorite</DropdownMenuItem>
48
+ <DropdownMenuSeparator />
49
+ <DropdownMenuSub>
50
+ <DropdownMenuSubTrigger>Labels</DropdownMenuSubTrigger>
51
+ <DropdownMenuSubContent>
52
+ <DropdownMenuRadioGroup value={task.label}>
53
+ {labels.map((label) => (
54
+ <DropdownMenuRadioItem key={label.value} value={label.value}>
55
+ {label.label}
56
+ </DropdownMenuRadioItem>
57
+ ))}
58
+ </DropdownMenuRadioGroup>
59
+ </DropdownMenuSubContent>
60
+ </DropdownMenuSub>
61
+ <DropdownMenuSeparator />
62
+ <DropdownMenuItem>
63
+ Delete
64
+ <DropdownMenuShortcut>⌘⌫</DropdownMenuShortcut>
65
+ </DropdownMenuItem>
66
+ </DropdownMenuContent>
67
+ </DropdownMenu>
68
+ )
69
+ }
src/components/business/tasks/data-table-toolbar.tsx ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { Cross2Icon } from "@radix-ui/react-icons"
4
+ import { Table } from "@tanstack/react-table"
5
+
6
+ import { Button } from "@/components/ui/button"
7
+ import { Input } from "@/components/ui/input"
8
+ import { DataTableViewOptions } from "@/components/business/tasks/data-table-view-options"
9
+
10
+ import { DataTableFacetedFilter } from "./data-table-faceted-filter"
11
+ import { priorities, statuses } from "@/app/data/data"
12
+
13
+ interface DataTableToolbarProps<TData> {
14
+ table: Table<TData>
15
+ }
16
+
17
+ export function DataTableToolbar<TData>({
18
+ table,
19
+ }: DataTableToolbarProps<TData>) {
20
+ const isFiltered = table.getState().columnFilters.length > 0
21
+
22
+ return (
23
+ <div className="flex items-center justify-between">
24
+ <div className="flex flex-1 items-center space-x-2">
25
+ <Input
26
+ placeholder="Filter tasks..."
27
+ value={(table.getColumn("title")?.getFilterValue() as string) ?? ""}
28
+ onChange={(event) =>
29
+ table.getColumn("title")?.setFilterValue(event.target.value)
30
+ }
31
+ className="h-8 w-[150px] lg:w-[250px]"
32
+ />
33
+ {table.getColumn("status") && (
34
+ <DataTableFacetedFilter
35
+ column={table.getColumn("status")}
36
+ title="Status"
37
+ options={statuses}
38
+ />
39
+ )}
40
+ {table.getColumn("priority") && (
41
+ <DataTableFacetedFilter
42
+ column={table.getColumn("priority")}
43
+ title="Priority"
44
+ options={priorities}
45
+ />
46
+ )}
47
+ {isFiltered && (
48
+ <Button
49
+ variant="ghost"
50
+ onClick={() => table.resetColumnFilters()}
51
+ className="h-8 px-2 lg:px-3"
52
+ >
53
+ Reset
54
+ <Cross2Icon className="ml-2 h-4 w-4" />
55
+ </Button>
56
+ )}
57
+ </div>
58
+ <DataTableViewOptions table={table} />
59
+ </div>
60
+ )
61
+ }
src/components/business/tasks/data-table-view-options.tsx ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu"
4
+ import { MixerHorizontalIcon } from "@radix-ui/react-icons"
5
+ import { Table } from "@tanstack/react-table"
6
+
7
+ import { Button } from "@/components/ui/button"
8
+ import {
9
+ DropdownMenu,
10
+ DropdownMenuCheckboxItem,
11
+ DropdownMenuContent,
12
+ DropdownMenuLabel,
13
+ DropdownMenuSeparator,
14
+ } from "@/components/ui/dropdown-menu"
15
+
16
+ interface DataTableViewOptionsProps<TData> {
17
+ table: Table<TData>
18
+ }
19
+
20
+ export function DataTableViewOptions<TData>({
21
+ table,
22
+ }: DataTableViewOptionsProps<TData>) {
23
+ return (
24
+ <DropdownMenu>
25
+ <DropdownMenuTrigger asChild>
26
+ <Button
27
+ variant="outline"
28
+ size="sm"
29
+ className="ml-auto hidden h-8 lg:flex"
30
+ >
31
+ <MixerHorizontalIcon className="mr-2 h-4 w-4" />
32
+ View
33
+ </Button>
34
+ </DropdownMenuTrigger>
35
+ <DropdownMenuContent align="end" className="w-[150px]">
36
+ <DropdownMenuLabel>Toggle columns</DropdownMenuLabel>
37
+ <DropdownMenuSeparator />
38
+ {table
39
+ .getAllColumns()
40
+ .filter(
41
+ (column) =>
42
+ typeof column.accessorFn !== "undefined" && column.getCanHide()
43
+ )
44
+ .map((column) => {
45
+ return (
46
+ <DropdownMenuCheckboxItem
47
+ key={column.id}
48
+ className="capitalize"
49
+ checked={column.getIsVisible()}
50
+ onCheckedChange={(value) => column.toggleVisibility(!!value)}
51
+ >
52
+ {column.id}
53
+ </DropdownMenuCheckboxItem>
54
+ )
55
+ })}
56
+ </DropdownMenuContent>
57
+ </DropdownMenu>
58
+ )
59
+ }
src/components/business/tasks/data-table.tsx ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import {
5
+ ColumnDef,
6
+ ColumnFiltersState,
7
+ SortingState,
8
+ VisibilityState,
9
+ flexRender,
10
+ getCoreRowModel,
11
+ getFacetedRowModel,
12
+ getFacetedUniqueValues,
13
+ getFilteredRowModel,
14
+ getPaginationRowModel,
15
+ getSortedRowModel,
16
+ useReactTable,
17
+ } from "@tanstack/react-table"
18
+
19
+ import {
20
+ Table,
21
+ TableBody,
22
+ TableCell,
23
+ TableHead,
24
+ TableHeader,
25
+ TableRow,
26
+ } from "@/components/ui/table"
27
+
28
+ import { DataTablePagination } from "@/components/business/tasks/data-table-pagination"
29
+ import { DataTableToolbar } from "@/components/business/tasks/data-table-toolbar"
30
+
31
+ interface DataTableProps<TData, TValue> {
32
+ columns: ColumnDef<TData, TValue>[]
33
+ data: TData[]
34
+ }
35
+
36
+ export function DataTable<TData, TValue>({
37
+ columns,
38
+ data,
39
+ }: DataTableProps<TData, TValue>) {
40
+ const [rowSelection, setRowSelection] = React.useState({})
41
+ const [columnVisibility, setColumnVisibility] =
42
+ React.useState<VisibilityState>({})
43
+ const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
44
+ []
45
+ )
46
+ const [sorting, setSorting] = React.useState<SortingState>([])
47
+
48
+ const table = useReactTable({
49
+ data,
50
+ columns,
51
+ state: {
52
+ sorting,
53
+ columnVisibility,
54
+ rowSelection,
55
+ columnFilters,
56
+ },
57
+ enableRowSelection: true,
58
+ onRowSelectionChange: setRowSelection,
59
+ onSortingChange: setSorting,
60
+ onColumnFiltersChange: setColumnFilters,
61
+ onColumnVisibilityChange: setColumnVisibility,
62
+ getCoreRowModel: getCoreRowModel(),
63
+ getFilteredRowModel: getFilteredRowModel(),
64
+ getPaginationRowModel: getPaginationRowModel(),
65
+ getSortedRowModel: getSortedRowModel(),
66
+ getFacetedRowModel: getFacetedRowModel(),
67
+ getFacetedUniqueValues: getFacetedUniqueValues(),
68
+ })
69
+
70
+ return (
71
+ <div className="space-y-4">
72
+ <DataTableToolbar table={table} />
73
+ <div className="rounded-md border">
74
+ <Table>
75
+ <TableHeader>
76
+ {table.getHeaderGroups().map((headerGroup) => (
77
+ <TableRow key={headerGroup.id}>
78
+ {headerGroup.headers.map((header) => {
79
+ return (
80
+ <TableHead key={header.id}>
81
+ {header.isPlaceholder
82
+ ? null
83
+ : flexRender(
84
+ header.column.columnDef.header,
85
+ header.getContext()
86
+ )}
87
+ </TableHead>
88
+ )
89
+ })}
90
+ </TableRow>
91
+ ))}
92
+ </TableHeader>
93
+ <TableBody>
94
+ {table.getRowModel().rows?.length ? (
95
+ table.getRowModel().rows.map((row) => (
96
+ <TableRow
97
+ key={row.id}
98
+ data-state={row.getIsSelected() && "selected"}
99
+ >
100
+ {row.getVisibleCells().map((cell) => (
101
+ <TableCell key={cell.id}>
102
+ {flexRender(
103
+ cell.column.columnDef.cell,
104
+ cell.getContext()
105
+ )}
106
+ </TableCell>
107
+ ))}
108
+ </TableRow>
109
+ ))
110
+ ) : (
111
+ <TableRow>
112
+ <TableCell
113
+ colSpan={columns.length}
114
+ className="h-24 text-center"
115
+ >
116
+ No results.
117
+ </TableCell>
118
+ </TableRow>
119
+ )}
120
+ </TableBody>
121
+ </Table>
122
+ </div>
123
+ <DataTablePagination table={table} />
124
+ </div>
125
+ )
126
+ }
src/components/business/tasks/user-nav.tsx ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ Avatar,
3
+ AvatarFallback,
4
+ AvatarImage,
5
+ } from "@/components/ui/avatar"
6
+ import { Button } from "@/components/ui/button"
7
+ import {
8
+ DropdownMenu,
9
+ DropdownMenuContent,
10
+ DropdownMenuGroup,
11
+ DropdownMenuItem,
12
+ DropdownMenuLabel,
13
+ DropdownMenuSeparator,
14
+ DropdownMenuShortcut,
15
+ DropdownMenuTrigger,
16
+ } from "@/components/ui/dropdown-menu"
17
+
18
+ export function UserNav() {
19
+ return (
20
+ <DropdownMenu>
21
+ <DropdownMenuTrigger asChild>
22
+ <Button variant="ghost" className="relative h-8 w-8 rounded-full">
23
+ <Avatar className="h-9 w-9">
24
+ <AvatarImage src="/avatars/03.png" alt="@shadcn" />
25
+ <AvatarFallback>SC</AvatarFallback>
26
+ </Avatar>
27
+ </Button>
28
+ </DropdownMenuTrigger>
29
+ <DropdownMenuContent className="w-56" align="end" forceMount>
30
+ <DropdownMenuLabel className="font-normal">
31
+ <div className="flex flex-col space-y-1">
32
+ <p className="text-sm font-medium leading-none">shadcn</p>
33
+ <p className="text-xs leading-none text-muted-foreground">
34
+ m@example.com
35
+ </p>
36
+ </div>
37
+ </DropdownMenuLabel>
38
+ <DropdownMenuSeparator />
39
+ <DropdownMenuGroup>
40
+ <DropdownMenuItem>
41
+ Profile
42
+ <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
43
+ </DropdownMenuItem>
44
+ <DropdownMenuItem>
45
+ Billing
46
+ <DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
47
+ </DropdownMenuItem>
48
+ <DropdownMenuItem>
49
+ Settings
50
+ <DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
51
+ </DropdownMenuItem>
52
+ <DropdownMenuItem>New Team</DropdownMenuItem>
53
+ </DropdownMenuGroup>
54
+ <DropdownMenuSeparator />
55
+ <DropdownMenuItem>
56
+ Log out
57
+ <DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
58
+ </DropdownMenuItem>
59
+ </DropdownMenuContent>
60
+ </DropdownMenu>
61
+ )
62
+ }
src/components/ui/avatar.tsx ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as AvatarPrimitive from "@radix-ui/react-avatar"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const Avatar = React.forwardRef<
9
+ React.ElementRef<typeof AvatarPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
11
+ >(({ className, ...props }, ref) => (
12
+ <AvatarPrimitive.Root
13
+ ref={ref}
14
+ className={cn(
15
+ "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
16
+ className
17
+ )}
18
+ {...props}
19
+ />
20
+ ))
21
+ Avatar.displayName = AvatarPrimitive.Root.displayName
22
+
23
+ const AvatarImage = React.forwardRef<
24
+ React.ElementRef<typeof AvatarPrimitive.Image>,
25
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
26
+ >(({ className, ...props }, ref) => (
27
+ <AvatarPrimitive.Image
28
+ ref={ref}
29
+ className={cn("aspect-square h-full w-full", className)}
30
+ {...props}
31
+ />
32
+ ))
33
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName
34
+
35
+ const AvatarFallback = React.forwardRef<
36
+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
37
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
38
+ >(({ className, ...props }, ref) => (
39
+ <AvatarPrimitive.Fallback
40
+ ref={ref}
41
+ className={cn(
42
+ "flex h-full w-full items-center justify-center rounded-full bg-zinc-100 dark:bg-zinc-800",
43
+ className
44
+ )}
45
+ {...props}
46
+ />
47
+ ))
48
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
49
+
50
+ export { Avatar, AvatarImage, AvatarFallback }
src/components/ui/dropdown-menu.tsx ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
5
+ import { Check, ChevronRight, Circle } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const DropdownMenu = DropdownMenuPrimitive.Root
10
+
11
+ const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
12
+
13
+ const DropdownMenuGroup = DropdownMenuPrimitive.Group
14
+
15
+ const DropdownMenuPortal = DropdownMenuPrimitive.Portal
16
+
17
+ const DropdownMenuSub = DropdownMenuPrimitive.Sub
18
+
19
+ const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
20
+
21
+ const DropdownMenuSubTrigger = React.forwardRef<
22
+ React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
23
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
24
+ inset?: boolean
25
+ }
26
+ >(({ className, inset, children, ...props }, ref) => (
27
+ <DropdownMenuPrimitive.SubTrigger
28
+ ref={ref}
29
+ className={cn(
30
+ "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-zinc-100 data-[state=open]:bg-zinc-100 dark:focus:bg-zinc-800 dark:data-[state=open]:bg-zinc-800",
31
+ inset && "pl-8",
32
+ className
33
+ )}
34
+ {...props}
35
+ >
36
+ {children}
37
+ <ChevronRight className="ml-auto h-4 w-4" />
38
+ </DropdownMenuPrimitive.SubTrigger>
39
+ ))
40
+ DropdownMenuSubTrigger.displayName =
41
+ DropdownMenuPrimitive.SubTrigger.displayName
42
+
43
+ const DropdownMenuSubContent = React.forwardRef<
44
+ React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
45
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
46
+ >(({ className, ...props }, ref) => (
47
+ <DropdownMenuPrimitive.SubContent
48
+ ref={ref}
49
+ className={cn(
50
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border border-zinc-200 bg-white p-1 text-zinc-950 shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50",
51
+ className
52
+ )}
53
+ {...props}
54
+ />
55
+ ))
56
+ DropdownMenuSubContent.displayName =
57
+ DropdownMenuPrimitive.SubContent.displayName
58
+
59
+ const DropdownMenuContent = React.forwardRef<
60
+ React.ElementRef<typeof DropdownMenuPrimitive.Content>,
61
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
62
+ >(({ className, sideOffset = 4, ...props }, ref) => (
63
+ <DropdownMenuPrimitive.Portal>
64
+ <DropdownMenuPrimitive.Content
65
+ ref={ref}
66
+ sideOffset={sideOffset}
67
+ className={cn(
68
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border border-zinc-200 bg-white p-1 text-zinc-950 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50",
69
+ className
70
+ )}
71
+ {...props}
72
+ />
73
+ </DropdownMenuPrimitive.Portal>
74
+ ))
75
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
76
+
77
+ const DropdownMenuItem = React.forwardRef<
78
+ React.ElementRef<typeof DropdownMenuPrimitive.Item>,
79
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
80
+ inset?: boolean
81
+ }
82
+ >(({ className, inset, ...props }, ref) => (
83
+ <DropdownMenuPrimitive.Item
84
+ ref={ref}
85
+ className={cn(
86
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-zinc-100 focus:text-zinc-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-zinc-800 dark:focus:text-zinc-50",
87
+ inset && "pl-8",
88
+ className
89
+ )}
90
+ {...props}
91
+ />
92
+ ))
93
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
94
+
95
+ const DropdownMenuCheckboxItem = React.forwardRef<
96
+ React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
97
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
98
+ >(({ className, children, checked, ...props }, ref) => (
99
+ <DropdownMenuPrimitive.CheckboxItem
100
+ ref={ref}
101
+ className={cn(
102
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-zinc-100 focus:text-zinc-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-zinc-800 dark:focus:text-zinc-50",
103
+ className
104
+ )}
105
+ checked={checked}
106
+ {...props}
107
+ >
108
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
109
+ <DropdownMenuPrimitive.ItemIndicator>
110
+ <Check className="h-4 w-4" />
111
+ </DropdownMenuPrimitive.ItemIndicator>
112
+ </span>
113
+ {children}
114
+ </DropdownMenuPrimitive.CheckboxItem>
115
+ ))
116
+ DropdownMenuCheckboxItem.displayName =
117
+ DropdownMenuPrimitive.CheckboxItem.displayName
118
+
119
+ const DropdownMenuRadioItem = React.forwardRef<
120
+ React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
121
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
122
+ >(({ className, children, ...props }, ref) => (
123
+ <DropdownMenuPrimitive.RadioItem
124
+ ref={ref}
125
+ className={cn(
126
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-zinc-100 focus:text-zinc-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-zinc-800 dark:focus:text-zinc-50",
127
+ className
128
+ )}
129
+ {...props}
130
+ >
131
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
132
+ <DropdownMenuPrimitive.ItemIndicator>
133
+ <Circle className="h-2 w-2 fill-current" />
134
+ </DropdownMenuPrimitive.ItemIndicator>
135
+ </span>
136
+ {children}
137
+ </DropdownMenuPrimitive.RadioItem>
138
+ ))
139
+ DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
140
+
141
+ const DropdownMenuLabel = React.forwardRef<
142
+ React.ElementRef<typeof DropdownMenuPrimitive.Label>,
143
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
144
+ inset?: boolean
145
+ }
146
+ >(({ className, inset, ...props }, ref) => (
147
+ <DropdownMenuPrimitive.Label
148
+ ref={ref}
149
+ className={cn(
150
+ "px-2 py-1.5 text-sm font-semibold",
151
+ inset && "pl-8",
152
+ className
153
+ )}
154
+ {...props}
155
+ />
156
+ ))
157
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
158
+
159
+ const DropdownMenuSeparator = React.forwardRef<
160
+ React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
161
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
162
+ >(({ className, ...props }, ref) => (
163
+ <DropdownMenuPrimitive.Separator
164
+ ref={ref}
165
+ className={cn("-mx-1 my-1 h-px bg-zinc-100 dark:bg-zinc-800", className)}
166
+ {...props}
167
+ />
168
+ ))
169
+ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
170
+
171
+ const DropdownMenuShortcut = ({
172
+ className,
173
+ ...props
174
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
175
+ return (
176
+ <span
177
+ className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
178
+ {...props}
179
+ />
180
+ )
181
+ }
182
+ DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
183
+
184
+ export {
185
+ DropdownMenu,
186
+ DropdownMenuTrigger,
187
+ DropdownMenuContent,
188
+ DropdownMenuItem,
189
+ DropdownMenuCheckboxItem,
190
+ DropdownMenuRadioItem,
191
+ DropdownMenuLabel,
192
+ DropdownMenuSeparator,
193
+ DropdownMenuShortcut,
194
+ DropdownMenuGroup,
195
+ DropdownMenuPortal,
196
+ DropdownMenuSub,
197
+ DropdownMenuSubContent,
198
+ DropdownMenuSubTrigger,
199
+ DropdownMenuRadioGroup,
200
+ }
src/components/ui/input.tsx ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ export interface InputProps
6
+ extends React.InputHTMLAttributes<HTMLInputElement> {}
7
+
8
+ const Input = React.forwardRef<HTMLInputElement, InputProps>(
9
+ ({ className, type, ...props }, ref) => {
10
+ return (
11
+ <input
12
+ type={type}
13
+ className={cn(
14
+ "flex h-10 w-full rounded-md border border-zinc-200 border-zinc-200 bg-white px-3 py-2 text-sm ring-offset-white file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-zinc-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-400 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-zinc-800 dark:border-zinc-800 dark:bg-zinc-950 dark:ring-offset-zinc-950 dark:placeholder:text-zinc-400 dark:focus-visible:ring-zinc-800",
15
+ className
16
+ )}
17
+ ref={ref}
18
+ {...props}
19
+ />
20
+ )
21
+ }
22
+ )
23
+ Input.displayName = "Input"
24
+
25
+ export { Input }
src/components/ui/popover.tsx ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as PopoverPrimitive from "@radix-ui/react-popover"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const Popover = PopoverPrimitive.Root
9
+
10
+ const PopoverTrigger = PopoverPrimitive.Trigger
11
+
12
+ const PopoverContent = React.forwardRef<
13
+ React.ElementRef<typeof PopoverPrimitive.Content>,
14
+ React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
15
+ >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16
+ <PopoverPrimitive.Portal>
17
+ <PopoverPrimitive.Content
18
+ ref={ref}
19
+ align={align}
20
+ sideOffset={sideOffset}
21
+ className={cn(
22
+ "z-50 w-72 rounded-md border border-zinc-200 bg-white p-4 text-zinc-950 shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50",
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ </PopoverPrimitive.Portal>
28
+ ))
29
+ PopoverContent.displayName = PopoverPrimitive.Content.displayName
30
+
31
+ export { Popover, PopoverTrigger, PopoverContent }
src/components/ui/select.tsx ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SelectPrimitive from "@radix-ui/react-select"
5
+ import { Check, ChevronDown } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const Select = SelectPrimitive.Root
10
+
11
+ const SelectGroup = SelectPrimitive.Group
12
+
13
+ const SelectValue = SelectPrimitive.Value
14
+
15
+ const SelectTrigger = React.forwardRef<
16
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
17
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
18
+ >(({ className, children, ...props }, ref) => (
19
+ <SelectPrimitive.Trigger
20
+ ref={ref}
21
+ className={cn(
22
+ "flex h-10 w-full items-center justify-between rounded-md border border-zinc-200 border-zinc-200 bg-transparent px-3 py-2 text-sm ring-offset-white placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-zinc-800 dark:border-zinc-800 dark:ring-offset-zinc-950 dark:placeholder:text-zinc-400 dark:focus:ring-zinc-800",
23
+ className
24
+ )}
25
+ {...props}
26
+ >
27
+ {children}
28
+ <SelectPrimitive.Icon asChild>
29
+ <ChevronDown className="h-4 w-4 opacity-50" />
30
+ </SelectPrimitive.Icon>
31
+ </SelectPrimitive.Trigger>
32
+ ))
33
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
34
+
35
+ const SelectContent = React.forwardRef<
36
+ React.ElementRef<typeof SelectPrimitive.Content>,
37
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
38
+ >(({ className, children, position = "popper", ...props }, ref) => (
39
+ <SelectPrimitive.Portal>
40
+ <SelectPrimitive.Content
41
+ ref={ref}
42
+ className={cn(
43
+ "relative z-50 min-w-[8rem] overflow-hidden rounded-md border border-zinc-200 bg-white text-zinc-950 shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-50",
44
+ position === "popper" &&
45
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
46
+ className
47
+ )}
48
+ position={position}
49
+ {...props}
50
+ >
51
+ <SelectPrimitive.Viewport
52
+ className={cn(
53
+ "p-1",
54
+ position === "popper" &&
55
+ "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
56
+ )}
57
+ >
58
+ {children}
59
+ </SelectPrimitive.Viewport>
60
+ </SelectPrimitive.Content>
61
+ </SelectPrimitive.Portal>
62
+ ))
63
+ SelectContent.displayName = SelectPrimitive.Content.displayName
64
+
65
+ const SelectLabel = React.forwardRef<
66
+ React.ElementRef<typeof SelectPrimitive.Label>,
67
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
68
+ >(({ className, ...props }, ref) => (
69
+ <SelectPrimitive.Label
70
+ ref={ref}
71
+ className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
72
+ {...props}
73
+ />
74
+ ))
75
+ SelectLabel.displayName = SelectPrimitive.Label.displayName
76
+
77
+ const SelectItem = React.forwardRef<
78
+ React.ElementRef<typeof SelectPrimitive.Item>,
79
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
80
+ >(({ className, children, ...props }, ref) => (
81
+ <SelectPrimitive.Item
82
+ ref={ref}
83
+ className={cn(
84
+ "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-zinc-100 focus:text-zinc-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-zinc-800 dark:focus:text-zinc-50",
85
+ className
86
+ )}
87
+ {...props}
88
+ >
89
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
90
+ <SelectPrimitive.ItemIndicator>
91
+ <Check className="h-4 w-4" />
92
+ </SelectPrimitive.ItemIndicator>
93
+ </span>
94
+
95
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
96
+ </SelectPrimitive.Item>
97
+ ))
98
+ SelectItem.displayName = SelectPrimitive.Item.displayName
99
+
100
+ const SelectSeparator = React.forwardRef<
101
+ React.ElementRef<typeof SelectPrimitive.Separator>,
102
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
103
+ >(({ className, ...props }, ref) => (
104
+ <SelectPrimitive.Separator
105
+ ref={ref}
106
+ className={cn("-mx-1 my-1 h-px bg-zinc-100 dark:bg-zinc-800", className)}
107
+ {...props}
108
+ />
109
+ ))
110
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName
111
+
112
+ export {
113
+ Select,
114
+ SelectGroup,
115
+ SelectValue,
116
+ SelectTrigger,
117
+ SelectContent,
118
+ SelectLabel,
119
+ SelectItem,
120
+ SelectSeparator,
121
+ }
src/components/ui/separator.tsx ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SeparatorPrimitive from "@radix-ui/react-separator"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const Separator = React.forwardRef<
9
+ React.ElementRef<typeof SeparatorPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
11
+ >(
12
+ (
13
+ { className, orientation = "horizontal", decorative = true, ...props },
14
+ ref
15
+ ) => (
16
+ <SeparatorPrimitive.Root
17
+ ref={ref}
18
+ decorative={decorative}
19
+ orientation={orientation}
20
+ className={cn(
21
+ "shrink-0 bg-zinc-200 dark:bg-zinc-800",
22
+ orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ )
28
+ )
29
+ Separator.displayName = SeparatorPrimitive.Root.displayName
30
+
31
+ export { Separator }
src/components/ui/table.tsx ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ const Table = React.forwardRef<
6
+ HTMLTableElement,
7
+ React.HTMLAttributes<HTMLTableElement>
8
+ >(({ className, ...props }, ref) => (
9
+ <div className="w-full overflow-auto">
10
+ <table
11
+ ref={ref}
12
+ className={cn("w-full caption-bottom text-sm", className)}
13
+ {...props}
14
+ />
15
+ </div>
16
+ ))
17
+ Table.displayName = "Table"
18
+
19
+ const TableHeader = React.forwardRef<
20
+ HTMLTableSectionElement,
21
+ React.HTMLAttributes<HTMLTableSectionElement>
22
+ >(({ className, ...props }, ref) => (
23
+ <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
24
+ ))
25
+ TableHeader.displayName = "TableHeader"
26
+
27
+ const TableBody = React.forwardRef<
28
+ HTMLTableSectionElement,
29
+ React.HTMLAttributes<HTMLTableSectionElement>
30
+ >(({ className, ...props }, ref) => (
31
+ <tbody
32
+ ref={ref}
33
+ className={cn("[&_tr:last-child]:border-0", className)}
34
+ {...props}
35
+ />
36
+ ))
37
+ TableBody.displayName = "TableBody"
38
+
39
+ const TableFooter = React.forwardRef<
40
+ HTMLTableSectionElement,
41
+ React.HTMLAttributes<HTMLTableSectionElement>
42
+ >(({ className, ...props }, ref) => (
43
+ <tfoot
44
+ ref={ref}
45
+ className={cn("bg-zinc-900 font-medium text-zinc-50 dark:bg-zinc-50 dark:text-zinc-900", className)}
46
+ {...props}
47
+ />
48
+ ))
49
+ TableFooter.displayName = "TableFooter"
50
+
51
+ const TableRow = React.forwardRef<
52
+ HTMLTableRowElement,
53
+ React.HTMLAttributes<HTMLTableRowElement>
54
+ >(({ className, ...props }, ref) => (
55
+ <tr
56
+ ref={ref}
57
+ className={cn(
58
+ "border-b transition-colors hover:bg-zinc-100/50 data-[state=selected]:bg-zinc-100 dark:hover:bg-zinc-800/50 dark:data-[state=selected]:bg-zinc-800",
59
+ className
60
+ )}
61
+ {...props}
62
+ />
63
+ ))
64
+ TableRow.displayName = "TableRow"
65
+
66
+ const TableHead = React.forwardRef<
67
+ HTMLTableCellElement,
68
+ React.ThHTMLAttributes<HTMLTableCellElement>
69
+ >(({ className, ...props }, ref) => (
70
+ <th
71
+ ref={ref}
72
+ className={cn(
73
+ "h-12 px-4 text-left align-middle font-medium text-zinc-500 [&:has([role=checkbox])]:pr-0 dark:text-zinc-400",
74
+ className
75
+ )}
76
+ {...props}
77
+ />
78
+ ))
79
+ TableHead.displayName = "TableHead"
80
+
81
+ const TableCell = React.forwardRef<
82
+ HTMLTableCellElement,
83
+ React.TdHTMLAttributes<HTMLTableCellElement>
84
+ >(({ className, ...props }, ref) => (
85
+ <td
86
+ ref={ref}
87
+ className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
88
+ {...props}
89
+ />
90
+ ))
91
+ TableCell.displayName = "TableCell"
92
+
93
+ const TableCaption = React.forwardRef<
94
+ HTMLTableCaptionElement,
95
+ React.HTMLAttributes<HTMLTableCaptionElement>
96
+ >(({ className, ...props }, ref) => (
97
+ <caption
98
+ ref={ref}
99
+ className={cn("mt-4 text-sm text-zinc-500 dark:text-zinc-400", className)}
100
+ {...props}
101
+ />
102
+ ))
103
+ TableCaption.displayName = "TableCaption"
104
+
105
+ export {
106
+ Table,
107
+ TableHeader,
108
+ TableBody,
109
+ TableFooter,
110
+ TableHead,
111
+ TableRow,
112
+ TableCell,
113
+ TableCaption,
114
+ }
src/core/hasBadWords.ts CHANGED
@@ -2,7 +2,7 @@
2
  // as the tool will be public, we need some level of moderation to bred unfair use
3
  // note: another possibility could be to use the moderation api from openai,
4
  // but it is paying
5
- const config = JSON.parse(`${process.env.VS_MODERATION_FILTER_PATTERN}`)
6
 
7
  export const hasBadWords = (userPrompt: string) => {
8
  const normalized = userPrompt.trim().toLowerCase()
 
2
  // as the tool will be public, we need some level of moderation to bred unfair use
3
  // note: another possibility could be to use the moderation api from openai,
4
  // but it is paying
5
+ const config = JSON.parse(`${process.env.VC_MODERATION_FILTER_PATTERN}`)
6
 
7
  export const hasBadWords = (userPrompt: string) => {
8
  const normalized = userPrompt.trim().toLowerCase()
src/core/interpolateVideo.ts CHANGED
@@ -9,7 +9,7 @@ import tmpDir from "temp-dir"
9
  import { downloadVideo } from "./downloadVideo"
10
 
11
  const instances: string[] = [
12
- `${process.env.VS_VIDEO_INTERPOLATION_SPACE_API_URL}`
13
  ]
14
 
15
  export const interpolateVideo = async (fileName: string) => {
 
9
  import { downloadVideo } from "./downloadVideo"
10
 
11
  const instances: string[] = [
12
+ `${process.env.VC_VIDEO_INTERPOLATION_SPACE_API_URL}`
13
  ]
14
 
15
  export const interpolateVideo = async (fileName: string) => {