From 75623f37bcbfeda2e1c68489deca9730018dcd6a Mon Sep 17 00:00:00 2001 From: Citali Date: Sat, 5 Jul 2025 10:08:09 +0200 Subject: [PATCH] Kanban shenanigans --- package-lock.json | 109 +++++++++++++-- package.json | 6 +- .../20250704173417_init/migration.sql | 97 ++++++++++++++ prisma/migrations/migration_lock.toml | 3 + prisma/schema.prisma | 81 ++++++++++++ src/app/commissions/kanban/page.tsx | 10 ++ src/components/kanban/KanbanBoard.tsx | 52 ++++++++ src/components/kanban/KanbanCardModal.tsx | 55 ++++++++ src/components/kanban/KanbanCardPreview.tsx | 58 ++++++++ src/components/kanban/KanbanColumn.tsx | 38 ++++++ src/components/ui/progress.tsx | 31 +++++ src/data/mockCards.ts | 124 ++++++++++++++++++ src/types/kanban.ts | 21 +++ 13 files changed, 674 insertions(+), 11 deletions(-) create mode 100644 prisma/migrations/20250704173417_init/migration.sql create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 src/app/commissions/kanban/page.tsx create mode 100644 src/components/kanban/KanbanBoard.tsx create mode 100644 src/components/kanban/KanbanCardModal.tsx create mode 100644 src/components/kanban/KanbanCardPreview.tsx create mode 100644 src/components/kanban/KanbanColumn.tsx create mode 100644 src/components/ui/progress.tsx create mode 100644 src/data/mockCards.ts create mode 100644 src/types/kanban.ts diff --git a/package-lock.json b/package-lock.json index 9012573..18cf248 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,12 +8,16 @@ "name": "admin.gaertan.art", "version": "0.1.0", "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/utilities": "^3.2.2", "@hookform/resolvers": "^5.1.1", + "@prisma/client": "^6.11.1", "@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-navigation-menu": "^1.2.13", + "@radix-ui/react-progress": "^1.1.7", "@radix-ui/react-select": "^2.2.5", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-switch": "^1.2.5", @@ -42,7 +46,7 @@ "prisma": "^6.11.1", "tailwindcss": "^4", "tw-animate-css": "^1.3.5", - "typescript": "^5" + "typescript": "^5.8.3" } }, "node_modules/@alloc/quick-lru": { @@ -101,6 +105,45 @@ } } }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@emnapi/core": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", @@ -1079,11 +1122,33 @@ "url": "https://opencollective.com/pkgr" } }, + "node_modules/@prisma/client": { + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.11.1.tgz", + "integrity": "sha512-5CLFh8QP6KxRm83pJ84jaVCeSVPQr8k0L2SEtOJHwdkS57/VQDcI/wQpGmdyOZi+D9gdNabdo8tj1Uk+w+upsQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, "node_modules/@prisma/config": { "version": "6.11.1", "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.11.1.tgz", "integrity": "sha512-z6rCTQN741wxDq82cpdzx2uVykpnQIXalLhrWQSR0jlBVOxCIkz3HZnd8ern3uYTcWKfB3IpVAF7K2FU8t/8AQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "jiti": "2.4.2" @@ -1093,14 +1158,14 @@ "version": "6.11.1", "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.11.1.tgz", "integrity": "sha512-lWRb/YSWu8l4Yum1UXfGLtqFzZkVS2ygkWYpgkbgMHn9XJlMITIgeMvJyX5GepChzhmxuSuiq/MY/kGFweOpGw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines": { "version": "6.11.1", "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.11.1.tgz", "integrity": "sha512-6eKEcV6V8W2eZAUwX2xTktxqPM4vnx3sxz3SDtpZwjHKpC6lhOtc4vtAtFUuf5+eEqBk+dbJ9Dcaj6uQU+FNNg==", - "dev": true, + "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -1114,14 +1179,14 @@ "version": "6.11.1-1.f40f79ec31188888a2e33acda0ecc8fd10a853a9", "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.11.1-1.f40f79ec31188888a2e33acda0ecc8fd10a853a9.tgz", "integrity": "sha512-swFJTOOg4tHyOM1zB/pHb3MeH0i6t7jFKn5l+ZsB23d9AQACuIRo9MouvuKGvnDogzkcjbWnXi/NvOZ0+n5Jfw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/fetch-engine": { "version": "6.11.1", "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.11.1.tgz", "integrity": "sha512-NBYzmkXTkj9+LxNPRSndaAeALOL1Gr3tjvgRYNqruIPlZ6/ixLeuE/5boYOewant58tnaYFZ5Ne0jFBPfGXHpQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@prisma/debug": "6.11.1", @@ -1133,7 +1198,7 @@ "version": "6.11.1", "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.11.1.tgz", "integrity": "sha512-b2Z8oV2gwvdCkFemBTFd0x4lsL4O2jLSx8lB7D+XqoFALOQZPa7eAPE1NU0Mj1V8gPHRxIsHnyUNtw2i92psUw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@prisma/debug": "6.11.1" @@ -1627,6 +1692,30 @@ } } }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz", + "integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.1.10", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.10.tgz", @@ -5168,7 +5257,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -6215,7 +6304,7 @@ "version": "6.11.1", "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.11.1.tgz", "integrity": "sha512-VzJToRlV0s9Vu2bfqHiRJw73hZNCG/AyJeX+kopbu4GATTjTUdEWUteO3p4BLYoHpMS4o8pD3v6tF44BHNZI1w==", - "dev": true, + "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -7266,7 +7355,7 @@ "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/package.json b/package.json index 2fb6f18..a57486b 100644 --- a/package.json +++ b/package.json @@ -9,12 +9,16 @@ "lint": "next lint" }, "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/utilities": "^3.2.2", "@hookform/resolvers": "^5.1.1", + "@prisma/client": "^6.11.1", "@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-navigation-menu": "^1.2.13", + "@radix-ui/react-progress": "^1.1.7", "@radix-ui/react-select": "^2.2.5", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-switch": "^1.2.5", @@ -43,6 +47,6 @@ "prisma": "^6.11.1", "tailwindcss": "^4", "tw-animate-css": "^1.3.5", - "typescript": "^5" + "typescript": "^5.8.3" } } diff --git a/prisma/migrations/20250704173417_init/migration.sql b/prisma/migrations/20250704173417_init/migration.sql new file mode 100644 index 0000000..8c174a3 --- /dev/null +++ b/prisma/migrations/20250704173417_init/migration.sql @@ -0,0 +1,97 @@ +-- CreateEnum +CREATE TYPE "Role" AS ENUM ('ADMIN', 'ARTIST'); + +-- CreateEnum +CREATE TYPE "RequestStatus" AS ENUM ('PENDING', 'ACCEPTED', 'IN_PROGRESS', 'DONE', 'REJECTED'); + +-- CreateTable +CREATE TABLE "User" ( + "id" TEXT NOT NULL, + "email" TEXT NOT NULL, + "name" TEXT, + "role" "Role" NOT NULL DEFAULT 'ADMIN', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "CommissionType" ( + "id" TEXT NOT NULL, + "title" TEXT NOT NULL, + "description" TEXT, + "basePrice" DOUBLE PRECISION NOT NULL, + "deliveryEst" TEXT, + "tags" TEXT[], + "active" BOOLEAN NOT NULL DEFAULT true, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "CommissionType_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "CommissionRequest" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "email" TEXT NOT NULL, + "message" TEXT NOT NULL, + "typeId" TEXT NOT NULL, + "status" "RequestStatus" NOT NULL DEFAULT 'PENDING', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "CommissionRequest_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Artwork" ( + "id" TEXT NOT NULL, + "title" TEXT NOT NULL, + "imageUrl" TEXT NOT NULL, + "description" TEXT, + "tags" TEXT[], + "formats" TEXT[], + "isPublic" BOOLEAN NOT NULL DEFAULT true, + "groupId" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Artwork_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "PresentationGroup" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "PresentationGroup_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Preferences" ( + "id" TEXT NOT NULL, + "commissionOpen" BOOLEAN NOT NULL DEFAULT true, + "defaultDelivery" TEXT, + "autoReplyMessage" TEXT, + "notifyByEmail" BOOLEAN NOT NULL DEFAULT true, + + CONSTRAINT "Preferences_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "TOS" ( + "id" TEXT NOT NULL, + "content" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "TOS_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); + +-- AddForeignKey +ALTER TABLE "CommissionRequest" ADD CONSTRAINT "CommissionRequest_typeId_fkey" FOREIGN KEY ("typeId") REFERENCES "CommissionType"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Artwork" ADD CONSTRAINT "Artwork_groupId_fkey" FOREIGN KEY ("groupId") REFERENCES "PresentationGroup"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..044d57c --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" diff --git a/prisma/schema.prisma b/prisma/schema.prisma index aaeee1b..6c1e4e1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -13,3 +13,84 @@ datasource db { provider = "postgresql" url = env("DATABASE_URL") } + +model User { + id String @id @default(cuid()) + email String @unique + name String? + role Role @default(ADMIN) + createdAt DateTime @default(now()) +} + +enum Role { + ADMIN + ARTIST +} + +model CommissionType { + id String @id @default(cuid()) + title String + description String? + basePrice Float + deliveryEst String? // e.g. "2 weeks" + tags String[] // e.g. shaded, sketch, full-body + active Boolean @default(true) + createdAt DateTime @default(now()) + CommissionRequest CommissionRequest[] +} + +model CommissionRequest { + id String @id @default(cuid()) + name String + email String + message String + typeId String + status RequestStatus @default(PENDING) + createdAt DateTime @default(now()) + + type CommissionType @relation(fields: [typeId], references: [id]) +} + +enum RequestStatus { + PENDING + ACCEPTED + IN_PROGRESS + DONE + REJECTED +} + +model Artwork { + id String @id @default(cuid()) + title String + imageUrl String + description String? + tags String[] + formats String[] + isPublic Boolean @default(true) + groupId String? + createdAt DateTime @default(now()) + + group PresentationGroup? @relation(fields: [groupId], references: [id]) +} + +model PresentationGroup { + id String @id @default(cuid()) + name String + description String? + createdAt DateTime @default(now()) + Artwork Artwork[] +} + +model Preferences { + id String @id @default(cuid()) + commissionOpen Boolean @default(true) + defaultDelivery String? // e.g. "7 days" + autoReplyMessage String? + notifyByEmail Boolean @default(true) +} + +model TOS { + id String @id @default(cuid()) + content String // Markdown or rich text + createdAt DateTime @default(now()) +} diff --git a/src/app/commissions/kanban/page.tsx b/src/app/commissions/kanban/page.tsx new file mode 100644 index 0000000..4c81423 --- /dev/null +++ b/src/app/commissions/kanban/page.tsx @@ -0,0 +1,10 @@ +import { KanbanBoard } from "@/components/kanban/KanbanBoard" + +export default function KanbanPage() { + return ( +
+

