diff --git a/package-lock.json b/package-lock.json index 59871be..ca28906 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,9 @@ "react": "^18", "react-dom": "^18", "react-hook-form": "^7.49.3", + "react-hot-toast": "^2.4.1", "react-icons": "^4.12.0", + "vaul": "^0.8.9", "zod": "^3.22.4", "zustand": "^4.4.7" }, @@ -1703,6 +1705,353 @@ "@prisma/debug": "5.8.0" } }, + "node_modules/@radix-ui/primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz", + "integrity": "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==", + "dependencies": { + "@babel/runtime": "^7.13.10" + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz", + "integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.1.tgz", + "integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz", + "integrity": "sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-focus-guards": "1.0.1", + "@radix-ui/react-focus-scope": "1.0.4", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2", + "@radix-ui/react-use-controllable-state": "1.0.1", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", + "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.3", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz", + "integrity": "sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-escape-keydown": "1.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz", + "integrity": "sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz", + "integrity": "sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz", + "integrity": "sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-layout-effect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.4.tgz", + "integrity": "sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.1.tgz", + "integrity": "sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-use-layout-effect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz", + "integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-slot": "1.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", + "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz", + "integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz", + "integrity": "sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz", + "integrity": "sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz", + "integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@react-aria/breadcrumbs": { "version": "3.5.9", "resolved": "https://registry.npmjs.org/@react-aria/breadcrumbs/-/breadcrumbs-3.5.9.tgz", @@ -2798,7 +3147,7 @@ "version": "18.2.18", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz", "integrity": "sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==", - "dev": true, + "devOptional": true, "dependencies": { "@types/react": "*" } @@ -2880,6 +3229,17 @@ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" }, + "node_modules/aria-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.3.tgz", + "integrity": "sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/autoprefixer": { "version": "10.4.16", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", @@ -3164,8 +3524,7 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "devOptional": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/date-fns": { "version": "3.2.0", @@ -3394,6 +3753,14 @@ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" }, + "node_modules/goober": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz", + "integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -4183,6 +4550,21 @@ "react": "^16.8.0 || ^17 || ^18" } }, + "node_modules/react-hot-toast": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz", + "integrity": "sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==", + "dependencies": { + "goober": "^2.1.10" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-icons": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz", @@ -4813,6 +5195,18 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/vaul": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/vaul/-/vaul-0.8.9.tgz", + "integrity": "sha512-gpmtmZRWDPP6niQh14JfRIFUYZVyfvAWyA/7rUINOfNlO/2K7uEvI5rLXEXkxZIRFyUZj+TPHLFMirkegPHjrw==", + "dependencies": { + "@radix-ui/react-dialog": "^1.0.4" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", diff --git a/package.json b/package.json index 456af32..c23f659 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,9 @@ "react": "^18", "react-dom": "^18", "react-hook-form": "^7.49.3", + "react-hot-toast": "^2.4.1", "react-icons": "^4.12.0", + "vaul": "^0.8.9", "zod": "^3.22.4", "zustand": "^4.4.7" }, diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 34f4d11..f9a908f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -3,8 +3,8 @@ generator client { } datasource db { - provider = "postgresql" - url = env("DATABASE_URL") + provider = "postgresql" + url = env("DATABASE_URL") //directUrl = env("DIRECT_URL") } @@ -73,7 +73,7 @@ model VerificationToken { model Todo { id String @id @default(cuid()) name String - goals Goals[] + goals Goal[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id]) @@ -86,14 +86,18 @@ enum Priority { HIGH } -model Goals { - id Int @id @default(autoincrement()) - todo Todo @relation(fields: [todoId], references: [id]) +model Goal { + id String @id @default(cuid()) + todo Todo @relation(fields: [todoId], references: [id]) todoId String - name String + name String? description String? - done Boolean @default(false) - priority Priority @default(LOW) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + done Boolean @default(false) + priority Priority @default(LOW) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + parentTask Goal? @relation("SubTasks", fields: [parentTaskId], references: [id], onDelete: NoAction, onUpdate: NoAction) + parentTaskId String? + subTasks Goal[] @relation("SubTasks") } + diff --git a/src/actions/todo.ts b/src/actions/todo.ts index 162ef16..a315eb9 100644 --- a/src/actions/todo.ts +++ b/src/actions/todo.ts @@ -1,31 +1,48 @@ "use server"; +import { validateAddTodoGoal } from "@/schema/todo"; import { addGoalTodoService, + handleCreateSubtaskService, handleDeleteGoalByIdService, handleUpdateTodoStatusService, } from "@/services/todo"; -import { InvalidAddTodoGoalException } from "@/exceptions/error"; -import { log } from "@/lib/log"; -import { validateAddTodoGoal } from "@/schema/todo"; import { GoalPayload } from "@/types/todo"; import { revalidatePath } from "next/cache"; +import { z } from "zod"; export async function addGoalTodoAction(data: GoalPayload) { try { //validation payload - if (!validateAddTodoGoal(data)) - throw new InvalidAddTodoGoalException("Your data seems incorrect"); + let validatePayload = (await validateAddTodoGoal(data)) as GoalPayload; //call service layer - await addGoalTodoService(data); + await addGoalTodoService(validatePayload); } catch (error) { - //toast notification - log.error(error); + if (error instanceof z.ZodError) { + //toast notification zod error + return { + error: error.message, + }; + } + return { + error: "Generic error", + }; } finally { //revalidate path goals page revalidatePath("/app/goals"); } } +export async function handleCreateSubtaskAction(id:string){ + try { + await handleCreateSubtaskService(id); + } catch (error) { + //toast notification + console.log(error); + } finally { + revalidatePath("/app/goals"); + } +} + export async function handleDeleteGoalAction(id: string) { try { await handleDeleteGoalByIdService(id); @@ -38,7 +55,7 @@ export async function handleDeleteGoalAction(id: string) { } export async function handleUpdateTodoStatusAction( - id: number, + id: string, payload: GoalPayload ) { try { diff --git a/src/app/(protected)/app/goals/(ui)/goal-compomemt.tsx b/src/app/(protected)/app/goals/(ui)/goal-compomemt.tsx new file mode 100644 index 0000000..f1d190b --- /dev/null +++ b/src/app/(protected)/app/goals/(ui)/goal-compomemt.tsx @@ -0,0 +1,234 @@ +"use client"; + +import { + handleCreateSubtaskAction, + handleDeleteGoalAction, + handleUpdateTodoStatusAction, +} from "@/actions/todo"; +import { cn, priorityLabel } from "@/lib/utils"; +import YayDrawer from "@/ui/YayDrawer"; +import { + Button, + Checkbox, + Chip, + Dropdown, + DropdownItem, + DropdownMenu, + DropdownTrigger, + Input, + Spinner, +} from "@nextui-org/react"; +import { Goal } from "@prisma/client"; +import { format } from "date-fns"; +import { FunctionComponent, useRef, useState, useTransition } from "react"; +import { PiInfoLight } from "react-icons/pi"; +import { RiMoreFill } from "react-icons/ri"; +import ModalGoal from "./modal-goal"; + +interface GoalProps { + goal: Goal & { subTasks?: Goal[] }; + disable?: boolean; +} + +const Goal: FunctionComponent = ({ goal, disable }) => { + const [isOpen, setIsOpen] = useState(false); + const [, startTransition] = useTransition(); + const [isPendigDeleteTransaction, handleDeleteTransaction] = useTransition(); + const [isPendingUpdateTransition, handleUpdateTransition] = useTransition(); + const [isPendingCreateSubtask, handleCreateSubtaskTransition] = + useTransition(); + const inputRef = useRef(); + + const updateGoalOnBlur = async (id: string, payload: any) => { + let initialValue = (inputRef.current as HTMLInputElement).getAttribute( + "data-initial-value" + ); + if (initialValue !== payload) { + startTransition(async () => { + await handleUpdateTodoStatusAction(id, payload); + }); + } + }; + + const handleCreateSubtask = async (id: string) => { + handleCreateSubtaskTransition(async () => { + await handleCreateSubtaskAction(id); + }); + }; + + const handleDeleteGoalFn = async (id: string) => { + handleDeleteTransaction(async () => { + await handleDeleteGoalAction(id); + }); + }; + + const HandleUpdateTodoStatus = async (id: string, payload: any) => { + handleUpdateTransition(async () => { + await handleUpdateTodoStatusAction(id, payload); + }); + }; + + return ( + <> +
+
+
+ HandleUpdateTodoStatus(goal.id, { done: true })} + name={goal.id.toString()} + color="default" + aria-label={goal.id.toString()} + classNames={{ + wrapper: cn( + "after:group-data-[selected=true]:bg-[#4b5563] after:group-data-[selected=true]:bg-[#4b5563] after:data-[selected=true]:bg-[#4b5563]", + "group-data-[selected=true]:bg-[#4b5563] group-data-[selected=true]:bg-[#4b5563] data-[selected=true]:bg-[#4b5563]", + "dark:group-data-[selected=true]:bg-[#4b5563]", + "before:border-[#4b5563] data-[selected=true]:bg-[#4b5563]" + ), + base: cn( + "w-fit", + "inline-flex p-0", + "hover:items-center justify-start", + "cursor-pointer rounded-lg m-auto border-2 border-transparent" + ), + label: cn("w-fit bg-gray-100 border-red-700"), + }} + /> + + updateGoalOnBlur(goal.id, { + name: (e.target as HTMLInputElement).value as string, + }) + } + defaultValue={goal.name as string} + classNames={{ + input: [ + "p-1 m-auto rounded-lg px-2 hover:bg-[#161616] bg-[#1d1d1d]", + "placeholder:text-black", + "w-full mr-2", + ], + innerWrapper: ["bg-transparent] p-0"], + inputWrapper: [ + "p-0", + "h-auto", + "bg-transparent", + "dark:hover:bg-transparent", + "dark:focus-within:bg-transparent", + ], + }} + /> +
+
+ {/* actions section */} +
+
+ + {goal.priority} + +
+
+ + {format(goal.createdAt, "yyyy-MM-dd")} + +
+ {!disable ? ( + + + + + + } + onClick={() => console.log("ciao")} + > + + + } + > + Create a copy + + handleCreateSubtask(goal.id)} + // startContent={} + > + Create sub-task + + handleDeleteGoalFn(goal.id.toString())} + //startContent={} + > + Delete + + + + ) : null} + {isPendigDeleteTransaction ? ( + <> + + + ) : null} +
+ {/* */} + {/* {isOpen && ( + + )} */} +
+ {/* {goal.subTasks && goal.subTasks.length > 0 ? ( + goal.subTasks.map((g:Goal) => { + {console.log("INSIDE THE MAP",goal.subTasks)} + return + }) + ) : null} */} + + ); +}; + +export default Goal; diff --git a/src/app/(protected)/app/goals/(ui)/goals-component.tsx b/src/app/(protected)/app/goals/(ui)/goals-component.tsx index 7047e31..85e90d7 100644 --- a/src/app/(protected)/app/goals/(ui)/goals-component.tsx +++ b/src/app/(protected)/app/goals/(ui)/goals-component.tsx @@ -1,23 +1,7 @@ "use client"; -import { - handleDeleteGoalAction, - handleUpdateTodoStatusAction, -} from "@/actions/todo"; -import { cn, priorityLabel } from "@/lib/utils"; -import { - Button, - Checkbox, - Chip, - Dropdown, - DropdownItem, - DropdownMenu, - DropdownTrigger, - Input -} from "@nextui-org/react"; -import { Goals } from "@prisma/client"; -import { format } from "date-fns"; -import { FunctionComponent, useTransition } from "react"; -import { RiMoreFill } from "react-icons/ri"; +import { Goal as GoalTye } from "@prisma/client"; +import { FunctionComponent } from "react"; +import Goal from "./goal-compomemt"; interface GoalsProps { goals: any; @@ -25,147 +9,13 @@ interface GoalsProps { } const Goals: FunctionComponent = ({ goals, disable = false }) => { - const [, startTransition] = useTransition(); - - const handleDeleteGoalFn = async (id: string) => { - startTransition(async () => { - await handleDeleteGoalAction(id); - }); - }; - - const HandleUpdateTodoStatus = async (id: number, payload: any) => { - startTransition(async () => { - await handleUpdateTodoStatusAction(id, payload); - }); - }; - return ( <> - {goals.map((goal: Goals) => { - return ( -
-
-
- - HandleUpdateTodoStatus(goal.id, { done: true }) - } - name={goal.id.toString()} - color="default" - aria-label={goal.id.toString()} - classNames={{ - wrapper: cn( - "after:group-data-[selected=true]:bg-[#4b5563] after:group-data-[selected=true]:bg-[#4b5563] after:data-[selected=true]:bg-[#4b5563]", - "group-data-[selected=true]:bg-[#4b5563] group-data-[selected=true]:bg-[#4b5563] data-[selected=true]:bg-[#4b5563]", - "dark:group-data-[selected=true]:bg-[#4b5563]", - "before:border-[#4b5563] data-[selected=true]:bg-[#4b5563]" - ), - base: cn( - "w-fit", - "inline-flex p-0", - "hover:items-center justify-start", - "cursor-pointer rounded-lg m-auto border-2 border-transparent" - ), - label: cn("w-fit bg-gray-100 border-red-700"), - }} - /> - -
-
- {/* actions section */} -
-
- - {goal.priority} - -
-
- - {format(goal.createdAt, "yyyy-MM-dd")} - -
- {!disable ? ( - - - - - - } - > - Create a copy - - } - > - Expiration data - - handleDeleteGoalFn(goal.id.toString())} - //startContent={} - > - Delete - - - - ) : null} -
-
- ); - })} + {Array.isArray(goals) && + goals.length > 0 && + goals.map((goal: GoalTye) => { + return ; + })} ); }; diff --git a/src/app/(protected)/app/goals/(ui)/header-todo.tsx b/src/app/(protected)/app/goals/(ui)/header-todo.tsx index ef4aedf..62391cb 100644 --- a/src/app/(protected)/app/goals/(ui)/header-todo.tsx +++ b/src/app/(protected)/app/goals/(ui)/header-todo.tsx @@ -1,19 +1,43 @@ "use client"; import { addGoalTodoAction } from "@/actions/todo"; +import { cn } from "@/lib/utils"; import { GoalPayload } from "@/types/todo"; import FormInput from "@/ui/Form/FormInput"; -import { Button } from "@nextui-org/react"; +import { Button, Spinner } from "@nextui-org/react"; import { FunctionComponent, useTransition } from "react"; import { SubmitHandler, useForm } from "react-hook-form"; +import toast from "react-hot-toast"; interface HeaderTodoProps {} const HeaderTodo: FunctionComponent = () => { const { handleSubmit, register } = useForm(); - const [, startTransition] = useTransition(); + const [isPendingAddTodo, addGoalTransaction] = useTransition(); const onSubmit: SubmitHandler = async (data) => { - startTransition(async () => { - await addGoalTodoAction(data as GoalPayload); + addGoalTransaction(async () => { + let result = await addGoalTodoAction(data as GoalPayload); + console.log(result); + if(result?.error){ + toast.error("Error adding todo",{ + icon:"❌", + style: { + backgroundColor:"#b7b7b7", + height:"fit-content", + border: '1px solid #713200', + color: '#713200', + }, + }) + return; + } + toast.success("Successfully added",{ + icon:"✅", + style: { + backgroundColor:"#b7b7b7", + height:"fit-content", + border: '1px solid #713200', + color: '#713200', + }, + }) }); }; @@ -34,7 +58,20 @@ const HeaderTodo: FunctionComponent = () => { type="submit" className="h-fit py-1 ml-2 border border-[#06d6a0] text-[#06d6a0] bg-transparent" > - Add Task +
+ Add Task + + {isPendingAddTodo ? ( + + ) : null} +
diff --git a/src/app/(protected)/app/goals/(ui)/modal-goal.tsx b/src/app/(protected)/app/goals/(ui)/modal-goal.tsx new file mode 100644 index 0000000..07687bc --- /dev/null +++ b/src/app/(protected)/app/goals/(ui)/modal-goal.tsx @@ -0,0 +1,185 @@ +import { cn, priorityLabelBg } from "@/lib/utils"; +import { + Accordion, + AccordionItem, + Chip, + Divider, + Input, + Modal, + ModalContent, +} from "@nextui-org/react"; +import { FunctionComponent } from "react"; +import { CiEdit } from "react-icons/ci"; +import { RiMoreFill } from "react-icons/ri"; + +interface ModalGoalProps { + goal: any; + isOpen: boolean; +} + +const ModalGoal: FunctionComponent = ({ goal, isOpen }) => { + return ( + <> + + + {(onClose) => ( +
+ + {goal.parentTaskId ? "indietro" : null} + +
+
+
+ +
+
+ } + classNames={{ + input: [ + "p-1 text-xl rounded-lg m-auto px-2 hover:bg-transparent bg-transparent", + "placeholder:text-black", + "w-full mr-2", + "group[data-has-value=true] group-data-[has-value=true]:text-black", + ], + innerWrapper: ["bg-transparent] p-0"], + base: ["text-black"], + inputWrapper: [ + "p-0", + "h-auto", + "bg-transparent", + "dark:hover:bg-transparent", + "dark:focus-within:bg-transparent", + "shadow-none", + ], + }} + defaultValue={goal.name} + /> +
+

{goal.name}

+ + {/* descriptio */} + + Add a description{" "} + + + + + + +
+ + change priority + +
+ {/* Priority */} + {Object.keys(priorityLabelBg).map((p) => { + return ( + + {p} + + ); + })} +
+
+ + + +
+ + + Sub-task + + {Array.isArray(goal.subTasks) && + goal.subTasks.length > 0 + ? goal.subTasks.length + : null} + +
+ } + > + {Array.isArray(goal.subTasks) && + goal.subTasks.length > 0 ? ( + <> + {goal.subTasks.map((g: Goal) => { + return ( +
+
setSelectedGoal(g)} + className="w-full px-2 m-2 border border-black rounded flex justify-between items-center" + > + + {g.name}{" "} + + +
+ +
+
+
+ + delete + +
+
+ ); + })} + + ) : null} + + +
+
+
+ + )} +
+
+ + ); +}; + +export default ModalGoal; diff --git a/src/app/(protected)/app/goals/[id]/page.tsx b/src/app/(protected)/app/goals/[id]/page.tsx new file mode 100644 index 0000000..c819e68 --- /dev/null +++ b/src/app/(protected)/app/goals/[id]/page.tsx @@ -0,0 +1,14 @@ +import { FunctionComponent } from "react"; + +interface GProps { + +} + +const G: FunctionComponent = () => { + return ( + <> + + ); +} + +export default G; \ No newline at end of file diff --git a/src/app/(protected)/app/goals/loading.tsx b/src/app/(protected)/app/goals/loading.tsx index f241504..b64e6ac 100644 --- a/src/app/(protected)/app/goals/loading.tsx +++ b/src/app/(protected)/app/goals/loading.tsx @@ -1,15 +1,13 @@ import { FunctionComponent } from "react"; -interface LoadingProps { - -} - +interface LoadingProps {} + const Loading: FunctionComponent = () => { - return ( + return ( <> -

loading...

+

loading...

- ); -} - -export default Loading; \ No newline at end of file + ); +}; + +export default Loading; diff --git a/src/app/(protected)/app/layout.tsx b/src/app/(protected)/app/layout.tsx index 0346ab6..266747e 100644 --- a/src/app/(protected)/app/layout.tsx +++ b/src/app/(protected)/app/layout.tsx @@ -10,7 +10,6 @@ interface LayoutProps { } const Layout: FunctionComponent = async ({ children }) => { - const session = await getServerSession(authOptions); const userInfo = await getUserInfo(session?.user.id); diff --git a/src/app/(protected)/app/loading.tsx b/src/app/(protected)/app/loading.tsx index f241504..b64e6ac 100644 --- a/src/app/(protected)/app/loading.tsx +++ b/src/app/(protected)/app/loading.tsx @@ -1,15 +1,13 @@ import { FunctionComponent } from "react"; -interface LoadingProps { - -} - +interface LoadingProps {} + const Loading: FunctionComponent = () => { - return ( + return ( <> -

loading...

+

loading...

- ); -} - -export default Loading; \ No newline at end of file + ); +}; + +export default Loading; diff --git a/src/app/api/task/route.ts b/src/app/api/task/route.ts index ca02da4..d0dd957 100644 --- a/src/app/api/task/route.ts +++ b/src/app/api/task/route.ts @@ -14,7 +14,7 @@ export async function GET(req: NextRequest) { if (todo) { const startDate = new Date("2024-01-01"); const endDate = new Date("2024-01-31"); - let events = await prisma.goals.findMany({ + let events = await prisma.goal.findMany({ where: { todoId: todo?.id, createdAt: { @@ -28,4 +28,3 @@ export async function GET(req: NextRequest) { } return NextResponse.json({ msg: "nothing to print" }); } - diff --git a/src/app/globals.css b/src/app/globals.css index a90f074..b5c61c9 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,4 +1,3 @@ @tailwind base; @tailwind components; @tailwind utilities; - diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 7e961d2..30a7c99 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -2,6 +2,7 @@ import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; import { Providers } from "./providers"; +import { Toaster } from "react-hot-toast"; const inter = Inter({ subsets: ["latin"] }); @@ -24,6 +25,7 @@ export default function RootLayout({ > {children} + diff --git a/src/app/page.tsx b/src/app/page.tsx index 5007115..8145988 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -6,7 +6,7 @@ import Link from "next/link"; export default function Home() { return ( <> -
+
diff --git a/src/config/site.ts b/src/config/site.ts index 99e2562..07b5e50 100644 --- a/src/config/site.ts +++ b/src/config/site.ts @@ -1,27 +1,26 @@ const siteConfig = { - siteName:"yay", + siteName: "yay", siteDescription: "scheduling the future", - priorities:[ + priorities: [ { - lvl:1, - label:"low", - key:"LOW", - color:"bg-[#06d6a0]" + lvl: 1, + label: "low", + key: "LOW", + color: "bg-[#06d6a0]", }, { - lvl:2, - label:"medium", - key:"MEDIUM", - color:"bg-[#ffd60a]" + lvl: 2, + label: "medium", + key: "MEDIUM", + color: "bg-[#ffd60a]", }, { - lvl:3, - label:"higt", - key:"HIGH", - color:"bg-[#ff0054]" - } - ] -} - + lvl: 3, + label: "higt", + key: "HIGH", + color: "bg-[#ff0054]", + }, + ], +}; -export default siteConfig; \ No newline at end of file +export default siteConfig; diff --git a/src/exceptions/error.ts b/src/exceptions/error.ts index fdab3b6..6f4fcaf 100644 --- a/src/exceptions/error.ts +++ b/src/exceptions/error.ts @@ -1,4 +1,4 @@ export class UserNotFoundException extends Error {} export class TodoNotFoundException extends Error {} export class TodoCreationError extends Error {} -export class InvalidAddTodoGoalException extends Error {} \ No newline at end of file +export class InvalidAddTodoGoalException extends Error {} diff --git a/src/lib/authOptions.ts b/src/lib/authOptions.ts index c18f2ea..83b9767 100644 --- a/src/lib/authOptions.ts +++ b/src/lib/authOptions.ts @@ -45,7 +45,6 @@ const authOptions: AuthOptions = { if (!user.email) { return false; } - try { let todo = await prisma.todo.findFirst({ where: { diff --git a/src/lib/calendar/index.ts b/src/lib/calendar/index.ts index cf7ed1c..16e5db0 100644 --- a/src/lib/calendar/index.ts +++ b/src/lib/calendar/index.ts @@ -1,6 +1,13 @@ -import { startOfMonth, startOfWeek, addDays, getYear, format, isToday } from "date-fns"; +import { + startOfMonth, + startOfWeek, + addDays, + getYear, + format, + isToday, +} from "date-fns"; -export function getMonth(month:number = new Date().getMonth()) { +export function getMonth(month: number = new Date().getMonth()) { //return current month in number es. JAN -> 0 month = Math.floor(month); //return current year es. 2024 @@ -10,7 +17,7 @@ export function getMonth(month:number = new Date().getMonth()) { const startOfTheWeek = startOfWeek(firstDayOfTheMonth); let currentMonthCount = 0 - startOfTheWeek.getDay(); - const daysMatrix = new Array(5).fill([]).map(() => { + const daysMatrix = new Array(6).fill([]).map(() => { return new Array(7).fill(null).map(() => { const currentDate = addDays(startOfTheWeek, currentMonthCount++); return format(currentDate, "yyyy-MM-dd"); // Adjust the format as needed @@ -20,7 +27,7 @@ export function getMonth(month:number = new Date().getMonth()) { return daysMatrix; } -export function getCurrentDayClass(day:string) { +export function getCurrentDayClass(day: string) { const formattedDay = format(day, "dd-MM-yy"); const isTodayFormatted = isToday(day) ? format(new Date(), "dd-MM-yy") : ""; return formattedDay === isTodayFormatted diff --git a/src/lib/goal.ts b/src/lib/goal.ts new file mode 100644 index 0000000..b623767 --- /dev/null +++ b/src/lib/goal.ts @@ -0,0 +1,22 @@ +function organizeTasksIntoHierarchy(tasks:any) { + const taskMap = new Map(); + + // Create a map with task ID as key and task object as value + tasks.forEach((task:any) => { + task.subTasks = []; // Initialize an empty array for subtasks + taskMap.set(task.id, task); + }); + + // Iterate over tasks to build the hierarchy + tasks.forEach((task:any) => { + const parentId = task.parentTaskId; + + if (parentId && taskMap.has(parentId)) { + // If the task has a parent, add it to the parent's subTasks array + taskMap.get(parentId).subTasks.push(task); + } + }); + + // Find and return the root tasks (tasks without a parent) + return tasks.filter((task:any) => !task.parentTaskId); +} \ No newline at end of file diff --git a/src/lib/notification/toast.ts b/src/lib/notification/toast.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/lib/utils.ts b/src/lib/utils.ts index b360955..a1ac6dc 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -7,7 +7,13 @@ export function cn(...inputs: ClassValue[]) { } export const priorityLabel = { - LOW:"border-[#06d6a0] text-[#06d6a0]", - MEDIUM: "border-[#ffd60a] text-[#ffd60a]", - HIGH: "border-[#ff0054] text-[#ff0054]" -} \ No newline at end of file + "LOW":"border-[#06d6a0] text-[#06d6a0]", + "MEDIUM": "border-[#ffd60a] text-[#ffd60a]", + "HIGH": "border-[#ff0054] text-[#ff0054]" +} + +export const priorityLabelBg = { + "LOW":"bg-[#06d6a0]", + "MEDIUM": "bg-[#ffd60a]", + "HIGH": "bg-[#ff0054]" +} diff --git a/src/queries/todo/index.ts b/src/queries/todo/index.ts index 5eb28d4..f6ebbaf 100644 --- a/src/queries/todo/index.ts +++ b/src/queries/todo/index.ts @@ -22,7 +22,7 @@ export async function getTodoList(userId: string) { } export async function addTodoGoal(payload: GoalPayload, todoId: string) { - let createdGaol = await prisma.goals.create({ + let createdGaol = await prisma.goal.create({ data: { name: payload.name, description: payload.description, @@ -33,23 +33,45 @@ export async function addTodoGoal(payload: GoalPayload, todoId: string) { return createdGaol; } +export async function createSubtask( + payload: { name: string }, + todoId: string, + goalId: string +) { + let createdSubtask = await prisma.goal.update({ + where: { + id: goalId, + }, + data: { + subTasks: { + create: { + todoId, + ...payload, + }, + }, + }, + }); + console.log(createdSubtask); + console.log(payload); +} + export async function deleteTodoGoal(id: string, todoId: string) { - return await prisma.goals.deleteMany({ + return await prisma.goal.deleteMany({ where: { - id: Number(id), + id: id, todoId, }, }); } export async function updateTodoGoal( - id: number, + id: string, todoId: string, payload: GoalPayload ) { - let value = await prisma.goals.update({ + let value = await prisma.goal.update({ where: { - id: Number(id), + id, todoId, }, data: { @@ -64,14 +86,19 @@ export async function getGoalsFromTodo( orderBy: "asc" | "desc", done: boolean = false ) { - let goals = await prisma.goals.findMany({ + let goals = await prisma.goal.findMany({ where: { - todoId: todoId, + todoId, done, + OR: [{ parentTaskId: null }, { parentTaskId: "" }], }, orderBy: { createdAt: orderBy, - }, + },include:{ + parentTask:true, + subTasks:true + } }); + console.log(goals); return goals; } diff --git a/src/queries/user/index.ts b/src/queries/user/index.ts index c901532..055042c 100644 --- a/src/queries/user/index.ts +++ b/src/queries/user/index.ts @@ -1,19 +1,19 @@ import { prisma } from "@/lib/prisma"; -export async function getUserInfo(userId:string){ +export async function getUserInfo(userId: string) { return await prisma.user.findFirst({ - where:{ - id:userId + where: { + id: userId, }, - select:{ - todo:{ - orderBy:{ - createdAt:"desc" - } + select: { + todo: { + orderBy: { + createdAt: "desc", + }, }, - password:false, - email:false, - emailVerified:false, - id:false, - } - }) -} \ No newline at end of file + password: false, + email: false, + emailVerified: false, + id: false, + }, + }); +} diff --git a/src/schema/todo.ts b/src/schema/todo.ts index 15ccc97..3a73c3f 100644 --- a/src/schema/todo.ts +++ b/src/schema/todo.ts @@ -1,10 +1,24 @@ import { z } from "zod"; import { GoalPayload } from "@/types/todo"; +import { Priority } from "@prisma/client"; -export async function validateAddTodoGoal(payload:GoalPayload) { - const schema = z.object({ - name:z.string().min(1), - description:z.string() - }).strict() - return schema.parse(payload); -} \ No newline at end of file +export async function validateAddTodoGoal(payload: GoalPayload) { + const schema = z + .object({ + name: z.string().min(1), + description: z.string().optional(), + priority: z.enum(["LOW", "MEDIUM", "HIGH"]).optional(), + }) + .strict(); + try { + return schema.parse(payload); + } catch (error) { + if (error instanceof z.ZodError) { + console.log("error zod"); + console.log(error); + //toast notification zod error + } else { + throw error; + } + } +} diff --git a/src/services/todo.ts b/src/services/todo.ts index e8c2a9e..6a8aee9 100644 --- a/src/services/todo.ts +++ b/src/services/todo.ts @@ -5,6 +5,7 @@ import { import authOptions from "@/lib/authOptions"; import { addTodoGoal, + createSubtask, deleteTodoGoal, getTodoList, updateTodoGoal, @@ -23,6 +24,15 @@ export async function addGoalTodoService(data: GoalPayload) { } } +export async function handleCreateSubtaskService(goalId: string) { + const session = await getServerSession(authOptions); + if (session && goalId) { + let todo = await getTodoList(session.user.id); + if (!todo) throw new TodoNotFoundException("The todo is not found"); + return await createSubtask({ name: "ciao mondo" },todo.id, goalId); + } +} + export async function handleDeleteGoalByIdService(todoId: string) { const session = await getServerSession(authOptions); if (session && todoId) { @@ -33,7 +43,7 @@ export async function handleDeleteGoalByIdService(todoId: string) { } export async function handleUpdateTodoStatusService( - todoId: number, + todoId: string, payload: GoalPayload ) { const session = await getServerSession(authOptions); diff --git a/src/store/drawer.ts b/src/store/drawer.ts new file mode 100644 index 0000000..e6f1a20 --- /dev/null +++ b/src/store/drawer.ts @@ -0,0 +1,11 @@ +import { create } from "zustand"; + +interface DrawerStore { + isOpen: boolean; + setIsOpen: (value: boolean) => void; +} + +export const useDrawerStore = create()((set) => ({ + isOpen: false, + setIsOpen: (data) => set({ isOpen: data }), +})); diff --git a/src/ui/Form/DatePicker/index.tsx b/src/ui/Form/DatePicker/index.tsx index 4b6f506..03b5228 100644 --- a/src/ui/Form/DatePicker/index.tsx +++ b/src/ui/Form/DatePicker/index.tsx @@ -7,29 +7,28 @@ import { PopoverContent, PopoverTrigger, } from "@nextui-org/react"; -import { format, getYear, startOfMonth } from "date-fns"; +import { format, getYear, isSameMonth, startOfMonth } from "date-fns"; import React, { FunctionComponent, useState } from "react"; import { FaCaretLeft, FaCaretRight } from "react-icons/fa"; -import { IoCalendarOutline } from "react-icons/io5"; import { GoDot } from "react-icons/go"; -import { useForm } from "react-hook-form"; -import { Inputs } from "../FormInput"; +import { IoCalendarOutline } from "react-icons/io5"; interface DatePickerProps { name:string; + register?:any; } -const DatePicker: FunctionComponent = ({ name }) => { +const DatePicker: FunctionComponent = ({ name, register }) => { const [isOpen, setIsOpen] = useState(false); - const { register } = useForm(); const [datePickValue, setDatePickValue] = useState( format(new Date(), "yyyy-MM-dd") ); - const { month, trackerMonth, prev, next, today } = useCalendarStore(); - let days = ["S", "M", "T", "W", "T", "F", "S"]; + let days = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]; return ( <> + {/* background: #b7b7b7; + color: black; */} = ({ name }) => { trigger: [ "h-auto", "dark:h-auto", - "min-h-[30px]", - "bg-[#0f0e0e]", - "hover:bg-[#0f0e0e]", - "hover:!bg-[#0f0e0e]", - "dark:hover:!bg-[#0f0e0e]", + "min-h-[30px]" , + "bg-[#b7b7b7]", + "hover:bg-[#b7b7b7]", + "hover:!bg-[#b7b7b7]", + "dark:hover:!bg-[#b7b7b7]", ], }} > - + - - + + +
+ {format( startOfMonth(new Date(getYear(new Date()), trackerMonth)), "MMMM yyyy" )} -
+ +
- +
- +
-
- +
+
-
+
+ + +
{days.map((e, i) => ( {e} ))}
-
+
{month.map((row: any, i: any) => ( - {row.map((day: any, idx: any) => ( + {row.map((day: any, idx: any) => { + return (
-
+
{ + setIsOpen(false); + setDatePickValue(day); + }} className="flex justify-center items-center px-2 py-0.5 h-fit"> = ({ name }) => { name="date" /> { - setIsOpen(false); - setDatePickValue(day); - }} className={cn( "text-sm my-1 text-start", - datePickValue === day && "text-[#00ffff]" + datePickValue === day && "text-[#e34e0d]", + !isSameMonth(day,startOfMonth(new Date(getYear(new Date()), trackerMonth))) && "text-gray-600" )} > - {format(day, "dd")} + {format(day, "d")}
- ))} + )})}
))}
diff --git a/src/ui/Form/FormInput/index.tsx b/src/ui/Form/FormInput/index.tsx index a342d52..e82347b 100644 --- a/src/ui/Form/FormInput/index.tsx +++ b/src/ui/Form/FormInput/index.tsx @@ -4,6 +4,7 @@ import { FunctionComponent } from "react"; import DatePicker from "../DatePicker"; import TaskInputOption from "../TaskInputOption"; import TaskPriority from "../TaskPriority"; +import { cn } from "@/lib/utils"; export type Inputs = { [key: string]: string; @@ -27,7 +28,7 @@ const FormInput: FunctionComponent = ({ const inputName = name as keyof Inputs; return ( -
+
{/* input section */}
= ({ type="text" placeholder="Add task..." classNames={{ + mainWrapper: "over", input: ["bg-transparent", "w-full", ...(classNames?.input || [])], innerWrapper: [ "bg-transparent", ...(classNames?.innerWrapper || []), ], - inputWrapper: [ - "h-auto", + inputWrapper:cn( + "h-auto dark:focus:bg-transparent", "bg-transparent", "dark:hover:bg-transparent", "dark:focus-within:bg-transparent", ...(classNames?.inputWrapper || []), - ], + ), }} {...register(inputName as string, { required: true })} {...inputProps} @@ -59,7 +61,7 @@ const FormInput: FunctionComponent = ({
{isDatePickerVisible ? (
- +
) : null}
diff --git a/src/ui/Form/TaskInputOption/index.tsx b/src/ui/Form/TaskInputOption/index.tsx index 497b1e6..8c5fa37 100644 --- a/src/ui/Form/TaskInputOption/index.tsx +++ b/src/ui/Form/TaskInputOption/index.tsx @@ -12,26 +12,27 @@ const TaskInputOption: FunctionComponent = () => { <> setIsOpen(open)} classNames={{ base: ["h-[30px]", "dark:w-full", "w-[30px]"], trigger: [ + "rounded-[5px]", "h-auto", "dark:h-auto", "min-h-[30px]", - "bg-[#0f0e0e]", - "hover:bg-[#0f0e0e]", - "hover:!bg-[#0f0e0e]", - "dark:hover:!bg-[#0f0e0e]", + "bg-[#b7b7b7]", + "hover:bg-[#b7b7b7]", + "hover:!bg-[#b7b7b7]", + "dark:hover:bg-[#b7b7b7]", ], }} > - -
- + +
+
diff --git a/src/ui/Form/TaskPriority/index.tsx b/src/ui/Form/TaskPriority/index.tsx index 0de22b3..0eceb02 100644 --- a/src/ui/Form/TaskPriority/index.tsx +++ b/src/ui/Form/TaskPriority/index.tsx @@ -1,4 +1,5 @@ "use client"; +import { cn } from "@/lib/utils"; import { Select, SelectItem } from "@nextui-org/react"; import { FunctionComponent } from "react"; @@ -10,87 +11,92 @@ const TaskPriority: FunctionComponent = ({ register }) => { return ( <> diff --git a/src/ui/YayDrawer/DraweContent/index.tsx b/src/ui/YayDrawer/DraweContent/index.tsx new file mode 100644 index 0000000..c91170c --- /dev/null +++ b/src/ui/YayDrawer/DraweContent/index.tsx @@ -0,0 +1,172 @@ +import { cn, priorityLabelBg } from "@/lib/utils"; +import { + Accordion, + AccordionItem, + Chip, + Divider, + Input, +} from "@nextui-org/react"; +import { Goal } from "@prisma/client"; +import { FunctionComponent } from "react"; +import { CiEdit } from "react-icons/ci"; +import { RiMoreFill } from "react-icons/ri"; +import { Drawer } from "vaul"; + +interface DrawerContentProps { + goal: any; + setSelectedGoal: any; +} + +const DrawerContent: FunctionComponent = ({ + goal, + setSelectedGoal, +}) => { + return ( + <> + + + + {goal.parentTaskId ? "indietro" : null} +
+
+
+ +
+ + } + classNames={{ + input: [ + "p-1 text-xl rounded-lg m-auto px-2 hover:bg-transparent bg-transparent", + "placeholder:text-black", + "w-full mr-2", + "group[data-has-value=true] group-data-[has-value=true]:text-black", + ], + innerWrapper: ["bg-transparent] p-0"], + base: ["text-black"], + inputWrapper: [ + "p-0", + "h-auto", + "bg-transparent", + "dark:hover:bg-transparent", + "dark:focus-within:bg-transparent", + "shadow-none", + ], + }} + defaultValue={goal.name} + /> + +

{goal.name}

+ + {/* descriptio */} + + Add a description{" "} + + + + +
+ + change priority + +
+ {/* Priority */} + {Object.keys(priorityLabelBg).map((p) => { + return ( + + {p} + + ); + })} +
+
+ + + +
+ + + Sub-task + + {Array.isArray(goal.subTasks) && + goal.subTasks.length > 0 + ? goal.subTasks.length + : null} + +
+ } + > + {Array.isArray(goal.subTasks) && + goal.subTasks.length > 0 ? ( + <> + {goal.subTasks.map((g: Goal) => { + return ( +
+
setSelectedGoal(g)} + className="w-full px-2 m-2 border border-black rounded flex justify-between items-center" + > + + {g.name}{" "} + + +
+ +
+
+
+ + delete + +
+
+ ); + })} + + ) : null} + + +
+
+
+ + + + ); +}; + +export default DrawerContent; diff --git a/src/ui/YayDrawer/index.tsx b/src/ui/YayDrawer/index.tsx new file mode 100644 index 0000000..b630e4a --- /dev/null +++ b/src/ui/YayDrawer/index.tsx @@ -0,0 +1,51 @@ +"use client"; +import { cn, priorityLabelBg } from "@/lib/utils"; +import { + Accordion, + AccordionItem, + Button, + Chip, + Divider, + Input, +} from "@nextui-org/react"; +import { Goal } from "@prisma/client"; +import { FunctionComponent, useState } from "react"; +import { RiMoreFill } from "react-icons/ri"; +import { Drawer } from "vaul"; +import { CiEdit } from "react-icons/ci"; +import Link from "next/link"; +import DrawerContent from "./DraweContent"; + + +interface YayDrawerProps { + goal: any; + isOpen: boolean; + setIsOpen: any; +} + +const YayDrawer: FunctionComponent = ({ + goal, + isOpen = false, + setIsOpen, +}) => { + const [isClose, setIsClose] = useState(false); + const [selectedGoal,setSelectedGoal] = useState(goal); + return ( +
+ { + setIsOpen(false); + setIsClose(true); + }} + open={isOpen || !isClose} + direction="right" + shouldScaleBackground + > + + + +
+ ); +}; + +export default YayDrawer; diff --git a/tailwind.config.ts b/tailwind.config.ts index 009e3c9..cc96eda 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -19,7 +19,8 @@ const config: Config = { boxShadow:{ 'hero-img': '0px 24px 100px 0px #9406ff59', 'nav-menu': '0px 10px 100px 0px #3535b78f', - 'modal': '0px 0px 200px 0px #634dff38' + 'modal': '0px 0px 200px 0px #634dff38', + 'drawer': '0px 0px 200px 0px #540f5d69' } }, },