Commission Queue

+ +
+ ) +} diff --git a/src/components/kanban/KanbanBoard.tsx b/src/components/kanban/KanbanBoard.tsx new file mode 100644 index 0000000..e1a066f --- /dev/null +++ b/src/components/kanban/KanbanBoard.tsx @@ -0,0 +1,52 @@ +"use client" + +import { mockCards } from "@/data/mockCards" +import type { KanbanCardData } from "@/types/kanban" +import { DndContext, DragEndEvent, closestCenter } from "@dnd-kit/core" +import { useState } from "react" +import { KanbanColumn } from "./KanbanColumn" + +export type Status = "PENDING" | "ACCEPTED" | "IN_PROGRESS" | "DONE" | "REJECTED" + +const columns: Status[] = ["PENDING", "ACCEPTED", "IN_PROGRESS", "DONE", "REJECTED"] + +export function KanbanBoard() { + const [cards, setCards] = useState>(mockCards) + + function findColumnByCardId(cardId: string): Status | null { + for (const status of columns) { + if (cards[status].some((c) => c.id === cardId)) return status + } + return null + } + + function handleDragEnd(event: DragEndEvent) { + const { active, over } = event + if (!active || !over || active.id === over.id) return + + const from = findColumnByCardId(active.id as string) + const to = over.id as Status + if (!from || !to || from === to) return + + const card = cards[from].find((c) => c.id === active.id) + if (!card) return + + setCards((prev) => { + return { + ...prev, + [from]: prev[from].filter((c) => c.id !== card.id), + [to]: [card, ...prev[to]], + } + }) + } + + return ( + +
+ {columns.map((status) => ( + + ))} +
+
+ ) +} diff --git a/src/components/kanban/KanbanCardModal.tsx b/src/components/kanban/KanbanCardModal.tsx new file mode 100644 index 0000000..ca77acf --- /dev/null +++ b/src/components/kanban/KanbanCardModal.tsx @@ -0,0 +1,55 @@ +"use client" + +import { Dialog, DialogContent } from "@/components/ui/dialog" +import type { KanbanCardData } from "@/types/kanban" + +export interface KanbanCardModalProps { + card: KanbanCardData | null + onClose: () => void +} + +export function KanbanCardModal({ card, onClose }: KanbanCardModalProps) { + return ( + + + {card && ( +
+

{card.title}

+ +
+ {card.labels?.map((label) => ( + + {label.name} + + ))} +
+ + {card.description && ( +

{card.description}

+ )} + + {card.todos?.length > 0 && ( +
+ {card.todos.map((todo) => ( +
+ + + {todo.text} + +
+ ))} +
+ )} +
+ )} +
+
+ ) +} diff --git a/src/components/kanban/KanbanCardPreview.tsx b/src/components/kanban/KanbanCardPreview.tsx new file mode 100644 index 0000000..93cb41c --- /dev/null +++ b/src/components/kanban/KanbanCardPreview.tsx @@ -0,0 +1,58 @@ +"use client" + +import type { KanbanCardData } from "@/types/kanban" +import { useDraggable } from "@dnd-kit/core" + +interface KanbanCardPreviewProps extends KanbanCardData { + onClick: () => void +} + +export function KanbanCardPreview({ + id, + title, + labels = [], + todos = [], + onClick, +}: KanbanCardPreviewProps) { + const { setNodeRef, attributes, listeners, transform, isDragging } = useDraggable({ + id, + }) + + const done = todos.filter((t) => t.done).length + const total = todos.length + + return ( +
+
+ {labels.map((label) => ( + + ))} +
+ +
{title}
+ + {total > 0 && ( +
+ {done} / {total} tasks done +
+ )} +
+ ) +} diff --git a/src/components/kanban/KanbanColumn.tsx b/src/components/kanban/KanbanColumn.tsx new file mode 100644 index 0000000..958daa2 --- /dev/null +++ b/src/components/kanban/KanbanColumn.tsx @@ -0,0 +1,38 @@ +"use client" + +import type { KanbanCardData } from "@/types/kanban" +import { useDroppable } from "@dnd-kit/core" +import { useState } from "react" +import type { Status } from "./KanbanBoard" +import { KanbanCardModal } from "./KanbanCardModal" +import { KanbanCardPreview } from "./KanbanCardPreview" + +interface KanbanColumnProps { + status: Status + items: KanbanCardData[] +} + +export function KanbanColumn({ status, items }: KanbanColumnProps) { + const { setNodeRef } = useDroppable({ id: status }) + const [activeCard, setActiveCard] = useState(null) + + return ( +
+

+ {status.replace("_", " ")} +

+ {items.map((item) => ( + setActiveCard(item)} + /> + ))} + + setActiveCard(null)} /> +
+ ) +} diff --git a/src/components/ui/progress.tsx b/src/components/ui/progress.tsx new file mode 100644 index 0000000..e7a416c --- /dev/null +++ b/src/components/ui/progress.tsx @@ -0,0 +1,31 @@ +"use client" + +import * as React from "react" +import * as ProgressPrimitive from "@radix-ui/react-progress" + +import { cn } from "@/lib/utils" + +function Progress({ + className, + value, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { Progress } diff --git a/src/data/mockCards.ts b/src/data/mockCards.ts new file mode 100644 index 0000000..ac4c901 --- /dev/null +++ b/src/data/mockCards.ts @@ -0,0 +1,124 @@ +import type { KanbanCardData } from "@/types/kanban" + +export const mockCards: Record = { + PENDING: [ + { + id: "card-1", + title: "New Commission Request", + description: "A shaded full body request with background.", + labels: [ + { id: "l1", name: "Full Body", color: "#6366f1" }, + { id: "l2", name: "Shaded", color: "#10b981" }, + ], + todos: [ + { id: "t1", text: "Confirm client", done: true }, + { id: "t2", text: "Send invoice", done: false }, + { id: "t3", text: "Start sketch", done: false }, + ], + }, + { + id: "c1", + title: "Initial sketch", + description: "Draft the sketch layout for the dragon commission.", + labels: [{ id: "l1", name: "Sketch", color: "#7c3aed" }], + todos: [ + { id: "t1", text: "Outline basic pose", done: true }, + { id: "t2", text: "Add background elements", done: false }, + ], + }, + { + id: "c6", + title: "Color palette ideas", + description: "Brainstorm palette options for upcoming sticker set.", + labels: [{ id: "l3", name: "Palette", color: "#f59e0b" }], + todos: [ + { id: "t11", text: "Collect 5 references", done: true }, + { id: "t12", text: "Make base color variants", done: false }, + ], + }, + ], + ACCEPTED: [ + { + id: "c2", + title: "Full body sketch (Fox)", + description: "Accepted commission from Alex, full body fox.", + labels: [ + { id: "l2", name: "Commission", color: "#10b981" }, + { id: "l3", name: "Lineart", color: "#3b82f6" }, + ], + todos: [ + { id: "t3", text: "Line pose", done: false }, + { id: "t4", text: "Inking details", done: false }, + ], + }, + { + id: "c7", + title: "Badge template layout", + description: "Design a reusable layout for convention badges.", + labels: [{ id: "l6", name: "Template", color: "#a855f7" }], + todos: [ + { id: "t13", text: "Vertical layout sketch", done: true }, + { id: "t14", text: "Add social name field", done: false }, + ], + }, + ], + IN_PROGRESS: [ + { + id: "c3", + title: "Shaded portrait (Wolf)", + description: "Working on detailed portrait with complex lighting.", + labels: [ + { id: "l4", name: "Portrait", color: "#f97316" }, + { id: "l5", name: "Shaded", color: "#6366f1" }, + ], + todos: [ + { id: "t5", text: "Refine shadows", done: false }, + { id: "t6", text: "Color correction", done: false }, + ], + }, + { + id: "c8", + title: "Telegram stickers", + description: "Finish final 2 stickers for the feline set.", + labels: [ + { id: "l1", name: "Sticker", color: "#84cc16" }, + { id: "l5", name: "Flat", color: "#eab308" }, + ], + todos: [ + { id: "t15", text: "Render #5", done: true }, + { id: "t16", text: "Render #6", done: false }, + ], + }, + ], + DONE: [ + { + id: "c4", + title: "Refsheet (Tiger)", + description: "Complete refsheet for character submission.", + labels: [{ id: "l6", name: "Refsheet", color: "#ec4899" }], + todos: [ + { id: "t7", text: "Label layers", done: true }, + { id: "t8", text: "Export PNG", done: true }, + ], + }, + { + id: "c9", + title: "Artfight entry", + description: "Submitted and posted Artfight attack.", + labels: [{ id: "l7", name: "Artfight", color: "#0ea5e9" }], + todos: [ + { id: "t17", text: "Add watermark", done: true }, + { id: "t18", text: "Post to site", done: true }, + ], + }, + ], + REJECTED: [ + { + id: "c5", + title: "NSFW icon (cancelled)", + description: "Client cancelled due to budget.", + labels: [{ id: "l8", name: "NSFW", color: "#ef4444" }], + todos: [], + }, + ], +} diff --git a/src/types/kanban.ts b/src/types/kanban.ts new file mode 100644 index 0000000..57b5e3e --- /dev/null +++ b/src/types/kanban.ts @@ -0,0 +1,21 @@ +// src/types/kanban.ts + +export type KanbanLabel = { + id: string + name: string + color: string // e.g. "red", "green", "blue" +} + +export type KanbanTodo = { + id: string + text: string + done: boolean +} + +export type KanbanCardData = { + id: string + title: string + labels: KanbanLabel[] + description: string + todos: KanbanTodo[] +}