From 6b2d2bc436a139c034134ed7640976639f32c8fd Mon Sep 17 00:00:00 2001 From: Oksana-Tnt Date: Fri, 18 Oct 2024 15:28:17 +0200 Subject: [PATCH 1/3] create front --- next.config.mjs | 6 +- package-lock.json | 804 ++++++++++++++++-- package.json | 19 +- prisma/schema.prisma | 200 +++++ public/{plus.png => create.png} | Bin public/{edit.png => update.png} | Bin src/app/(dashboard)/admin/page.tsx | 39 + src/app/(dashboard)/layout.tsx | 38 + .../(dashboard)/list/announcements/page.tsx | 92 ++ src/app/(dashboard)/list/assignments/page.tsx | 94 ++ src/app/(dashboard)/list/classes/page.tsx | 93 ++ src/app/(dashboard)/list/events/page.tsx | 98 +++ src/app/(dashboard)/list/exams/page.tsx | 92 ++ src/app/(dashboard)/list/lessons/page.tsx | 85 ++ src/app/(dashboard)/list/parents/page.tsx | 99 +++ src/app/(dashboard)/list/results/page.tsx | 106 +++ .../(dashboard)/list/students/[id]/page.tsx | 146 ++++ src/app/(dashboard)/list/students/page.tsx | 115 +++ src/app/(dashboard)/list/subjects/page.tsx | 79 ++ .../(dashboard)/list/teachers/[id]/page.tsx | 167 ++++ src/app/(dashboard)/list/teachers/page.tsx | 120 +++ src/app/(dashboard)/parent/page.tsx | 23 + src/app/(dashboard)/student/page.tsx | 25 + src/app/(dashboard)/teacher/page.tsx | 23 + src/app/globals.css | 105 ++- src/app/sign-in/page.tsx | 7 + src/components/Announcements.tsx | 49 ++ src/components/AttendanceChart.tsx | 87 ++ src/components/BigCalendar.tsx | 32 + src/components/CountChart.tsx | 75 ++ src/components/EventCalendar.tsx | 62 ++ src/components/FinanceChart.tsx | 135 +++ src/components/FormModal.tsx | 136 +++ src/components/InputFiels.tsx | 40 + src/components/Menu.tsx | 37 +- src/components/Navbar.tsx | 41 + src/components/Pagination.tsx | 26 + src/components/Performance.tsx | 43 + src/components/Table.tsx | 28 + src/components/TableSearch.tsx | 17 + src/components/UserCard.tsx | 19 + src/components/forms/AnnouncementForm.tsx | 171 ++++ src/components/forms/AssignmentForm.tsx | 171 ++++ src/components/forms/AttendanceForm.tsx | 171 ++++ src/components/forms/ClassForm.tsx | 171 ++++ src/components/forms/EventForm.tsx | 171 ++++ src/components/forms/ExamForm.tsx | 171 ++++ src/components/forms/LessonForm.tsx | 171 ++++ src/components/forms/ParentForm .tsx | 171 ++++ src/components/forms/ResultForm.tsx | 171 ++++ src/components/forms/StudentForm.tsx | 171 ++++ src/components/forms/SubjectForm.tsx | 171 ++++ src/components/forms/TeacherForm.tsx | 171 ++++ src/lib/data.ts | 24 +- tailwind.config.ts | 8 + tsconfig.json | 2 +- 56 files changed, 5505 insertions(+), 83 deletions(-) create mode 100644 prisma/schema.prisma rename public/{plus.png => create.png} (100%) rename public/{edit.png => update.png} (100%) create mode 100644 src/app/(dashboard)/admin/page.tsx create mode 100644 src/app/(dashboard)/layout.tsx create mode 100644 src/app/(dashboard)/list/announcements/page.tsx create mode 100644 src/app/(dashboard)/list/assignments/page.tsx create mode 100644 src/app/(dashboard)/list/classes/page.tsx create mode 100644 src/app/(dashboard)/list/events/page.tsx create mode 100644 src/app/(dashboard)/list/exams/page.tsx create mode 100644 src/app/(dashboard)/list/lessons/page.tsx create mode 100644 src/app/(dashboard)/list/parents/page.tsx create mode 100644 src/app/(dashboard)/list/results/page.tsx create mode 100644 src/app/(dashboard)/list/students/[id]/page.tsx create mode 100644 src/app/(dashboard)/list/students/page.tsx create mode 100644 src/app/(dashboard)/list/subjects/page.tsx create mode 100644 src/app/(dashboard)/list/teachers/[id]/page.tsx create mode 100644 src/app/(dashboard)/list/teachers/page.tsx create mode 100644 src/app/(dashboard)/parent/page.tsx create mode 100644 src/app/(dashboard)/student/page.tsx create mode 100644 src/app/(dashboard)/teacher/page.tsx create mode 100644 src/app/sign-in/page.tsx create mode 100644 src/components/Announcements.tsx create mode 100644 src/components/AttendanceChart.tsx create mode 100644 src/components/BigCalendar.tsx create mode 100644 src/components/CountChart.tsx create mode 100644 src/components/EventCalendar.tsx create mode 100644 src/components/FinanceChart.tsx create mode 100644 src/components/FormModal.tsx create mode 100644 src/components/InputFiels.tsx create mode 100644 src/components/Navbar.tsx create mode 100644 src/components/Pagination.tsx create mode 100644 src/components/Performance.tsx create mode 100644 src/components/Table.tsx create mode 100644 src/components/TableSearch.tsx create mode 100644 src/components/UserCard.tsx create mode 100644 src/components/forms/AnnouncementForm.tsx create mode 100644 src/components/forms/AssignmentForm.tsx create mode 100644 src/components/forms/AttendanceForm.tsx create mode 100644 src/components/forms/ClassForm.tsx create mode 100644 src/components/forms/EventForm.tsx create mode 100644 src/components/forms/ExamForm.tsx create mode 100644 src/components/forms/LessonForm.tsx create mode 100644 src/components/forms/ParentForm .tsx create mode 100644 src/components/forms/ResultForm.tsx create mode 100644 src/components/forms/StudentForm.tsx create mode 100644 src/components/forms/SubjectForm.tsx create mode 100644 src/components/forms/TeacherForm.tsx diff --git a/next.config.mjs b/next.config.mjs index 4678774e6..f6c572929 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,4 +1,8 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {}; +const nextConfig = { + images: { + remotePatterns: [{ hostname: "images.pexels.com" }], + }, +}; export default nextConfig; diff --git a/package-lock.json b/package-lock.json index cf999766d..b6bd087a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,20 @@ "name": "lama-dev-next-dashboard", "version": "0.1.0", "dependencies": { - "next": "14.2.5", + "@hookform/resolvers": "^3.9.0", + "@types/react-big-calendar": "^1.8.9", + "date-fns": "^3.6.0", + "dayjs": "^1.11.13", + "moment": "^2.30.1", + "next": "^14.2.6", + "prisma": "^5.21.0", "react": "^18", - "react-dom": "^18" + "react-big-calendar": "^1.13.4", + "react-calendar": "^5.0.0", + "react-dom": "^18", + "react-hook-form": "^7.53.0", + "recharts": "^2.12.7", + "zod": "^3.23.8" }, "devDependencies": { "@types/node": "^20", @@ -36,6 +47,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@babel/runtime": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.4.tgz", + "integrity": "sha512-DSgLeL/FNcpXuzav5wfYvHCGvynXkJbn3Zvc3823AEe9nPwW9IK4UoCSS5yGymmQzN0pCPvivtgS6/8U2kkm1w==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -96,6 +118,14 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@hookform/resolvers": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.9.0.tgz", + "integrity": "sha512-bU0Gr4EepJ/EQsH/IwEzYLsT/PEj5C0ynLQ4m+GSHS+xKH4TfSelhluTgOaoc4kA5s7eCsQbM4wvZLzELmWzUg==", + "peerDependencies": { + "react-hook-form": "^7.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -235,10 +265,9 @@ } }, "node_modules/@next/env": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.5.tgz", - "integrity": "sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA==", - "license": "MIT" + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.6.tgz", + "integrity": "sha512-bs5DFKV+08EjWrl8EB+KKqev1ZTNONH1vFCaHh911aaB362NnP32UDTbE9VQhyiAgbFqJsfDkSxFERNDDb3j0g==" }, "node_modules/@next/eslint-plugin-next": { "version": "14.2.5", @@ -251,13 +280,12 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.5.tgz", - "integrity": "sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.6.tgz", + "integrity": "sha512-BtJZb+hYXGaVJJivpnDoi3JFVn80SHKCiiRUW3kk1SY6UCUy5dWFFSbh+tGi5lHAughzeduMyxbLt3pspvXNSg==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "darwin" @@ -267,13 +295,12 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.5.tgz", - "integrity": "sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.6.tgz", + "integrity": "sha512-ZHRbGpH6KHarzm6qEeXKSElSXh8dS2DtDPjQt3IMwY8QVk7GbdDYjvV4NgSnDA9huGpGgnyy3tH8i5yHCqVkiQ==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "darwin" @@ -283,13 +310,12 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.5.tgz", - "integrity": "sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.6.tgz", + "integrity": "sha512-O4HqUEe3ZvKshXHcDUXn1OybN4cSZg7ZdwHJMGCXSUEVUqGTJVsOh17smqilIjooP/sIJksgl+1kcf2IWMZWHg==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -299,13 +325,12 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.5.tgz", - "integrity": "sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.6.tgz", + "integrity": "sha512-xUcdhr2hfalG8RDDGSFxQ75yOG894UlmFS4K2M0jLrUhauRBGOtUOxoDVwiIIuZQwZ3Y5hDsazNjdYGB0cQ9yQ==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -315,13 +340,12 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.5.tgz", - "integrity": "sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.6.tgz", + "integrity": "sha512-InosKxw8UMcA/wEib5n2QttwHSKHZHNSbGcMepBM0CTcNwpxWzX32KETmwbhKod3zrS8n1vJ+DuJKbL9ZAB0Ag==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -331,13 +355,12 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.5.tgz", - "integrity": "sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.6.tgz", + "integrity": "sha512-d4QXfJmt5pGJ7cG8qwxKSBnO5AXuKAFYxV7qyDRHnUNvY/dgDh+oX292gATpB2AAHgjdHd5ks1wXxIEj6muLUQ==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -347,13 +370,12 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.5.tgz", - "integrity": "sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.6.tgz", + "integrity": "sha512-AlgIhk4/G+PzOG1qdF1b05uKTMsuRatFlFzAi5G8RZ9h67CVSSuZSbqGHbJDlcV1tZPxq/d4G0q6qcHDKWf4aQ==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "win32" @@ -363,13 +385,12 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.5.tgz", - "integrity": "sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.6.tgz", + "integrity": "sha512-hNukAxq7hu4o5/UjPp5jqoBEtrpCbOmnUqZSKNJG8GrUVzfq0ucdhQFVrHcLRMvQcwqqDh1a5AJN9ORnNDpgBQ==", "cpu": [ "ia32" ], - "license": "MIT", "optional": true, "os": [ "win32" @@ -379,13 +400,12 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.5.tgz", - "integrity": "sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.6.tgz", + "integrity": "sha512-NANtw+ead1rSDK1jxmzq3TYkl03UNK2KHqUYf1nIhNci6NkeqBD4s1njSzYGIlSHxCK+wSaL8RXZm4v+NF/pMw==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "win32" @@ -443,6 +463,66 @@ "node": ">=14" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@prisma/debug": { + "version": "5.21.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.21.0.tgz", + "integrity": "sha512-8gX68E36OKImh7LBz5fFIuTRLZgM1ObnDA8ukhC1kZvTK7k7Unti6pJe3ZiudzuFAxae06PV1rhq1u9DZbXVnQ==" + }, + "node_modules/@prisma/engines": { + "version": "5.21.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.21.0.tgz", + "integrity": "sha512-IBewQJiDnFiz39pl8kEIzmzV4RAoBPBD2DoLDntMMXObg1an90Dp+xeb1mmwrTgRDE3elu/LYxyVPEkKw9LZ7A==", + "hasInstallScript": true, + "dependencies": { + "@prisma/debug": "5.21.0", + "@prisma/engines-version": "5.21.0-36.08713a93b99d58f31485621c634b04983ae01d95", + "@prisma/fetch-engine": "5.21.0", + "@prisma/get-platform": "5.21.0" + } + }, + "node_modules/@prisma/engines-version": { + "version": "5.21.0-36.08713a93b99d58f31485621c634b04983ae01d95", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.21.0-36.08713a93b99d58f31485621c634b04983ae01d95.tgz", + "integrity": "sha512-hfq7c8MnkhcZTY0bGXG6bV5Cr7OsnHLERNy4xkZy6rbpWnhtfjuj3yUVM4u1GKXd6uWmFbg0+HDw8KXTgTVepQ==" + }, + "node_modules/@prisma/fetch-engine": { + "version": "5.21.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.21.0.tgz", + "integrity": "sha512-nXKJrsxVKng6yjJzl7vBjrr3S34cOmWQ9SiGTo9xidVTmVSgg5GCTwDL4r2be8DE3RntqK5BW2LWQ1gF80eINw==", + "dependencies": { + "@prisma/debug": "5.21.0", + "@prisma/engines-version": "5.21.0-36.08713a93b99d58f31485621c634b04983ae01d95", + "@prisma/get-platform": "5.21.0" + } + }, + "node_modules/@prisma/get-platform": { + "version": "5.21.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.21.0.tgz", + "integrity": "sha512-NAyaAcHJhs0IysGYJtM6Fm3ccEs/LkCZqz/8riVkkJswFrRtFV93jAUIVKWO/wj1Ca1gO7HaMd/tr6e/9Xmvww==", + "dependencies": { + "@prisma/debug": "5.21.0" + } + }, + "node_modules/@restart/hooks": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", + "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@rushstack/eslint-patch": { "version": "1.10.4", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz", @@ -466,6 +546,65 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", + "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", + "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==" + }, + "node_modules/@types/date-arithmetic": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@types/date-arithmetic/-/date-arithmetic-4.1.4.tgz", + "integrity": "sha512-p9eZ2X9B80iKiTW4ukVj8B4K6q9/+xFtQ5MGYA5HWToY9nL4EkhV9+6ftT2VHpVMEZb5Tv00Iel516bVdO+yRw==" + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -487,20 +626,28 @@ "version": "15.7.12", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", - "dev": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.3", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", - "dev": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" } }, + "node_modules/@types/react-big-calendar": { + "version": "1.8.9", + "resolved": "https://registry.npmjs.org/@types/react-big-calendar/-/react-big-calendar-1.8.9.tgz", + "integrity": "sha512-HIHLUxR3PzWHrFdZ00VnCMvDjAh5uzlL0vMC2b7tL3bKaAJsqq9T8h+x0GVeDbZfMfHAd1cs5tZBhVvourNJXQ==", + "dependencies": { + "@types/date-arithmetic": "*", + "@types/prop-types": "*", + "@types/react": "*" + } + }, "node_modules/@types/react-dom": { "version": "18.3.0", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", @@ -511,6 +658,11 @@ "@types/react": "*" } }, + "node_modules/@types/warning": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", + "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==" + }, "node_modules/@typescript-eslint/parser": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz", @@ -652,6 +804,14 @@ "dev": true, "license": "ISC" }, + "node_modules/@wojtekmaj/date-utils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@wojtekmaj/date-utils/-/date-utils-1.5.1.tgz", + "integrity": "sha512-+i7+JmNiE/3c9FKxzWFi2IjRJ+KzZl1QPu6QNrsgaa2MuBgXvUy4gA1TVzf/JMdIIloB76xSKikTWuyYAIVLww==", + "funding": { + "url": "https://github.com/wojtekmaj/date-utils?sponsor=1" + } + }, "node_modules/acorn": { "version": "8.12.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", @@ -1150,6 +1310,14 @@ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1219,9 +1387,118 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -1283,6 +1560,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-arithmetic": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-arithmetic/-/date-arithmetic-4.1.0.tgz", + "integrity": "sha512-QWxYLR5P/6GStZcdem+V1xoto6DMadYWpMXU82ES3/RfR3Wdwr3D0+be7mgOJ+Ov0G9D5Dmb9T17sNLQYj9XOg==" + }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" + }, "node_modules/debug": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", @@ -1301,6 +1597,11 @@ } } }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" + }, "node_modules/deep-equal": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", @@ -1377,6 +1678,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -1417,6 +1726,15 @@ "node": ">=6.0.0" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -2081,6 +2399,11 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2088,6 +2411,14 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-equals": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz", + "integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -2245,7 +2576,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -2346,6 +2676,17 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/get-user-locale": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/get-user-locale/-/get-user-locale-2.3.2.tgz", + "integrity": "sha512-O2GWvQkhnbDoWFUJfaBlDIKUEdND8ATpBXD6KXcbhxlfktyD/d8w6mkzM/IlQEqGZAMz/PW6j6Hv53BiigKLUQ==", + "dependencies": { + "mem": "^8.0.0" + }, + "funding": { + "url": "https://github.com/wojtekmaj/get-user-locale?sponsor=1" + } + }, "node_modules/glob": { "version": "10.3.10", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", @@ -2647,6 +2988,22 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -3265,6 +3622,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -3291,6 +3658,45 @@ "dev": true, "license": "ISC" }, + "node_modules/luxon": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz", + "integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dependencies": { + "p-defer": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/mem": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/mem/-/mem-8.1.1.tgz", + "integrity": "sha512-qFCFUDs7U3b8mBDPyz5EToEKoAkgCzqquIgi9nkkR9bixxOVOre+09lbuH7+9Kn2NFpm56M3GUWVbU2hQgdACA==", + "dependencies": { + "map-age-cleaner": "^0.1.3", + "mimic-fn": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/mem?sponsor=1" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3315,6 +3721,14 @@ "node": ">=8.6" } }, + "node_modules/mimic-fn": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", + "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==", + "engines": { + "node": ">=8" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3348,6 +3762,25 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.45", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.45.tgz", + "integrity": "sha512-HIWmqA86KcmCAhnMAN0wuDOARV/525R2+lOLotuGFzn4HO+FH+/645z2wx0Dt3iDv6/p61SIvKnDstISainhLQ==", + "dependencies": { + "moment": "^2.29.4" + }, + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -3393,12 +3826,11 @@ "license": "MIT" }, "node_modules/next": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/next/-/next-14.2.5.tgz", - "integrity": "sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==", - "license": "MIT", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.6.tgz", + "integrity": "sha512-57Su7RqXs5CBKKKOagt8gPhMM3CpjgbeQhrtei2KLAA1vTNm7jfKS+uDARkSW8ZETUflDCBIsUKGSyQdRs4U4g==", "dependencies": { - "@next/env": "14.2.5", + "@next/env": "14.2.6", "@swc/helpers": "0.5.5", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", @@ -3413,15 +3845,15 @@ "node": ">=18.17.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.2.5", - "@next/swc-darwin-x64": "14.2.5", - "@next/swc-linux-arm64-gnu": "14.2.5", - "@next/swc-linux-arm64-musl": "14.2.5", - "@next/swc-linux-x64-gnu": "14.2.5", - "@next/swc-linux-x64-musl": "14.2.5", - "@next/swc-win32-arm64-msvc": "14.2.5", - "@next/swc-win32-ia32-msvc": "14.2.5", - "@next/swc-win32-x64-msvc": "14.2.5" + "@next/swc-darwin-arm64": "14.2.6", + "@next/swc-darwin-x64": "14.2.6", + "@next/swc-linux-arm64-gnu": "14.2.6", + "@next/swc-linux-arm64-musl": "14.2.6", + "@next/swc-linux-x64-gnu": "14.2.6", + "@next/swc-linux-x64-musl": "14.2.6", + "@next/swc-win32-arm64-msvc": "14.2.6", + "@next/swc-win32-ia32-msvc": "14.2.6", + "@next/swc-win32-x64-msvc": "14.2.6" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -3484,7 +3916,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -3654,6 +4085,14 @@ "node": ">= 0.8.0" } }, + "node_modules/p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==", + "engines": { + "node": ">=4" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -3985,11 +4424,28 @@ "node": ">= 0.8.0" } }, + "node_modules/prisma": { + "version": "5.21.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.21.0.tgz", + "integrity": "sha512-Pc/xn2eZPiANbFFGp+YoCQSKQ5t9W/dBzQ+3HrcfrOWZAzt7n77VsVCPa9WdfTjim4CuodkCFyiP2+uGY5/gqw==", + "hasInstallScript": true, + "dependencies": { + "@prisma/engines": "5.21.0" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=16.13" + }, + "optionalDependencies": { + "fsevents": "2.3.3" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -4040,6 +4496,70 @@ "node": ">=0.10.0" } }, + "node_modules/react-big-calendar": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/react-big-calendar/-/react-big-calendar-1.13.4.tgz", + "integrity": "sha512-eTc67wNHrEbtK7SW2dKlAWjPTcaMfg8BaL5ei4BQ/5FKzVOt3LrmBiq7hr9sOxOYtOfMRxWYMkjkZRvrlRWRBw==", + "dependencies": { + "@babel/runtime": "^7.20.7", + "clsx": "^1.2.1", + "date-arithmetic": "^4.1.0", + "dayjs": "^1.11.7", + "dom-helpers": "^5.2.1", + "globalize": "^0.1.1", + "invariant": "^2.2.4", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "luxon": "^3.2.1", + "memoize-one": "^6.0.0", + "moment": "^2.29.4", + "moment-timezone": "^0.5.40", + "prop-types": "^15.8.1", + "react-overlays": "^5.2.1", + "uncontrollable": "^7.2.1" + }, + "peerDependencies": { + "react": "^16.14.0 || ^17 || ^18", + "react-dom": "^16.14.0 || ^17 || ^18" + } + }, + "node_modules/react-big-calendar/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/react-big-calendar/node_modules/globalize": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/globalize/-/globalize-0.1.1.tgz", + "integrity": "sha512-5e01v8eLGfuQSOvx2MsDMOWS0GFtCx1wPzQSmcHw4hkxFzrQDBO3Xwg/m8Hr/7qXMrHeOIE29qWVzyv06u1TZA==" + }, + "node_modules/react-calendar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-calendar/-/react-calendar-5.0.0.tgz", + "integrity": "sha512-bHcE5e5f+VUKLd4R19BGkcSQLpuwjKBVG0fKz74cwPW5xDfNsReHdDbfd4z3mdjuUuZzVtw4Q920mkwK5/ZOEg==", + "dependencies": { + "@wojtekmaj/date-utils": "^1.1.3", + "clsx": "^2.0.0", + "get-user-locale": "^2.2.1", + "warning": "^4.0.0" + }, + "funding": { + "url": "https://github.com/wojtekmaj/react-calendar?sponsor=1" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -4053,13 +4573,80 @@ "react": "^18.3.1" } }, + "node_modules/react-hook-form": { + "version": "7.53.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.53.0.tgz", + "integrity": "sha512-M1n3HhqCww6S2hxLxciEXy2oISPnAzxY7gvwVPrtlczTM/1dDadXgUxDpHMrMTblDOcm/AXtXxHwZ3jpg1mqKQ==", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, "license": "MIT" }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "node_modules/react-overlays": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-5.2.1.tgz", + "integrity": "sha512-GLLSOLWr21CqtJn8geSwQfoJufdt3mfdsnIiQswouuQ2MMPns+ihZklxvsTDKD3cR2tF8ELbi5xUsvqVhR6WvA==", + "dependencies": { + "@babel/runtime": "^7.13.8", + "@popperjs/core": "^2.11.6", + "@restart/hooks": "^0.4.7", + "@types/warning": "^3.0.0", + "dom-helpers": "^5.2.0", + "prop-types": "^15.7.2", + "uncontrollable": "^7.2.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + } + }, + "node_modules/react-smooth": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.1.tgz", + "integrity": "sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w==", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -4083,6 +4670,36 @@ "node": ">=8.10.0" } }, + "node_modules/recharts": { + "version": "2.12.7", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.7.tgz", + "integrity": "sha512-hlLJMhPQfv4/3NBSAyq3gzGg4h2v69RJh6KU7b3pXYNNAELs9kEoXOjbkxdXpALqKBoVmVptGfLpxdaVYqjmXQ==", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^16.10.2", + "react-smooth": "^4.0.0", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", @@ -4105,6 +4722,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", @@ -4795,6 +5417,11 @@ "node": ">=0.8" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -4980,6 +5607,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/uncontrollable": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", + "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", + "dependencies": { + "@babel/runtime": "^7.6.3", + "@types/react": ">=16.9.11", + "invariant": "^2.2.4", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": ">=15.0.0" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -5004,6 +5645,35 @@ "dev": true, "license": "MIT" }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5246,6 +5916,14 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index dd3389bbf..db384af1b 100644 --- a/package.json +++ b/package.json @@ -9,18 +9,29 @@ "lint": "next lint" }, "dependencies": { + "@hookform/resolvers": "^3.9.0", + "@types/react-big-calendar": "^1.8.9", + "date-fns": "^3.6.0", + "dayjs": "^1.11.13", + "moment": "^2.30.1", + "next": "^14.2.6", + "prisma": "^5.21.0", "react": "^18", + "react-big-calendar": "^1.13.4", + "react-calendar": "^5.0.0", "react-dom": "^18", - "next": "14.2.5" + "react-hook-form": "^7.53.0", + "recharts": "^2.12.7", + "zod": "^3.23.8" }, "devDependencies": { - "typescript": "^5", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "eslint": "^8", + "eslint-config-next": "14.2.5", "postcss": "^8", "tailwindcss": "^3.4.1", - "eslint": "^8", - "eslint-config-next": "14.2.5" + "typescript": "^5" } } diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 000000000..ba670f77b --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,200 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? +// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model Admin { + id String @id + username String @unique +} + +model Student { + id String @id + username String @unique + name String + surname String + email String? @unique + phone String? @unique + address String + img String? + bloodType String + sex UserSex + createdAT DateTime @default(now()) + + parentId String + parent Parent @relation(fields: [parentId], references: [id]) + classId Int + class Class @relation(fields: [classId], references: [id]) + gradeId Int + grade Grade @relation(fields: [gradeId], references: [id]) + attendances Attendance[] + results Result[] +} + +model Teacher { + id String @id + username String @unique + name String + surname String + email String? @unique + phone String? @unique + address String + img String? + bloodType String + sex UserSex + createdAT DateTime @default(now()) + + subjects Subject[] + lessons Lesson[] + + classes Class[] +} + +model Parent { + id String @id + username String @unique + name String + surname String + email String? @unique + phone String @unique + address String + createdAT DateTime @default(now()) + students Student[] +} + +model Grade { + id Int @id @default(autoincrement()) + level Int @unique + + students Student[] + classes Class[] +} + +model Class { + id Int @id @default(autoincrement()) + name String @unique + capacity Int + + supervisorId String + supervisor Teacher @relation(fields: [supervisorId], references: [id]) + lessons Lesson[] + students Student[] + gradeId Int + grade Grade @relation(fields: [gradeId], references: [id]) + events Event[] + announcements Announcement[] +} + +model Subject { + id Int @id @default(autoincrement()) + name String @unique + teachers Teacher[] + lessons Lesson[] +} + +model Lesson { + id Int @id @default(autoincrement()) + name String + day Day + startTime DateTime + endTime DateTime + + subjectId Int + subject Subject @relation(fields: [subjectId], references: [id]) + classId Int + class Class @relation(fields: [classId], references: [id]) + teacherId String + teacher Teacher @relation(fields: [teacherId], references: [id]) + exams Exam[] + assignments Assignment[] + attendances Attendance[] +} + +model Exam { + id Int @id @default(autoincrement()) + title String + startTime DateTime + endTime DateTime + + lessonId Int + lesson Lesson @relation(fields: [lessonId], references: [id]) + results Result[] +} + +model Assignment { + id Int @id @default(autoincrement()) + title String + startDate DateTime + dueDate DateTime + + lessonId Int + lesson Lesson @relation(fields: [lessonId], references: [id]) + reults Result[] +} + +model Result { + id Int @id @default(autoincrement()) + score Int + + examId Int? + exam Exam? @relation(fields: [examId], references: [id]) + assignmentId Int? + assignment Assignment? @relation(fields: [assignmentId], references: [id]) + studentId String + student Student @relation(fields: [studentId], references: [id]) +} + +model Attendance { + id Int @id @default(autoincrement()) + date DateTime + present Boolean + + studentId String + student Student @relation(fields: [studentId], references: [id]) + lessonId Int + lesson Lesson @relation(fields: [lessonId], references: [id]) +} + +model Event { + id Int @id @default(autoincrement()) + title String + description String + startTime DateTime + endTime DateTime + + classId Int? + class Class? @relation(fields: [classId], references: [id]) +} + +model Announcement { + id Int @id @default(autoincrement()) + title String + description String + date DateTime + + classId Int? + class Class? @relation(fields: [classId], references: [id]) +} + +enum UserSex { + MALE + FEMALE +} + +enum Day { + MONDAY + TUESDAY + WEDNESDAY + THURSDAY + FRIDAY +} diff --git a/public/plus.png b/public/create.png similarity index 100% rename from public/plus.png rename to public/create.png diff --git a/public/edit.png b/public/update.png similarity index 100% rename from public/edit.png rename to public/update.png diff --git a/src/app/(dashboard)/admin/page.tsx b/src/app/(dashboard)/admin/page.tsx new file mode 100644 index 000000000..5a3917b8f --- /dev/null +++ b/src/app/(dashboard)/admin/page.tsx @@ -0,0 +1,39 @@ +import Announcements from "@/components/Announcements"; +import AttendanceChart from "@/components/AttendanceChart"; +import CountChart from "@/components/CountChart"; +import EventCalendar from "@/components/EventCalendar"; +import FinanceChart from "@/components/FinanceChart"; +import UserCard from "@/components/UserCard"; +import React from "react"; + +const AdminPage = () => { + return ( +
+
+
+ + + + +
+
+
+ +
+
+ +
+
+
+ +
+
+
+ + +
+
+ ); +}; + +export default AdminPage; diff --git a/src/app/(dashboard)/layout.tsx b/src/app/(dashboard)/layout.tsx new file mode 100644 index 000000000..15148b35a --- /dev/null +++ b/src/app/(dashboard)/layout.tsx @@ -0,0 +1,38 @@ +import Menu from "@/components/Menu"; +import Navbar from "@/components/Navbar"; +import type { Metadata } from "next"; +import { Inter } from "next/font/google"; +import Image from "next/image"; +import Link from "next/link"; + +const inter = Inter({ subsets: ["latin"] }); + +export const metadata: Metadata = { + title: "Lama Dev School Management Dashboard", + description: "Next.js School Management System", +}; + +export default function DashboardLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( +
+
+ + logo + SchooLama + + +
+
+ + {children} +
+
+ ); +} diff --git a/src/app/(dashboard)/list/announcements/page.tsx b/src/app/(dashboard)/list/announcements/page.tsx new file mode 100644 index 000000000..4830170a5 --- /dev/null +++ b/src/app/(dashboard)/list/announcements/page.tsx @@ -0,0 +1,92 @@ +import FormModal from "@/components/FormModal"; +import Pagination from "@/components/Pagination"; +import Table from "@/components/Table"; +import TableSearch from "@/components/TableSearch"; +import { announcementsData, role } from "@/lib/data"; +import Image from "next/image"; +import Link from "next/link"; +import React from "react"; + +type Announcement = { + id: number; + title: string; + class: string; + date: string; +}; +const columns = [ + { + header: "Title", + accessor: "title", + }, + { + header: "Class", + accessor: "class", + }, + { + header: "Date", + accessor: "date", + className: "hidden md:table-cell", + }, + { + header: "Actions", + accessor: "action", + }, +]; +const AnnouncementListPage = () => { + const renderRow = (item: Announcement) => ( + + {item.title} + {item.class} + {item.date} + +
+ {role === "admin" && ( + <> + + + + )} +
+ + + ); + return ( +
+ {/*TOP*/} +
+

+ All Announcements +

+
+ +
+ + + {role === "admin" && ( + + )} +
+
+
+ {/*LIST*/} +
+ + + {/*PAGINATION*/} + + + ); +}; + +export default AnnouncementListPage; diff --git a/src/app/(dashboard)/list/assignments/page.tsx b/src/app/(dashboard)/list/assignments/page.tsx new file mode 100644 index 000000000..4af3064c4 --- /dev/null +++ b/src/app/(dashboard)/list/assignments/page.tsx @@ -0,0 +1,94 @@ +import FormModal from "@/components/FormModal"; +import Pagination from "@/components/Pagination"; +import Table from "@/components/Table"; +import TableSearch from "@/components/TableSearch"; +import { assignmentsData, role } from "@/lib/data"; +import Image from "next/image"; +import Link from "next/link"; +import React from "react"; + +type Assignment = { + id: number; + subject: string; + class: string; + teacher: string; + dueDate: string; +}; +const columns = [ + { + header: "Subject Name", + accessor: "name", + }, + { + header: "Class", + accessor: "class", + }, + { + header: "Teacher", + accessor: "teacher", + className: "hidden md:table-cell", + }, + { + header: "Due Date", + accessor: "dueDate", + className: "hidden md:table-cell", + }, + { + header: "Actions", + accessor: "action", + }, +]; +const AssignmentListPage = () => { + const renderRow = (item: Assignment) => ( + + + + + + + + + ); + return ( +
+ {/*TOP*/} +
+

+ All Assignments +

+
+ +
+ + + {role === "admin" && } +
+
+
+ {/*LIST*/} +
+
{item.subject}{item.class}{item.teacher}{item.dueDate} +
+ {role === "admin" && ( + <> + + + + )} +
+
+ + {/*PAGINATION*/} + + + ); +}; + +export default AssignmentListPage; diff --git a/src/app/(dashboard)/list/classes/page.tsx b/src/app/(dashboard)/list/classes/page.tsx new file mode 100644 index 000000000..6a30d9437 --- /dev/null +++ b/src/app/(dashboard)/list/classes/page.tsx @@ -0,0 +1,93 @@ +import FormModal from "@/components/FormModal"; +import Pagination from "@/components/Pagination"; +import Table from "@/components/Table"; +import TableSearch from "@/components/TableSearch"; +import { classesData, role, subjectsData } from "@/lib/data"; +import Image from "next/image"; +import Link from "next/link"; +import React from "react"; + +type Class = { + id: number; + name: string; + capacity: number; + grade: number; + supervisor: string; +}; +const columns = [ + { + header: "Class Name", + accessor: "name", + }, + { + header: "Capacity", + accessor: "capacity", + className: "hidden md:table-cell", + }, + { + header: "Grade", + accessor: "grade", + className: "hidden md:table-cell", + }, + { + header: "Supervisor", + accessor: "supervisor", + className: "hidden md:table-cell", + }, + { + header: "Actions", + accessor: "action", + }, +]; +const ClassListPage = () => { + const renderRow = (item: Class) => ( + + + + + + + + + ); + return ( +
+ {/*TOP*/} +
+

All Classes

+
+ +
+ + + {role === "admin" && } +
+
+
+ {/*LIST*/} +
+
{item.name}{item.capacity}{item.grade}{item.supervisor} +
+ {role === "admin" && ( + <> + + + + )} +
+
+ + {/*PAGINATION*/} + + + ); +}; + +export default ClassListPage; diff --git a/src/app/(dashboard)/list/events/page.tsx b/src/app/(dashboard)/list/events/page.tsx new file mode 100644 index 000000000..88e5d6845 --- /dev/null +++ b/src/app/(dashboard)/list/events/page.tsx @@ -0,0 +1,98 @@ +import FormModal from "@/components/FormModal"; +import Pagination from "@/components/Pagination"; +import Table from "@/components/Table"; +import TableSearch from "@/components/TableSearch"; +import { eventsData, role } from "@/lib/data"; +import Image from "next/image"; +import Link from "next/link"; +import React from "react"; + +type Event = { + id: number; + title: string; + class: string; + date: string; + startTime: string; + endTime: number; +}; +const columns = [ + { + header: "Title", + accessor: "title", + }, + { + header: "Class", + accessor: "class", + }, + { + header: "Date", + accessor: "date", + className: "hidden md:table-cell", + }, + { + header: "Start Time", + accessor: "startTime", + className: "hidden md:table-cell", + }, + { + header: "End Time", + accessor: "endTime", + className: "hidden md:table-cell", + }, + { + header: "Actions", + accessor: "action", + }, +]; +const EventListPage = () => { + const renderRow = (item: Event) => ( + + + + + + + + + ); + return ( +
+ {/*TOP*/} +
+

All Events

+
+ +
+ + + {role === "admin" && } +
+
+
+ {/*LIST*/} +
+
{item.title}{item.class}{item.date}{item.startTime}{item.endTime} +
+ {role === "admin" && ( + <> + + + + )} +
+
+ + {/*PAGINATION*/} + + + ); +}; + +export default EventListPage; diff --git a/src/app/(dashboard)/list/exams/page.tsx b/src/app/(dashboard)/list/exams/page.tsx new file mode 100644 index 000000000..facab88bb --- /dev/null +++ b/src/app/(dashboard)/list/exams/page.tsx @@ -0,0 +1,92 @@ +import FormModal from "@/components/FormModal"; +import Pagination from "@/components/Pagination"; +import Table from "@/components/Table"; +import TableSearch from "@/components/TableSearch"; +import { examsData, lessonsData, role } from "@/lib/data"; +import Image from "next/image"; +import Link from "next/link"; +import React from "react"; + +type Exam = { + id: number; + subject: string; + class: string; + teacher: string; + date: string; +}; +const columns = [ + { + header: "Subject Name", + accessor: "name", + }, + { + header: "Class", + accessor: "class", + }, + { + header: "Teacher", + accessor: "teacher", + className: "hidden md:table-cell", + }, + { + header: "Date", + accessor: "date", + className: "hidden md:table-cell", + }, + { + header: "Actions", + accessor: "action", + }, +]; +const ExamListPage = () => { + const renderRow = (item: Exam) => ( + + + + + + + + + ); + return ( +
+ {/*TOP*/} +
+

All Exams

+
+ +
+ + + {role === "admin" && } +
+
+
+ {/*LIST*/} +
+
{item.subject}{item.class}{item.teacher}{item.date} +
+ {role === "admin" && ( + <> + + + + )} +
+
+ + {/*PAGINATION*/} + + + ); +}; + +export default ExamListPage; diff --git a/src/app/(dashboard)/list/lessons/page.tsx b/src/app/(dashboard)/list/lessons/page.tsx new file mode 100644 index 000000000..fe8a1604b --- /dev/null +++ b/src/app/(dashboard)/list/lessons/page.tsx @@ -0,0 +1,85 @@ +import FormModal from "@/components/FormModal"; +import Pagination from "@/components/Pagination"; +import Table from "@/components/Table"; +import TableSearch from "@/components/TableSearch"; +import { lessonsData, role, subjectsData } from "@/lib/data"; +import Image from "next/image"; +import Link from "next/link"; +import React from "react"; + +type Lesson = { + id: number; + subject: string; + class: string; + teacher: string; +}; +const columns = [ + { + header: "Subject Name", + accessor: "name", + }, + { + header: "Class", + accessor: "class", + }, + { + header: "Teacher", + accessor: "teacher", + className: "hidden md:table-cell", + }, + { + header: "Actions", + accessor: "action", + }, +]; +const LessonListPage = () => { + const renderRow = (item: Lesson) => ( + + + + + + + + ); + return ( +
+ {/*TOP*/} +
+

All Lessons

+
+ +
+ + + {role === "admin" && } +
+
+
+ {/*LIST*/} +
+
{item.subject}{item.class}{item.teacher} +
+ {role === "admin" && ( + <> + + + + )} +
+
+ + {/*PAGINATION*/} + + + ); +}; + +export default LessonListPage; diff --git a/src/app/(dashboard)/list/parents/page.tsx b/src/app/(dashboard)/list/parents/page.tsx new file mode 100644 index 000000000..75ada0b14 --- /dev/null +++ b/src/app/(dashboard)/list/parents/page.tsx @@ -0,0 +1,99 @@ +import FormModal from "@/components/FormModal"; +import Pagination from "@/components/Pagination"; +import Table from "@/components/Table"; +import TableSearch from "@/components/TableSearch"; +import { parentsData, role } from "@/lib/data"; +import Image from "next/image"; +import Link from "next/link"; +import React from "react"; + +type Parent = { + id: number; + name: string; + email?: string; + students: string[]; + phone: string; + address: string; +}; +const columns = [ + { + header: "Info", + accessor: "info", + }, + { + header: "Student Names", + accessor: "studentNames", + className: "hidden md:table-cell", + }, + { + header: "Phone", + accessor: "phone", + className: "hidden lg:table-cell", + }, + { + header: "Address", + accessor: "address", + className: "hidden lg:table-cell", + }, + { + header: "Actions", + accessor: "action", + }, +]; +const ParentListPage = () => { + const renderRow = (item: Parent) => ( + + + + + + + + ); + return ( +
+ {/*TOP*/} +
+

All Parents

+
+ +
+ + + {role === "admin" && } +
+
+
+ {/*LIST*/} +
+
+
+

{item.name}

+

{item?.email}

+
+
{item.students.join(",")}{item.phone}{item.address} +
+ {role === "admin" && ( + <> + + + + + )} +
+
+ + {/*PAGINATION*/} + + + ); +}; + +export default ParentListPage; diff --git a/src/app/(dashboard)/list/results/page.tsx b/src/app/(dashboard)/list/results/page.tsx new file mode 100644 index 000000000..ddaf5e6dd --- /dev/null +++ b/src/app/(dashboard)/list/results/page.tsx @@ -0,0 +1,106 @@ +import FormModal from "@/components/FormModal"; +import Pagination from "@/components/Pagination"; +import Table from "@/components/Table"; +import TableSearch from "@/components/TableSearch"; +import { assignmentsData, resultsData, role } from "@/lib/data"; +import Image from "next/image"; +import Link from "next/link"; +import React from "react"; + +type Result = { + id: number; + subject: string; + class: string; + teacher: string; + student: string; + type: "exam" | "assignment"; + date: string; + score: number; +}; +const columns = [ + { + header: "Subject Name", + accessor: "name", + }, + { + header: "Student", + accessor: "student", + }, + { + header: "Score", + accessor: "score", + className: "hidden md:table-cell", + }, + { + header: "Teacher", + accessor: "teacher", + className: "hidden md:table-cell", + }, + { + header: "Class", + accessor: "class", + className: "hidden md:table-cell", + }, + { + header: "Date", + accessor: "Date", + className: "hidden md:table-cell", + }, + { + header: "Actions", + accessor: "action", + }, +]; +const ResultListPage = () => { + const renderRow = (item: Result) => ( + + + + + + + + + + ); + return ( +
+ {/*TOP*/} +
+

All Result

+
+ +
+ + + {role === "admin" && } +
+
+
+ {/*LIST*/} +
+
{item.subject}{item.student}{item.score}{item.teacher}{item.class}{item.date} +
+ {role === "admin" && ( + <> + + + + )} +
+
+ + {/*PAGINATION*/} + + + ); +}; + +export default ResultListPage; diff --git a/src/app/(dashboard)/list/students/[id]/page.tsx b/src/app/(dashboard)/list/students/[id]/page.tsx new file mode 100644 index 000000000..1f331a55a --- /dev/null +++ b/src/app/(dashboard)/list/students/[id]/page.tsx @@ -0,0 +1,146 @@ +import Announcements from "@/components/Announcements"; +import BigCalendar from "@/components/BigCalendar"; +import Performance from "@/components/Performance"; +import Image from "next/image"; +import Link from "next/link"; +import React from "react"; + +const SingleStudentPage = () => { + return ( +
+ {/*LEFT*/} +
+ {/*TOP*/} +
+ {/*USER INFO CARD*/} +
+
+ +
+
+

Cameron Moran

+

+ Lorem ipsum dolor sit amet. +

+
+
+ + A+ +
+
+ + January 2025 +
+
+ + user@gmail.com +
+
+ + +1 234 567 +
+
+
+
+ {/*SMALL CARDS*/} +
+ {/*CARD*/} +
+ +
+

90%

+ Attendance +
+
+ {/*CARD*/} +
+ +
+

6th

+ Grade +
+
+ {/*CARD*/} +
+ +
+

18

+ Lessons +
+
+ {/*CARD*/} +
+ +
+

6A

+ Class +
+
+
+
+ {/*BOTTOM*/} +
+

Student's Schedule

+ +
+
+ {/*RIGHT*/} +
+
+

Shortcuts

+
+ + Student's Lessons + + + Student's Teachers + + + Student's Exams + + + Student's Assignments + + + Student's Results + +
+
+ + +
+
+ ); +}; + +export default SingleStudentPage; diff --git a/src/app/(dashboard)/list/students/page.tsx b/src/app/(dashboard)/list/students/page.tsx new file mode 100644 index 000000000..9fd5cd05c --- /dev/null +++ b/src/app/(dashboard)/list/students/page.tsx @@ -0,0 +1,115 @@ +import FormModal from "@/components/FormModal"; +import Pagination from "@/components/Pagination"; +import Table from "@/components/Table"; +import TableSearch from "@/components/TableSearch"; +import { role, studentsData } from "@/lib/data"; +import Image from "next/image"; +import Link from "next/link"; +import React from "react"; + +type Student = { + id: number; + studentId: string; + name: string; + email?: string; + photo: string; + phone?: string; + grade: number; + class: string; + address: string; +}; +const columns = [ + { + header: "Info", + accessor: "info", + }, + { + header: "Student ID", + accessor: "studentId", + className: "hidden md:table-cell", + }, + { + header: "Grade", + accessor: "grade", + className: "hidden md:table-cell", + }, + + { + header: "Phone", + accessor: "phone", + className: "hidden lg:table-cell", + }, + { + header: "Address", + accessor: "address", + className: "hidden lg:table-cell", + }, + { + header: "Actions", + accessor: "action", + }, +]; +const StudentListPage = () => { + const renderRow = (item: Student) => ( +
+ + + + + + + + ); + return ( +
+ {/*TOP*/} +
+

All Students

+
+ +
+ + + {role === "admin" && } +
+
+
+ {/*LIST*/} +
+
+ +
+

{item.name}

+

{item.class}

+
+
{item.studentId}{item.grade}{item.phone}{item.address} +
+ + {role === "admin" && ( + + )} +
+
+ + {/*PAGINATION*/} + + + ); +}; + +export default StudentListPage; diff --git a/src/app/(dashboard)/list/subjects/page.tsx b/src/app/(dashboard)/list/subjects/page.tsx new file mode 100644 index 000000000..933a31d0c --- /dev/null +++ b/src/app/(dashboard)/list/subjects/page.tsx @@ -0,0 +1,79 @@ +import FormModal from "@/components/FormModal"; +import Pagination from "@/components/Pagination"; +import Table from "@/components/Table"; +import TableSearch from "@/components/TableSearch"; +import { role, subjectsData } from "@/lib/data"; +import Image from "next/image"; +import Link from "next/link"; +import React from "react"; + +type Subject = { + id: number; + name: string; + teachers: string[]; +}; +const columns = [ + { + header: "Subject Name", + accessor: "name", + }, + { + header: "Teachers", + accessor: "teachers", + className: "hidden md:table-cell", + }, + { + header: "Actions", + accessor: "action", + }, +]; +const SubjectListPage = () => { + const renderRow = (item: Subject) => ( + + + + + + + ); + return ( +
+ {/*TOP*/} +
+

All Subjects

+
+ +
+ + + {role === "admin" && } +
+
+
+ {/*LIST*/} +
+
{item.name}{item.teachers.join(",")} +
+ {role === "admin" && ( + <> + + + + )} +
+
+ + {/*PAGINATION*/} + + + ); +}; + +export default SubjectListPage; diff --git a/src/app/(dashboard)/list/teachers/[id]/page.tsx b/src/app/(dashboard)/list/teachers/[id]/page.tsx new file mode 100644 index 000000000..1e0b76bde --- /dev/null +++ b/src/app/(dashboard)/list/teachers/[id]/page.tsx @@ -0,0 +1,167 @@ +import Announcements from "@/components/Announcements"; +import BigCalendar from "@/components/BigCalendar"; +import FormModal from "@/components/FormModal"; +import Performance from "@/components/Performance"; +import Image from "next/image"; +import Link from "next/link"; +import React from "react"; + +const SingleTeacherPage = () => { + return ( +
+ {/*LEFT*/} +
+ {/*TOP*/} +
+ {/*USER INFO CARD*/} +
+
+ +
+
+
+

Leonard Snyder

+ +
+

+ Lorem ipsum dolor sit amet. +

+
+
+ + A+ +
+
+ + January 2025 +
+
+ + user@gmail.com +
+
+ + +1 234 567 +
+
+
+
+ {/*SMALL CARDS*/} +
+ {/*CARD*/} +
+ +
+

90%

+ Attendance +
+
+ {/*CARD*/} +
+ +
+

2

+ Branches +
+
+ {/*CARD*/} +
+ +
+

6

+ Lessons +
+
+ {/*CARD*/} +
+ +
+

6

+ Classes +
+
+
+
+ {/*BOTTOM*/} +
+

Teacher's Schedule

+ +
+
+ {/*RIGHT*/} +
+
+

Shortcuts

+
+ + Teacher's Classes + + + Teacher's Students + + + Teacher's Lessons + + + Teacher's Exams + + + Teacher's Assignments + +
+
+ + +
+
+ ); +}; + +export default SingleTeacherPage; diff --git a/src/app/(dashboard)/list/teachers/page.tsx b/src/app/(dashboard)/list/teachers/page.tsx new file mode 100644 index 000000000..a26cb5b0a --- /dev/null +++ b/src/app/(dashboard)/list/teachers/page.tsx @@ -0,0 +1,120 @@ +import FormModal from "@/components/FormModal"; +import Pagination from "@/components/Pagination"; +import Table from "@/components/Table"; +import TableSearch from "@/components/TableSearch"; +import { role, teachersData } from "@/lib/data"; +import Image from "next/image"; +import Link from "next/link"; +import React from "react"; + +type Teacher = { + id: number; + teacherId: string; + name: string; + email?: string; + photo: string; + phone: string; + subjects: string[]; + classes: string[]; + address: string; +}; +const columns = [ + { + header: "Info", + accessor: "info", + }, + { + header: "Teacher ID", + accessor: "teacherId", + className: "hidden md:table-cell", + }, + { + header: "Subjects", + accessor: "subjects", + className: "hidden md:table-cell", + }, + { + header: "Classes", + accessor: "classes", + className: "hidden md:table-cell", + }, + { + header: "Phone", + accessor: "phone", + className: "hidden lg:table-cell", + }, + { + header: "Address", + accessor: "address", + className: "hidden lg:table-cell", + }, + { + header: "Actions", + accessor: "action", + }, +]; +const TeacherListPage = () => { + const renderRow = (item: Teacher) => ( +
+ + + + + + + + + ); + return ( +
+ {/*TOP*/} +
+

All Teachers

+
+ +
+ + + {role === "admin" && } +
+
+
+ {/*LIST*/} +
+
+ +
+

{item.name}

+

{item?.email}

+
+
{item.teacherId}{item.subjects.join(",")}{item.classes.join(",")}{item.phone}{item.address} +
+ + {role === "admin" && ( + + )} +
+
+ + {/*PAGINATION*/} + + + ); +}; + +export default TeacherListPage; diff --git a/src/app/(dashboard)/parent/page.tsx b/src/app/(dashboard)/parent/page.tsx new file mode 100644 index 000000000..4f93729a9 --- /dev/null +++ b/src/app/(dashboard)/parent/page.tsx @@ -0,0 +1,23 @@ +import Announcements from "@/components/Announcements"; +import BigCalendar from "@/components/BigCalendar"; +import React from "react"; + +const ParentPage = () => { + return ( +
+ {/*LEFT*/} +
+
+

Schedule (John Doe)

+ +
+
+ {/*RIGHT*/} +
+ +
+
+ ); +}; + +export default ParentPage; diff --git a/src/app/(dashboard)/student/page.tsx b/src/app/(dashboard)/student/page.tsx new file mode 100644 index 000000000..ff427a01e --- /dev/null +++ b/src/app/(dashboard)/student/page.tsx @@ -0,0 +1,25 @@ +import Announcements from "@/components/Announcements"; +import BigCalendar from "@/components/BigCalendar"; +import EventCalendar from "@/components/EventCalendar"; +import React from "react"; + +const StudentPage = () => { + return ( +
+ {/*LEFT*/} +
+
+

Schedule (4A)

+ +
+
+ {/*RIGHT*/} +
+ + +
+
+ ); +}; + +export default StudentPage; diff --git a/src/app/(dashboard)/teacher/page.tsx b/src/app/(dashboard)/teacher/page.tsx new file mode 100644 index 000000000..d02b726b4 --- /dev/null +++ b/src/app/(dashboard)/teacher/page.tsx @@ -0,0 +1,23 @@ +import Announcements from "@/components/Announcements"; +import BigCalendar from "@/components/BigCalendar"; +import React from "react"; + +const TeacherPage = () => { + return ( +
+ {/*LEFT*/} +
+
+

Schedule

+ +
+
+ {/*RIGHT*/} +
+ +
+
+ ); +}; + +export default TeacherPage; diff --git a/src/app/globals.css b/src/app/globals.css index bd6213e1d..2b16e5d5e 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,3 +1,106 @@ @tailwind base; @tailwind components; -@tailwind utilities; \ No newline at end of file +@tailwind utilities; + +.react-calendar { + width: 100% !important; + border: none !important; + font-family: "Inter", sans-serif !important; +} + +.react-calendar__navigation__label__labelText{ + font-weight: 600 !important; +} + +.react-calendar__tile--active{ + background-color: #C3EBFA !important; + color: black !important; +} + +.rbc-btn-group:first-child { + display: none !important; +} + +.rbc-toolbar-label{ + text-align: right !important; + padding: 0px 20px !important; +} + +.rbc-btn-group:last-child { + font-size: 13px !important; +} + +.rbc-btn-group:last-child button{ + border: none !important; + background-color: #f1f0ff !important; + margin-left: 2px !important; +} + +.rbc-toolbar button.rbc-active { + background-color: #dbdafe !important; + box-shadow: none !important; +} + +.rbc-time-view { + border-color: #eee !important; +} + +.rbc-time-header { + display: none !important; +} + +.rbc-time-content { + border:none !important; +} + +.rbc-time-gutter.rbc-time-column{ + font-size: 12px !important; +} + +.rbc-time-gutter.rbc-time-column .rbc-timeslot-group{ + padding: 0px 20px !important; +} + +.rbc-timeslot-group { + background-color: #f7fdff !important; +} + +.rbc-day-slot { + font-size: 14px !important; +} + +.rbc-event { + border: none !important; + color: black !important; + padding: 10px !important; + margin: 10px !important; + width: 99%; +} +.rbc-event:nth-child(1) { + background-color: #e2f8ff !important; +} +.rbc-event:nth-child(2) { + background-color: #fefce8 !important; +} +.rbc-event:nth-child(3) { + background-color: #f2f1ff !important; +} +.rbc-event:nth-child(4) { + background-color: #fdf2fb !important; +} +.rbc-event:nth-child(5) { + background-color: #e2f8ff !important; +} +.rbc-event:nth-child(6) { + background-color: #fefce8 !important; +} +.rbc-event:nth-child(7) { + background-color: #f2f1ff !important; +} +.rbc-event:nth-child(8) { + background-color: #fdf2fb !important; +} +.rbc-event-label { + color:gray !important; + margin-bottom: 5px; +} \ No newline at end of file diff --git a/src/app/sign-in/page.tsx b/src/app/sign-in/page.tsx new file mode 100644 index 000000000..5ffb1de56 --- /dev/null +++ b/src/app/sign-in/page.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +const LoginPage = () => { + return
LoginPage
; +}; + +export default LoginPage; diff --git a/src/components/Announcements.tsx b/src/components/Announcements.tsx new file mode 100644 index 000000000..611559376 --- /dev/null +++ b/src/components/Announcements.tsx @@ -0,0 +1,49 @@ +import React from "react"; + +const Announcements = () => { + return ( +
+
+

Announcements

+ View All +
+
+
+
+

Lorem ipsum dolor sit amet

+ + 2025-01-01 + +
+

+ Lorem ipsum, dolor sit amet consectetur adipisicing elit. Velit, ad. +

+
+
+
+

Lorem ipsum dolor sit amet

+ + 2025-01-01 + +
+

+ Lorem ipsum, dolor sit amet consectetur adipisicing elit. Velit, ad. +

+
+
+
+

Lorem ipsum dolor sit amet

+ + 2025-01-01 + +
+

+ Lorem ipsum, dolor sit amet consectetur adipisicing elit. Velit, ad. +

+
+
+
+ ); +}; + +export default Announcements; diff --git a/src/components/AttendanceChart.tsx b/src/components/AttendanceChart.tsx new file mode 100644 index 000000000..3ffe9e149 --- /dev/null +++ b/src/components/AttendanceChart.tsx @@ -0,0 +1,87 @@ +"use client"; +import Image from "next/image"; +import React from "react"; + +import { + BarChart, + Bar, + Rectangle, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer, +} from "recharts"; + +const data = [ + { + name: "Mon", + present: 60, + absent: 40, + }, + { + name: "Tue", + present: 70, + absent: 60, + }, + { + name: "Wed", + present: 90, + absent: 75, + }, + { + name: "Thu", + present: 90, + absent: 75, + }, + { + name: "Fri", + present: 65, + absent: 55, + }, +]; +const AttendanceChart = () => { + return ( +
+
+

Attendance

+ +
+ + + + + + + + + + + +
+ ); +}; + +export default AttendanceChart; diff --git a/src/components/BigCalendar.tsx b/src/components/BigCalendar.tsx new file mode 100644 index 000000000..4d371d028 --- /dev/null +++ b/src/components/BigCalendar.tsx @@ -0,0 +1,32 @@ +"use client"; +import { Calendar, momentLocalizer, View, Views } from "react-big-calendar"; +import { calendarEvents } from "@/lib/data"; +import "react-big-calendar/lib/css/react-big-calendar.css"; +import moment from "moment"; +import { useState } from "react"; + +const localizer = momentLocalizer(moment); + +const BigCalendar = () => { + const [view, setView] = useState(Views.WORK_WEEK); + + const handleOnChangeView = (selectedView: View) => { + setView(selectedView); + }; + return ( + + ); +}; + +export default BigCalendar; diff --git a/src/components/CountChart.tsx b/src/components/CountChart.tsx new file mode 100644 index 000000000..058febe1a --- /dev/null +++ b/src/components/CountChart.tsx @@ -0,0 +1,75 @@ +"use client"; +import Image from "next/image"; +import React from "react"; +import { + RadialBarChart, + RadialBar, + Legend, + ResponsiveContainer, +} from "recharts"; + +const data = [ + { + name: "Total", + count: 106, + fill: "white", + }, + + { + name: "Girls", + count: 53, + fill: "#FAE27C", + }, + { + name: "Boys", + count: 53, + fill: "#C3EBFA", + }, +]; +const CountChart = () => { + return ( +
+ {/*Title*/} +
+

Students

+ +
+ {/*Chart*/} +
+ + + + + + +
+
+
+
+

1,234

+

Boys (55%)

+
+
+
+

1,234

+

Girls (45%)

+
+
+
+ ); +}; + +export default CountChart; diff --git a/src/components/EventCalendar.tsx b/src/components/EventCalendar.tsx new file mode 100644 index 000000000..d44e5a4fd --- /dev/null +++ b/src/components/EventCalendar.tsx @@ -0,0 +1,62 @@ +"use client"; +import Image from "next/image"; +import React, { useState } from "react"; +import Calendar from "react-calendar"; +import "react-calendar/dist/Calendar.css"; +import moment from "moment"; + +type ValuePiece = Date | null; + +type Value = ValuePiece | [ValuePiece, ValuePiece]; +const events = [ + { + id: 1, + title: "Lorem ipsum dolor sit amet.", + time: "12:00 PM - 2:00 PM", + description: + "Lorem ipsum dolor sit, amet consectetur adipisicing elit. Eius, aut!", + }, + { + id: 2, + title: "Lorem ipsum dolor sit amet.", + time: "12:00 PM - 2:00 PM", + description: + "Lorem ipsum dolor sit, amet consectetur adipisicing elit. Eius, aut!", + }, + { + id: 3, + title: "Lorem ipsum dolor sit amet.", + time: "12:00 PM - 2:00 PM", + description: + "Lorem ipsum dolor sit, amet consectetur adipisicing elit. Eius, aut!", + }, +]; + +const EventCalendar = () => { + const [value, onChange] = useState(new Date()); + return ( +
+ +
+

Events

+ +
+
+ {events.map((event) => ( +
+
+

{event.title}

+ {event.time} +
+

{event.description}

+
+ ))} +
+
+ ); +}; + +export default EventCalendar; diff --git a/src/components/FinanceChart.tsx b/src/components/FinanceChart.tsx new file mode 100644 index 000000000..62f9c6966 --- /dev/null +++ b/src/components/FinanceChart.tsx @@ -0,0 +1,135 @@ +"use client"; +import Image from "next/image"; +import React from "react"; + +import { + LineChart, + Line, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer, +} from "recharts"; + +const data = [ + { + name: "Jan", + income: 4000, + expense: 2400, + }, + { + name: "Feb", + income: 3000, + expense: 1398, + }, + { + name: "Mar", + income: 2000, + expense: 9800, + }, + { + name: "Apr", + income: 2780, + expense: 3908, + }, + { + name: "May", + income: 1890, + expense: 4800, + }, + { + name: "Jun", + income: 2390, + expense: 3800, + }, + { + name: "Jul", + income: 3490, + expense: 4300, + }, + { + name: "Aug", + income: 3490, + expense: 4300, + }, + { + name: "Sep", + income: 3490, + expense: 4300, + }, + { + name: "Oct", + income: 3490, + expense: 4300, + }, + { + name: "Nov", + income: 3490, + expense: 4300, + }, + { + name: "Dec", + income: 3490, + expense: 4300, + }, +]; +const FinanceChart = () => { + return ( +
+
+

Finance

+ +
+ + + + + + + + + + + +
+ ); +}; + +export default FinanceChart; diff --git a/src/components/FormModal.tsx b/src/components/FormModal.tsx new file mode 100644 index 000000000..c3eaf6fe3 --- /dev/null +++ b/src/components/FormModal.tsx @@ -0,0 +1,136 @@ +"use client"; + +import dynamic from "next/dynamic"; +import Image from "next/image"; +import React, { FormEvent, useState } from "react"; +// import TeacherForm from "./forms/TeacherForm"; +// import StudentForm from "./forms/StudentForm"; + +const TeacherForm = dynamic(() => import("./forms/TeacherForm"), { + loading: () =>

Loading...

, +}); +const StudentForm = dynamic(() => import("./forms/StudentForm"), { + loading: () =>

Loading...

, +}); + +const ParentForm = dynamic(() => import("./forms/ParentForm "), { + loading: () =>

Loading...

, +}); +const ClassForm = dynamic(() => import("./forms/ClassForm"), { + loading: () =>

Loading...

, +}); +const SubjectForm = dynamic(() => import("./forms/StudentForm"), { + loading: () =>

Loading...

, +}); +const LessonForm = dynamic(() => import("./forms/LessonForm"), { + loading: () =>

Loading...

, +}); +const ExamForm = dynamic(() => import("./forms/ExamForm"), { + loading: () =>

Loading...

, +}); +const AssignmentForm = dynamic(() => import("./forms/AssignmentForm"), { + loading: () =>

Loading...

, +}); +const ResultForm = dynamic(() => import("./forms/ResultForm"), { + loading: () =>

Loading...

, +}); +const AttendanceForm = dynamic(() => import("./forms/AttendanceForm"), { + loading: () =>

Loading...

, +}); +const EventForm = dynamic(() => import("./forms/EventForm"), { + loading: () =>

Loading...

, +}); +const AnnouncementForm = dynamic(() => import("./forms/AnnouncementForm"), { + loading: () =>

Loading...

, +}); +const forms: { + [key: string]: (type: "create" | "update", data?: any) => JSX.Element; +} = { + teacher: (type, data) => , + student: (type, data) => , + parent: (type, data) => , + subject: (type, data) => , + class: (type, data) => , + lesson: (type, data) => , + exam: (type, data) => , + assignment: (type, data) => , + result: (type, data) => , + attendance: (type, data) => , + event: (type, data) => , + announcement: (type, data) => , +}; +const FormModal = ({ + table, + type, + data, + id, +}: { + table: + | "teacher" + | "student" + | "parent" + | "subject" + | "class" + | "lesson" + | "exam" + | "assignment" + | "result" + | "attendance" + | "event" + | "announcement"; + type: "create" | "update" | "delete"; + data?: any; + id?: number; +}) => { + const size = type === "create" ? "w-8 h-8" : "w-7 h-7"; + const bgColor = + type === "create" + ? "bg-lamaYellow" + : type === "update" + ? "bg-lamaSky" + : "bg-lamaPurple"; + + const [open, setOpen] = useState(false); + + const Form = () => { + return type === "delete" && id ? ( +
+ + All data will be lost. Are you sure you want to delete this {table}? + + + + ) : type === "create" || type === "update" ? ( + forms[table](type, data) + ) : ( + "Form not found" + ); + }; + return ( + <> + + {open && ( +
+
+
+
setOpen(false)} + > + +
+
+
+ )} + + ); +}; + +export default FormModal; diff --git a/src/components/InputFiels.tsx b/src/components/InputFiels.tsx new file mode 100644 index 000000000..0471a8008 --- /dev/null +++ b/src/components/InputFiels.tsx @@ -0,0 +1,40 @@ +import React from "react"; +import { FieldError } from "react-hook-form"; + +type InputFieldProps = { + label: string; + type?: string; + register: any; + name: string; + defaultValue?: string; + error?: FieldError; + inputProps?: React.InputHTMLAttributes; +}; + +const InputFiels = ({ + label, + type = "text", + register, + name, + defaultValue, + error, + inputProps, +}: InputFieldProps) => { + return ( +
+ + + {error?.message && ( +

{error.message.toString()}

+ )} +
+ ); +}; + +export default InputFiels; diff --git a/src/components/Menu.tsx b/src/components/Menu.tsx index de074fa04..27d1af904 100644 --- a/src/components/Menu.tsx +++ b/src/components/Menu.tsx @@ -111,4 +111,39 @@ const menuItems = [ }, ], }, -]; \ No newline at end of file +]; + +import { role } from "@/lib/data"; +import Image from "next/image"; +import Link from "next/link"; +import React from "react"; + +const Menu = () => { + return ( +
+ {menuItems.map((i) => ( +
+ + {i.title} + + {i.items.map((item) => { + if (item.visible.includes(role)) { + return ( + + + {item.label} + + ); + } + })} +
+ ))} +
+ ); +}; + +export default Menu; diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx new file mode 100644 index 000000000..40a76f470 --- /dev/null +++ b/src/components/Navbar.tsx @@ -0,0 +1,41 @@ +import Image from "next/image"; +import React from "react"; + +const Navbar = () => { + return ( +
+
+ + +
+
+
+ +
+
+ +
+ 1 +
+
+
+ John Doe + Admin +
+ +
+
+ ); +}; + +export default Navbar; diff --git a/src/components/Pagination.tsx b/src/components/Pagination.tsx new file mode 100644 index 000000000..ab45e9486 --- /dev/null +++ b/src/components/Pagination.tsx @@ -0,0 +1,26 @@ +import React from "react"; + +const Pagination = () => { + return ( +
+ +
+ + + + ... + +
+ +
+ ); +}; + +export default Pagination; diff --git a/src/components/Performance.tsx b/src/components/Performance.tsx new file mode 100644 index 000000000..f1d2023f9 --- /dev/null +++ b/src/components/Performance.tsx @@ -0,0 +1,43 @@ +"use client"; +import Image from "next/image"; +import React from "react"; +import { PieChart, Pie, Sector, Cell, ResponsiveContainer } from "recharts"; + +const data = [ + { name: "Group A", value: 92, fill: "#C3EBFA" }, + { name: "Group B", value: 8, fill: "#FAE27C" }, +]; + +const Performance = () => { + return ( +
+
+

Performance

+ +
+ + + + + +
+

9.2

+

of 10 mzx LTS

+
+

+ 1st Semester -2nd Semester +

+
+ ); +}; + +export default Performance; diff --git a/src/components/Table.tsx b/src/components/Table.tsx new file mode 100644 index 000000000..cae020412 --- /dev/null +++ b/src/components/Table.tsx @@ -0,0 +1,28 @@ +import React from "react"; + +const Table = ({ + columns, + renderRow, + data, +}: { + columns: { header: string; accessor: string; className?: string }[]; + renderRow: (item: any) => React.ReactNode; + data: any[]; +}) => { + return ( +
+ + + {columns.map((col) => ( + + ))} + + + {data.map((item) => renderRow(item))} +
+ {col.header} +
+ ); +}; + +export default Table; diff --git a/src/components/TableSearch.tsx b/src/components/TableSearch.tsx new file mode 100644 index 000000000..e065b6d73 --- /dev/null +++ b/src/components/TableSearch.tsx @@ -0,0 +1,17 @@ +import Image from "next/image"; +import React from "react"; + +const TableSearch = () => { + return ( +
+ + +
+ ); +}; + +export default TableSearch; diff --git a/src/components/UserCard.tsx b/src/components/UserCard.tsx new file mode 100644 index 000000000..65ec2ed01 --- /dev/null +++ b/src/components/UserCard.tsx @@ -0,0 +1,19 @@ +import Image from "next/image"; +import React from "react"; + +const UserCard = ({ type }: { type: string }) => { + return ( +
+
+ + 2024/25 + + +
+

1,234

+

{type}s

+
+ ); +}; + +export default UserCard; diff --git a/src/components/forms/AnnouncementForm.tsx b/src/components/forms/AnnouncementForm.tsx new file mode 100644 index 000000000..b1d95c9d9 --- /dev/null +++ b/src/components/forms/AnnouncementForm.tsx @@ -0,0 +1,171 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import React from "react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import InputFiels from "../InputFiels"; +import Image from "next/image"; + +const schema = z.object({ + username: z + .string() + .min(3, { message: "Username must be at least 3 characters long!" }) + .max(20, { message: "Username must be at most 20 characters long!" }), + age: z.string().email({ message: "Invalid email address!" }), + password: z + .string() + .min(8, { message: "Password must be at least 8 characters long!" }), + firstName: z.string().min(1, { message: "First name is required!" }), + lastName: z.string().min(1, { message: "Last name is required!" }), + phone: z.string().min(1, { message: "Phone is required!" }), + address: z.string().min(1, { message: "Address is required!" }), + bloodType: z.string().min(1, { message: "Blood Type is required!" }), + + birthday: z.date({ message: "Birthday is required!" }), + sex: z.enum(["male", "female"], { message: "Sex is required!" }), + img: z.instanceof(File, { message: "Image is reauired!" }), +}); + +type Inputs = z.infer; + +const AnnouncementForm = ({ + type, + data, +}: { + type: "create" | "update"; + data?: any; +}) => { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ resolver: zodResolver(schema) }); + + const onSubmit = handleSubmit((data) => { + console.log(data); + }); + + return ( + +

Create a new teacher

+ + Authentication Information + +
+ + + +
+ + Personal Information + +
+ + + + + + + + + + + +
+ + + {errors.sex?.message && ( +

+ {errors.sex.message.toString()} +

+ )} +
+
+ + + {errors.sex?.message && ( +

+ {errors.sex.message.toString()} +

+ )} +
+
+ + + ); +}; + +export default AnnouncementForm; diff --git a/src/components/forms/AssignmentForm.tsx b/src/components/forms/AssignmentForm.tsx new file mode 100644 index 000000000..27b04b2db --- /dev/null +++ b/src/components/forms/AssignmentForm.tsx @@ -0,0 +1,171 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import React from "react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import InputFiels from "../InputFiels"; +import Image from "next/image"; + +const schema = z.object({ + username: z + .string() + .min(3, { message: "Username must be at least 3 characters long!" }) + .max(20, { message: "Username must be at most 20 characters long!" }), + age: z.string().email({ message: "Invalid email address!" }), + password: z + .string() + .min(8, { message: "Password must be at least 8 characters long!" }), + firstName: z.string().min(1, { message: "First name is required!" }), + lastName: z.string().min(1, { message: "Last name is required!" }), + phone: z.string().min(1, { message: "Phone is required!" }), + address: z.string().min(1, { message: "Address is required!" }), + bloodType: z.string().min(1, { message: "Blood Type is required!" }), + + birthday: z.date({ message: "Birthday is required!" }), + sex: z.enum(["male", "female"], { message: "Sex is required!" }), + img: z.instanceof(File, { message: "Image is reauired!" }), +}); + +type Inputs = z.infer; + +const AssignmentForm = ({ + type, + data, +}: { + type: "create" | "update"; + data?: any; +}) => { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ resolver: zodResolver(schema) }); + + const onSubmit = handleSubmit((data) => { + console.log(data); + }); + + return ( +
+

Create a new teacher

+ + Authentication Information + +
+ + + +
+ + Personal Information + +
+ + + + + + + + + + + +
+ + + {errors.sex?.message && ( +

+ {errors.sex.message.toString()} +

+ )} +
+
+ + + {errors.sex?.message && ( +

+ {errors.sex.message.toString()} +

+ )} +
+
+ +
+ ); +}; + +export default AssignmentForm; diff --git a/src/components/forms/AttendanceForm.tsx b/src/components/forms/AttendanceForm.tsx new file mode 100644 index 000000000..ed3d4cbdb --- /dev/null +++ b/src/components/forms/AttendanceForm.tsx @@ -0,0 +1,171 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import React from "react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import InputFiels from "../InputFiels"; +import Image from "next/image"; + +const schema = z.object({ + username: z + .string() + .min(3, { message: "Username must be at least 3 characters long!" }) + .max(20, { message: "Username must be at most 20 characters long!" }), + age: z.string().email({ message: "Invalid email address!" }), + password: z + .string() + .min(8, { message: "Password must be at least 8 characters long!" }), + firstName: z.string().min(1, { message: "First name is required!" }), + lastName: z.string().min(1, { message: "Last name is required!" }), + phone: z.string().min(1, { message: "Phone is required!" }), + address: z.string().min(1, { message: "Address is required!" }), + bloodType: z.string().min(1, { message: "Blood Type is required!" }), + + birthday: z.date({ message: "Birthday is required!" }), + sex: z.enum(["male", "female"], { message: "Sex is required!" }), + img: z.instanceof(File, { message: "Image is reauired!" }), +}); + +type Inputs = z.infer; + +const AttendanceForm = ({ + type, + data, +}: { + type: "create" | "update"; + data?: any; +}) => { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ resolver: zodResolver(schema) }); + + const onSubmit = handleSubmit((data) => { + console.log(data); + }); + + return ( +
+

Create a new teacher

+ + Authentication Information + +
+ + + +
+ + Personal Information + +
+ + + + + + + + + + + +
+ + + {errors.sex?.message && ( +

+ {errors.sex.message.toString()} +

+ )} +
+
+ + + {errors.sex?.message && ( +

+ {errors.sex.message.toString()} +

+ )} +
+
+ +
+ ); +}; + +export default AttendanceForm; diff --git a/src/components/forms/ClassForm.tsx b/src/components/forms/ClassForm.tsx new file mode 100644 index 000000000..6f3a45eca --- /dev/null +++ b/src/components/forms/ClassForm.tsx @@ -0,0 +1,171 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import React from "react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import InputFiels from "../InputFiels"; +import Image from "next/image"; + +const schema = z.object({ + username: z + .string() + .min(3, { message: "Username must be at least 3 characters long!" }) + .max(20, { message: "Username must be at most 20 characters long!" }), + age: z.string().email({ message: "Invalid email address!" }), + password: z + .string() + .min(8, { message: "Password must be at least 8 characters long!" }), + firstName: z.string().min(1, { message: "First name is required!" }), + lastName: z.string().min(1, { message: "Last name is required!" }), + phone: z.string().min(1, { message: "Phone is required!" }), + address: z.string().min(1, { message: "Address is required!" }), + bloodType: z.string().min(1, { message: "Blood Type is required!" }), + + birthday: z.date({ message: "Birthday is required!" }), + sex: z.enum(["male", "female"], { message: "Sex is required!" }), + img: z.instanceof(File, { message: "Image is reauired!" }), +}); + +type Inputs = z.infer; + +const ClassForm = ({ + type, + data, +}: { + type: "create" | "update"; + data?: any; +}) => { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ resolver: zodResolver(schema) }); + + const onSubmit = handleSubmit((data) => { + console.log(data); + }); + + return ( +
+

Create a new teacher

+ + Authentication Information + +
+ + + +
+ + Personal Information + +
+ + + + + + + + + + + +
+ + + {errors.sex?.message && ( +

+ {errors.sex.message.toString()} +

+ )} +
+
+ + + {errors.sex?.message && ( +

+ {errors.sex.message.toString()} +

+ )} +
+
+ +
+ ); +}; + +export default ClassForm; diff --git a/src/components/forms/EventForm.tsx b/src/components/forms/EventForm.tsx new file mode 100644 index 000000000..530d9e9ab --- /dev/null +++ b/src/components/forms/EventForm.tsx @@ -0,0 +1,171 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import React from "react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import InputFiels from "../InputFiels"; +import Image from "next/image"; + +const schema = z.object({ + username: z + .string() + .min(3, { message: "Username must be at least 3 characters long!" }) + .max(20, { message: "Username must be at most 20 characters long!" }), + age: z.string().email({ message: "Invalid email address!" }), + password: z + .string() + .min(8, { message: "Password must be at least 8 characters long!" }), + firstName: z.string().min(1, { message: "First name is required!" }), + lastName: z.string().min(1, { message: "Last name is required!" }), + phone: z.string().min(1, { message: "Phone is required!" }), + address: z.string().min(1, { message: "Address is required!" }), + bloodType: z.string().min(1, { message: "Blood Type is required!" }), + + birthday: z.date({ message: "Birthday is required!" }), + sex: z.enum(["male", "female"], { message: "Sex is required!" }), + img: z.instanceof(File, { message: "Image is reauired!" }), +}); + +type Inputs = z.infer; + +const EventForm = ({ + type, + data, +}: { + type: "create" | "update"; + data?: any; +}) => { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ resolver: zodResolver(schema) }); + + const onSubmit = handleSubmit((data) => { + console.log(data); + }); + + return ( +
+

Create a new teacher

+ + Authentication Information + +
+ + + +
+ + Personal Information + +
+ + + + + + + + + + + +
+ + + {errors.sex?.message && ( +

+ {errors.sex.message.toString()} +

+ )} +
+
+ + + {errors.sex?.message && ( +

+ {errors.sex.message.toString()} +

+ )} +
+
+ +
+ ); +}; + +export default EventForm; diff --git a/src/components/forms/ExamForm.tsx b/src/components/forms/ExamForm.tsx new file mode 100644 index 000000000..dc4848af1 --- /dev/null +++ b/src/components/forms/ExamForm.tsx @@ -0,0 +1,171 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import React from "react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import InputFiels from "../InputFiels"; +import Image from "next/image"; + +const schema = z.object({ + username: z + .string() + .min(3, { message: "Username must be at least 3 characters long!" }) + .max(20, { message: "Username must be at most 20 characters long!" }), + age: z.string().email({ message: "Invalid email address!" }), + password: z + .string() + .min(8, { message: "Password must be at least 8 characters long!" }), + firstName: z.string().min(1, { message: "First name is required!" }), + lastName: z.string().min(1, { message: "Last name is required!" }), + phone: z.string().min(1, { message: "Phone is required!" }), + address: z.string().min(1, { message: "Address is required!" }), + bloodType: z.string().min(1, { message: "Blood Type is required!" }), + + birthday: z.date({ message: "Birthday is required!" }), + sex: z.enum(["male", "female"], { message: "Sex is required!" }), + img: z.instanceof(File, { message: "Image is reauired!" }), +}); + +type Inputs = z.infer; + +const ExamForm = ({ + type, + data, +}: { + type: "create" | "update"; + data?: any; +}) => { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ resolver: zodResolver(schema) }); + + const onSubmit = handleSubmit((data) => { + console.log(data); + }); + + return ( +
+

Create a new teacher

+ + Authentication Information + +
+ + + +
+ + Personal Information + +
+ + + + + + + + + + + +
+ + + {errors.sex?.message && ( +

+ {errors.sex.message.toString()} +

+ )} +
+
+ + + {errors.sex?.message && ( +

+ {errors.sex.message.toString()} +

+ )} +
+
+ +
+ ); +}; + +export default ExamForm; diff --git a/src/components/forms/LessonForm.tsx b/src/components/forms/LessonForm.tsx new file mode 100644 index 000000000..74a668f58 --- /dev/null +++ b/src/components/forms/LessonForm.tsx @@ -0,0 +1,171 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import React from "react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import InputFiels from "../InputFiels"; +import Image from "next/image"; + +const schema = z.object({ + username: z + .string() + .min(3, { message: "Username must be at least 3 characters long!" }) + .max(20, { message: "Username must be at most 20 characters long!" }), + age: z.string().email({ message: "Invalid email address!" }), + password: z + .string() + .min(8, { message: "Password must be at least 8 characters long!" }), + firstName: z.string().min(1, { message: "First name is required!" }), + lastName: z.string().min(1, { message: "Last name is required!" }), + phone: z.string().min(1, { message: "Phone is required!" }), + address: z.string().min(1, { message: "Address is required!" }), + bloodType: z.string().min(1, { message: "Blood Type is required!" }), + + birthday: z.date({ message: "Birthday is required!" }), + sex: z.enum(["male", "female"], { message: "Sex is required!" }), + img: z.instanceof(File, { message: "Image is reauired!" }), +}); + +type Inputs = z.infer; + +const LessonForm = ({ + type, + data, +}: { + type: "create" | "update"; + data?: any; +}) => { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ resolver: zodResolver(schema) }); + + const onSubmit = handleSubmit((data) => { + console.log(data); + }); + + return ( +
+

Create a new teacher

+ + Authentication Information + +
+ + + +
+ + Personal Information + +
+ + + + + + + + + + + +
+ + + {errors.sex?.message && ( +

+ {errors.sex.message.toString()} +

+ )} +
+
+ + + {errors.sex?.message && ( +

+ {errors.sex.message.toString()} +

+ )} +
+
+ +
+ ); +}; + +export default LessonForm; diff --git a/src/components/forms/ParentForm .tsx b/src/components/forms/ParentForm .tsx new file mode 100644 index 000000000..75f967715 --- /dev/null +++ b/src/components/forms/ParentForm .tsx @@ -0,0 +1,171 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import React from "react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import InputFiels from "../InputFiels"; +import Image from "next/image"; + +const schema = z.object({ + username: z + .string() + .min(3, { message: "Username must be at least 3 characters long!" }) + .max(20, { message: "Username must be at most 20 characters long!" }), + age: z.string().email({ message: "Invalid email address!" }), + password: z + .string() + .min(8, { message: "Password must be at least 8 characters long!" }), + firstName: z.string().min(1, { message: "First name is required!" }), + lastName: z.string().min(1, { message: "Last name is required!" }), + phone: z.string().min(1, { message: "Phone is required!" }), + address: z.string().min(1, { message: "Address is required!" }), + bloodType: z.string().min(1, { message: "Blood Type is required!" }), + + birthday: z.date({ message: "Birthday is required!" }), + sex: z.enum(["male", "female"], { message: "Sex is required!" }), + img: z.instanceof(File, { message: "Image is reauired!" }), +}); + +type Inputs = z.infer; + +const ParentForm = ({ + type, + data, +}: { + type: "create" | "update"; + data?: any; +}) => { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ resolver: zodResolver(schema) }); + + const onSubmit = handleSubmit((data) => { + console.log(data); + }); + + return ( +
+

Create a new teacher

+ + Authentication Information + +
+ + + +
+ + Personal Information + +
+ + + + + + + + + + + +
+ + + {errors.sex?.message && ( +

+ {errors.sex.message.toString()} +

+ )} +
+
+ + + {errors.sex?.message && ( +

+ {errors.sex.message.toString()} +

+ )} +
+
+ +
+ ); +}; + +export default ParentForm; diff --git a/src/components/forms/ResultForm.tsx b/src/components/forms/ResultForm.tsx new file mode 100644 index 000000000..f82dc6b53 --- /dev/null +++ b/src/components/forms/ResultForm.tsx @@ -0,0 +1,171 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import React from "react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import InputFiels from "../InputFiels"; +import Image from "next/image"; + +const schema = z.object({ + username: z + .string() + .min(3, { message: "Username must be at least 3 characters long!" }) + .max(20, { message: "Username must be at most 20 characters long!" }), + age: z.string().email({ message: "Invalid email address!" }), + password: z + .string() + .min(8, { message: "Password must be at least 8 characters long!" }), + firstName: z.string().min(1, { message: "First name is required!" }), + lastName: z.string().min(1, { message: "Last name is required!" }), + phone: z.string().min(1, { message: "Phone is required!" }), + address: z.string().min(1, { message: "Address is required!" }), + bloodType: z.string().min(1, { message: "Blood Type is required!" }), + + birthday: z.date({ message: "Birthday is required!" }), + sex: z.enum(["male", "female"], { message: "Sex is required!" }), + img: z.instanceof(File, { message: "Image is reauired!" }), +}); + +type Inputs = z.infer; + +const ResultForm = ({ + type, + data, +}: { + type: "create" | "update"; + data?: any; +}) => { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ resolver: zodResolver(schema) }); + + const onSubmit = handleSubmit((data) => { + console.log(data); + }); + + return ( +
+

Create a new teacher

+ + Authentication Information + +
+ + + +
+ + Personal Information + +
+ + + + + + + + + + + +
+ + + {errors.sex?.message && ( +

+ {errors.sex.message.toString()} +

+ )} +
+
+ + + {errors.sex?.message && ( +

+ {errors.sex.message.toString()} +

+ )} +
+
+ +
+ ); +}; + +export default ResultForm; diff --git a/src/components/forms/StudentForm.tsx b/src/components/forms/StudentForm.tsx new file mode 100644 index 000000000..945ccd9a3 --- /dev/null +++ b/src/components/forms/StudentForm.tsx @@ -0,0 +1,171 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import React from "react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import InputFiels from "../InputFiels"; +import Image from "next/image"; + +const schema = z.object({ + username: z + .string() + .min(3, { message: "Username must be at least 3 characters long!" }) + .max(20, { message: "Username must be at most 20 characters long!" }), + age: z.string().email({ message: "Invalid email address!" }), + password: z + .string() + .min(8, { message: "Password must be at least 8 characters long!" }), + firstName: z.string().min(1, { message: "First name is required!" }), + lastName: z.string().min(1, { message: "Last name is required!" }), + phone: z.string().min(1, { message: "Phone is required!" }), + address: z.string().min(1, { message: "Address is required!" }), + bloodType: z.string().min(1, { message: "Blood Type is required!" }), + + birthday: z.date({ message: "Birthday is required!" }), + sex: z.enum(["male", "female"], { message: "Sex is required!" }), + img: z.instanceof(File, { message: "Image is reauired!" }), +}); + +type Inputs = z.infer; + +const StudentForm = ({ + type, + data, +}: { + type: "create" | "update"; + data?: any; +}) => { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ resolver: zodResolver(schema) }); + + const onSubmit = handleSubmit((data) => { + console.log(data); + }); + + return ( +
+

Create a new student

+ + Authentication Information + +
+ + + +
+ + Personal Information + +
+ + + + + + + + + + + +
+ + + {errors.sex?.message && ( +

+ {errors.sex.message.toString()} +

+ )} +
+
+ + + {errors.sex?.message && ( +

+ {errors.sex.message.toString()} +

+ )} +
+
+ +
+ ); +}; + +export default StudentForm; diff --git a/src/components/forms/SubjectForm.tsx b/src/components/forms/SubjectForm.tsx new file mode 100644 index 000000000..d4ddd53e9 --- /dev/null +++ b/src/components/forms/SubjectForm.tsx @@ -0,0 +1,171 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import React from "react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import InputFiels from "../InputFiels"; +import Image from "next/image"; + +const schema = z.object({ + username: z + .string() + .min(3, { message: "Username must be at least 3 characters long!" }) + .max(20, { message: "Username must be at most 20 characters long!" }), + age: z.string().email({ message: "Invalid email address!" }), + password: z + .string() + .min(8, { message: "Password must be at least 8 characters long!" }), + firstName: z.string().min(1, { message: "First name is required!" }), + lastName: z.string().min(1, { message: "Last name is required!" }), + phone: z.string().min(1, { message: "Phone is required!" }), + address: z.string().min(1, { message: "Address is required!" }), + bloodType: z.string().min(1, { message: "Blood Type is required!" }), + + birthday: z.date({ message: "Birthday is required!" }), + sex: z.enum(["male", "female"], { message: "Sex is required!" }), + img: z.instanceof(File, { message: "Image is reauired!" }), +}); + +type Inputs = z.infer; + +const SubjectForm = ({ + type, + data, +}: { + type: "create" | "update"; + data?: any; +}) => { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ resolver: zodResolver(schema) }); + + const onSubmit = handleSubmit((data) => { + console.log(data); + }); + + return ( +
+

Create a new teacher

+ + Authentication Information + +
+ + + +
+ + Personal Information + +
+ + + + + + + + + + + +
+ + + {errors.sex?.message && ( +

+ {errors.sex.message.toString()} +

+ )} +
+
+ + + {errors.sex?.message && ( +

+ {errors.sex.message.toString()} +

+ )} +
+
+ +
+ ); +}; + +export default SubjectForm; diff --git a/src/components/forms/TeacherForm.tsx b/src/components/forms/TeacherForm.tsx new file mode 100644 index 000000000..b608e74e2 --- /dev/null +++ b/src/components/forms/TeacherForm.tsx @@ -0,0 +1,171 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import React from "react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import InputFiels from "../InputFiels"; +import Image from "next/image"; + +const schema = z.object({ + username: z + .string() + .min(3, { message: "Username must be at least 3 characters long!" }) + .max(20, { message: "Username must be at most 20 characters long!" }), + age: z.string().email({ message: "Invalid email address!" }), + password: z + .string() + .min(8, { message: "Password must be at least 8 characters long!" }), + firstName: z.string().min(1, { message: "First name is required!" }), + lastName: z.string().min(1, { message: "Last name is required!" }), + phone: z.string().min(1, { message: "Phone is required!" }), + address: z.string().min(1, { message: "Address is required!" }), + bloodType: z.string().min(1, { message: "Blood Type is required!" }), + + birthday: z.date({ message: "Birthday is required!" }), + sex: z.enum(["male", "female"], { message: "Sex is required!" }), + img: z.instanceof(File, { message: "Image is reauired!" }), +}); + +type Inputs = z.infer; + +const TeacherForm = ({ + type, + data, +}: { + type: "create" | "update"; + data?: any; +}) => { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ resolver: zodResolver(schema) }); + + const onSubmit = handleSubmit((data) => { + console.log(data); + }); + + return ( +
+

Create a new teacher

+ + Authentication Information + +
+ + + +
+ + Personal Information + +
+ + + + + + + + + + + +
+ + + {errors.sex?.message && ( +

+ {errors.sex.message.toString()} +

+ )} +
+
+ + + {errors.sex?.message && ( +

+ {errors.sex.message.toString()} +

+ )} +
+
+ +
+ ); +}; + +export default TeacherForm; diff --git a/src/lib/data.ts b/src/lib/data.ts index 5fcd9281e..15b2a5b8e 100644 --- a/src/lib/data.ts +++ b/src/lib/data.ts @@ -921,38 +921,38 @@ export const calendarEvents = [ { title: "Math", allDay: false, - start: new Date(2024, 7, 12, 8, 0), - end: new Date(2024, 7, 12, 8, 45), + start: new Date(2024, 8, 26, 8, 0), + end: new Date(2024, 8, 26, 8, 45), }, { title: "English", allDay: false, - start: new Date(2024, 7, 12, 9, 0), - end: new Date(2024, 7, 12, 9, 45), + start: new Date(2024, 8, 26, 9, 0), + end: new Date(2024, 8, 26, 9, 45), }, { title: "Biology", allDay: false, - start: new Date(2024, 7, 12, 10, 0), - end: new Date(2024, 7, 12, 10, 45), + start: new Date(2024, 8, 26, 10, 0), + end: new Date(2024, 8, 26, 10, 45), }, { title: "Physics", allDay: false, - start: new Date(2024, 7, 12, 11, 0), - end: new Date(2024, 7, 12, 11, 45), + start: new Date(2024, 8, 20, 11, 0), + end: new Date(2024, 8, 20, 11, 45), }, { title: "Chemistry", allDay: false, - start: new Date(2024, 7, 12, 13, 0), - end: new Date(2024, 7, 12, 13, 45), + start: new Date(2024, 8, 20, 13, 0), + end: new Date(2024, 8, 20, 13, 45), }, { title: "History", allDay: false, - start: new Date(2024, 7, 12, 14, 0), - end: new Date(2024, 7, 12, 14, 45), + start: new Date(2024, 8, 20, 14, 0), + end: new Date(2024, 8, 20, 14, 45), }, { title: "English", diff --git a/tailwind.config.ts b/tailwind.config.ts index e9a0944e7..507c57638 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -13,6 +13,14 @@ const config: Config = { "gradient-conic": "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", }, + colors: { + lamaSky: "#C3EBFA", + lamaSkyLight: "#EDF9FD", + lamaPurple: "#CFCEFF", + lamaPurpleLight: "#F1F0FF", + lamaYellow: "#FAE27C", + lamaYellowLight: "#FEFCE8", + } }, }, plugins: [], diff --git a/tsconfig.json b/tsconfig.json index 7b2858930..5a2f96971 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,6 +21,6 @@ "@/*": ["./src/*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "src/components/forms/SubjectForm.tsx"], "exclude": ["node_modules"] } From de9a1ce2bb474107df966283f579612c8e213954 Mon Sep 17 00:00:00 2001 From: Oksana-Tnt Date: Tue, 12 Nov 2024 16:29:03 +0100 Subject: [PATCH 2/3] commit --- Dockerfile | 26 + docker-compose.yml | 24 + next.config.mjs | 2 +- package-lock.json | 1595 ++++++++++++++++- package.json | 11 + .../20241025113419_init/migration.sql | 282 +++ prisma/migrations/migration_lock.toml | 3 + prisma/schema.prisma | 61 +- prisma/seed.ts | 215 +++ public/noAvatar.png | Bin 0 -> 3155 bytes src/app/(dashboard)/admin/page.tsx | 16 +- .../(dashboard)/list/announcements/page.tsx | 112 +- src/app/(dashboard)/list/assignments/page.tsx | 167 +- src/app/(dashboard)/list/classes/page.tsx | 141 +- src/app/(dashboard)/list/events/page.tsx | 143 +- src/app/(dashboard)/list/exams/page.tsx | 196 +- src/app/(dashboard)/list/lessons/page.tsx | 118 +- src/app/(dashboard)/list/loading.tsx | 11 + src/app/(dashboard)/list/parents/page.tsx | 117 +- src/app/(dashboard)/list/results/page.tsx | 210 ++- .../(dashboard)/list/students/[id]/page.tsx | 73 +- src/app/(dashboard)/list/students/page.tsx | 144 +- src/app/(dashboard)/list/subjects/page.tsx | 122 +- .../(dashboard)/list/teachers/[id]/page.tsx | 83 +- src/app/(dashboard)/list/teachers/page.tsx | 149 +- src/app/(dashboard)/parent/page.tsx | 22 +- src/app/(dashboard)/student/page.tsx | 15 +- src/app/(dashboard)/teacher/page.tsx | 8 +- src/app/[[...sign-in]]/page.tsx | 48 + src/app/layout.tsx | 12 +- src/app/page.tsx | 7 - src/app/sign-in/page.tsx | 7 - src/components/Announcements.tsx | 55 +- src/components/AttendanceChart.tsx | 37 +- src/components/AttendanceChartContainer.tsx | 63 + src/components/BigCalendar.tsx | 19 +- src/components/BigCalendarContainer.tsx | 35 + src/components/CountChart.tsx | 68 +- src/components/CountChartContainer.tsx | 39 + src/components/EventCalendar.tsx | 59 +- src/components/EventCalendarContainer.tsx | 23 + src/components/EventList.tsx | 33 + src/components/FormContainer.tsx | 117 ++ src/components/FormModal.tsx | 164 +- .../{InputFiels.tsx => InputField.tsx} | 10 +- src/components/Menu.tsx | 7 +- src/components/Navbar.tsx | 12 +- src/components/Pagination.tsx | 42 +- src/components/StudentAttendanceCard.tsx | 25 + src/components/TableSearch.tsx | 16 +- src/components/UserCard.tsx | 15 +- src/components/forms/AnnouncementForm.tsx | 20 +- src/components/forms/AssignmentForm.tsx | 212 +-- src/components/forms/AttendanceForm.tsx | 20 +- src/components/forms/ClassForm.tsx | 215 +-- src/components/forms/EventForm.tsx | 215 +-- src/components/forms/ExamForm.tsx | 212 +-- src/components/forms/LessonForm.tsx | 261 +-- src/components/forms/ParentForm .tsx | 167 +- src/components/forms/ResultForm.tsx | 20 +- src/components/forms/StudentForm.tsx | 211 ++- src/components/forms/SubjectForm.tsx | 196 +- src/components/forms/TeacherForm.tsx | 182 +- src/lib/actions.ts | 800 +++++++++ src/lib/formValidationSchemas.ts | 153 ++ src/lib/prisma.ts | 15 + src/lib/settings.ts | 26 + src/lib/utils.ts | 52 + src/middleware.ts | 40 + 69 files changed, 6097 insertions(+), 1899 deletions(-) create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 prisma/migrations/20241025113419_init/migration.sql create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 prisma/seed.ts create mode 100644 public/noAvatar.png create mode 100644 src/app/(dashboard)/list/loading.tsx create mode 100644 src/app/[[...sign-in]]/page.tsx delete mode 100644 src/app/page.tsx delete mode 100644 src/app/sign-in/page.tsx create mode 100644 src/components/AttendanceChartContainer.tsx create mode 100644 src/components/BigCalendarContainer.tsx create mode 100644 src/components/CountChartContainer.tsx create mode 100644 src/components/EventCalendarContainer.tsx create mode 100644 src/components/EventList.tsx create mode 100644 src/components/FormContainer.tsx rename src/components/{InputFiels.tsx => InputField.tsx} (82%) create mode 100644 src/components/StudentAttendanceCard.tsx create mode 100644 src/lib/actions.ts create mode 100644 src/lib/formValidationSchemas.ts create mode 100644 src/lib/prisma.ts create mode 100644 src/lib/settings.ts create mode 100644 src/lib/utils.ts create mode 100644 src/middleware.ts diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..8117fccef --- /dev/null +++ b/Dockerfile @@ -0,0 +1,26 @@ +# Use Node.js as the base image +FROM node:18 + +# Set the working directory inside the container +WORKDIR /app + +# Copy package.json and package-lock.json files +COPY package*.json ./ + +# Install dependencies +RUN npm install + +# Copy the rest of the application code +COPY . . + +# Generate Database +RUN npx prisma generate + +# Build the Next.js application +RUN npm run build + +# Expose the port the app runs on +EXPOSE 3000 + +# Start the Next.js application +CMD ["npm", "run", "start"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..de6195b1b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +version: "3.8" + + +services: + postgres: + image: postgres:15 + container_name: postgres_db + environment: + POSTGRES_USER:myuser + POSTGRES_PASSWORD:mypassword + POSTGRES_DB:mydb + ports: + - '5432:5432' + volumes: + - postgres_data:/var/lib/postgresql/data + + app: + container_name: nextjs_app + ports: + - '3000:3000' + environment: + - DATABASE_URL: postgresql://myuser:mypassword@localhost:5432/mydb + depends_on: + - postgres \ No newline at end of file diff --git a/next.config.mjs b/next.config.mjs index f6c572929..00b68ce21 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,7 +1,7 @@ /** @type {import('next').NextConfig} */ const nextConfig = { images: { - remotePatterns: [{ hostname: "images.pexels.com" }], + remotePatterns: [{ hostname: "images.pexels.com" },{ hostname: "res.cloudinary.com" } ], }, }; diff --git a/package-lock.json b/package-lock.json index b6bd087a5..b57c8af0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,19 +8,27 @@ "name": "lama-dev-next-dashboard", "version": "0.1.0", "dependencies": { + "@clerk/clerk-sdk-node": "^5.0.60", + "@clerk/elements": "^0.17.1", + "@clerk/express": "^1.3.9", + "@clerk/nextjs": "^6.0.2", "@hookform/resolvers": "^3.9.0", + "@prisma/client": "^5.21.1", "@types/react-big-calendar": "^1.8.9", "date-fns": "^3.6.0", "dayjs": "^1.11.13", "moment": "^2.30.1", "next": "^14.2.6", + "next-cloudinary": "^6.16.0", "prisma": "^5.21.0", "react": "^18", "react-big-calendar": "^1.13.4", "react-calendar": "^5.0.0", "react-dom": "^18", "react-hook-form": "^7.53.0", + "react-toastify": "^10.0.6", "recharts": "^2.12.7", + "ts-node": "^10.9.2", "zod": "^3.23.8" }, "devDependencies": { @@ -58,6 +66,396 @@ "node": ">=6.9.0" } }, + "node_modules/@clerk/backend": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@clerk/backend/-/backend-1.15.1.tgz", + "integrity": "sha512-yoBCji0bJFn2bUxBOO0+6XmlN6Tb5M2CiW+DAX7V3pFQ7g7DnHjSZ/LVkt9yB0AmqHKPv1ISXWM/NFYSDBRVuA==", + "license": "MIT", + "dependencies": { + "@clerk/shared": "2.10.1", + "@clerk/types": "4.28.0", + "cookie": "0.7.0", + "snakecase-keys": "5.4.4", + "tslib": "2.4.1" + }, + "engines": { + "node": ">=18.17.0" + } + }, + "node_modules/@clerk/backend/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "license": "0BSD" + }, + "node_modules/@clerk/clerk-react": { + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/@clerk/clerk-react/-/clerk-react-5.13.1.tgz", + "integrity": "sha512-d+6RhRdSIGZpZhrn/f4ZPpx+ZfXCWDV8DFFvCzXjkNqeJDmCBWOeUYNHM5Ag2pXWp+wl0dU7C9qxgSLwrh7rvQ==", + "license": "MIT", + "dependencies": { + "@clerk/shared": "2.10.1", + "@clerk/types": "4.28.0", + "tslib": "2.4.1" + }, + "engines": { + "node": ">=18.17.0" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-beta", + "react-dom": ">=18 || >=19.0.0-beta" + } + }, + "node_modules/@clerk/clerk-react/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "license": "0BSD" + }, + "node_modules/@clerk/clerk-sdk-node": { + "version": "5.0.60", + "resolved": "https://registry.npmjs.org/@clerk/clerk-sdk-node/-/clerk-sdk-node-5.0.60.tgz", + "integrity": "sha512-ghOQSyGUoozSb3yGqiGzP4boc3Gjwc00wdPba0DsilahXQeSjpEuH7/UOxEY1jKuCfLF97of5NYshDqIhVHePA==", + "license": "MIT", + "dependencies": { + "@clerk/backend": "1.15.7", + "@clerk/shared": "2.11.5", + "@clerk/types": "4.30.0", + "tslib": "2.4.1" + }, + "engines": { + "node": ">=18.17.0" + } + }, + "node_modules/@clerk/clerk-sdk-node/node_modules/@clerk/backend": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@clerk/backend/-/backend-1.15.7.tgz", + "integrity": "sha512-srJZE3fMY9cHgTsFpO5rxFJmX30GMMplML7M8hvP58y5s4D+8OgqJmAPK3FsqME4ABp9rIXmDCbxMZav9DhP2g==", + "license": "MIT", + "dependencies": { + "@clerk/shared": "2.11.5", + "@clerk/types": "4.30.0", + "cookie": "0.7.0", + "snakecase-keys": "5.4.4", + "tslib": "2.4.1" + }, + "engines": { + "node": ">=18.17.0" + } + }, + "node_modules/@clerk/clerk-sdk-node/node_modules/@clerk/shared": { + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@clerk/shared/-/shared-2.11.5.tgz", + "integrity": "sha512-GQiqKgIBhqnXfLmohNbDvTz8GNK/nYRSWciGArdezaB+w2q+QpQyo2v1Y7Bh6j608iKZCnc4jG4yxcAI/pRQDA==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@clerk/types": "4.30.0", + "glob-to-regexp": "0.4.1", + "js-cookie": "3.0.5", + "std-env": "^3.7.0", + "swr": "^2.2.0" + }, + "engines": { + "node": ">=18.17.0" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-beta", + "react-dom": ">=18 || >=19.0.0-beta" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@clerk/clerk-sdk-node/node_modules/@clerk/types": { + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@clerk/types/-/types-4.30.0.tgz", + "integrity": "sha512-/RLWXsz4yp9uFvJhDZDyZGRDyx3VdHRyPYQS7onhGVTY846X6iCzJVlMFzdpzW3PITxMBgCI9MjgKdH50vBPBA==", + "license": "MIT", + "dependencies": { + "csstype": "3.1.1" + }, + "engines": { + "node": ">=18.17.0" + } + }, + "node_modules/@clerk/clerk-sdk-node/node_modules/csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==", + "license": "MIT" + }, + "node_modules/@clerk/clerk-sdk-node/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "license": "0BSD" + }, + "node_modules/@clerk/elements": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@clerk/elements/-/elements-0.17.1.tgz", + "integrity": "sha512-GxWxjTKh+3YFW5zSd0IXmypWMv06Y7VRbF0iu2SS0C572J/zd1epI8muNlkwopWnHjheirVLAacdXBuREj724w==", + "license": "MIT", + "dependencies": { + "@clerk/shared": "2.10.1", + "@clerk/types": "^4.28.0", + "@radix-ui/react-form": "^0.1.0", + "@radix-ui/react-slot": "^1.1.0", + "@xstate/react": "^4.1.1", + "client-only": "^0.0.1", + "xstate": "^5.15.0" + }, + "engines": { + "node": ">=18.17.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0-beta", + "react-dom": "^18.0.0 || ^19.0.0-beta" + }, + "peerDependenciesMeta": { + "next": { + "optional": true + } + } + }, + "node_modules/@clerk/express": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@clerk/express/-/express-1.3.9.tgz", + "integrity": "sha512-orSqOuQtqxW5Qo+f/3Y+fenwS+AqHbk6AMoMci0ESc/pxxQTt4T/j2saacfkoJ24P+BxGfgqLsZBgAodZ9+8WA==", + "license": "MIT", + "dependencies": { + "@clerk/backend": "^1.15.7", + "@clerk/shared": "^2.11.5", + "@clerk/types": "4.30.0", + "tslib": "2.4.1" + }, + "engines": { + "node": ">=18.17.0" + }, + "peerDependencies": { + "express": "^4.17.0 || ^5.0.0" + } + }, + "node_modules/@clerk/express/node_modules/@clerk/backend": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@clerk/backend/-/backend-1.15.7.tgz", + "integrity": "sha512-srJZE3fMY9cHgTsFpO5rxFJmX30GMMplML7M8hvP58y5s4D+8OgqJmAPK3FsqME4ABp9rIXmDCbxMZav9DhP2g==", + "license": "MIT", + "dependencies": { + "@clerk/shared": "2.11.5", + "@clerk/types": "4.30.0", + "cookie": "0.7.0", + "snakecase-keys": "5.4.4", + "tslib": "2.4.1" + }, + "engines": { + "node": ">=18.17.0" + } + }, + "node_modules/@clerk/express/node_modules/@clerk/shared": { + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@clerk/shared/-/shared-2.11.5.tgz", + "integrity": "sha512-GQiqKgIBhqnXfLmohNbDvTz8GNK/nYRSWciGArdezaB+w2q+QpQyo2v1Y7Bh6j608iKZCnc4jG4yxcAI/pRQDA==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@clerk/types": "4.30.0", + "glob-to-regexp": "0.4.1", + "js-cookie": "3.0.5", + "std-env": "^3.7.0", + "swr": "^2.2.0" + }, + "engines": { + "node": ">=18.17.0" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-beta", + "react-dom": ">=18 || >=19.0.0-beta" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@clerk/express/node_modules/@clerk/types": { + "version": "4.30.0", + "resolved": "https://registry.npmjs.org/@clerk/types/-/types-4.30.0.tgz", + "integrity": "sha512-/RLWXsz4yp9uFvJhDZDyZGRDyx3VdHRyPYQS7onhGVTY846X6iCzJVlMFzdpzW3PITxMBgCI9MjgKdH50vBPBA==", + "license": "MIT", + "dependencies": { + "csstype": "3.1.1" + }, + "engines": { + "node": ">=18.17.0" + } + }, + "node_modules/@clerk/express/node_modules/csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==", + "license": "MIT" + }, + "node_modules/@clerk/express/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "license": "0BSD" + }, + "node_modules/@clerk/nextjs": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@clerk/nextjs/-/nextjs-6.0.2.tgz", + "integrity": "sha512-8+Sn+ZrzAZOB9nlbn/Vdfh5s5DziAED9T8njU/b/+hK7sqCpIyfuJBOpDeHjlWVHZaTSd911KYSPtNpSOUZ4MA==", + "license": "MIT", + "dependencies": { + "@clerk/backend": "1.15.1", + "@clerk/clerk-react": "5.13.1", + "@clerk/shared": "2.10.1", + "@clerk/types": "4.28.0", + "crypto-js": "4.2.0", + "server-only": "0.0.1", + "tslib": "2.4.1" + }, + "engines": { + "node": ">=18.17.0" + }, + "peerDependencies": { + "next": "^13.5.4 || ^14.0.3 || >=15.0.0-rc", + "react": ">=18 || >=19.0.0-beta", + "react-dom": ">=18 || >=19.0.0-beta" + } + }, + "node_modules/@clerk/nextjs/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "license": "0BSD" + }, + "node_modules/@clerk/shared": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@clerk/shared/-/shared-2.10.1.tgz", + "integrity": "sha512-9dPuCcTd2qaK+YU9BiO5mPPnet9B38ZSp0gutnaUQmve9013qO0p9Lx7ympiPSulwkTG4NAfYxjr/pyIUUFqCQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@clerk/types": "4.28.0", + "glob-to-regexp": "0.4.1", + "js-cookie": "3.0.5", + "std-env": "^3.7.0", + "swr": "^2.2.0" + }, + "engines": { + "node": ">=18.17.0" + }, + "peerDependencies": { + "react": ">=18 || >=19.0.0-beta", + "react-dom": ">=18 || >=19.0.0-beta" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@clerk/types": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/@clerk/types/-/types-4.28.0.tgz", + "integrity": "sha512-RPdrUs8HYfhXaZ0MOVBkzy7lilsU9lDVSC88a5o/cEMmTML+BTDfLHMlLG81kgvagSLCKKbl28iocb8y7stm1Q==", + "license": "MIT", + "dependencies": { + "csstype": "3.1.1" + }, + "engines": { + "node": ">=18.17.0" + } + }, + "node_modules/@clerk/types/node_modules/csstype": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", + "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==", + "license": "MIT" + }, + "node_modules/@cloudinary-util/types": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/@cloudinary-util/types/-/types-1.5.10.tgz", + "integrity": "sha512-n5lrm7SdAXhgWEbkSJKHZGnaoO9G/g4WYS6HYnq/k4nLj79sYfQZOoKjyR8hF2iyLRdLkT+qlk68RNFFv5tKew==", + "license": "MIT" + }, + "node_modules/@cloudinary-util/url-loader": { + "version": "5.10.4", + "resolved": "https://registry.npmjs.org/@cloudinary-util/url-loader/-/url-loader-5.10.4.tgz", + "integrity": "sha512-gHkdvOaV+rlcwuIT7Vqd0ts/H5bsH4+bwFten/gIZ8oRjzdTBvgIY3R6F8bbJt0pFIEfpFEQLe4rPkl0NNqEWg==", + "license": "MIT", + "dependencies": { + "@cloudinary-util/types": "1.5.10", + "@cloudinary-util/util": "3.3.2", + "@cloudinary/url-gen": "1.15.0", + "zod": "^3.22.4" + } + }, + "node_modules/@cloudinary-util/url-loader/node_modules/@cloudinary-util/util": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@cloudinary-util/util/-/util-3.3.2.tgz", + "integrity": "sha512-Cc0iFxzfl7fcOXuznpeZFGYC885Of/vDgccRDnhTe/8Rf8YKv2PjLtezyo0VgmdA/CpeZy29NCXAsf6liokbwg==", + "license": "MIT" + }, + "node_modules/@cloudinary-util/util": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@cloudinary-util/util/-/util-4.0.0.tgz", + "integrity": "sha512-S4xcou/3A7l5o+bcKlw2VHBNgwups7/0lbVDT/cO5YmtrcEYXgj6LGmwnjvpTm/x571VPVN8x5jWdT3rLZiKJQ==", + "license": "MIT" + }, + "node_modules/@cloudinary/transformation-builder-sdk": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@cloudinary/transformation-builder-sdk/-/transformation-builder-sdk-1.15.2.tgz", + "integrity": "sha512-oXYaW/whGaQVlR1O/ocp7vYcxaMAy3uGcQ8+8xjbfDCy/+c+zFIuP6JKI8L05kzqw7Wjh0cqzG+4u0X1UpPh+A==", + "license": "MIT", + "dependencies": { + "@cloudinary/url-gen": "^1.7.0" + } + }, + "node_modules/@cloudinary/url-gen": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@cloudinary/url-gen/-/url-gen-1.15.0.tgz", + "integrity": "sha512-bjU67eZxLUgoRy/Plli4TQio7q6P31OYqnEgXxeN9TKXrzr6h0DeEdIUhKI9gy3HkEBWXWWJIPh7j7gkOJPnyA==", + "license": "MIT", + "dependencies": { + "@cloudinary/transformation-builder-sdk": "^1.10.0" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -230,7 +628,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -250,7 +647,6 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { @@ -472,6 +868,24 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@prisma/client": { + "version": "5.21.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.21.1.tgz", + "integrity": "sha512-3n+GgbAZYjaS/k0M03yQsQfR1APbr411r74foknnsGpmhNKBG49VuUkxIU6jORgvJPChoD4WC4PqoHImN1FP0w==", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.13" + }, + "peerDependencies": { + "prisma": "*" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + } + } + }, "node_modules/@prisma/debug": { "version": "5.21.0", "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.21.0.tgz", @@ -512,6 +926,167 @@ "@prisma/debug": "5.21.0" } }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-form": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.0.tgz", + "integrity": "sha512-1/oVYPDjbFILOLIarcGcMKo+y6SbTVT/iUKVEw59CF4offwZgBgC3ZOeSBewjqU0vdA6FWTPWTN63obj55S/tQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-label": "2.1.0", + "@radix-ui/react-primitive": "2.0.0" + }, + "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-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.0.tgz", + "integrity": "sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "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-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "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-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", + "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@restart/hooks": { "version": "0.4.16", "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", @@ -546,6 +1121,30 @@ "tslib": "^2.4.0" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "license": "MIT" + }, "node_modules/@types/d3-array": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", @@ -616,7 +1215,6 @@ "version": "20.14.14", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.14.tgz", "integrity": "sha512-d64f00982fS9YoOgJkAMolK7MN8Iq3TDdVjchbYHdEmjth/DHowx82GnoA+tVUAN+7vxfYUgAzi+JXbKNd2SDQ==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~5.26.4" @@ -652,7 +1250,7 @@ "version": "18.3.0", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/react": "*" @@ -812,11 +1410,43 @@ "url": "https://github.com/wojtekmaj/date-utils?sponsor=1" } }, + "node_modules/@xstate/react": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@xstate/react/-/react-4.1.3.tgz", + "integrity": "sha512-zhE+ZfrcCR87bu71Rkh5Z5ruZBivR/7uD/dkelzJqjQdI45IZc9DqTI8lL4Cg5+VN2p5k86KxDsusqW1kW11Tg==", + "license": "MIT", + "dependencies": { + "use-isomorphic-layout-effect": "^1.1.2", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "xstate": "^5.18.2" + }, + "peerDependenciesMeta": { + "xstate": { + "optional": true + } + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "peer": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.12.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -835,6 +1465,18 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -940,6 +1582,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT", + "peer": true + }, "node_modules/array-includes": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", @@ -1154,6 +1803,48 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "peer": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", + "peer": true + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1189,11 +1880,20 @@ "node": ">=10.16.0" } }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -1355,6 +2055,51 @@ "dev": true, "license": "MIT" }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.0.tgz", + "integrity": "sha512-qCf+V4dtlNhSRXGAZatc1TasyFO6GjohcOul807YOb5ik3+kQSnb4d7iajeCL8QHaJ4uZEjCgiCJerKXwdRVlQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT", + "peer": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -1370,6 +2115,12 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -1646,7 +2397,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -1678,6 +2428,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -1686,6 +2446,17 @@ "node": ">=6" } }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -1693,6 +2464,15 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -1735,6 +2515,16 @@ "csstype": "^3.0.2" } }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -1742,6 +2532,13 @@ "dev": true, "license": "MIT" }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT", + "peer": true + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -1749,6 +2546,16 @@ "dev": true, "license": "MIT" }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/enhanced-resolve": { "version": "5.17.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", @@ -1828,7 +2635,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, "license": "MIT", "dependencies": { "get-intrinsic": "^1.2.4" @@ -1841,7 +2647,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -1950,6 +2755,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT", + "peer": true + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -2399,11 +3211,91 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" }, + "node_modules/express": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.10", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", + "peer": true + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2499,6 +3391,42 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", + "peer": true + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2565,6 +3493,26 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2590,7 +3538,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2629,7 +3576,6 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2723,6 +3669,12 @@ "node": ">=10.13.0" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause" + }, "node_modules/glob/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -2807,7 +3759,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, "license": "MIT", "dependencies": { "get-intrinsic": "^1.1.3" @@ -2853,7 +3804,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" @@ -2866,7 +3816,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2879,7 +3828,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2908,7 +3856,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -2917,6 +3864,36 @@ "node": ">= 0.4" } }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "peer": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -2970,7 +3947,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, "license": "ISC" }, "node_modules/internal-slot": { @@ -3004,6 +3980,16 @@ "loose-envify": "^1.0.0" } }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -3476,6 +4462,15 @@ "jiti": "bin/jiti.js" } }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3651,6 +4646,15 @@ "loose-envify": "cli.js" } }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -3666,6 +4670,12 @@ "node": ">=12" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "license": "ISC" + }, "node_modules/map-age-cleaner": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", @@ -3674,7 +4684,29 @@ "p-defer": "^1.0.0" }, "engines": { - "node": ">=6" + "node": ">=6" + } + }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" } }, "node_modules/mem": { @@ -3697,6 +4729,16 @@ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3707,6 +4749,16 @@ "node": ">= 8" } }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/micromatch": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", @@ -3721,6 +4773,42 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "peer": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "peer": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", @@ -3825,6 +4913,16 @@ "dev": true, "license": "MIT" }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/next": { "version": "14.2.6", "resolved": "https://registry.npmjs.org/next/-/next-14.2.6.tgz", @@ -3874,6 +4972,21 @@ } } }, + "node_modules/next-cloudinary": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/next-cloudinary/-/next-cloudinary-6.16.0.tgz", + "integrity": "sha512-gbw/A1aBHJBC2thm4/veI77IkmbAQXuRUj8kNEuxf1zgjGkUWkShho/cXS4VsFhnHuDieqb30gMjZnZdBDrQ/Q==", + "license": "MIT", + "dependencies": { + "@cloudinary-util/types": "1.5.10", + "@cloudinary-util/url-loader": "5.10.4", + "@cloudinary-util/util": "4.0.0" + }, + "peerDependencies": { + "next": "^12 || ^13 || ^14 || >=15.0.0-rc || ^15", + "react": "^17 || ^18 || >=19.0.0-beta || ^19" + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -3902,6 +5015,16 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -3935,7 +5058,6 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4057,6 +5179,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "peer": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -4138,6 +5273,16 @@ "node": ">=6" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4192,6 +5337,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/path-to-regexp": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "license": "MIT", + "peer": true + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -4453,6 +5605,20 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "peer": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -4463,6 +5629,22 @@ "node": ">=6" } }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -4484,6 +5666,32 @@ ], "license": "MIT" }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "peer": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -4632,6 +5840,19 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-toastify": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.6.tgz", + "integrity": "sha512-yYjp+omCDf9lhZcrZHKbSq7YMuK0zcYkDFTzfRFgTXkTFHZ1ToxwAonzA4JI5CxA91JpjFLmwEsZEgfYfOqI1A==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -4877,6 +6098,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "peer": true + }, "node_modules/safe-regex-test": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", @@ -4895,6 +6137,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT", + "peer": true + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -4917,11 +6166,91 @@ "node": ">=10" } }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", + "peer": true + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "peer": true + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "peer": true, + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/server-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz", + "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==", + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -4951,6 +6280,13 @@ "node": ">= 0.4" } }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC", + "peer": true + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -4978,7 +6314,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -5016,6 +6351,42 @@ "node": ">=8" } }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/snakecase-keys": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/snakecase-keys/-/snakecase-keys-5.4.4.tgz", + "integrity": "sha512-YTywJG93yxwHLgrYLZjlC75moVEX04LZM4FHfihjHe1FCXm+QaLOFfSf535aXOAd0ArVQMWUAe8ZPm4VtWyXaA==", + "license": "MIT", + "dependencies": { + "map-obj": "^4.1.0", + "snake-case": "^3.0.4", + "type-fest": "^2.5.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/snakecase-keys/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/source-map-js": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", @@ -5025,6 +6396,22 @@ "node": ">=0.10.0" } }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", + "license": "MIT" + }, "node_modules/stop-iteration-iterator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", @@ -5339,6 +6726,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swr": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.5.tgz", + "integrity": "sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==", + "license": "MIT", + "dependencies": { + "client-only": "^0.0.1", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/tailwindcss": { "version": "3.4.9", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.9.tgz", @@ -5435,6 +6835,16 @@ "node": ">=8.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.6" + } + }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -5455,6 +6865,55 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "license": "MIT" + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -5500,6 +6959,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "peer": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typed-array-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", @@ -5581,7 +7054,6 @@ "version": "5.5.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", - "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -5625,9 +7097,18 @@ "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true, "license": "MIT" }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -5638,6 +7119,29 @@ "punycode": "^2.1.0" } }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", + "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -5645,6 +7149,32 @@ "dev": true, "license": "MIT" }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/victory-vendor": { "version": "36.9.2", "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", @@ -5891,6 +7421,16 @@ "dev": true, "license": "ISC" }, + "node_modules/xstate": { + "version": "5.18.2", + "resolved": "https://registry.npmjs.org/xstate/-/xstate-5.18.2.tgz", + "integrity": "sha512-hab5VOe29D0agy8/7dH1lGw+7kilRQyXwpaChoMu4fe6rDP+nsHYhDYKfS2O4iXE7myA98TW6qMEudj/8NXEkA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/xstate" + } + }, "node_modules/yaml": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz", @@ -5904,6 +7444,15 @@ "node": ">= 14" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index db384af1b..c22ffc54d 100644 --- a/package.json +++ b/package.json @@ -9,19 +9,27 @@ "lint": "next lint" }, "dependencies": { + "@clerk/clerk-sdk-node": "^5.0.60", + "@clerk/elements": "^0.17.1", + "@clerk/express": "^1.3.9", + "@clerk/nextjs": "^6.0.2", "@hookform/resolvers": "^3.9.0", + "@prisma/client": "^5.21.1", "@types/react-big-calendar": "^1.8.9", "date-fns": "^3.6.0", "dayjs": "^1.11.13", "moment": "^2.30.1", "next": "^14.2.6", + "next-cloudinary": "^6.16.0", "prisma": "^5.21.0", "react": "^18", "react-big-calendar": "^1.13.4", "react-calendar": "^5.0.0", "react-dom": "^18", "react-hook-form": "^7.53.0", + "react-toastify": "^10.0.6", "recharts": "^2.12.7", + "ts-node": "^10.9.2", "zod": "^3.23.8" }, "devDependencies": { @@ -33,5 +41,8 @@ "postcss": "^8", "tailwindcss": "^3.4.1", "typescript": "^5" + }, + "prisma": { + "seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts" } } diff --git a/prisma/migrations/20241025113419_init/migration.sql b/prisma/migrations/20241025113419_init/migration.sql new file mode 100644 index 000000000..f680a19dd --- /dev/null +++ b/prisma/migrations/20241025113419_init/migration.sql @@ -0,0 +1,282 @@ +-- CreateEnum +CREATE TYPE "UserSex" AS ENUM ('MALE', 'FEMALE'); + +-- CreateEnum +CREATE TYPE "Day" AS ENUM ('MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY'); + +-- CreateTable +CREATE TABLE "Admin" ( + "id" TEXT NOT NULL, + "username" TEXT NOT NULL, + + CONSTRAINT "Admin_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Student" ( + "id" TEXT NOT NULL, + "username" TEXT NOT NULL, + "name" TEXT NOT NULL, + "surname" TEXT NOT NULL, + "email" TEXT, + "phone" TEXT, + "address" TEXT NOT NULL, + "img" TEXT, + "bloodType" TEXT NOT NULL, + "sex" "UserSex" NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "parentId" TEXT NOT NULL, + "classId" INTEGER NOT NULL, + "gradeId" INTEGER NOT NULL, + "birthday" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Student_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Teacher" ( + "id" TEXT NOT NULL, + "username" TEXT NOT NULL, + "name" TEXT NOT NULL, + "surname" TEXT NOT NULL, + "email" TEXT, + "phone" TEXT, + "address" TEXT NOT NULL, + "img" TEXT, + "bloodType" TEXT NOT NULL, + "sex" "UserSex" NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "birthday" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Teacher_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Parent" ( + "id" TEXT NOT NULL, + "username" TEXT NOT NULL, + "name" TEXT NOT NULL, + "surname" TEXT NOT NULL, + "email" TEXT, + "phone" TEXT NOT NULL, + "address" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Parent_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Grade" ( + "id" SERIAL NOT NULL, + "level" INTEGER NOT NULL, + + CONSTRAINT "Grade_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Class" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "capacity" INTEGER NOT NULL, + "supervisorId" TEXT, + "gradeId" INTEGER NOT NULL, + + CONSTRAINT "Class_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Subject" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + + CONSTRAINT "Subject_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Lesson" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "day" "Day" NOT NULL, + "startTime" TIMESTAMP(3) NOT NULL, + "endTime" TIMESTAMP(3) NOT NULL, + "subjectId" INTEGER NOT NULL, + "classId" INTEGER NOT NULL, + "teacherId" TEXT NOT NULL, + + CONSTRAINT "Lesson_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Exam" ( + "id" SERIAL NOT NULL, + "title" TEXT NOT NULL, + "startTime" TIMESTAMP(3) NOT NULL, + "endTime" TIMESTAMP(3) NOT NULL, + "lessonId" INTEGER NOT NULL, + + CONSTRAINT "Exam_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Assignment" ( + "id" SERIAL NOT NULL, + "title" TEXT NOT NULL, + "startDate" TIMESTAMP(3) NOT NULL, + "dueDate" TIMESTAMP(3) NOT NULL, + "lessonId" INTEGER NOT NULL, + + CONSTRAINT "Assignment_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Result" ( + "id" SERIAL NOT NULL, + "score" INTEGER NOT NULL, + "examId" INTEGER, + "assignmentId" INTEGER, + "studentId" TEXT NOT NULL, + + CONSTRAINT "Result_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Attendance" ( + "id" SERIAL NOT NULL, + "date" TIMESTAMP(3) NOT NULL, + "present" BOOLEAN NOT NULL, + "studentId" TEXT NOT NULL, + "lessonId" INTEGER NOT NULL, + + CONSTRAINT "Attendance_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Event" ( + "id" SERIAL NOT NULL, + "title" TEXT NOT NULL, + "description" TEXT NOT NULL, + "startTime" TIMESTAMP(3) NOT NULL, + "endTime" TIMESTAMP(3) NOT NULL, + "classId" INTEGER, + + CONSTRAINT "Event_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Announcement" ( + "id" SERIAL NOT NULL, + "title" TEXT NOT NULL, + "description" TEXT NOT NULL, + "date" TIMESTAMP(3) NOT NULL, + "classId" INTEGER, + + CONSTRAINT "Announcement_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "_SubjectToTeacher" ( + "A" INTEGER NOT NULL, + "B" TEXT NOT NULL +); + +-- CreateIndex +CREATE UNIQUE INDEX "Admin_username_key" ON "Admin"("username"); + +-- CreateIndex +CREATE UNIQUE INDEX "Student_username_key" ON "Student"("username"); + +-- CreateIndex +CREATE UNIQUE INDEX "Student_email_key" ON "Student"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "Student_phone_key" ON "Student"("phone"); + +-- CreateIndex +CREATE UNIQUE INDEX "Teacher_username_key" ON "Teacher"("username"); + +-- CreateIndex +CREATE UNIQUE INDEX "Teacher_email_key" ON "Teacher"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "Teacher_phone_key" ON "Teacher"("phone"); + +-- CreateIndex +CREATE UNIQUE INDEX "Parent_username_key" ON "Parent"("username"); + +-- CreateIndex +CREATE UNIQUE INDEX "Parent_email_key" ON "Parent"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "Parent_phone_key" ON "Parent"("phone"); + +-- CreateIndex +CREATE UNIQUE INDEX "Grade_level_key" ON "Grade"("level"); + +-- CreateIndex +CREATE UNIQUE INDEX "Class_name_key" ON "Class"("name"); + +-- CreateIndex +CREATE UNIQUE INDEX "Subject_name_key" ON "Subject"("name"); + +-- CreateIndex +CREATE UNIQUE INDEX "_SubjectToTeacher_AB_unique" ON "_SubjectToTeacher"("A", "B"); + +-- CreateIndex +CREATE INDEX "_SubjectToTeacher_B_index" ON "_SubjectToTeacher"("B"); + +-- AddForeignKey +ALTER TABLE "Student" ADD CONSTRAINT "Student_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "Parent"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Student" ADD CONSTRAINT "Student_classId_fkey" FOREIGN KEY ("classId") REFERENCES "Class"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Student" ADD CONSTRAINT "Student_gradeId_fkey" FOREIGN KEY ("gradeId") REFERENCES "Grade"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Class" ADD CONSTRAINT "Class_supervisorId_fkey" FOREIGN KEY ("supervisorId") REFERENCES "Teacher"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Class" ADD CONSTRAINT "Class_gradeId_fkey" FOREIGN KEY ("gradeId") REFERENCES "Grade"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Lesson" ADD CONSTRAINT "Lesson_subjectId_fkey" FOREIGN KEY ("subjectId") REFERENCES "Subject"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Lesson" ADD CONSTRAINT "Lesson_classId_fkey" FOREIGN KEY ("classId") REFERENCES "Class"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Lesson" ADD CONSTRAINT "Lesson_teacherId_fkey" FOREIGN KEY ("teacherId") REFERENCES "Teacher"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Exam" ADD CONSTRAINT "Exam_lessonId_fkey" FOREIGN KEY ("lessonId") REFERENCES "Lesson"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Assignment" ADD CONSTRAINT "Assignment_lessonId_fkey" FOREIGN KEY ("lessonId") REFERENCES "Lesson"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Result" ADD CONSTRAINT "Result_examId_fkey" FOREIGN KEY ("examId") REFERENCES "Exam"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Result" ADD CONSTRAINT "Result_assignmentId_fkey" FOREIGN KEY ("assignmentId") REFERENCES "Assignment"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Result" ADD CONSTRAINT "Result_studentId_fkey" FOREIGN KEY ("studentId") REFERENCES "Student"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Attendance" ADD CONSTRAINT "Attendance_studentId_fkey" FOREIGN KEY ("studentId") REFERENCES "Student"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Attendance" ADD CONSTRAINT "Attendance_lessonId_fkey" FOREIGN KEY ("lessonId") REFERENCES "Lesson"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Event" ADD CONSTRAINT "Event_classId_fkey" FOREIGN KEY ("classId") REFERENCES "Class"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Announcement" ADD CONSTRAINT "Announcement_classId_fkey" FOREIGN KEY ("classId") REFERENCES "Class"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_SubjectToTeacher" ADD CONSTRAINT "_SubjectToTeacher_A_fkey" FOREIGN KEY ("A") REFERENCES "Subject"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_SubjectToTeacher" ADD CONSTRAINT "_SubjectToTeacher_B_fkey" FOREIGN KEY ("B") REFERENCES "Teacher"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 000000000..fbffa92c2 --- /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 (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index ba670f77b..9ce648a76 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,9 +1,3 @@ -// This is your Prisma schema file, -// learn more about it in the docs: https://pris.ly/d/prisma-schema - -// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? -// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init - generator client { provider = "prisma-client-js" } @@ -19,18 +13,17 @@ model Admin { } model Student { - id String @id - username String @unique - name String - surname String - email String? @unique - phone String? @unique - address String - img String? - bloodType String - sex UserSex - createdAT DateTime @default(now()) - + id String @id + username String @unique + name String + surname String + email String? @unique + phone String? @unique + address String + img String? + bloodType String + sex UserSex + createdAt DateTime @default(now()) parentId String parent Parent @relation(fields: [parentId], references: [id]) classId Int @@ -39,25 +32,25 @@ model Student { grade Grade @relation(fields: [gradeId], references: [id]) attendances Attendance[] results Result[] + birthday DateTime } model Teacher { - id String @id - username String @unique + id String @id + username String @unique name String surname String - email String? @unique - phone String? @unique + email String? @unique + phone String? @unique address String img String? bloodType String sex UserSex - createdAT DateTime @default(now()) - - subjects Subject[] - lessons Lesson[] - - classes Class[] + createdAt DateTime @default(now()) + subjects Subject[] + lessons Lesson[] + classes Class[] + birthday DateTime } model Parent { @@ -68,7 +61,7 @@ model Parent { email String? @unique phone String @unique address String - createdAT DateTime @default(now()) + createdAt DateTime @default(now()) students Student[] } @@ -77,7 +70,7 @@ model Grade { level Int @unique students Student[] - classes Class[] + classess Class[] } model Class { @@ -85,8 +78,8 @@ model Class { name String @unique capacity Int - supervisorId String - supervisor Teacher @relation(fields: [supervisorId], references: [id]) + supervisorId String? + supervisor Teacher? @relation(fields: [supervisorId], references: [id]) lessons Lesson[] students Student[] gradeId Int @@ -139,7 +132,7 @@ model Assignment { lessonId Int lesson Lesson @relation(fields: [lessonId], references: [id]) - reults Result[] + results Result[] } model Result { @@ -197,4 +190,4 @@ enum Day { WEDNESDAY THURSDAY FRIDAY -} +} \ No newline at end of file diff --git a/prisma/seed.ts b/prisma/seed.ts new file mode 100644 index 000000000..fafa62b22 --- /dev/null +++ b/prisma/seed.ts @@ -0,0 +1,215 @@ +import { Day, PrismaClient, UserSex } from "@prisma/client"; +const prisma = new PrismaClient(); + +async function main() { + // ADMIN + await prisma.admin.create({ + data: { + id: "admin1", + username: "admin1", + }, + }); + await prisma.admin.create({ + data: { + id: "admin2", + username: "admin2", + }, + }); + + // GRADE + for (let i = 1; i <= 6; i++) { + await prisma.grade.create({ + data: { + level: i, + }, + }); + } + + // CLASS + for (let i = 1; i <= 6; i++) { + await prisma.class.create({ + data: { + name: `${i}A`, + gradeId: i, + capacity: Math.floor(Math.random() * (20 - 15 + 1)) + 15, + }, + }); + } + + // SUBJECT + const subjectData = [ + { name: "Mathematics" }, + { name: "Science" }, + { name: "English" }, + { name: "History" }, + { name: "Geography" }, + { name: "Physics" }, + { name: "Chemistry" }, + { name: "Biology" }, + { name: "Computer Science" }, + { name: "Art" }, + ]; + + for (const subject of subjectData) { + await prisma.subject.create({ data: subject }); + } + + // TEACHER + for (let i = 1; i <= 15; i++) { + await prisma.teacher.create({ + data: { + id: `teacher${i}`, // Unique ID for the teacher + username: `teacher${i}`, + name: `TName${i}`, + surname: `TSurname${i}`, + email: `teacher${i}@example.com`, + phone: `123-456-789${i}`, + address: `Address${i}`, + bloodType: "A+", + sex: i % 2 === 0 ? UserSex.MALE : UserSex.FEMALE, + subjects: { connect: [{ id: (i % 10) + 1 }] }, + classes: { connect: [{ id: (i % 6) + 1 }] }, + birthday: new Date(new Date().setFullYear(new Date().getFullYear() - 30)), + }, + }); + } + + // LESSON + for (let i = 1; i <= 30; i++) { + await prisma.lesson.create({ + data: { + name: `Lesson${i}`, + day: Day[ + Object.keys(Day)[ + Math.floor(Math.random() * Object.keys(Day).length) + ] as keyof typeof Day + ], + startTime: new Date(new Date().setHours(new Date().getHours() + 1)), + endTime: new Date(new Date().setHours(new Date().getHours() + 3)), + subjectId: (i % 10) + 1, + classId: (i % 6) + 1, + teacherId: `teacher${(i % 15) + 1}`, + }, + }); + } + + // PARENT + for (let i = 1; i <= 25; i++) { + await prisma.parent.create({ + data: { + id: `parentId${i}`, + username: `parentId${i}`, + name: `PName ${i}`, + surname: `PSurname ${i}`, + email: `parent${i}@example.com`, + phone: `123-456-789${i}`, + address: `Address${i}`, + }, + }); + } + + // STUDENT + for (let i = 1; i <= 50; i++) { + await prisma.student.create({ + data: { + id: `student${i}`, + username: `student${i}`, + name: `SName${i}`, + surname: `SSurname ${i}`, + email: `student${i}@example.com`, + phone: `987-654-321${i}`, + address: `Address${i}`, + bloodType: "O-", + sex: i % 2 === 0 ? UserSex.MALE : UserSex.FEMALE, + parentId: `parentId${Math.ceil(i / 2) % 25 || 25}`, + gradeId: (i % 6) + 1, + classId: (i % 6) + 1, + birthday: new Date(new Date().setFullYear(new Date().getFullYear() - 10)), + }, + }); + } + + // EXAM + for (let i = 1; i <= 10; i++) { + await prisma.exam.create({ + data: { + title: `Exam ${i}`, + startTime: new Date(new Date().setHours(new Date().getHours() + 1)), + endTime: new Date(new Date().setHours(new Date().getHours() + 2)), + lessonId: (i % 30) + 1, + }, + }); + } + + // ASSIGNMENT + for (let i = 1; i <= 10; i++) { + await prisma.assignment.create({ + data: { + title: `Assignment ${i}`, + startDate: new Date(new Date().setHours(new Date().getHours() + 1)), + dueDate: new Date(new Date().setDate(new Date().getDate() + 1)), + lessonId: (i % 30) + 1, + }, + }); + } + + // RESULT + for (let i = 1; i <= 10; i++) { + await prisma.result.create({ + data: { + score: 90, + studentId: `student${i}`, + ...(i <= 5 ? { examId: i } : { assignmentId: i - 5 }), + }, + }); + } + + // ATTENDANCE + for (let i = 1; i <= 10; i++) { + await prisma.attendance.create({ + data: { + date: new Date(), + present: true, + studentId: `student${i}`, + lessonId: (i % 30) + 1, + }, + }); + } + + // EVENT + for (let i = 1; i <= 5; i++) { + await prisma.event.create({ + data: { + title: `Event ${i}`, + description: `Description for Event ${i}`, + startTime: new Date(new Date().setHours(new Date().getHours() + 1)), + endTime: new Date(new Date().setHours(new Date().getHours() + 2)), + classId: (i % 5) + 1, + }, + }); + } + + // ANNOUNCEMENT + for (let i = 1; i <= 5; i++) { + await prisma.announcement.create({ + data: { + title: `Announcement ${i}`, + description: `Description for Announcement ${i}`, + date: new Date(), + classId: (i % 5) + 1, + }, + }); + } + + console.log("Seeding completed successfully."); +} + +main() + .then(async () => { + await prisma.$disconnect(); + }) + .catch(async (e) => { + console.error(e); + await prisma.$disconnect(); + process.exit(1); + }); \ No newline at end of file diff --git a/public/noAvatar.png b/public/noAvatar.png new file mode 100644 index 0000000000000000000000000000000000000000..eb2a81cc50b4fc826c1a397f53841225ff70b7ca GIT binary patch literal 3155 zcmb_edpwl+8XvOjk|=8{Y>Z2d#atT`W89}Qg%BE6F_+hv%+1UzqZHeP8e59RqSRK( zwaFq|ZgUQsl~fo>v(!-%nwEr8>bzrH8=Z6hIOn{d_xe2F-}8H}-_Lu*&v)Yz9b+9B z47P;gMGkd)(1F0a@$D7FDb5S%V zpAMkJTmgiJ!AN2v^vVT58eGif@I*whGh!A&gx*zRG{Qv({^1}AnUTZ*@`evk=*t-q z1%d)18XXrGhl*Q^;tN@5tfQkN8iPaQa7YM&6vgvEni$CwSwfcPn<4`uhL9})*?b;c zWtv9k$AZoX#2o4mmza#XZ3MAG&g>>k1{&Z1T!06P&{!09o|jAp5oCiLV1eWA9B_ez z!&XH?TrYaqndFl9L715n3`p9urBwK-) z1rjJBDg_j}2ndW90M3X2Km;X&0esvA9}Id50U8J}U2qs29*J>8V%Cz-3siH3Bz;h< z7lRb03m!+uVDU@~REp zM=Tz@&e7iCuQjvEPnHDoV;NDZuv~C+*8h_I1^+i?o}aEiL!F=2|5Kp_A5E63%CjS@ zsxPE~>brJceIXgqJr<1O3tha)Bo*Y(&jlg{EwzjEwRAnym7AWt zpWM6W-JnyVvYK_--cR7lo4JQ4R+Vnw;2{4V;+HET-x#K7W3 ziE4(Xy}|c&)CJF87HtincJ@ig7Oe%kn{`@J#U>6j))w+3&pR*r?!}g}51jC_+IsWM zQ>h&#eMq%dv!2ZiO8sSj_ef*c9XBo)c{S`8NG8JwX7?V%jI`s9>U7FHOmh6J7M$Mgj2V;JRhI(+A!fo&Y^8-ryX8r_(ZSp zt;J(Ty+xw&)ZKO6U0vphPb%>7q}A(TTKkhqepIVgv~ln|$v0OpDaLzPQBPe*5bKyo zN_xUib~Cdf{JgFv*H{O2KWJ6&hX^f-U7c z8qSig5SMnh)w>y?fAx5^ONldRGS+aHJ(@87Q36#uJ9-hU;VcN-Vl;O5-cy2y0#;qq zVm(om@XXeON!gvu?R!z+7hPm;+~jFimAqH}m~ngYiGqp^&fLtMt%t0K``V+A14C?B zyVSZ(Mmu*bU~}?=j%aFVXz{^)a!6(|y2Qp>YsK>ZOb!1fX6mE%<=xVRO9tr96JD!d z7QcErVcSeTQ)7S_6Q6v2)v@ty&Jf$+2zj+R43wI~P2qZ~m#`*nY4rXEXVRs36slUAH9+(QDQVXwl zz;1CH4o$#wybaz>Y2xkUN5#%~Jb_+LZ5a^SIg8y$F1Kg)OdW2vy$v41s8M8oUBNmP z75N&7wk5f7IyKgeq*~Y&@>`aOb$*M2Q?&-LHy*=M_c-1k9&PMm8mwPz)>PibO zjH|0uu@Q5=evP_k>C10gujfzhx^}I`wm5XLxt98y$v8n_#b&g#gpcN+;AtTb^MpXzq)&4r=inVdU~eA3Fj?fkT=LTB{BAs zUz+Dmt~uU&47^-XQmiO4)NnsjQBqoy-_~4j0r5qK>|wC3^ma;Xm8eY|y?qrDS*FNY z@`RCi&t5xOzM`l9(zCvbi#BS*?uxt7C{52{X|QIrhez@5&$N~^?r{iVmn5mo-pdN5 z!>L;V*`DS>GlJO5w8p6k%^eX1%pO=;_cn(rM$`*KWqjKJV|(N0=D8PqQX21%AF`{i z*;()d@8>)MODdqgDZjw(92q1OoPkIs!lAV8S|nWJ5I1U$KG@6KXlQojx4+Yr{a4*0 zw$g2KHy#{~CHm?ge0adJ!whzhVp((G@|+ zQ|nWX=$~xImpu*VuU4FgnB|MRm4B1Ej5R&dUv380#>NMArYoihSFl@b1aLqRdB-+sz^U-Bi7i2eTr!|@8A literal 0 HcmV?d00001 diff --git a/src/app/(dashboard)/admin/page.tsx b/src/app/(dashboard)/admin/page.tsx index 5a3917b8f..80a451fab 100644 --- a/src/app/(dashboard)/admin/page.tsx +++ b/src/app/(dashboard)/admin/page.tsx @@ -1,27 +1,27 @@ import Announcements from "@/components/Announcements"; -import AttendanceChart from "@/components/AttendanceChart"; -import CountChart from "@/components/CountChart"; -import EventCalendar from "@/components/EventCalendar"; +import AttendanceChartContainer from "@/components/AttendanceChartContainer"; +import CountChartContainer from "@/components/CountChartContainer"; +import EventCalendarContainer from "@/components/EventCalendarContainer"; import FinanceChart from "@/components/FinanceChart"; import UserCard from "@/components/UserCard"; import React from "react"; -const AdminPage = () => { +const AdminPage = ({ searchParams }: { searchParams: { [keys: string]: string | undefined } }) => { return (
+ -
- +
- +
@@ -29,7 +29,7 @@ const AdminPage = () => {
- +
diff --git a/src/app/(dashboard)/list/announcements/page.tsx b/src/app/(dashboard)/list/announcements/page.tsx index 4830170a5..dcb0bbd6f 100644 --- a/src/app/(dashboard)/list/announcements/page.tsx +++ b/src/app/(dashboard)/list/announcements/page.tsx @@ -2,45 +2,50 @@ import FormModal from "@/components/FormModal"; import Pagination from "@/components/Pagination"; import Table from "@/components/Table"; import TableSearch from "@/components/TableSearch"; -import { announcementsData, role } from "@/lib/data"; +import prisma from "@/lib/prisma"; +import { ITEM_PER_PAGE } from "@/lib/settings"; +import { getUserRole } from "@/lib/utils"; +import { Announcement, Class, Prisma } from "@prisma/client"; import Image from "next/image"; -import Link from "next/link"; import React from "react"; -type Announcement = { - id: number; - title: string; - class: string; - date: string; -}; -const columns = [ - { - header: "Title", - accessor: "title", - }, - { - header: "Class", - accessor: "class", - }, - { - header: "Date", - accessor: "date", - className: "hidden md:table-cell", - }, - { - header: "Actions", - accessor: "action", - }, -]; -const AnnouncementListPage = () => { - const renderRow = (item: Announcement) => ( +type AnnouncementList = Announcement & { class: Class } + +const AnnouncementListPage = async ({ searchParams }: { searchParams: { [key: string]: string | undefined }}) => { + const { page, ...queryParams } = searchParams; + const p = page ? parseInt(page) : 1; + + const { role, userId } = await getUserRole(); + + const columns = [ + { + header: "Title", + accessor: "title", + }, + { + header: "Class", + accessor: "class", + }, + { + header: "Date", + accessor: "date", + className: "hidden md:table-cell", + }, + ...(role === "admin" ? [ + { + header: "Actions", + accessor: "action", + }]:[]), + ]; + const renderRow = async (item: AnnouncementList) => ( + {item.title} - {item.class} - {item.date} + {item.class?.name || "-"} + {new Intl.DateTimeFormat("en-US").format(item.date)}
{role === "admin" && ( @@ -53,6 +58,47 @@ const AnnouncementListPage = () => { ); + + //URL PARAMS CONDITION + const query:Prisma.AnnouncementWhereInput = {} + if (queryParams) { + for (const [key, value] of Object.entries(queryParams)) { + if (value !== undefined) { + switch (key) { + case "search": + query.title = {contains:value, mode:"insensitive" } + break; + default: + break; + }; + } + } + } + + //ROLE CONDITIONS + // const roleConditions = { + // teacher: { lessons: { some: { teacherId: userId! } } }, + // student: { students: { some: { id: userId! } } }, + // parent: { students: { some: { parentId: userId! } } }, + // } + + // query.OR = [ + // { classId: null }, + // { + // class:roleConditions[role as keyof typeof roleConditions] || {} + // } + // ] +const [data, count] = await prisma.$transaction([ + prisma.announcement.findMany({ + where:query, + include: { + class:true, + }, + take: ITEM_PER_PAGE, + skip: ITEM_PER_PAGE * (p - 1), + }), + prisma.announcement.count({where:query}), + ]); return (
{/*TOP*/} @@ -80,11 +126,11 @@ const AnnouncementListPage = () => { {/*PAGINATION*/} - + ); }; diff --git a/src/app/(dashboard)/list/assignments/page.tsx b/src/app/(dashboard)/list/assignments/page.tsx index 4af3064c4..e0eeb2bed 100644 --- a/src/app/(dashboard)/list/assignments/page.tsx +++ b/src/app/(dashboard)/list/assignments/page.tsx @@ -1,66 +1,139 @@ -import FormModal from "@/components/FormModal"; +import FormContainer from "@/components/FormContainer"; import Pagination from "@/components/Pagination"; import Table from "@/components/Table"; import TableSearch from "@/components/TableSearch"; -import { assignmentsData, role } from "@/lib/data"; +import prisma from "@/lib/prisma"; +import { ITEM_PER_PAGE } from "@/lib/settings"; +import { getUserRole } from "@/lib/utils"; +import { Assignment, Class, Prisma, Subject, Teacher } from "@prisma/client"; import Image from "next/image"; -import Link from "next/link"; import React from "react"; -type Assignment = { - id: number; - subject: string; - class: string; - teacher: string; - dueDate: string; -}; -const columns = [ - { - header: "Subject Name", - accessor: "name", - }, - { - header: "Class", - accessor: "class", - }, - { - header: "Teacher", - accessor: "teacher", - className: "hidden md:table-cell", - }, - { - header: "Due Date", - accessor: "dueDate", - className: "hidden md:table-cell", - }, - { - header: "Actions", - accessor: "action", - }, -]; -const AssignmentListPage = () => { - const renderRow = (item: Assignment) => ( +type AssignmentList = Assignment & {lesson:{subject:Subject,class:Class, teacher:Teacher}} +const AssignmentListPage = async ({ searchParams }: { searchParams: { [key: string]: string | undefined }}) => { + const { page, ...queryParams } = searchParams; + const p = page ? parseInt(page) : 1; + + const {role, userId} = await getUserRole(); + + const columns = [ + { + header: "Subject Name", + accessor: "name", + }, + { + header: "Class", + accessor: "class", + }, + { + header: "Teacher", + accessor: "teacher", + className: "hidden md:table-cell", + }, + { + header: "Due Date", + accessor: "dueDate", + className: "hidden md:table-cell", + }, + ...(role === "admin" || role === "teacher" ? [ + { + header: "Actions", + accessor: "action", + }]:[]), + ]; + const renderRow = (item: AssignmentList) => ( - - - - - + + + + + ); + //URL PARAMS CONDITION + const query: Prisma.AssignmentWhereInput = {}; + query.lesson = {}; + if (queryParams) { + for (const [key, value] of Object.entries(queryParams)) { + if (value !== undefined) { + switch (key) { + case "classId": + query.lesson.classId = parseInt(value); + break; + case "teacherId": + query.lesson.teacherId = value; + break; + case "search": + query.lesson.subject= { + name: {contains:value, mode:"insensitive"} + } + + break; + default: + break; + }; + } + } + } + + //ROLE CONDITIONS + switch (role) { + case "admin": + break; + case "teacher": + query.lesson.teacherId = userId!; + break; + case "student": + query.lesson.class = { + students: { + some: { + id: userId!, + } + } + } + break; + case "parent": + query.lesson.class = { + students: { + some: { + parentId: userId!, + } + } + } + break; + default: + break; + } +const [data, count] = await prisma.$transaction([ + prisma.assignment.findMany({ + where:query, + include: { + lesson: { + select: { + subject: { select: { name: true } }, + teacher: { select: { name: true, surname:true } }, + class:{select:{name:true}}, + } + } + }, + take: ITEM_PER_PAGE, + skip: ITEM_PER_PAGE * (p - 1), + }), + prisma.assignment.count({where:query}), + ]); return (
{/*TOP*/} @@ -77,16 +150,16 @@ const AssignmentListPage = () => { - {role === "admin" && } + {(role === "admin" || role === "teacher") && }
{/*LIST*/}
-
{item.subject}{item.class}{item.teacher}{item.dueDate}{item.lesson.subject.name}{item.lesson.class.name}{item.lesson.teacher.name + " " + item.lesson.teacher.surname}{new Intl.DateTimeFormat("en-US").format(item.dueDate)}
- {role === "admin" && ( + {(role === "admin" || role === "teacher") && ( <> - - + + )}
+
{/*PAGINATION*/} - + ); }; diff --git a/src/app/(dashboard)/list/classes/page.tsx b/src/app/(dashboard)/list/classes/page.tsx index 6a30d9437..97760d4ae 100644 --- a/src/app/(dashboard)/list/classes/page.tsx +++ b/src/app/(dashboard)/list/classes/page.tsx @@ -1,19 +1,25 @@ -import FormModal from "@/components/FormModal"; +import FormContainer from "@/components/FormContainer"; import Pagination from "@/components/Pagination"; import Table from "@/components/Table"; import TableSearch from "@/components/TableSearch"; -import { classesData, role, subjectsData } from "@/lib/data"; +import prisma from "@/lib/prisma"; +import { ITEM_PER_PAGE } from "@/lib/settings"; +import { Class, Prisma, Teacher } from "@prisma/client"; import Image from "next/image"; -import Link from "next/link"; -import React from "react"; +import { auth } from "@clerk/nextjs/server"; + +type ClassList = Class & { supervisor: Teacher }; + +const ClassListPage = async ({ + searchParams, +}: { + searchParams: { [key: string]: string | undefined }; +}) => { + +const { sessionClaims } = await auth(); +const role = (sessionClaims?.metadata as { role?: string })?.role; + -type Class = { - id: number; - name: string; - capacity: number; - grade: number; - supervisor: string; -}; const columns = [ { header: "Class Name", @@ -34,39 +40,82 @@ const columns = [ accessor: "supervisor", className: "hidden md:table-cell", }, - { - header: "Actions", - accessor: "action", - }, + ...(role === "admin" + ? [ + { + header: "Actions", + accessor: "action", + }, + ] + : []), ]; -const ClassListPage = () => { - const renderRow = (item: Class) => ( - - - - - - - - ); +const renderRow = (item: ClassList) => ( + + + + + + + +); + + const { page, ...queryParams } = searchParams; + + const p = page ? parseInt(page) : 1; + + // URL PARAMS CONDITION + + const query: Prisma.ClassWhereInput = {}; + + if (queryParams) { + for (const [key, value] of Object.entries(queryParams)) { + if (value !== undefined) { + switch (key) { + case "supervisorId": + query.supervisorId = value; + break; + case "search": + query.name = { contains: value, mode: "insensitive" }; + break; + default: + break; + } + } + } + } + + const [data, count] = await prisma.$transaction([ + prisma.class.findMany({ + where: query, + include: { + supervisor: true, + }, + take: ITEM_PER_PAGE, + skip: ITEM_PER_PAGE * (p - 1), + }), + prisma.class.count({ where: query }), + ]); + return (
- {/*TOP*/} + {/* TOP */}
-

All Classes

+

All Classes

@@ -76,18 +125,16 @@ const ClassListPage = () => { - {role === "admin" && } + {role === "admin" && }
- {/*LIST*/} -
-
{item.name}{item.capacity}{item.grade}{item.supervisor} -
- {role === "admin" && ( - <> - - - - )} -
-
{item.name}{item.capacity}{item.name[0]} + {item.supervisor.name + " " + item.supervisor.surname} + +
+ {role === "admin" && ( + <> + + + + )} +
+
- - {/*PAGINATION*/} - + {/* LIST */} +
+ {/* PAGINATION */} + ); }; -export default ClassListPage; +export default ClassListPage; \ No newline at end of file diff --git a/src/app/(dashboard)/list/events/page.tsx b/src/app/(dashboard)/list/events/page.tsx index 88e5d6845..e477aedd1 100644 --- a/src/app/(dashboard)/list/events/page.tsx +++ b/src/app/(dashboard)/list/events/page.tsx @@ -1,72 +1,113 @@ -import FormModal from "@/components/FormModal"; +import FormContainer from "@/components/FormContainer"; import Pagination from "@/components/Pagination"; import Table from "@/components/Table"; import TableSearch from "@/components/TableSearch"; -import { eventsData, role } from "@/lib/data"; +import prisma from "@/lib/prisma"; +import { ITEM_PER_PAGE } from "@/lib/settings"; +import { getUserRole } from "@/lib/utils"; +import { Class, Event, Prisma } from "@prisma/client"; import Image from "next/image"; -import Link from "next/link"; import React from "react"; -type Event = { - id: number; - title: string; - class: string; - date: string; - startTime: string; - endTime: number; -}; -const columns = [ - { - header: "Title", - accessor: "title", - }, - { - header: "Class", - accessor: "class", - }, - { - header: "Date", - accessor: "date", - className: "hidden md:table-cell", - }, - { - header: "Start Time", - accessor: "startTime", - className: "hidden md:table-cell", - }, - { - header: "End Time", - accessor: "endTime", - className: "hidden md:table-cell", - }, - { - header: "Actions", - accessor: "action", - }, -]; -const EventListPage = () => { - const renderRow = (item: Event) => ( +type EventList = Event & {class:Class} +const EventListPage = async ({ searchParams }: { searchParams: { [key: string]: string | undefined }}) => { + const { page, ...queryParams } = searchParams; + const p = page ? parseInt(page) : 1; + const {role, userId} = await getUserRole(); + + + const columns = [ + { + header: "Title", + accessor: "title", + }, + { + header: "Class", + accessor: "class", + }, + { + header: "Date", + accessor: "date", + className: "hidden md:table-cell", + }, + { + header: "Start Time", + accessor: "startTime", + className: "hidden md:table-cell", + }, + { + header: "End Time", + accessor: "endTime", + className: "hidden md:table-cell", + }, + ...(role === "admin" ? [ + { + header: "Actions", + accessor: "action", + }]:[]), + ]; + const renderRow = (item: EventList) => ( - - - - + + + + ); + //URL PARAMS CONDITION + const query:Prisma.EventWhereInput = {} + if (queryParams) { + for (const [key, value] of Object.entries(queryParams)) { + if (value !== undefined) { + switch (key) { + case "search": + query.title = {contains:value, mode:"insensitive" } + break; + default: + break; + }; + } + } + } + + //ROLE CONDITIONS + // const roleConditions = { + // teacher: { lessons: { some: { teacherId: userId! } } }, + // student: { students: { some: { id: userId! } } }, + // parent: { students: { some: { parentId: userId! } } }, + // } + + // query.OR = [ + // { classId: null }, + // { + // class:roleConditions[role as keyof typeof roleConditions] || {} + // } + // ] +const [data, count] = await prisma.$transaction([ + prisma.event.findMany({ + where:query, + include: { + class:true, + }, + take: ITEM_PER_PAGE, + skip: ITEM_PER_PAGE * (p - 1), + }), + prisma.event.count({where:query}), + ]); return (
{/*TOP*/} @@ -81,16 +122,16 @@ const EventListPage = () => { - {role === "admin" && } + {role === "admin" && }
{/*LIST*/}
-
{item.title}{item.class}{item.date}{item.startTime}{item.endTime}{item.class?.name || "-"}{new Intl.DateTimeFormat("en-US").format(item.startTime)}{item.startTime.toLocaleTimeString("en-US", {hour:"2-digit", minute:"2-digit", hour12:false,})}{item.endTime.toLocaleTimeString("en-US", {hour:"2-digit", minute:"2-digit", hour12:false,})}
{role === "admin" && ( <> - - + + )}
+
{/*PAGINATION*/} - + ); }; diff --git a/src/app/(dashboard)/list/exams/page.tsx b/src/app/(dashboard)/list/exams/page.tsx index facab88bb..649652e5f 100644 --- a/src/app/(dashboard)/list/exams/page.tsx +++ b/src/app/(dashboard)/list/exams/page.tsx @@ -1,19 +1,31 @@ -import FormModal from "@/components/FormModal"; +import FormContainer from "@/components/FormContainer"; import Pagination from "@/components/Pagination"; import Table from "@/components/Table"; import TableSearch from "@/components/TableSearch"; -import { examsData, lessonsData, role } from "@/lib/data"; +import prisma from "@/lib/prisma"; +import { ITEM_PER_PAGE } from "@/lib/settings"; +import { Class, Exam, Prisma, Subject, Teacher } from "@prisma/client"; import Image from "next/image"; -import Link from "next/link"; -import React from "react"; - -type Exam = { - id: number; - subject: string; - class: string; - teacher: string; - date: string; +import { getUserRole } from "@/lib/utils"; + +type ExamList = Exam & { + lesson: { + subject: Subject; + class: Class; + teacher: Teacher; + }; }; + +const ExamListPage = async ({ + searchParams, +}: { + searchParams: { [key: string]: string | undefined }; +}) => { + +const { userId, role } = await getUserRole(); +const currentUserId = userId; + + const columns = [ { header: "Subject Name", @@ -33,39 +45,127 @@ const columns = [ accessor: "date", className: "hidden md:table-cell", }, - { - header: "Actions", - accessor: "action", - }, + ...(role === "admin" || role === "teacher" + ? [ + { + header: "Actions", + accessor: "action", + }, + ] + : []), ]; -const ExamListPage = () => { - const renderRow = (item: Exam) => ( - - - - - - - - - ); + +const renderRow = (item: ExamList) => ( + + + + + + + +); + + const { page, ...queryParams } = searchParams; + + const p = page ? parseInt(page) : 1; + + // URL PARAMS CONDITION + + const query: Prisma.ExamWhereInput = {}; + + query.lesson = {}; + if (queryParams) { + for (const [key, value] of Object.entries(queryParams)) { + if (value !== undefined) { + switch (key) { + case "classId": + query.lesson.classId = parseInt(value); + break; + case "teacherId": + query.lesson.teacherId = value; + break; + case "search": + query.lesson.subject = { + name: { contains: value, mode: "insensitive" }, + }; + break; + default: + break; + } + } + } + } + + // ROLE CONDITIONS + + switch (role) { + case "admin": + break; + case "teacher": + query.lesson.teacherId = currentUserId!; + break; + case "student": + query.lesson.class = { + students: { + some: { + id: currentUserId!, + }, + }, + }; + break; + case "parent": + query.lesson.class = { + students: { + some: { + parentId: currentUserId!, + }, + }, + }; + break; + + default: + break; + } + + const [data, count] = await prisma.$transaction([ + prisma.exam.findMany({ + where: query, + include: { + lesson: { + select: { + subject: { select: { name: true } }, + teacher: { select: { name: true, surname: true } }, + class: { select: { name: true } }, + }, + }, + }, + take: ITEM_PER_PAGE, + skip: ITEM_PER_PAGE * (p - 1), + }), + prisma.exam.count({ where: query }), + ]); + return (
- {/*TOP*/} + {/* TOP */}
-

All Exams

+

All Exams

@@ -75,18 +175,18 @@ const ExamListPage = () => { - {role === "admin" && } + {(role === "admin" || role === "teacher") && ( + + )}
- {/*LIST*/} -
-
{item.subject}{item.class}{item.teacher}{item.date} -
- {role === "admin" && ( - <> - - - - )} -
-
{item.lesson.subject.name}{item.lesson.class.name} + {item.lesson.teacher.name + " " + item.lesson.teacher.surname} + + {new Intl.DateTimeFormat("en-US").format(item.startTime)} + +
+ {(role === "admin" || role === "teacher") && ( + <> + + + + )} +
+
- - {/*PAGINATION*/} - + {/* LIST */} +
+ {/* PAGINATION */} + ); }; -export default ExamListPage; +export default ExamListPage; \ No newline at end of file diff --git a/src/app/(dashboard)/list/lessons/page.tsx b/src/app/(dashboard)/list/lessons/page.tsx index fe8a1604b..42bd586fd 100644 --- a/src/app/(dashboard)/list/lessons/page.tsx +++ b/src/app/(dashboard)/list/lessons/page.tsx @@ -1,59 +1,99 @@ -import FormModal from "@/components/FormModal"; +import FormContainer from "@/components/FormContainer"; import Pagination from "@/components/Pagination"; import Table from "@/components/Table"; import TableSearch from "@/components/TableSearch"; -import { lessonsData, role, subjectsData } from "@/lib/data"; +import prisma from "@/lib/prisma"; +import { ITEM_PER_PAGE } from "@/lib/settings"; +import { getUserRole } from "@/lib/utils"; +import { Class, Lesson, Prisma, Subject, Teacher } from "@prisma/client"; import Image from "next/image"; -import Link from "next/link"; import React from "react"; -type Lesson = { - id: number; - subject: string; - class: string; - teacher: string; -}; -const columns = [ - { - header: "Subject Name", - accessor: "name", - }, - { - header: "Class", - accessor: "class", - }, - { - header: "Teacher", - accessor: "teacher", - className: "hidden md:table-cell", - }, - { - header: "Actions", - accessor: "action", - }, -]; -const LessonListPage = () => { - const renderRow = (item: Lesson) => ( +type LessonList = Lesson & {subject:Subject} & {class:Class} & {teacher:Teacher} +const LessonListPage = async ({ searchParams }: { searchParams: { [key: string]: string | undefined }}) => { + const { page, ...queryParams } = searchParams; + const p = page ? parseInt(page) : 1; + const {role} = await getUserRole(); + + const columns = [ + { + header: "Subject Name", + accessor: "name", + }, + { + header: "Class", + accessor: "class", + }, + { + header: "Teacher", + accessor: "teacher", + className: "hidden md:table-cell", + }, + ...(role === "admin"? [ + { + header: "Actions", + accessor: "action", + }]:[]), + ]; + const renderRow = (item: LessonList) => ( - - - - + + + + ); + + //URL PARAMS CONDITION + const query:Prisma.LessonWhereInput = {} + if (queryParams) { + for (const [key, value] of Object.entries(queryParams)) { + if (value !== undefined) { + switch (key) { + case "classId": + query.classId = parseInt(value); + break; + case "teacherId": + query.teacherId = value; + break; + case "search": + query.OR = [ + { subject: { name: { contains: value, mode: "insensitive" } } }, + { teacher: { name: { contains: value, mode: "insensitive" } } } + ] + break; + default: + break; + }; + } + } + } +const [data, count] = await prisma.$transaction([ + prisma.lesson.findMany({ + where:query, + include: { + subject: {select:{name:true}}, + class: {select:{name:true}}, + teacher:{select:{name:true, surname:true}}, + }, + take: ITEM_PER_PAGE, + skip: ITEM_PER_PAGE * (p - 1), + }), + prisma.lesson.count({where:query}), + ]); return (
{/*TOP*/} @@ -68,16 +108,16 @@ const LessonListPage = () => { - {role === "admin" && } + {role === "admin" && }
{/*LIST*/}
-
{item.subject}{item.class}{item.teacher}{item.subject.name}{item.class.name}{item.teacher.name + " " + item.teacher.surname}
{role === "admin" && ( <> - - + + )}
+
{/*PAGINATION*/} - + ); }; diff --git a/src/app/(dashboard)/list/loading.tsx b/src/app/(dashboard)/list/loading.tsx new file mode 100644 index 000000000..2425d6ee2 --- /dev/null +++ b/src/app/(dashboard)/list/loading.tsx @@ -0,0 +1,11 @@ +import React from 'react' + +const loading = () => { + return ( +
+ ...Loading +
+ ) +} + +export default loading diff --git a/src/app/(dashboard)/list/parents/page.tsx b/src/app/(dashboard)/list/parents/page.tsx index 75ada0b14..15d00bf36 100644 --- a/src/app/(dashboard)/list/parents/page.tsx +++ b/src/app/(dashboard)/list/parents/page.tsx @@ -1,47 +1,47 @@ -import FormModal from "@/components/FormModal"; +import FormContainer from "@/components/FormContainer"; import Pagination from "@/components/Pagination"; import Table from "@/components/Table"; import TableSearch from "@/components/TableSearch"; -import { parentsData, role } from "@/lib/data"; +import prisma from "@/lib/prisma"; +import { ITEM_PER_PAGE } from "@/lib/settings"; +import { getUserRole } from "@/lib/utils"; +import { Parent, Prisma, Student } from "@prisma/client"; import Image from "next/image"; -import Link from "next/link"; import React from "react"; -type Parent = { - id: number; - name: string; - email?: string; - students: string[]; - phone: string; - address: string; -}; -const columns = [ - { - header: "Info", - accessor: "info", - }, - { - header: "Student Names", - accessor: "studentNames", - className: "hidden md:table-cell", - }, - { - header: "Phone", - accessor: "phone", - className: "hidden lg:table-cell", - }, - { - header: "Address", - accessor: "address", - className: "hidden lg:table-cell", - }, - { - header: "Actions", - accessor: "action", - }, -]; -const ParentListPage = () => { - const renderRow = (item: Parent) => ( +type ParentList = Parent & { students: Student[] }; +const ParentListPage = async ({ searchParams }: { searchParams: { [key: string]: string | undefined }}) => { + const { page, ...queryParams } = searchParams; + const p = page ? parseInt(page) : 1; + const {role} = await getUserRole(); + + const columns = [ + { + header: "Info", + accessor: "info", + }, + { + header: "Student Names", + accessor: "studentNames", + className: "hidden md:table-cell", + }, + { + header: "Phone", + accessor: "phone", + className: "hidden lg:table-cell", + }, + { + header: "Address", + accessor: "address", + className: "hidden lg:table-cell", + }, + ...(role === "admin"? [ + { + header: "Actions", + accessor: "action", + }]:[]), + ]; + const renderRow = (item: ParentList) => (
{

{item?.email}

- + ); + //URL PARAMS CONDITION + const query:Prisma.ParentWhereInput = {} + if (queryParams) { + for (const [key, value] of Object.entries(queryParams)) { + if (value !== undefined) { + switch (key) { + case "search": + query.name = { contains: value, mode: "insensitive" } + break; + default: + break; + }; + } + } + } + + const [data, count] = await prisma.$transaction([ + prisma.parent.findMany({ + where:query, + include: { + students: true, + }, + take: ITEM_PER_PAGE, + skip: ITEM_PER_PAGE * (p - 1), + }), + prisma.parent.count({where:query}), + ]); return (
{/*TOP*/} @@ -82,16 +109,16 @@ const ParentListPage = () => { - {role === "admin" && } + {role === "admin" && }
{/*LIST*/}
-
{item.students.join(",")}{item.students.map(student=>student.name).join(",")} {item.phone} {item.address}
{role === "admin" && ( <> - - - + + + )}
+
{/*PAGINATION*/} - + ); }; diff --git a/src/app/(dashboard)/list/results/page.tsx b/src/app/(dashboard)/list/results/page.tsx index ddaf5e6dd..d8857cd2a 100644 --- a/src/app/(dashboard)/list/results/page.tsx +++ b/src/app/(dashboard)/list/results/page.tsx @@ -2,70 +2,78 @@ import FormModal from "@/components/FormModal"; import Pagination from "@/components/Pagination"; import Table from "@/components/Table"; import TableSearch from "@/components/TableSearch"; -import { assignmentsData, resultsData, role } from "@/lib/data"; +import prisma from "@/lib/prisma"; +import { ITEM_PER_PAGE } from "@/lib/settings"; +import { getUserRole } from "@/lib/utils"; +import { Prisma } from "@prisma/client"; import Image from "next/image"; -import Link from "next/link"; import React from "react"; -type Result = { +type ResultList = { id: number; - subject: string; - class: string; - teacher: string; - student: string; - type: "exam" | "assignment"; - date: string; + title: string; + studentName: string; + studenSurname: string; + teacherName: string; + teacherSurname: string; score: number; -}; -const columns = [ - { - header: "Subject Name", - accessor: "name", - }, - { - header: "Student", - accessor: "student", - }, - { - header: "Score", - accessor: "score", - className: "hidden md:table-cell", - }, - { - header: "Teacher", - accessor: "teacher", - className: "hidden md:table-cell", - }, - { - header: "Class", - accessor: "class", - className: "hidden md:table-cell", - }, - { - header: "Date", - accessor: "Date", - className: "hidden md:table-cell", - }, - { - header: "Actions", - accessor: "action", - }, -]; -const ResultListPage = () => { - const renderRow = (item: Result) => ( + className: string; + startTime: Date; +} +const ResultListPage = async ({ searchParams }: { searchParams: { [key: string]: string | undefined }}) => { + const { page, ...queryParams } = searchParams; + const p = page ? parseInt(page) : 1; + const {role, userId} = await getUserRole(); + + const columns = [ + { + header: "Title", + accessor: "title", + }, + { + header: "Student", + accessor: "student", + }, + { + header: "Score", + accessor: "score", + className: "hidden md:table-cell", + }, + { + header: "Teacher", + accessor: "teacher", + className: "hidden md:table-cell", + }, + { + header: "Class", + accessor: "class", + className: "hidden md:table-cell", + }, + { + header: "Date", + accessor: "Date", + className: "hidden md:table-cell", + }, + ...(role === "admin" || role === "teacher" ? [ + { + header: "Actions", + accessor: "action", + }]:[]), + ]; + const renderRow = (item: ResultList) => ( - - + + - - - + + + ); + //URL PARAMS CONDITION + const query:Prisma.ResultWhereInput = {} + if (queryParams) { + for (const [key, value] of Object.entries(queryParams)) { + if (value !== undefined) { + switch (key) { + case "studentId": + query.studentId = value; + break; + case "search": + query.OR = [ + { exam: { title: { contains: value, mode: "insensitive" } } }, + {student:{name:{contains:value, mode:"insensitive"}}} + ] + break; + default: + break; + }; + } + } + } + + //ROLE CONDITIONS + switch (role) { + case "admin": + break; + case "teacher": + query.OR = [ + { exam: { lesson: { teacherId: userId! } } }, + { assignment: { lesson: { teacherId: userId! } } }, + ] + break; + case "student": + query.studentId = userId!; + break; + case "parent": + query.student = { + parentId: userId!, + }; + break; + default: + break; + } +const [dataRes, count] = await prisma.$transaction([ + prisma.result.findMany({ + where:query, + include: { + student: { select: { name: true, surname: true } }, + exam: { + include: { + lesson: { + select: { + class: { select: { name: true } }, + teacher:{ select: { name: true, surname:true } } + }, + }, + }, + }, + assignment: { + include: { + lesson: { + select: { + class: { select: { name: true } }, + teacher:{ select: { name: true, surname:true } } + } + } + } + } + }, + + take: ITEM_PER_PAGE, + skip: ITEM_PER_PAGE * (p - 1), + }), + prisma.result.count({where:query}), +]); + + const data = dataRes.map(item => { + const assessment = item.exam || item.assignment; + if (!assessment) return; + const isExam = "startTime" in assessment; + return { + id: item.id, + title: assessment.title, + studentName: item.student.name, + studentSurname: item.student.surname, + teacherName: assessment.lesson.teacher.name, + teacherSurname: assessment.lesson.teacher.surname, + score: item.score, + className: assessment.lesson.class.name, + startTime:isExam ? assessment.startTime : assessment.startDate, + } + }) return (
{/*TOP*/} @@ -89,16 +189,16 @@ const ResultListPage = () => { - {role === "admin" && } + {(role === "admin" || role === "teacher") && }
{/*LIST*/}
-
{item.subject}{item.student}{item.title}{item.studentName + " " + item.studenSurname} {item.score}{item.teacher}{item.class}{item.date}{item.teacherName + " " + item.teacherSurname}{item.className}{new Intl.DateTimeFormat("en-US").format(item.startTime)}
- {role === "admin" && ( + {(role === "admin" || role === "teacher") && ( <> @@ -75,6 +83,98 @@ const ResultListPage = () => {
+
{/*PAGINATION*/} - + ); }; diff --git a/src/app/(dashboard)/list/students/[id]/page.tsx b/src/app/(dashboard)/list/students/[id]/page.tsx index 1f331a55a..206b7f318 100644 --- a/src/app/(dashboard)/list/students/[id]/page.tsx +++ b/src/app/(dashboard)/list/students/[id]/page.tsx @@ -1,11 +1,31 @@ import Announcements from "@/components/Announcements"; -import BigCalendar from "@/components/BigCalendar"; +import BigCalendarContainer from "@/components/BigCalendarContainer"; +import FormContainer from "@/components/FormContainer"; import Performance from "@/components/Performance"; +import StudentAttendanceCard from "@/components/StudentAttendanceCard"; +import prisma from "@/lib/prisma"; +import { getUserRole } from "@/lib/utils"; +import { Class, Student } from "@prisma/client"; import Image from "next/image"; import Link from "next/link"; -import React from "react"; +import { notFound } from "next/navigation"; +import React, { Suspense } from "react"; -const SingleStudentPage = () => { +const SingleStudentPage = async ({ params: { id } }: { params: { id: string } }) => { + const {role} = await getUserRole(); + + const student: (Student & { class: (Class & {_count:{lessons:number}})})|null = await prisma.student.findUnique({ + where: { id }, + include: { + class: { + include:{_count:{select:{lessons:true}}} + } + }, + }); + + if (!student) { + return notFound(); + } return (
{/*LEFT*/} @@ -16,7 +36,7 @@ const SingleStudentPage = () => {
{ />
-

Cameron Moran

+
+

{student.name + "" + student.surname}

+ {role === "admin" && ( + + )} +

Lorem ipsum dolor sit amet.

- A+ + {student.bloodType}
- January 2025 + {new Intl.DateTimeFormat("en-GB").format(student.birthday)}
- user@gmail.com + {student.email || "-"}
- +1 234 567 + {student.phone || "-"}
@@ -59,10 +88,10 @@ const SingleStudentPage = () => { height={24} className="w-6 h-6" /> -
-

90%

- Attendance -
+ + + +
{/*CARD*/}
@@ -74,7 +103,7 @@ const SingleStudentPage = () => { className="w-6 h-6" />
-

6th

+

{student.class.name.charAt(0)}

Grade
@@ -88,7 +117,7 @@ const SingleStudentPage = () => { className="w-6 h-6" />
-

18

+

{student.class._count.lessons}

Lessons
@@ -102,7 +131,7 @@ const SingleStudentPage = () => { className="w-6 h-6" />
-

6A

+

{student.class.name}

Class
@@ -111,7 +140,7 @@ const SingleStudentPage = () => { {/*BOTTOM*/}

Student's Schedule

- +
{/*RIGHT*/} @@ -119,19 +148,19 @@ const SingleStudentPage = () => {

Shortcuts

- + Student's Lessons - + Student's Teachers - + Student's Exams - + Student's Assignments - + Student's Results
diff --git a/src/app/(dashboard)/list/students/page.tsx b/src/app/(dashboard)/list/students/page.tsx index 9fd5cd05c..5013f7f29 100644 --- a/src/app/(dashboard)/list/students/page.tsx +++ b/src/app/(dashboard)/list/students/page.tsx @@ -1,63 +1,61 @@ -import FormModal from "@/components/FormModal"; +import FormContainer from "@/components/FormContainer"; import Pagination from "@/components/Pagination"; import Table from "@/components/Table"; import TableSearch from "@/components/TableSearch"; -import { role, studentsData } from "@/lib/data"; +import prisma from "@/lib/prisma"; +import { ITEM_PER_PAGE } from "@/lib/settings"; +import { getUserRole } from "@/lib/utils"; +import { Class, Prisma, Student } from "@prisma/client"; import Image from "next/image"; import Link from "next/link"; import React from "react"; -type Student = { - id: number; - studentId: string; - name: string; - email?: string; - photo: string; - phone?: string; - grade: number; - class: string; - address: string; -}; -const columns = [ - { - header: "Info", - accessor: "info", - }, - { - header: "Student ID", - accessor: "studentId", - className: "hidden md:table-cell", - }, - { - header: "Grade", - accessor: "grade", - className: "hidden md:table-cell", - }, +type StudentList= Student & {class:Class} +const StudentListPage = async ({ searchParams }: { searchParams: { [key: string]: string | undefined }}) => { + const { page, ...queryParams } = searchParams; + const p = page ? parseInt(page) : 1; + const {role} = await getUserRole(); - { - header: "Phone", - accessor: "phone", - className: "hidden lg:table-cell", - }, - { - header: "Address", - accessor: "address", - className: "hidden lg:table-cell", - }, - { - header: "Actions", - accessor: "action", - }, -]; -const StudentListPage = () => { - const renderRow = (item: Student) => ( + const columns = [ + { + header: "Info", + accessor: "info", + }, + { + header: "Student ID", + accessor: "studentId", + className: "hidden md:table-cell", + }, + { + header: "Grade", + accessor: "grade", + className: "hidden md:table-cell", + }, + + { + header: "Phone", + accessor: "phone", + className: "hidden lg:table-cell", + }, + { + header: "Address", + accessor: "address", + className: "hidden lg:table-cell", + }, + ...(role === "admin" ? [ + { + header: "Actions", + accessor: "action", + }]:[]), + ]; + const renderRow = (item: StudentList) => (
- - + + ); + //URL PARAMS CONDITION + const query:Prisma.StudentWhereInput = {} + if (queryParams) { + for (const [key, value] of Object.entries(queryParams)) { + if (value !== undefined) { + switch (key) { + case "teacherId": + query.class = { + lessons: { + some: { + teacherId:value, + } + } + }; + break; + case "search": + query.name = { contains: value, mode: "insensitive" } + break; + default: + break; + }; + } + } + } + + const [data, count] = await prisma.$transaction([ + prisma.student.findMany({ + where:query, + include: { + class: true, + }, + take: ITEM_PER_PAGE, + skip: ITEM_PER_PAGE * (p - 1), + }), + prisma.student.count({where:query}), + ]); return (
{/*TOP*/} @@ -98,16 +134,16 @@ const StudentListPage = () => { - {role === "admin" && } + {role === "admin" && }
{/*LIST*/}
-
{ />

{item.name}

-

{item.class}

+

{item.class.name}

{item.studentId}{item.grade}{item.username}{item.class.name[0]} {item.phone} {item.address}
+ + + {role === "admin" && ( - + )}
+
{/*PAGINATION*/} - + ); }; diff --git a/src/app/(dashboard)/list/subjects/page.tsx b/src/app/(dashboard)/list/subjects/page.tsx index 933a31d0c..0b1ad4601 100644 --- a/src/app/(dashboard)/list/subjects/page.tsx +++ b/src/app/(dashboard)/list/subjects/page.tsx @@ -1,58 +1,100 @@ -import FormModal from "@/components/FormModal"; +import FormContainer from "@/components/FormContainer"; import Pagination from "@/components/Pagination"; import Table from "@/components/Table"; import TableSearch from "@/components/TableSearch"; -import { role, subjectsData } from "@/lib/data"; +import prisma from "@/lib/prisma"; +import { ITEM_PER_PAGE } from "@/lib/settings"; +import { Prisma, Subject, Teacher } from "@prisma/client"; import Image from "next/image"; -import Link from "next/link"; -import React from "react"; +import { auth } from "@clerk/nextjs/server"; -type Subject = { - id: number; - name: string; - teachers: string[]; -}; -const columns = [ - { - header: "Subject Name", - accessor: "name", - }, - { - header: "Teachers", - accessor: "teachers", - className: "hidden md:table-cell", - }, - { - header: "Actions", - accessor: "action", - }, -]; -const SubjectListPage = () => { - const renderRow = (item: Subject) => ( +type SubjectList = Subject & { teachers: Teacher[] }; + +const SubjectListPage = async ({ + searchParams, +}: { + searchParams: { [key: string]: string | undefined }; +}) => { + const { sessionClaims } = await auth(); + const role = (sessionClaims?.metadata as { role?: string })?.role; + + const columns = [ + { + header: "Subject Name", + accessor: "name", + }, + { + header: "Teachers", + accessor: "teachers", + className: "hidden md:table-cell", + }, + { + header: "Actions", + accessor: "action", + }, + ]; + + const renderRow = (item: SubjectList) => ( - - + ); + + const { page, ...queryParams } = searchParams; + + const p = page ? parseInt(page) : 1; + + // URL PARAMS CONDITION + + const query: Prisma.SubjectWhereInput = {}; + + if (queryParams) { + for (const [key, value] of Object.entries(queryParams)) { + if (value !== undefined) { + switch (key) { + case "search": + query.name = { contains: value, mode: "insensitive" }; + break; + default: + break; + } + } + } + } + + const [data, count] = await prisma.$transaction([ + prisma.subject.findMany({ + where: query, + include: { + teachers: true, + }, + take: ITEM_PER_PAGE, + skip: ITEM_PER_PAGE * (p - 1), + }), + prisma.subject.count({ where: query }), + ]); + return (
- {/*TOP*/} + {/* TOP */}
-

All Subjects

+

All Subjects

@@ -62,18 +104,18 @@ const SubjectListPage = () => { - {role === "admin" && } + {role === "admin" && ( + + )}
- {/*LIST*/} -
-
{item.name}{item.teachers.join(",")} + {item.teachers.map((teacher) => teacher.name).join(",")} +
{role === "admin" && ( <> - - + + )}
- - {/*PAGINATION*/} - + {/* LIST */} +
+ {/* PAGINATION */} + ); }; -export default SubjectListPage; +export default SubjectListPage; \ No newline at end of file diff --git a/src/app/(dashboard)/list/teachers/[id]/page.tsx b/src/app/(dashboard)/list/teachers/[id]/page.tsx index 1e0b76bde..12d6af7a9 100644 --- a/src/app/(dashboard)/list/teachers/[id]/page.tsx +++ b/src/app/(dashboard)/list/teachers/[id]/page.tsx @@ -1,12 +1,34 @@ import Announcements from "@/components/Announcements"; -import BigCalendar from "@/components/BigCalendar"; -import FormModal from "@/components/FormModal"; +import BigCalendarContainer from "@/components/BigCalendarContainer"; +import FormContainer from "@/components/FormContainer"; import Performance from "@/components/Performance"; +import prisma from "@/lib/prisma"; +import { getUserRole } from "@/lib/utils"; +import { Teacher } from "@prisma/client"; import Image from "next/image"; import Link from "next/link"; +import { notFound } from "next/navigation"; import React from "react"; -const SingleTeacherPage = () => { +const SingleTeacherPage = async ({ params: { id } }: { params: { id: string } }) => { + const {role} = await getUserRole(); + + const teacher: (Teacher & { _count: { subjects: number; lessons: number; classes:number}})|null = await prisma.teacher.findUnique({ + where: { id }, + include: { + _count: { + select: { + subjects: true, + lessons: true, + classes:true, + } + } + } + }); + + if (!teacher) { + return notFound(); + } return (
{/*LEFT*/} @@ -17,7 +39,7 @@ const SingleTeacherPage = () => {
{
-

Leonard Snyder

- +

{teacher.name + "" + teacher.surname}

+ {role === "admin" && ( + + )}

Lorem ipsum dolor sit amet. @@ -52,19 +63,19 @@ const SingleTeacherPage = () => {

- A+ + {teacher.bloodType}
- January 2025 + {new Intl.DateTimeFormat("en-GB").format(teacher.birthday)}
- user@gmail.com + {teacher.email || "-"}
- +1 234 567 + {teacher.phone || "-"}
@@ -95,7 +106,7 @@ const SingleTeacherPage = () => { className="w-6 h-6" />
-

2

+

{teacher._count.subjects}

Branches
@@ -109,7 +120,7 @@ const SingleTeacherPage = () => { className="w-6 h-6" />
-

6

+

{teacher._count.lessons}

Lessons
@@ -123,7 +134,7 @@ const SingleTeacherPage = () => { className="w-6 h-6" />
-

6

+

{teacher._count.classes}

Classes
@@ -132,7 +143,7 @@ const SingleTeacherPage = () => { {/*BOTTOM*/}

Teacher's Schedule

- +
{/*RIGHT*/} @@ -140,19 +151,19 @@ const SingleTeacherPage = () => {

Shortcuts

- + Teacher's Classes - + Teacher's Students - + Teacher's Lessons - + Teacher's Exams - + Teacher's Assignments
diff --git a/src/app/(dashboard)/list/teachers/page.tsx b/src/app/(dashboard)/list/teachers/page.tsx index a26cb5b0a..09f4dc017 100644 --- a/src/app/(dashboard)/list/teachers/page.tsx +++ b/src/app/(dashboard)/list/teachers/page.tsx @@ -1,67 +1,64 @@ -import FormModal from "@/components/FormModal"; +import FormContainer from "@/components/FormContainer"; import Pagination from "@/components/Pagination"; import Table from "@/components/Table"; import TableSearch from "@/components/TableSearch"; -import { role, teachersData } from "@/lib/data"; +import prisma from "@/lib/prisma"; +import { ITEM_PER_PAGE } from "@/lib/settings"; +import { getUserRole } from "@/lib/utils"; +import { Class, Prisma, Subject, Teacher } from "@prisma/client"; import Image from "next/image"; import Link from "next/link"; import React from "react"; +type TeacherList = Teacher & { subjects: Subject[] } & { classes: Class[] }; +const TeacherListPage = async ({ searchParams }: { searchParams: { [key: string]: string | undefined }}) => { + const { page, ...queryParams } = searchParams; + const p = page ? parseInt(page) : 1; + const {role} = await getUserRole(); -type Teacher = { - id: number; - teacherId: string; - name: string; - email?: string; - photo: string; - phone: string; - subjects: string[]; - classes: string[]; - address: string; -}; -const columns = [ - { - header: "Info", - accessor: "info", - }, - { - header: "Teacher ID", - accessor: "teacherId", - className: "hidden md:table-cell", - }, - { - header: "Subjects", - accessor: "subjects", - className: "hidden md:table-cell", - }, - { - header: "Classes", - accessor: "classes", - className: "hidden md:table-cell", - }, - { - header: "Phone", - accessor: "phone", - className: "hidden lg:table-cell", - }, - { - header: "Address", - accessor: "address", - className: "hidden lg:table-cell", - }, - { - header: "Actions", - accessor: "action", - }, -]; -const TeacherListPage = () => { - const renderRow = (item: Teacher) => ( + const columns = [ + { + header: "Info", + accessor: "info", + }, + { + header: "Teacher ID", + accessor: "teacherId", + className: "hidden md:table-cell", + }, + { + header: "Subjects", + accessor: "subjects", + className: "hidden md:table-cell", + }, + { + header: "Classes", + accessor: "classes", + className: "hidden md:table-cell", + }, + { + header: "Phone", + accessor: "phone", + className: "hidden lg:table-cell", + }, + { + header: "Address", + accessor: "address", + className: "hidden lg:table-cell", + }, + ...(role === "admin" ? [ + { + header: "Actions", + accessor: "action", + }]:[]), + ]; + const renderRow = (item: TeacherList) => (
- - - + + + ); + //URL PARAMS CONDITION + const query:Prisma.TeacherWhereInput = {} + if (queryParams) { + for (const [key, value] of Object.entries(queryParams)) { + if (value !== undefined) { + switch (key) { + case "classId": + query.lessons = { some: { classId: parseInt(value) } }; + break; + case "search": + query.name = { contains: value, mode: "insensitive" } + break; + default: + break; + }; + } + } + } + const [data, count] = await prisma.$transaction([ + prisma.teacher.findMany({ + where:query, + include: { + subjects: true, + classes: true, + }, + take: ITEM_PER_PAGE, + skip: ITEM_PER_PAGE * (p - 1), + }), + prisma.teacher.count({where:query}), + ]); + + return (
{/*TOP*/} @@ -103,16 +134,16 @@ const TeacherListPage = () => { - {role === "admin" && } + {role === "admin" && }
{/*LIST*/}
-
{

{item?.email}

{item.teacherId}{item.subjects.join(",")}{item.classes.join(",")}{item.username}{item.subjects.map(subject=>subject.name).join(",")}{item.classes.map(classItem=>classItem.name).join(",")} {item.phone} {item.address}
+ + + {role === "admin" && ( - + )}
+
{/*PAGINATION*/} - + ); }; diff --git a/src/app/(dashboard)/parent/page.tsx b/src/app/(dashboard)/parent/page.tsx index 4f93729a9..b8d7cb0d3 100644 --- a/src/app/(dashboard)/parent/page.tsx +++ b/src/app/(dashboard)/parent/page.tsx @@ -1,17 +1,29 @@ import Announcements from "@/components/Announcements"; -import BigCalendar from "@/components/BigCalendar"; +import BigCalendarContainer from "@/components/BigCalendarContainer"; +import prisma from "@/lib/prisma"; +import { auth } from "@clerk/nextjs/server"; import React from "react"; -const ParentPage = () => { +const ParentPage = async () => { + const { userId } = await auth(); + const currentUserId = userId; + + const students = await prisma.student.findMany({ + where: { + parentId: currentUserId!, + }, + }); return (
{/*LEFT*/} -
+ {students.map((student) => ( +
-

Schedule (John Doe)

- +

Schedule ({student.name + " " + student.surname})

+
+ ))} {/*RIGHT*/}
diff --git a/src/app/(dashboard)/student/page.tsx b/src/app/(dashboard)/student/page.tsx index ff427a01e..88adae6e5 100644 --- a/src/app/(dashboard)/student/page.tsx +++ b/src/app/(dashboard)/student/page.tsx @@ -1,16 +1,25 @@ import Announcements from "@/components/Announcements"; -import BigCalendar from "@/components/BigCalendar"; +import BigCalendarContainer from "@/components/BigCalendarContainer"; import EventCalendar from "@/components/EventCalendar"; +import prisma from "@/lib/prisma"; +import { auth } from "@clerk/nextjs/server"; import React from "react"; -const StudentPage = () => { +const StudentPage = async () => { + const { userId } = await auth(); + const classItem = await prisma.class.findMany({ + where: { + students:{some:{id:userId!}}, + } + }) + return (
{/*LEFT*/}

Schedule (4A)

- +
{/*RIGHT*/} diff --git a/src/app/(dashboard)/teacher/page.tsx b/src/app/(dashboard)/teacher/page.tsx index d02b726b4..9aaed4640 100644 --- a/src/app/(dashboard)/teacher/page.tsx +++ b/src/app/(dashboard)/teacher/page.tsx @@ -1,15 +1,17 @@ import Announcements from "@/components/Announcements"; -import BigCalendar from "@/components/BigCalendar"; +import BigCalendarContainer from "@/components/BigCalendarContainer"; +import { auth } from "@clerk/nextjs/server"; import React from "react"; -const TeacherPage = () => { +const TeacherPage = async() => { + const { userId } = await auth(); return (
{/*LEFT*/}

Schedule

- +
{/*RIGHT*/} diff --git a/src/app/[[...sign-in]]/page.tsx b/src/app/[[...sign-in]]/page.tsx new file mode 100644 index 000000000..49f932a49 --- /dev/null +++ b/src/app/[[...sign-in]]/page.tsx @@ -0,0 +1,48 @@ +'use client' + +import { role } from '@/lib/data' +import * as Clerk from '@clerk/elements/common' +import * as SignIn from '@clerk/elements/sign-in' +import { useUser } from '@clerk/nextjs' +import Image from 'next/image' +import { useRouter } from 'next/navigation' +import { useEffect } from 'react' + +const LoginPage = () => { + const { isLoaded, isSignedIn, user } = useUser(); + const router = useRouter(); + useEffect(() => { + const role = user?.publicMetadata.role; + if (role) { + router.push(`${role}`) + } + },[user, router]) + return ( +
+ + +

+ + School +

+

Sign in to your account

+ + + + Username + + + + + Password + + + + Sign in +
+
+
+ ) +} + +export default LoginPage \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 44c71f8db..3f6cf379d 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,9 @@ import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; +import { ClerkProvider } from "@clerk/nextjs"; +import { ToastContainer } from "react-toastify"; +import 'react-toastify/dist/ReactToastify.css'; const inter = Inter({ subsets: ["latin"] }); @@ -15,8 +18,13 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( + - {children} - + + {children} + + + + ); } diff --git a/src/app/page.tsx b/src/app/page.tsx deleted file mode 100644 index 3d365a496..000000000 --- a/src/app/page.tsx +++ /dev/null @@ -1,7 +0,0 @@ -const Homepage = () => { - return ( -
Homepage
- ) -} - -export default Homepage \ No newline at end of file diff --git a/src/app/sign-in/page.tsx b/src/app/sign-in/page.tsx deleted file mode 100644 index 5ffb1de56..000000000 --- a/src/app/sign-in/page.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from "react"; - -const LoginPage = () => { - return
LoginPage
; -}; - -export default LoginPage; diff --git a/src/components/Announcements.tsx b/src/components/Announcements.tsx index 611559376..a7076376f 100644 --- a/src/components/Announcements.tsx +++ b/src/components/Announcements.tsx @@ -1,46 +1,67 @@ +import prisma from "@/lib/prisma"; +import { auth } from "@clerk/nextjs/server"; import React from "react"; -const Announcements = () => { +const Announcements = async () => { + const { userId, sessionClaims } = await auth(); + const role = (sessionClaims?.metadata as { role?: string })?.role; + const roleConditions = { + teacher: { lessons: { some: { teacherId: userId! } } }, + student: { students: {some:{id:userId!}}}, + parent: { students: {some:{parentId:userId!}}}, + }; + + const data = await prisma.announcement.findMany({ + take: 3, + orderBy:{date:"desc"}, + where: { + ...(role !== "admin" && { + OR: + [{ classId: null }, + { class: roleConditions[role as keyof typeof roleConditions] || {} }] + }) + }, + }) return (

Announcements

View All
-
-
+
+ {data[0] &&
-

Lorem ipsum dolor sit amet

+

{data[0].title}

- 2025-01-01 + {new Intl.DateTimeFormat("en-GB").format(data[0].date)}

- Lorem ipsum, dolor sit amet consectetur adipisicing elit. Velit, ad. + {data[0].description}

-
-
+
} + {data[1] &&
-

Lorem ipsum dolor sit amet

+

{data[1].title}

- 2025-01-01 + {new Intl.DateTimeFormat("en-GB").format(data[1].date)}

- Lorem ipsum, dolor sit amet consectetur adipisicing elit. Velit, ad. + {data[1].description}

-
-
+
} + {data[2] &&
-

Lorem ipsum dolor sit amet

+

{data[2].title}

- 2025-01-01 + {new Intl.DateTimeFormat("en-GB").format(data[2].date)}

- Lorem ipsum, dolor sit amet consectetur adipisicing elit. Velit, ad. + {data[2].description}

-
+
}
); diff --git a/src/components/AttendanceChart.tsx b/src/components/AttendanceChart.tsx index 3ffe9e149..27ff3644e 100644 --- a/src/components/AttendanceChart.tsx +++ b/src/components/AttendanceChart.tsx @@ -5,7 +5,6 @@ import React from "react"; import { BarChart, Bar, - Rectangle, XAxis, YAxis, CartesianGrid, @@ -14,40 +13,9 @@ import { ResponsiveContainer, } from "recharts"; -const data = [ - { - name: "Mon", - present: 60, - absent: 40, - }, - { - name: "Tue", - present: 70, - absent: 60, - }, - { - name: "Wed", - present: 90, - absent: 75, - }, - { - name: "Thu", - present: 90, - absent: 75, - }, - { - name: "Fri", - present: 65, - absent: 55, - }, -]; -const AttendanceChart = () => { +const AttendanceChart = ({data}:{data:{name:string, present:number, absent:number}[]}) => { return ( -
-
-

Attendance

- -
+ @@ -80,7 +48,6 @@ const AttendanceChart = () => { /> -
); }; diff --git a/src/components/AttendanceChartContainer.tsx b/src/components/AttendanceChartContainer.tsx new file mode 100644 index 000000000..40c159c1d --- /dev/null +++ b/src/components/AttendanceChartContainer.tsx @@ -0,0 +1,63 @@ +import React from 'react' +import AttendanceChart from './AttendanceChart' +import Image from 'next/image' +import prisma from '@/lib/prisma'; + +const AttendanceChartContainer = async () => { + const today = new Date(); + const dayOfWeek = today.getDay(); + const daysSinceMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1; + const lastMonday = new Date(today); + lastMonday.setDate(today.getDate() - daysSinceMonday); + + const resData = await prisma.attendance.findMany({ + where: { + date: { + gte: lastMonday, + }, + }, + select: { + date: true, + present: true, + }, + }); + + const daysOfWeek = ["Mon", "Tue", "Wed", "Thu", "Fri"]; + const attendanceMap: { [key: string]: { present: number; absent:number}} = { + Mon: { present: 0, absent: 0 }, + Tue: { present: 0, absent: 0 }, + Wed: { present: 0, absent: 0 }, + Thu: { present: 0, absent: 0 }, + Fri: { present: 0, absent: 0 }, + }; + + resData.forEach(item => { + const itemDate = new Date(item.date); + if (dayOfWeek >= 1 && dayOfWeek <= 5) { + const dayDame = daysOfWeek[dayOfWeek - 1]; + + if (item.present) { + attendanceMap[dayDame].present += 1; + } else { + attendanceMap[dayDame].absent += 1; + } + } + }) + + const data = daysOfWeek.map((day) => ({ + name: day, + present: attendanceMap[day].present, + absent: attendanceMap[day].absent, + })); + return ( +
+
+

Attendance

+ +
+ +
+ ) +} + +export default AttendanceChartContainer diff --git a/src/components/BigCalendar.tsx b/src/components/BigCalendar.tsx index 4d371d028..cd50101c9 100644 --- a/src/components/BigCalendar.tsx +++ b/src/components/BigCalendar.tsx @@ -1,32 +1,37 @@ "use client"; + import { Calendar, momentLocalizer, View, Views } from "react-big-calendar"; -import { calendarEvents } from "@/lib/data"; -import "react-big-calendar/lib/css/react-big-calendar.css"; import moment from "moment"; +import "react-big-calendar/lib/css/react-big-calendar.css"; import { useState } from "react"; const localizer = momentLocalizer(moment); -const BigCalendar = () => { +const BigCalendar = ({ + data, +}: { + data: { title: string; start: Date; end: Date }[]; +}) => { const [view, setView] = useState(Views.WORK_WEEK); const handleOnChangeView = (selectedView: View) => { setView(selectedView); }; + return ( ); }; -export default BigCalendar; +export default BigCalendar; \ No newline at end of file diff --git a/src/components/BigCalendarContainer.tsx b/src/components/BigCalendarContainer.tsx new file mode 100644 index 000000000..0b27e50d1 --- /dev/null +++ b/src/components/BigCalendarContainer.tsx @@ -0,0 +1,35 @@ +import prisma from "@/lib/prisma"; +import { adjustScheduleToCurrentWeek } from "@/lib/utils"; +import BigCalendar from "./BigCalendar"; + +const BigCalendarContainer = async ({ + type, + id, +}: { + type: "teacherId" | "classId"; + id: string | number; +}) => { + const dataRes = await prisma.lesson.findMany({ + where: { + ...(type === "teacherId" + ? { teacherId: id as string } + : { classId: id as number }), + }, + }); + + const data = dataRes.map((lesson) => ({ + title: lesson.name, + start: lesson.startTime, + end: lesson.endTime, + })); + + const schedule = adjustScheduleToCurrentWeek(data); + + return ( +
+ +
+ ); +}; + +export default BigCalendarContainer; \ No newline at end of file diff --git a/src/components/CountChart.tsx b/src/components/CountChart.tsx index 058febe1a..c4b12630b 100644 --- a/src/components/CountChart.tsx +++ b/src/components/CountChart.tsx @@ -4,38 +4,31 @@ import React from "react"; import { RadialBarChart, RadialBar, - Legend, ResponsiveContainer, } from "recharts"; -const data = [ - { - name: "Total", - count: 106, - fill: "white", - }, - - { - name: "Girls", - count: 53, - fill: "#FAE27C", - }, - { - name: "Boys", - count: 53, - fill: "#C3EBFA", - }, -]; -const CountChart = () => { +const CountChart = ({ boys, girls }: { boys: number; girls:number}) => { + const data = [ + { + name: "Total", + count: boys + girls, + fill: "white", + }, + + { + name: "Girls", + count: girls, + fill: "#FAE27C", + }, + { + name: "Boys", + count: boys, + fill: "#C3EBFA", + }, + ]; return ( -
- {/*Title*/} -
-

Students

- -
- {/*Chart*/} -
+
+ { width={50} height={50} className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2" - /> -
-
-
-
-

1,234

-

Boys (55%)

-
-
-
-

1,234

-

Girls (45%)

-
-
-
+ /> +
+ + ); }; diff --git a/src/components/CountChartContainer.tsx b/src/components/CountChartContainer.tsx new file mode 100644 index 000000000..02cd63ecb --- /dev/null +++ b/src/components/CountChartContainer.tsx @@ -0,0 +1,39 @@ +import React from 'react' +import CountChart from './CountChart' +import Image from 'next/image' +import prisma from '@/lib/prisma' + +const CountChartContainer = async () => { + const data = await prisma.student.groupBy({ + by: ["sex"], + _count: true, + }); + const boys = data.find((d) => d.sex === "MALE")?._count || 0; + const girls = data.find((d) => d.sex === "FEMALE")?._count || 0; + + return ( +
+ {/*Title*/} +
+

Students

+ +
+ {/*Chart*/} + +
+
+
+

{boys}

+

Boys ({Math.round((boys/(boys+girls))*100)}%)

+
+
+
+

{girls}

+

Girls ({Math.round((girls/(boys+girls))*100)}%)

+
+
+
+ ) +} + +export default CountChartContainer diff --git a/src/components/EventCalendar.tsx b/src/components/EventCalendar.tsx index d44e5a4fd..141a55bee 100644 --- a/src/components/EventCalendar.tsx +++ b/src/components/EventCalendar.tsx @@ -1,62 +1,23 @@ "use client"; -import Image from "next/image"; -import React, { useState } from "react"; +import { useRouter } from "next/navigation"; +import React, { useEffect, useState } from "react"; import Calendar from "react-calendar"; import "react-calendar/dist/Calendar.css"; -import moment from "moment"; type ValuePiece = Date | null; type Value = ValuePiece | [ValuePiece, ValuePiece]; -const events = [ - { - id: 1, - title: "Lorem ipsum dolor sit amet.", - time: "12:00 PM - 2:00 PM", - description: - "Lorem ipsum dolor sit, amet consectetur adipisicing elit. Eius, aut!", - }, - { - id: 2, - title: "Lorem ipsum dolor sit amet.", - time: "12:00 PM - 2:00 PM", - description: - "Lorem ipsum dolor sit, amet consectetur adipisicing elit. Eius, aut!", - }, - { - id: 3, - title: "Lorem ipsum dolor sit amet.", - time: "12:00 PM - 2:00 PM", - description: - "Lorem ipsum dolor sit, amet consectetur adipisicing elit. Eius, aut!", - }, -]; + const EventCalendar = () => { const [value, onChange] = useState(new Date()); - return ( -
- -
-

Events

- -
-
- {events.map((event) => ( -
-
-

{event.title}

- {event.time} -
-

{event.description}

-
- ))} -
-
- ); + const router = useRouter(); + useEffect(() => { + if (value instanceof Date) { + router.push(`?date=${value}`) + } + },[value, router]) + return }; export default EventCalendar; diff --git a/src/components/EventCalendarContainer.tsx b/src/components/EventCalendarContainer.tsx new file mode 100644 index 000000000..d0e5faab7 --- /dev/null +++ b/src/components/EventCalendarContainer.tsx @@ -0,0 +1,23 @@ +import Image from 'next/image' +import React from 'react' +import EventList from './EventList' +import EventCalendar from './EventCalendar' + +const EventCalendarContainer = async ({ searchParams }: { searchParams: { [keys: string]: string | undefined } }) => { + const { date } = searchParams; + return ( +
+ +
+

Events

+ +
+
+ +
+
+ + ) +} + +export default EventCalendarContainer diff --git a/src/components/EventList.tsx b/src/components/EventList.tsx new file mode 100644 index 000000000..cde74deaf --- /dev/null +++ b/src/components/EventList.tsx @@ -0,0 +1,33 @@ +import prisma from '@/lib/prisma'; +import React from 'react' + +const EventList = async ({ dateParam }: { dateParam: string | undefined }) => { + const date = dateParam ? new Date(dateParam) : new Date(); + + const data = await prisma.event.findMany({ + where: { + startTime: { + gte: new Date(date.setHours(0, 0, 0, 0)), + lte:new Date(date.setHours(23,59,59,999)), + } + } + }) + return data.map((event) => ( +
+
+

{event.title}

+ {event.startTime.toLocaleTimeString("en-UK", { + hour: "2-digit", + minute: "2-digit", + hour12:false, + })} +
+

{event.description}

+
+ )); +} + +export default EventList diff --git a/src/components/FormContainer.tsx b/src/components/FormContainer.tsx new file mode 100644 index 000000000..4b5899019 --- /dev/null +++ b/src/components/FormContainer.tsx @@ -0,0 +1,117 @@ +import prisma from "@/lib/prisma"; +import FormModal from "./FormModal"; +import { auth } from "@clerk/nextjs/server"; + +export type FormContainerProps = { + table: + | "teacher" + | "student" + | "parent" + | "subject" + | "class" + | "lesson" + | "exam" + | "assignment" + | "result" + | "attendance" + | "event" + | "announcement"; + type: "create" | "update" | "delete"; + data?: any; + id?: number | string; +}; + +const FormContainer = async ({ table, type, data, id }: FormContainerProps) => { + let relatedData = {}; + + const { userId, sessionClaims } = await auth(); + const role = (sessionClaims?.metadata as { role?: string })?.role; + const currentUserId = userId; + + if (type !== "delete") { + switch (table) { + case "subject": + const subjectTeachers = await prisma.teacher.findMany({ + select: { id: true, name: true, surname: true }, + }); + relatedData = { teachers: subjectTeachers }; + break; + case "class": + const classGrades = await prisma.grade.findMany({ + select: { id: true, level: true }, + }); + const classTeachers = await prisma.teacher.findMany({ + select: { id: true, name: true, surname: true }, + }); + relatedData = { teachers: classTeachers, grades: classGrades }; + break; + case "teacher": + const teacherSubjects = await prisma.subject.findMany({ + select: { id: true, name: true }, + }); + relatedData = { subjects: teacherSubjects}; + break; + case "student": + const studentGrades = await prisma.grade.findMany({ + select: { id: true, level: true }, + }); + const studentClasses = await prisma.class.findMany({ + include:{_count:{select:{students:true}}}, + }); + relatedData = { classes: studentClasses, grades:studentGrades}; + break; + case "exam": + + const examLessons = await prisma.lesson.findMany({ + where: { + ...(role === "teacher" ? { teacherId: currentUserId! } : {}), + }, + select: { id: true, name: true }, + }); + relatedData = { lessons: examLessons }; + break; + case "lesson": + const lessonSubjects = await prisma.subject.findMany({ + select: { id: true, name: true }, + }); + const lessonClasses = await prisma.class.findMany({ + select: { id: true, name: true }, + }); + const lessonTeachers = await prisma.teacher.findMany({ + select: { id: true, name: true, surname:true}, + }); + relatedData = { subjects: lessonSubjects, classes: lessonClasses, teachers: lessonTeachers}; + break; + case "assignment": + const assignmentLessons = await prisma.lesson.findMany({ + select: { id: true, name: true }, + }); + + relatedData = { lessons: assignmentLessons}; + break; + case "event": + const eventClasses = await prisma.class.findMany({ + select: { id: true, name: true }, + }); + + relatedData = { classes: eventClasses}; + break; + default: + break; + } + } + + return ( +
+ +
+ ); +}; + +export default FormContainer; \ No newline at end of file diff --git a/src/components/FormModal.tsx b/src/components/FormModal.tsx index c3eaf6fe3..fc730aeb3 100644 --- a/src/components/FormModal.tsx +++ b/src/components/FormModal.tsx @@ -1,10 +1,28 @@ "use client"; +import { deleteAssignment, deleteClass, deleteEvent, deleteExam, deleteLesson, deleteParent, deleteStudent, deleteSubject, deleteTeacher } from "@/lib/actions"; import dynamic from "next/dynamic"; import Image from "next/image"; -import React, { FormEvent, useState } from "react"; -// import TeacherForm from "./forms/TeacherForm"; -// import StudentForm from "./forms/StudentForm"; +import { useRouter } from "next/navigation"; +import React, { Dispatch, SetStateAction, useEffect, useState } from "react"; +import { useFormState } from "react-dom"; +import { toast } from "react-toastify"; +import { FormContainerProps } from "./FormContainer"; + +const deleteActionMap = { + subject: deleteSubject, + class: deleteClass, + teacher: deleteTeacher, + student: deleteStudent, + exam: deleteExam, + parent: deleteParent, + lesson: deleteLesson, + assignment: deleteAssignment, + result: deleteSubject, + attendance: deleteSubject, + event: deleteEvent, + announcement: deleteSubject, +}; const TeacherForm = dynamic(() => import("./forms/TeacherForm"), { loading: () =>

Loading...

, @@ -19,7 +37,7 @@ const ParentForm = dynamic(() => import("./forms/ParentForm "), { const ClassForm = dynamic(() => import("./forms/ClassForm"), { loading: () =>

Loading...

, }); -const SubjectForm = dynamic(() => import("./forms/StudentForm"), { +const SubjectForm = dynamic(() => import("./forms/SubjectForm"), { loading: () =>

Loading...

, }); const LessonForm = dynamic(() => import("./forms/LessonForm"), { @@ -44,44 +62,102 @@ const AnnouncementForm = dynamic(() => import("./forms/AnnouncementForm"), { loading: () =>

Loading...

, }); const forms: { - [key: string]: (type: "create" | "update", data?: any) => JSX.Element; + [key: string]: ( + setOpen: Dispatch>, + type: "create" | "update", + data?: any, + relatedData?:any + ) => JSX.Element; } = { - teacher: (type, data) => , - student: (type, data) => , - parent: (type, data) => , - subject: (type, data) => , - class: (type, data) => , - lesson: (type, data) => , - exam: (type, data) => , - assignment: (type, data) => , - result: (type, data) => , - attendance: (type, data) => , - event: (type, data) => , - announcement: (type, data) => , + subject: (setOpen, type, data, relatedData) => ( + + ), + class: (setOpen, type, data, relatedData) => ( + + ), + teacher: (setOpen, type, data, relatedData) => ( + + ), + student: (setOpen, type, data, relatedData) => ( + + ), + exam: (setOpen, type, data, relatedData) => ( + + //TODO OTHER LIST ITEMS + ), + parent: (setOpen, type, data, relatedData) => ( + + ), + lesson: (setOpen, type, data, relatedData) => ( + + ), + assignment: (setOpen, type, data, relatedData) => ( + + ), + event: (setOpen, type, data, relatedData) => ( + + ) }; const FormModal = ({ table, type, data, id, -}: { - table: - | "teacher" - | "student" - | "parent" - | "subject" - | "class" - | "lesson" - | "exam" - | "assignment" - | "result" - | "attendance" - | "event" - | "announcement"; - type: "create" | "update" | "delete"; - data?: any; - id?: number; -}) => { + relatedData +}: FormContainerProps & {relatedData?:any}) => { const size = type === "create" ? "w-8 h-8" : "w-7 h-7"; const bgColor = type === "create" @@ -91,10 +167,20 @@ const FormModal = ({ : "bg-lamaPurple"; const [open, setOpen] = useState(false); - const Form = () => { + + const [state, formAction] = useFormState(deleteActionMap[table], { success: false, error: false }); + const router = useRouter(); + useEffect(() => { + if (state.success) { + toast(`${table} has been deleted`); + setOpen(false); + router.refresh(); + } + }, [state, router]) return type === "delete" && id ? ( -
+ + All data will be lost. Are you sure you want to delete this {table}? @@ -103,9 +189,9 @@ const FormModal = ({ ) : type === "create" || type === "update" ? ( - forms[table](type, data) + forms[table](setOpen, type, data, relatedData) ) : ( - "Form not found" + "Form not found!" ); }; return ( diff --git a/src/components/InputFiels.tsx b/src/components/InputField.tsx similarity index 82% rename from src/components/InputFiels.tsx rename to src/components/InputField.tsx index 0471a8008..845213961 100644 --- a/src/components/InputFiels.tsx +++ b/src/components/InputField.tsx @@ -8,20 +8,22 @@ type InputFieldProps = { name: string; defaultValue?: string; error?: FieldError; + hidden?: boolean; inputProps?: React.InputHTMLAttributes; }; -const InputFiels = ({ +const InputField = ({ label, type = "text", register, name, defaultValue, error, - inputProps, + hidden, + inputProps }: InputFieldProps) => { return ( -
+
{ +const Menu = async() => { + const user = await currentUser(); + const role = user?.publicMetadata.role as string; + return (
{menuItems.map((i) => ( diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 40a76f470..8a384083a 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -1,7 +1,10 @@ +import { UserButton } from "@clerk/nextjs"; +import { currentUser } from "@clerk/nextjs/server"; import Image from "next/image"; import React from "react"; -const Navbar = () => { +const Navbar = async () => { + const user = await currentUser(); return (
@@ -24,15 +27,16 @@ const Navbar = () => {
John Doe - Admin + {user?.publicMetadata.role as string}
- + /> */} +
); diff --git a/src/components/Pagination.tsx b/src/components/Pagination.tsx index ab45e9486..205ab7e01 100644 --- a/src/components/Pagination.tsx +++ b/src/components/Pagination.tsx @@ -1,22 +1,48 @@ +"use client" + +import { ITEM_PER_PAGE } from "@/lib/settings"; +import { useRouter } from "next/navigation"; import React from "react"; -const Pagination = () => { +const Pagination = ({ page, count }: { page: number; count: number }) => { + const router = useRouter(); + + const hasPrev = ITEM_PER_PAGE * (page - 1) > 0; + const hasNext = ITEM_PER_PAGE * (page - 1) + ITEM_PER_PAGE < count; + + const changePage = (newPage: number) => { + const params = new URLSearchParams(window.location.search); + params.set("page", newPage.toString()); + router.push(`${window.location.pathname}?${params}`); + } return (
- - - - ... - + {Array.from({ length: Math.ceil(count / ITEM_PER_PAGE) }, (_, index) => { + const pageIndex = index + 1; + return ( + + + ) + })}
-
diff --git a/src/components/StudentAttendanceCard.tsx b/src/components/StudentAttendanceCard.tsx new file mode 100644 index 000000000..b5701f851 --- /dev/null +++ b/src/components/StudentAttendanceCard.tsx @@ -0,0 +1,25 @@ +import prisma from '@/lib/prisma' +import React from 'react' + +const StudentAttendanceCard = async ({ id }: { id: string }) => { + const attendance = await prisma.attendance.findMany({ + where: { + studentId: id, + date: { + gte: new Date(new Date().getFullYear(), 0, 1), + } + } + }); + + const totalDays = attendance.length; + const presentDays = attendance.filter(day => day.present).length; + const percentage = (presentDays / totalDays) * 100; + return ( +
+

{percentage || "-"}

+ Attendance +
+ ) +} + +export default StudentAttendanceCard diff --git a/src/components/TableSearch.tsx b/src/components/TableSearch.tsx index e065b6d73..600566971 100644 --- a/src/components/TableSearch.tsx +++ b/src/components/TableSearch.tsx @@ -1,16 +1,28 @@ +"use client" import Image from "next/image"; +import { useRouter } from "next/navigation"; import React from "react"; const TableSearch = () => { + const router = useRouter(); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + const value = (e.currentTarget[0] as HTMLInputElement).value + const params = new URLSearchParams(window.location.search); + params.set("search", value); + router.push(`${window.location.pathname}?${params}`); + } return ( -
+
-
+ ); }; diff --git a/src/components/UserCard.tsx b/src/components/UserCard.tsx index 65ec2ed01..ef27d6565 100644 --- a/src/components/UserCard.tsx +++ b/src/components/UserCard.tsx @@ -1,7 +1,18 @@ +import prisma from "@/lib/prisma"; import Image from "next/image"; import React from "react"; -const UserCard = ({ type }: { type: string }) => { +const UserCard = async ({ type }: { type: "admin" | "teacher" | "student" | "parent" }) => { + + const modelMap:Record = { + admin: prisma.admin, + teacher: prisma.teacher, + student: prisma.student, + parent:prisma.parent, + } + + const data = await modelMap[type].count(); + console.log(data); return (
@@ -10,7 +21,7 @@ const UserCard = ({ type }: { type: string }) => {
-

1,234

+

{data}

{type}s

); diff --git a/src/components/forms/AnnouncementForm.tsx b/src/components/forms/AnnouncementForm.tsx index b1d95c9d9..2b0b5738b 100644 --- a/src/components/forms/AnnouncementForm.tsx +++ b/src/components/forms/AnnouncementForm.tsx @@ -4,7 +4,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import React from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; -import InputFiels from "../InputFiels"; +import InputField from "../InputField"; import Image from "next/image"; const schema = z.object({ @@ -53,14 +53,14 @@ const AnnouncementForm = ({ Authentication Information
- - -
- - - - - - ; +import { + AssignmentSchema, +} from "@/lib/formValidationSchemas"; +import { + createAssignment, + updateAssignment, +} from "@/lib/actions"; +import { useFormState } from "react-dom"; +import { Dispatch, SetStateAction, useEffect } from "react"; +import { toast } from "react-toastify"; +import { useRouter } from "next/navigation"; +import InputField from "../InputField"; const AssignmentForm = ({ type, data, + setOpen, + relatedData, }: { type: "create" | "update"; data?: any; + setOpen: Dispatch>; + relatedData?: any; }) => { const { register, handleSubmit, formState: { errors }, - } = useForm({ resolver: zodResolver(schema) }); + } = useForm({ + resolver: zodResolver(AssignmentSchema), + }); + + // AFTER REACT 19 IT'LL BE USEACTIONSTATE + + const [state, formAction] = useFormState( + type === "create" ? createAssignment : updateAssignment, + { + success: false, + error: false, + } + ); const onSubmit = handleSubmit((data) => { - console.log(data); + formAction(data); }); + const router = useRouter(); + + useEffect(() => { + if (state.success) { + toast(`Assignment has been ${type === "create" ? "created" : "updated"}!`); + setOpen(false); + router.refresh(); + } + }, [state, router, type, setOpen]); + + const { lessons} = relatedData; + return (
-

Create a new teacher

- - Authentication Information - -
- - - -
- - Personal Information - -
- - +

+ {type === "create" ? "Create a new assignment" : "Update the assignment"} +

- + - - - - - - - + {data && ( +
+ {state.error && ( + Something went wrong! + )} @@ -168,4 +130,4 @@ const AssignmentForm = ({ ); }; -export default AssignmentForm; +export default AssignmentForm; \ No newline at end of file diff --git a/src/components/forms/AttendanceForm.tsx b/src/components/forms/AttendanceForm.tsx index ed3d4cbdb..f3ba7efc1 100644 --- a/src/components/forms/AttendanceForm.tsx +++ b/src/components/forms/AttendanceForm.tsx @@ -4,8 +4,8 @@ import { zodResolver } from "@hookform/resolvers/zod"; import React from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; -import InputFiels from "../InputFiels"; import Image from "next/image"; +import InputField from "../InputField"; const schema = z.object({ username: z @@ -53,14 +53,14 @@ const AttendanceForm = ({ Authentication Information
- - -
- - - - - - ; const ClassForm = ({ type, data, + setOpen, + relatedData }: { type: "create" | "update"; - data?: any; + data?: any; + setOpen: Dispatch>; + relatedData: any; }) => { const { register, handleSubmit, formState: { errors }, - } = useForm({ resolver: zodResolver(schema) }); + } = useForm({ + resolver: zodResolver(ClassSchema), + }); + + //AFTER REACT 19 IT'LL BE USEACTIONSTATE + const [state, formAction] = useFormState(type==="create" ? createClass : updateClass, {success:false, error:false}) const onSubmit = handleSubmit((data) => { - console.log(data); + formAction(data); }); + + const router = useRouter(); + useEffect(() => { + if (state.success) { + toast(`Class has been ${type === "create" ? "created" : "updated"}`); + setOpen(false); + router.refresh(); + } + }, [state, router, type, setOpen]) + + const { teachers, grades } = relatedData; return ( -

Create a new teacher

- - Authentication Information - +

{type === "create" ? "Create a new class" : "Update the class"}

+
- - - -
- - Personal Information - -
- - - - - - - - + - - + {data && + - -
- - - - + {teachers.map( + (teacher: { id: string; name: string; surname: string }) => ( + + ) + )} - {errors.sex?.message && ( + {errors.supervisorId?.message && (

- {errors.sex.message.toString()} + {errors.supervisorId.message.toString()}

)} -
-
-
+
+ +
+ {state.error && Something went wrong!} diff --git a/src/components/forms/EventForm.tsx b/src/components/forms/EventForm.tsx index 530d9e9ab..5d7f9fba1 100644 --- a/src/components/forms/EventForm.tsx +++ b/src/components/forms/EventForm.tsx @@ -1,166 +1,135 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; -import React from "react"; import { useForm } from "react-hook-form"; -import { z } from "zod"; -import InputFiels from "../InputFiels"; -import Image from "next/image"; - -const schema = z.object({ - username: z - .string() - .min(3, { message: "Username must be at least 3 characters long!" }) - .max(20, { message: "Username must be at most 20 characters long!" }), - age: z.string().email({ message: "Invalid email address!" }), - password: z - .string() - .min(8, { message: "Password must be at least 8 characters long!" }), - firstName: z.string().min(1, { message: "First name is required!" }), - lastName: z.string().min(1, { message: "Last name is required!" }), - phone: z.string().min(1, { message: "Phone is required!" }), - address: z.string().min(1, { message: "Address is required!" }), - bloodType: z.string().min(1, { message: "Blood Type is required!" }), - - birthday: z.date({ message: "Birthday is required!" }), - sex: z.enum(["male", "female"], { message: "Sex is required!" }), - img: z.instanceof(File, { message: "Image is reauired!" }), -}); - -type Inputs = z.infer; +import { + EventSchema, +} from "@/lib/formValidationSchemas"; +import { + createEvent, + updateEvent, +} from "@/lib/actions"; +import { useFormState } from "react-dom"; +import { Dispatch, SetStateAction, useEffect } from "react"; +import { toast } from "react-toastify"; +import { useRouter } from "next/navigation"; +import InputField from "../InputField"; const EventForm = ({ type, data, + setOpen, + relatedData, }: { type: "create" | "update"; data?: any; + setOpen: Dispatch>; + relatedData?: any; }) => { const { register, handleSubmit, formState: { errors }, - } = useForm({ resolver: zodResolver(schema) }); + } = useForm({ + resolver: zodResolver(EventSchema), + }); + + // AFTER REACT 19 IT'LL BE USEACTIONSTATE + + const [state, formAction] = useFormState( + type === "create" ? createEvent : updateEvent, + { + success: false, + error: false, + } + ); const onSubmit = handleSubmit((data) => { - console.log(data); + formAction(data); }); + const router = useRouter(); + + useEffect(() => { + if (state.success) { + toast(`Event has been ${type === "create" ? "created" : "updated"}!`); + setOpen(false); + router.refresh(); + } + }, [state, router, type, setOpen]); + + const { classes } = relatedData; + return ( -

Create a new teacher

- - Authentication Information - -
- - - -
- - Personal Information - -
- - +

+ {type === "create" ? "Create a new event" : "Update the event"} +

- + - - - - - - - + {data && ( +
+ {state.error && ( + Something went wrong! + )} @@ -168,4 +137,4 @@ const EventForm = ({ ); }; -export default EventForm; +export default EventForm; \ No newline at end of file diff --git a/src/components/forms/ExamForm.tsx b/src/components/forms/ExamForm.tsx index dc4848af1..b6e194063 100644 --- a/src/components/forms/ExamForm.tsx +++ b/src/components/forms/ExamForm.tsx @@ -1,166 +1,128 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; -import React from "react"; import { useForm } from "react-hook-form"; -import { z } from "zod"; -import InputFiels from "../InputFiels"; -import Image from "next/image"; - -const schema = z.object({ - username: z - .string() - .min(3, { message: "Username must be at least 3 characters long!" }) - .max(20, { message: "Username must be at most 20 characters long!" }), - age: z.string().email({ message: "Invalid email address!" }), - password: z - .string() - .min(8, { message: "Password must be at least 8 characters long!" }), - firstName: z.string().min(1, { message: "First name is required!" }), - lastName: z.string().min(1, { message: "Last name is required!" }), - phone: z.string().min(1, { message: "Phone is required!" }), - address: z.string().min(1, { message: "Address is required!" }), - bloodType: z.string().min(1, { message: "Blood Type is required!" }), - - birthday: z.date({ message: "Birthday is required!" }), - sex: z.enum(["male", "female"], { message: "Sex is required!" }), - img: z.instanceof(File, { message: "Image is reauired!" }), -}); - -type Inputs = z.infer; +import { + ExamSchema, +} from "@/lib/formValidationSchemas"; +import { + createExam, + updateExam, +} from "@/lib/actions"; +import { useFormState } from "react-dom"; +import { Dispatch, SetStateAction, useEffect } from "react"; +import { toast } from "react-toastify"; +import { useRouter } from "next/navigation"; +import InputField from "../InputField"; const ExamForm = ({ type, data, + setOpen, + relatedData, }: { type: "create" | "update"; data?: any; + setOpen: Dispatch>; + relatedData?: any; }) => { const { register, handleSubmit, formState: { errors }, - } = useForm({ resolver: zodResolver(schema) }); + } = useForm({ + resolver: zodResolver(ExamSchema), + }); + + // AFTER REACT 19 IT'LL BE USEACTIONSTATE + + const [state, formAction] = useFormState( + type === "create" ? createExam : updateExam, + { + success: false, + error: false, + } + ); const onSubmit = handleSubmit((data) => { - console.log(data); + formAction(data); }); + const router = useRouter(); + + useEffect(() => { + if (state.success) { + toast(`Exam has been ${type === "create" ? "created" : "updated"}!`); + setOpen(false); + router.refresh(); + } + }, [state, router, type, setOpen]); + + const { lessons } = relatedData; + return ( -

Create a new teacher

- - Authentication Information - -
- - - -
- - Personal Information - -
- - +

+ {type === "create" ? "Create a new exam" : "Update the exam"} +

- + - - - - - - - + {data && ( +
+ {state.error && ( + Something went wrong! + )} @@ -168,4 +130,4 @@ const ExamForm = ({ ); }; -export default ExamForm; +export default ExamForm; \ No newline at end of file diff --git a/src/components/forms/LessonForm.tsx b/src/components/forms/LessonForm.tsx index 74a668f58..b00f0bd6d 100644 --- a/src/components/forms/LessonForm.tsx +++ b/src/components/forms/LessonForm.tsx @@ -1,166 +1,187 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; -import React from "react"; import { useForm } from "react-hook-form"; -import { z } from "zod"; -import InputFiels from "../InputFiels"; -import Image from "next/image"; - -const schema = z.object({ - username: z - .string() - .min(3, { message: "Username must be at least 3 characters long!" }) - .max(20, { message: "Username must be at most 20 characters long!" }), - age: z.string().email({ message: "Invalid email address!" }), - password: z - .string() - .min(8, { message: "Password must be at least 8 characters long!" }), - firstName: z.string().min(1, { message: "First name is required!" }), - lastName: z.string().min(1, { message: "Last name is required!" }), - phone: z.string().min(1, { message: "Phone is required!" }), - address: z.string().min(1, { message: "Address is required!" }), - bloodType: z.string().min(1, { message: "Blood Type is required!" }), - - birthday: z.date({ message: "Birthday is required!" }), - sex: z.enum(["male", "female"], { message: "Sex is required!" }), - img: z.instanceof(File, { message: "Image is reauired!" }), -}); - -type Inputs = z.infer; +import { + LessonSchema, +} from "@/lib/formValidationSchemas"; +import { + createLesson, + updateLesson, +} from "@/lib/actions"; +import { useFormState } from "react-dom"; +import { Dispatch, SetStateAction, useEffect } from "react"; +import { toast } from "react-toastify"; +import { useRouter } from "next/navigation"; +import { DAY_OF_WEEK } from "@/lib/settings"; +import InputField from "../InputField"; const LessonForm = ({ type, data, + setOpen, + relatedData, }: { type: "create" | "update"; data?: any; + setOpen: Dispatch>; + relatedData?: any; }) => { const { register, handleSubmit, formState: { errors }, - } = useForm({ resolver: zodResolver(schema) }); + } = useForm({ + resolver: zodResolver(LessonSchema), + }); + + // AFTER REACT 19 IT'LL BE USEACTIONSTATE + + const [state, formAction] = useFormState( + type === "create" ? createLesson : updateLesson, + { + success: false, + error: false, + } + ); const onSubmit = handleSubmit((data) => { - console.log(data); + formAction(data); }); + const router = useRouter(); + + useEffect(() => { + if (state.success) { + toast(`Lesson has been ${type === "create" ? "created" : "updated"}!`); + setOpen(false); + router.refresh(); + } + }, [state, router, type, setOpen]); + + const { subjects, classes, teachers } = relatedData; + return ( -

Create a new teacher

- - Authentication Information - -
- - - -
- - Personal Information - -
- - - - +

+ {type === "create" ? "Create a new exam" : "Update the exam"} +

- + - - - - - + {data && ( +
+ {state.error && ( + Something went wrong! + )} @@ -168,4 +189,4 @@ const LessonForm = ({ ); }; -export default LessonForm; +export default LessonForm; \ No newline at end of file diff --git a/src/components/forms/ParentForm .tsx b/src/components/forms/ParentForm .tsx index 75f967715..ec077797a 100644 --- a/src/components/forms/ParentForm .tsx +++ b/src/components/forms/ParentForm .tsx @@ -1,166 +1,135 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; -import React from "react"; import { useForm } from "react-hook-form"; -import { z } from "zod"; -import InputFiels from "../InputFiels"; -import Image from "next/image"; - -const schema = z.object({ - username: z - .string() - .min(3, { message: "Username must be at least 3 characters long!" }) - .max(20, { message: "Username must be at most 20 characters long!" }), - age: z.string().email({ message: "Invalid email address!" }), - password: z - .string() - .min(8, { message: "Password must be at least 8 characters long!" }), - firstName: z.string().min(1, { message: "First name is required!" }), - lastName: z.string().min(1, { message: "Last name is required!" }), - phone: z.string().min(1, { message: "Phone is required!" }), - address: z.string().min(1, { message: "Address is required!" }), - bloodType: z.string().min(1, { message: "Blood Type is required!" }), - - birthday: z.date({ message: "Birthday is required!" }), - sex: z.enum(["male", "female"], { message: "Sex is required!" }), - img: z.instanceof(File, { message: "Image is reauired!" }), -}); - -type Inputs = z.infer; +import { Dispatch, SetStateAction, useEffect, useState } from "react"; +import {ParentSchema } from "@/lib/formValidationSchemas"; +import { useFormState } from "react-dom"; +import { createParent, updateParent } from "@/lib/actions"; +import { useRouter } from "next/navigation"; +import { toast } from "react-toastify"; +import InputField from "../InputField"; const ParentForm = ({ type, data, + setOpen, + relatedData, }: { type: "create" | "update"; data?: any; + setOpen: Dispatch>; + relatedData?: any; }) => { const { register, handleSubmit, formState: { errors }, - } = useForm({ resolver: zodResolver(schema) }); + } = useForm({ + resolver: zodResolver(ParentSchema), + }); + + const [state, formAction] = useFormState( + type === "create" ? createParent : updateParent, + { + success: false, + error: false, + } + ); const onSubmit = handleSubmit((data) => { - console.log(data); + formAction(data); }); + const router = useRouter(); + + useEffect(() => { + if (state.success) { + toast(`Parent has been ${type === "create" ? "created" : "updated"}!`); + setOpen(false); + router.refresh(); + } + }, [state, router, type, setOpen]); + + return ( -

Create a new teacher

+

+ {type === "create" ? "Create a new parent" : "Update the parent"} +

Authentication Information
- - -
Personal Information
- - - - - - - - - - - -
- - - {errors.sex?.message && ( -

- {errors.sex.message.toString()} -

- )} -
-
- - - {errors.sex?.message && ( -

- {errors.sex.message.toString()} -

- )} -
+ {data && ( +
+ {state.error && ( + Something went wrong! + )} @@ -168,4 +137,4 @@ const ParentForm = ({ ); }; -export default ParentForm; +export default ParentForm; \ No newline at end of file diff --git a/src/components/forms/ResultForm.tsx b/src/components/forms/ResultForm.tsx index f82dc6b53..a527b903f 100644 --- a/src/components/forms/ResultForm.tsx +++ b/src/components/forms/ResultForm.tsx @@ -4,8 +4,8 @@ import { zodResolver } from "@hookform/resolvers/zod"; import React from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; -import InputFiels from "../InputFiels"; import Image from "next/image"; +import InputField from "../InputField"; const schema = z.object({ username: z @@ -53,14 +53,14 @@ const ResultForm = ({ Authentication Information
- - -
- - - - - - ; +import { Dispatch, SetStateAction, useEffect, useState } from "react"; +import {StudentSchema } from "@/lib/formValidationSchemas"; +import { useFormState } from "react-dom"; +import { createStudent, updateStudent} from "@/lib/actions"; +import { useRouter } from "next/navigation"; +import { toast } from "react-toastify"; +import { CldUploadWidget } from "next-cloudinary"; +import InputField from "../InputField"; const StudentForm = ({ type, data, + setOpen, + relatedData, }: { type: "create" | "update"; data?: any; + setOpen: Dispatch>; + relatedData?: any; }) => { const { register, handleSubmit, formState: { errors }, - } = useForm({ resolver: zodResolver(schema) }); + } = useForm({ + resolver: zodResolver(StudentSchema), + }); + + const [img, setImg] = useState(); + + const [state, formAction] = useFormState( + type === "create" ? createStudent : updateStudent, + { + success: false, + error: false, + } + ); const onSubmit = handleSubmit((data) => { - console.log(data); + formAction({ ...data, img: img?.secure_url }); }); + const router = useRouter(); + + useEffect(() => { + if (state.success) { + toast(`Student has been ${type === "create" ? "created" : "updated"}!`); + setOpen(false); + router.refresh(); + } + }, [state, router, type, setOpen]); + + const { classes, grades } = relatedData; + return ( -

Create a new student

+

+ {type === "create" ? "Create a new student" : "Update the student"} +

Authentication Information
- - -
Personal Information + { + setImg(result.info); + widget.close(); + }} + > + {({ open }) => { + return ( +
open()} + > + + Upload a photo +
+ ); + }} +
- - - - - - - - - - - + + {data && ( +
+ {state.error && ( + Something went wrong! + )} @@ -168,4 +241,4 @@ const StudentForm = ({ ); }; -export default StudentForm; +export default StudentForm; \ No newline at end of file diff --git a/src/components/forms/SubjectForm.tsx b/src/components/forms/SubjectForm.tsx index d4ddd53e9..8d37a04da 100644 --- a/src/components/forms/SubjectForm.tsx +++ b/src/components/forms/SubjectForm.tsx @@ -1,166 +1,98 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; -import React from "react"; -import { useForm } from "react-hook-form"; -import { z } from "zod"; -import InputFiels from "../InputFiels"; -import Image from "next/image"; +import React, { Dispatch, SetStateAction, useEffect } from "react"; +import { useForm} from "react-hook-form"; +import { createSubject, updateSubject } from "@/lib/actions"; +import { useFormState } from "react-dom"; +import { SubjectSchema } from "@/lib/formValidationSchemas"; +import { toast } from "react-toastify"; +import { useRouter } from "next/navigation"; +import InputField from "../InputField"; -const schema = z.object({ - username: z - .string() - .min(3, { message: "Username must be at least 3 characters long!" }) - .max(20, { message: "Username must be at most 20 characters long!" }), - age: z.string().email({ message: "Invalid email address!" }), - password: z - .string() - .min(8, { message: "Password must be at least 8 characters long!" }), - firstName: z.string().min(1, { message: "First name is required!" }), - lastName: z.string().min(1, { message: "Last name is required!" }), - phone: z.string().min(1, { message: "Phone is required!" }), - address: z.string().min(1, { message: "Address is required!" }), - bloodType: z.string().min(1, { message: "Blood Type is required!" }), - - birthday: z.date({ message: "Birthday is required!" }), - sex: z.enum(["male", "female"], { message: "Sex is required!" }), - img: z.instanceof(File, { message: "Image is reauired!" }), -}); - -type Inputs = z.infer; const SubjectForm = ({ type, data, + setOpen, + relatedData }: { type: "create" | "update"; - data?: any; + data?: any; + setOpen: Dispatch>; + relatedData: any; }) => { const { register, handleSubmit, formState: { errors }, - } = useForm({ resolver: zodResolver(schema) }); + } = useForm({ resolver: zodResolver(SubjectSchema) }); + + const [state, formAction] = useFormState(type==="create" ? createSubject : updateSubject, {success:false, error:false}) const onSubmit = handleSubmit((data) => { - console.log(data); + formAction(data); }); + const router = useRouter(); + + useEffect(() => { + if (state.success) { + toast(`Subject has been ${type === "create" ? "created" : "updated"!}`); + setOpen(false); + router.refresh(); + } + }, [state, router, type, setOpen]) + + const { teachers } = relatedData; return ( -

Create a new teacher

- - Authentication Information - -
- - - -
- - Personal Information - +

{type === "create" ? "Create a new subject" : "Update the subject"}

+
- - + + {data && + - - - - - - - - - -
- - - - + {teachers.map( + (teacher: { id: string; name: string; surname: string }) => ( + + ) + )} - {errors.sex?.message && ( + {errors.teachers?.message && (

- {errors.sex.message.toString()} + {errors.teachers.message.toString()}

)} -
-
- - - {errors.sex?.message && ( -

- {errors.sex.message.toString()} -

- )} -
+ +
+ {state.error && Something went wrong!} diff --git a/src/components/forms/TeacherForm.tsx b/src/components/forms/TeacherForm.tsx index b608e74e2..59466e335 100644 --- a/src/components/forms/TeacherForm.tsx +++ b/src/components/forms/TeacherForm.tsx @@ -1,143 +1,160 @@ "use client"; import { zodResolver } from "@hookform/resolvers/zod"; -import React from "react"; import { useForm } from "react-hook-form"; -import { z } from "zod"; -import InputFiels from "../InputFiels"; import Image from "next/image"; - -const schema = z.object({ - username: z - .string() - .min(3, { message: "Username must be at least 3 characters long!" }) - .max(20, { message: "Username must be at most 20 characters long!" }), - age: z.string().email({ message: "Invalid email address!" }), - password: z - .string() - .min(8, { message: "Password must be at least 8 characters long!" }), - firstName: z.string().min(1, { message: "First name is required!" }), - lastName: z.string().min(1, { message: "Last name is required!" }), - phone: z.string().min(1, { message: "Phone is required!" }), - address: z.string().min(1, { message: "Address is required!" }), - bloodType: z.string().min(1, { message: "Blood Type is required!" }), - - birthday: z.date({ message: "Birthday is required!" }), - sex: z.enum(["male", "female"], { message: "Sex is required!" }), - img: z.instanceof(File, { message: "Image is reauired!" }), -}); - -type Inputs = z.infer; +import { Dispatch, SetStateAction, useEffect, useState } from "react"; +import {TeacherSchema } from "@/lib/formValidationSchemas"; +import { useFormState } from "react-dom"; +import { createTeacher, updateTeacher } from "@/lib/actions"; +import { useRouter } from "next/navigation"; +import { toast } from "react-toastify"; +import { CldUploadWidget } from "next-cloudinary"; +import InputField from "../InputField"; const TeacherForm = ({ type, data, + setOpen, + relatedData, }: { type: "create" | "update"; data?: any; + setOpen: Dispatch>; + relatedData?: any; }) => { const { register, handleSubmit, formState: { errors }, - } = useForm({ resolver: zodResolver(schema) }); + } = useForm({ + resolver: zodResolver(TeacherSchema), + }); + + const [img, setImg] = useState(); + + const [state, formAction] = useFormState( + type === "create" ? createTeacher : updateTeacher, + { + success: false, + error: false, + } + ); const onSubmit = handleSubmit((data) => { - console.log(data); + formAction({ ...data, img: img?.secure_url }); }); + const router = useRouter(); + + useEffect(() => { + if (state.success) { + toast(`Teacher has been ${type === "create" ? "created" : "updated"}!`); + setOpen(false); + router.refresh(); + } + }, [state, router, type, setOpen]); + + const { subjects } = relatedData; + return ( -

Create a new teacher

+

+ {type === "create" ? "Create a new teacher" : "Update the teacher"} +

Authentication Information
- - -
Personal Information
- - - - - - - - - - - + {data && ( +
+ {state.error && ( + Something went wrong! + )} @@ -168,4 +212,4 @@ const TeacherForm = ({ ); }; -export default TeacherForm; +export default TeacherForm; \ No newline at end of file diff --git a/src/lib/actions.ts b/src/lib/actions.ts new file mode 100644 index 000000000..7d6b74985 --- /dev/null +++ b/src/lib/actions.ts @@ -0,0 +1,800 @@ +"use server" + +import { revalidatePath } from "next/cache"; +import prisma from "./prisma"; +import { AssignmentSchema, ClassSchema, EventSchema, ExamSchema, LessonSchema, ParentSchema, StudentSchema, SubjectSchema, TeacherSchema } from "./formValidationSchemas"; +import { clerkClient } from "@clerk/clerk-sdk-node"; +import { getUserRole } from "./utils"; + +type CurrentState = { success: boolean; error: boolean }; + +export const createSubject = async (currentState: CurrentState, data: SubjectSchema) => { + try { + await prisma.subject.create({ + data: { + name: data.name, + teachers: { + connect:data.teachers.map((teacherId)=>({id:teacherId})), + } + }, + }); + + return {success:true, error:false} + + } catch (err) { + console.log(err); + return {success:false, error:true} + } +} + +export const updateSubject = async (currentState: CurrentState, data: SubjectSchema) => { + try { + await prisma.subject.update({ + where: { + id:data.id + }, + data: { + name: data.name, + teachers: { + set:data.teachers.map((teacherId)=>({id:teacherId})), + } + }, + }); + + return {success:true, error:false} + + } catch (err) { + console.log(err); + return {success:false, error:true} + } +} +export const deleteSubject = async (currentState: CurrentState, data: FormData) => { + const id = data.get("id") as string; + try { + await prisma.subject.delete({ + where: { + id:parseInt(id), + }, + }); + + return {success:true, error:false} + + } catch (err) { + console.log(err); + return {success:false, error:true} + } +} + +export const createClass = async ( + currentState: CurrentState, + data: ClassSchema +) => { + try { + await prisma.class.create({ + data, + }); + console.log(data); + // revalidatePath("/list/class"); + return { success: true, error: false }; + } catch (err) { + console.log(err); + return { success: false, error: true }; + } +}; + +export const updateClass = async ( + currentState: CurrentState, + data: ClassSchema +) => { + try { + await prisma.class.update({ + where: { + id: data.id, + }, + data, + }); + + // revalidatePath("/list/class"); + return { success: true, error: false }; + } catch (err) { + console.log(err); + return { success: false, error: true }; + } +}; + +export const deleteClass = async ( + currentState: CurrentState, + data: FormData +) => { + const id = data.get("id") as string; + try { + await prisma.class.delete({ + where: { + id: parseInt(id), + }, + }); + + // revalidatePath("/list/class"); + return { success: true, error: false }; + } catch (err) { + console.log(err); + return { success: false, error: true }; + } +}; + + +export const createTeacher = async ( + currentState: CurrentState, + data: TeacherSchema +) => { + try { + const user = await clerkClient.users.createUser({ + username: data.username, + password: data.password, + firstName: data.name, + lastName: data.surname, + publicMetadata:{role:"teacher"} + }); + + await prisma.teacher.create({ + data: { + id: user.id, + username: data.username, + name: data.name, + surname: data.surname, + email: data.email || null, + phone: data.phone || null, + address: data.address, + img: data.img || null, + bloodType: data.bloodType, + sex: data.sex, + birthday: data.birthday, + subjects: { + connect: data.subjects?.map((subjectId: string) => ({ + id: parseInt(subjectId), + })), + }, + }, + }); + + // revalidatePath("/list/teachers"); + return { success: true, error: false }; + } catch (err) { + console.log(err); + return { success: false, error: true }; + } +}; + +export const updateTeacher = async ( + currentState: CurrentState, + data: TeacherSchema +) => { + if (!data.id) { + return { success: false, error: true }; + } + try { + const user = await clerkClient.users.updateUser(data.id, { + username: data.username, + ...(data.password !== "" && { password: data.password }), + firstName: data.name, + lastName: data.surname, + }); + + await prisma.teacher.update({ + where: { + id: data.id, + }, + data: { + ...(data.password !== "" && { password: data.password }), + username: data.username, + name: data.name, + surname: data.surname, + email: data.email || null, + phone: data.phone || null, + address: data.address, + img: data.img || null, + bloodType: data.bloodType, + sex: data.sex, + birthday: data.birthday, + subjects: { + set: data.subjects?.map((subjectId: string) => ({ + id: parseInt(subjectId), + })), + }, + }, + }); + // revalidatePath("/list/teachers"); + return { success: true, error: false }; + } catch (err) { + console.log(err); + return { success: false, error: true }; + } +}; + +export const deleteTeacher = async ( + currentState: CurrentState, + data: FormData +) => { + const id = data.get("id") as string; + try { + await clerkClient.users.deleteUser(id); + + await prisma.teacher.delete({ + where: { + id: id, + }, + }); + + // revalidatePath("/list/teachers"); + return { success: true, error: false }; + } catch (err) { + console.log(err); + return { success: false, error: true }; + } +}; + +export const createStudent = async ( + currentState: CurrentState, + data: StudentSchema +) => { + try { + const classItem = await prisma.class.findUnique({ + where: { id: data.classId }, + include:{_count:{select:{students:true}}}, + }) + + if (classItem && classItem.capacity === classItem._count.students) { + return { success: false, error: true }; + } + + const user = await clerkClient.users.createUser({ + username: data.username, + password: data.password, + firstName: data.name, + lastName: data.surname, + publicMetadata:{role:"teacher"} + }); + + await prisma.student.create({ + data: { + id: user.id, + username: data.username, + name: data.name, + surname: data.surname, + email: data.email || null, + phone: data.phone || null, + address: data.address, + img: data.img || null, + bloodType: data.bloodType, + sex: data.sex, + birthday: data.birthday, + gradeId: data.gradeId, + classId: data.classId, + parentId:data.parentId, + }, + }); + + // revalidatePath("/list/students"); + return { success: true, error: false }; + } catch (err) { + console.log(err); + return { success: false, error: true }; + } +}; + +export const updateStudent = async ( + currentState: CurrentState, + data: StudentSchema +) => { + if (!data.id) { + return { success: false, error: true }; + } + try { + const user = await clerkClient.users.updateUser(data.id, { + username: data.username, + ...(data.password !== "" && { password: data.password }), + firstName: data.name, + lastName: data.surname, + }); + + await prisma.student.update({ + where: { + id: data.id, + }, + data: { + ...(data.password !== "" && { password: data.password }), + username: data.username, + name: data.name, + surname: data.surname, + email: data.email || null, + phone: data.phone || null, + address: data.address, + img: data.img || null, + bloodType: data.bloodType, + sex: data.sex, + birthday: data.birthday, + gradeId: data.gradeId, + classId: data.classId, + parentId:data.parentId, + }, + }); + // revalidatePath("/list/students"); + return { success: true, error: false }; + } catch (err) { + console.log(err); + return { success: false, error: true }; + } +}; + +export const deleteStudent = async ( + currentState: CurrentState, + data: FormData +) => { + const id = data.get("id") as string; + try { + await clerkClient.users.deleteUser(id); + + await prisma.student.delete({ + where: { + id: id, + }, + }); + + // revalidatePath("/list/students"); + return { success: true, error: false }; + } catch (err) { + console.log(err); + return { success: false, error: true }; + } +}; + +export const createExam = async ( + currentState: CurrentState, + data: ExamSchema +) => { + const { userId, role} = await getUserRole(); + + try { + if (role === "teacher") { + const teacherLesson = await prisma.lesson.findFirst({ + where: { + teacherId: userId!, + id: data.lessonId, + }, + }); + + if (!teacherLesson) { + return { success: false, error: true }; + } + } + + await prisma.exam.create({ + data: { + title: data.title, + startTime: data.startTime, + endTime: data.endTime, + lessonId: data.lessonId, + }, + }); + + // revalidatePath("/list/subjects"); + return { success: true, error: false }; + } catch (err) { + console.log(err); + return { success: false, error: true }; + } +}; + +export const updateExam = async ( + currentState: CurrentState, + data: ExamSchema +) => { + const { userId, role} = await getUserRole(); + + try { + if (role === "teacher") { + const teacherLesson = await prisma.lesson.findFirst({ + where: { + teacherId: userId!, + id: data.lessonId, + }, + }); + + if (!teacherLesson) { + return { success: false, error: true }; + } + } + + await prisma.exam.update({ + where: { + id: data.id, + }, + data: { + title: data.title, + startTime: data.startTime, + endTime: data.endTime, + lessonId: data.lessonId, + }, + }); + + // revalidatePath("/list/subjects"); + return { success: true, error: false }; + } catch (err) { + console.log(err); + return { success: false, error: true }; + } +}; + +export const deleteExam = async ( + currentState: CurrentState, + data: FormData +) => { + const id = data.get("id") as string; + + const { userId, role} = await getUserRole(); + + try { + await prisma.exam.delete({ + where: { + id: parseInt(id), + ...(role === "teacher" ? { lesson: { teacherId: userId! } } : {}), + }, + }); + + // revalidatePath("/list/subjects"); + return { success: true, error: false }; + } catch (err) { + console.log(err); + return { success: false, error: true }; + } +}; + +export const createParent = async ( + currentState: CurrentState, + data: ParentSchema +) => { + try { + const user = await clerkClient.users.createUser({ + username: data.username, + password: data.password, + firstName: data.name, + lastName: data.surname, + publicMetadata:{role:"parent"} + }); + + await prisma.parent.create({ + data: { + id: user.id, + username: data.username, + name: data.name, + surname: data.surname, + email: data.email || null, + phone: data.phone, + address: data.address, + // students: { + // connect: data.students?.map((studentId: string) => ({ + // id: studentId, + // })), + // }, + }, + }); + + // revalidatePath("/list/parent"); + return { success: true, error: false }; + } catch (err) { + console.log(err); + return { success: false, error: true }; + } +}; + +export const updateParent = async ( + currentState: CurrentState, + data: ParentSchema +) => { + console.log(data) + if (!data.id) { + return { success: false, error: true }; + } + try { + const user = await clerkClient.users.updateUser(data.id, { + username: data.username, + ...(data.password !== "" && { password: data.password }), + firstName: data.name, + lastName: data.surname, + }); + + await prisma.parent.update({ + where: { + id: data.id, + }, + data: { + ...(data.password !== "" && { password: data.password }), + id: user.id, + username: data.username, + name: data.name, + surname: data.surname, + email: data.email || null, + phone: data.phone, + address: data.address, + students: { + set: data.students?.map((studentId: string) => ({ + id: studentId, + })), + }, + }, + }); + // revalidatePath("/list/parent"); + return { success: true, error: false }; + } catch (err) { + console.log(err); + return { success: false, error: true }; + } +}; + +export const deleteParent = async ( + currentState: CurrentState, + data: FormData +) => { + const id = data.get("id") as string; + console.log(id) + try{ + await clerkClient.users.deleteUser(id); + + await prisma.parent.delete({ + where: { + id: id, + }, + }); + + // revalidatePath("/list/parent"); + return { success: true, error: false }; + } catch (err) { + console.log(err); + return { success: false, error: true }; + } +}; + +export const createLesson = async ( + currentState: CurrentState, + data: LessonSchema +) => { + try { + await prisma.lesson.create({ + data: { + name: data.name, + day:data.day, + startTime: data.startTime, + endTime: data.endTime, + subjectId: data.subjectId, + classId: data.classId, + teacherId:data.teacherId, + }, + }); + + // revalidatePath("/list/subjects"); + return { success: true, error: false }; + } catch (err) { + console.log(err); + return { success: false, error: true }; + } +}; + +export const updateLesson = async ( + currentState: CurrentState, + data: LessonSchema +) => { +try{ + await prisma.lesson.update({ + where: { + id: data.id, + }, + data: { + name: data.name, + day:data.day, + startTime: data.startTime, + endTime: data.endTime, + subjectId: data.subjectId, + classId: data.classId, + teacherId:data.teacherId, + }, + }); + + // revalidatePath("/list/subjects"); + return { success: true, error: false }; + } catch (err) { + console.log(err); + return { success: false, error: true }; + } +}; + +export const deleteLesson = async ( + currentState: CurrentState, + data: FormData +) => { + const id = data.get("id") as string; + + try { + await prisma.lesson.delete({ + where: { + id: parseInt(id), + }, + }); + + // revalidatePath("/list/subjects"); + return { success: true, error: false }; + } catch (err) { + console.log(err); + return { success: false, error: true }; + } +}; + +export const createAssignment= async ( + currentState: CurrentState, + data: AssignmentSchema +) => { + const { userId, role} = await getUserRole(); + + try { + if (role === "teacher") { + const teacherLesson = await prisma.lesson.findFirst({ + where: { + teacherId: userId!, + id: data.lessonId, + }, + }); + + if (!teacherLesson) { + return { success: false, error: true }; + } + } + + await prisma.assignment.create({ + data: { + title: data.title, + startDate: data.startDate, + dueDate: data.dueDate, + lessonId: data.lessonId, + }, + }); + + // revalidatePath("/list/subjects"); + return { success: true, error: false }; + } catch (err) { + console.log(err); + return { success: false, error: true }; + } +}; + +export const updateAssignment = async ( + currentState: CurrentState, + data: AssignmentSchema +) => { + const { userId, role} = await getUserRole(); + + try { + if (role === "teacher") { + const teacherLesson = await prisma.lesson.findFirst({ + where: { + teacherId: userId!, + id: data.lessonId, + }, + }); + + if (!teacherLesson) { + return { success: false, error: true }; + } + } + + await prisma.assignment.update({ + where: { + id: data.id, + }, + data: { + title: data.title, + startDate: data.startDate, + dueDate: data.dueDate, + lessonId: data.lessonId, + }, + }); + + // revalidatePath("/list/subjects"); + return { success: true, error: false }; + } catch (err) { + console.log(err); + return { success: false, error: true }; + } +}; + +export const deleteAssignment = async ( + currentState: CurrentState, + data: FormData +) => { + const id = data.get("id") as string; + + const { userId, role} = await getUserRole(); + + try { + await prisma.assignment.delete({ + where: { + id: parseInt(id), + ...(role === "teacher" ? { lesson: { teacherId: userId! } } : {}), + }, + }); + + // revalidatePath("/list/subjects"); + return { success: true, error: false }; + } catch (err) { + console.log(err); + return { success: false, error: true }; + } +}; + +export const createEvent = async ( + currentState: CurrentState, + data: EventSchema +) => { + try { + await prisma.event.create({ + data: { + title: data.title, + description:data.description, + startTime: data.startTime, + endTime: data.endTime, + classId: data.classId, + }, + }); + + // revalidatePath("/list/subjects"); + return { success: true, error: false }; + } catch (err) { + console.log(err); + return { success: false, error: true }; + } +}; + +export const updateEvent = async ( + currentState: CurrentState, + data: EventSchema +) => { +try{ + await prisma.event.update({ + where: { + id: data.id, + }, + data: { + title: data.title, + description:data.description, + startTime: data.startTime, + endTime: data.endTime, + classId: data.classId, + }, + }); + + // revalidatePath("/list/subjects"); + return { success: true, error: false }; + } catch (err) { + console.log(err); + return { success: false, error: true }; + } +}; + +export const deleteEvent = async ( + currentState: CurrentState, + data: FormData +) => { + const id = data.get("id") as string; + + try { + await prisma.event.delete({ + where: { + id: parseInt(id), + }, + }); + + // revalidatePath("/list/subjects"); + return { success: true, error: false }; + } catch (err) { + console.log(err); + return { success: false, error: true }; + } +}; diff --git a/src/lib/formValidationSchemas.ts b/src/lib/formValidationSchemas.ts new file mode 100644 index 000000000..3334ace18 --- /dev/null +++ b/src/lib/formValidationSchemas.ts @@ -0,0 +1,153 @@ +import { z } from "zod"; + +export const SubjectSchema = z.object({ + id:z.coerce.number().optional(), + name: z + .string() + .min(1, { message: "Subject name is required!" }), + teachers:z.array(z.string()), + +}); +export type SubjectSchema = z.infer; + +export const ClassSchema = z.object({ + id: z.coerce.number().optional(), + name: z.string().min(1, { message: "Subject name is required!" }), + capacity: z.coerce.number().min(1, { message: "Capacity name is required!" }), + gradeId: z.coerce.number().min(1, { message: "Grade name is required!" }), + supervisorId: z.coerce.string().optional(), +}); + +export type ClassSchema = z.infer; + +export const TeacherSchema = z.object({ + id: z.string().optional(), + username: z + .string() + .min(3, { message: "Username must be at least 3 characters long!" }) + .max(20, { message: "Username must be at most 20 characters long!" }), + password: z + .string() + .min(8, { message: "Password must be at least 8 characters long!" }) + .optional() + .or(z.literal("")), + name: z.string().min(1, { message: "First name is required!" }), + surname: z.string().min(1, { message: "Last name is required!" }), + email: z + .string() + .email({ message: "Invalid email address!" }) + .optional() + .or(z.literal("")), + phone: z.string().optional(), + address: z.string(), + img: z.string().optional(), + bloodType: z.string().min(1, { message: "Blood Type is required!" }), + birthday: z.coerce.date({ message: "Birthday is required!" }), + sex: z.enum(["MALE", "FEMALE"], { message: "Sex is required!" }), + subjects: z.array(z.string()).optional(), // subject ids +}); + +export type TeacherSchema = z.infer; + + +export const StudentSchema = z.object({ + id: z.string().optional(), + username: z + .string() + .min(3, { message: "Username must be at least 3 characters long!" }) + .max(20, { message: "Username must be at most 20 characters long!" }), + password: z + .string() + .min(8, { message: "Password must be at least 8 characters long!" }) + .optional() + .or(z.literal("")), + name: z.string().min(1, { message: "First name is required!" }), + surname: z.string().min(1, { message: "Last name is required!" }), + email: z + .string() + .email({ message: "Invalid email address!" }) + .optional() + .or(z.literal("")), + phone: z.string().optional(), + address: z.string(), + img: z.string().optional(), + bloodType: z.string().min(1, { message: "Blood Type is required!" }), + birthday: z.coerce.date({ message: "Birthday is required!" }), + sex: z.enum(["MALE", "FEMALE"], { message: "Sex is required!" }), + gradeId: z.coerce.number().min(1, { message: "Grade is required!" }), + classId:z.coerce.number().min(1,{message:"Class is required!"}), + parentId:z.string().min(1,{message:"Parent Id is required!"}), + +}); + +export type StudentSchema = z.infer; + +export const ExamSchema = z.object({ + id: z.coerce.number().optional(), + title: z.string().min(1, { message: "Title name is required!" }), + startTime: z.coerce.date({ message: "Start time is required!" }), + endTime: z.coerce.date({ message: "End time is required!" }), + lessonId: z.coerce.number({ message: "Lesson is required!" }), +}); + +export type ExamSchema = z.infer; + +export const ParentSchema = z.object({ + id: z.string().optional(), + username: z + .string() + .min(3, { message: "Username must be at least 3 characters long!" }) + .max(20, { message: "Username must be at most 20 characters long!" }), + password: z + .string() + .min(8, { message: "Password must be at least 8 characters long!" }) + .optional() + .or(z.literal("")), + name: z.string().min(1, { message: "First name is required!" }), + surname: z.string().min(1, { message: "Last name is required!" }), + email: z + .string() + .email({ message: "Invalid email address!" }) + .optional() + .or(z.literal("")), + phone: z.coerce.string().min(6, { message: "Phone number is required!" }), + address: z.string(), + createdAt: z.coerce.date().optional(), + + students: z.array(z.string()).optional(), // subject ids +}); + +export type ParentSchema = z.infer; + +export const LessonSchema = z.object({ + id: z.coerce.number().optional(), + name: z.string().min(1, { message: "Title name is required!" }), + day: z.enum(["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY"], { message: "Day is required!" }), + startTime: z.coerce.date({ message: "Start time is required!" }), + endTime: z.coerce.date({ message: "End time is required!" }), + subjectId: z.coerce.number({ message: "Subject is required!" }), + classId: z.coerce.number({ message: "Class is required!" }), + teacherId:z.coerce.string({ message: "Teacher is required!" }), +}); +export type LessonSchema = z.infer; + +export const AssignmentSchema = z.object({ + id: z.coerce.number().optional(), + title: z.string().min(1, { message: "Title name is required!" }), + startDate: z.coerce.date({ message: "Start time is required!" }), + dueDate: z.coerce.date({ message: "Due date is required!" }), + lessonId: z.coerce.number({ message: "Lesson is required!" }), +}); + +export type AssignmentSchema = z.infer; + +export const EventSchema = z.object({ + id: z.coerce.number().optional(), + title: z.string().min(1, { message: "Title name is required!" }), + description:z.string().min(3, { message: "Description is required!" }), + startTime: z.coerce.date({ message: "Start time is required!" }), + endTime: z.coerce.date({ message: "End time is required!" }), + classId: z.coerce.number().optional(), +}); + +export type EventSchema = z.infer; \ No newline at end of file diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts new file mode 100644 index 000000000..7ddc22a98 --- /dev/null +++ b/src/lib/prisma.ts @@ -0,0 +1,15 @@ +import { PrismaClient } from '@prisma/client' + +const prismaClientSingleton = () => { + return new PrismaClient() +} + +declare const globalThis: { + prismaGlobal: ReturnType; +} & typeof global; + +const prisma = globalThis.prismaGlobal ?? prismaClientSingleton() + +export default prisma + +if (process.env.NODE_ENV !== 'production') globalThis.prismaGlobal = prisma \ No newline at end of file diff --git a/src/lib/settings.ts b/src/lib/settings.ts new file mode 100644 index 000000000..6d518f555 --- /dev/null +++ b/src/lib/settings.ts @@ -0,0 +1,26 @@ +export const ITEM_PER_PAGE = 10; + +type RouteAccessMap = { + [key: string]: string[]; +}; +export const routeAccessMap: RouteAccessMap = { + "/admin(.*)": ["admin"], + "/student(.*)": ["student"], + "/teacher(.*)": ["teacher"], + "/parent(.*)": ["parent"], + "/list/teachers": ["admin", "teacher"], + "/list/students": ["admin", "teacher"], + "/list/parents": ["admin", "teacher"], + "/list/subjects": ["admin"], + "/list/classes": ["admin", "teacher"], + "/list/exams": ["admin", "teacher", "student", "parent"], + "/list/assignments": ["admin", "teacher", "student", "parent"], + "/list/results": ["admin", "teacher", "student", "parent"], + "/list/attendance": ["admin", "teacher", "student", "parent"], + "/list/events": ["admin", "teacher", "student", "parent"], + "/list/announcements": ["admin", "teacher", "student", "parent"], +}; + +export const DAY_OF_WEEK: string[] = [ + "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY" +]; \ No newline at end of file diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 000000000..1dd2ce1c0 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,52 @@ +import { auth } from "@clerk/nextjs/server"; +import { z } from "zod"; + +export const getUserRole = async () => { +const { userId, sessionClaims } = await auth(); + return { + role: (sessionClaims?.metadata as { role?: string })?.role, + userId, + } +} + +const getLatestMonday = (): Date => { + const today = new Date(); + const dayOfWeek = today.getDay(); + const daysSinceMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1; + const latestMonday = today; + latestMonday.setDate(today.getDate() - daysSinceMonday); + return latestMonday; +}; + +export const adjustScheduleToCurrentWeek = ( + lessons: { title: string; start: Date; end: Date }[] +): { title: string; start: Date; end: Date }[] => { + const latestMonday = getLatestMonday(); + + return lessons.map((lesson) => { + const lessonDayOfWeek = lesson.start.getDay(); + + const daysFromMonday = lessonDayOfWeek === 0 ? 6 : lessonDayOfWeek - 1; + + const adjustedStartDate = new Date(latestMonday); + + adjustedStartDate.setDate(latestMonday.getDate() + daysFromMonday); + adjustedStartDate.setHours( + lesson.start.getHours(), + lesson.start.getMinutes(), + lesson.start.getSeconds() + ); + const adjustedEndDate = new Date(adjustedStartDate); + adjustedEndDate.setHours( + lesson.end.getHours(), + lesson.end.getMinutes(), + lesson.end.getSeconds() + ); + + return { + title: lesson.title, + start: adjustedStartDate, + end: adjustedEndDate, + }; + }); +}; \ No newline at end of file diff --git a/src/middleware.ts b/src/middleware.ts new file mode 100644 index 000000000..424609d9d --- /dev/null +++ b/src/middleware.ts @@ -0,0 +1,40 @@ +import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server"; +import { NextResponse } from "next/server"; +import { routeAccessMap } from "./lib/settings"; + + +// const isProtectedRoute = createRouteMatcher(['/dashboard(.*)', '/forum(.*)']) + +// export default clerkMiddleware(async (auth, req) => { +// if (isProtectedRoute(req)) await auth.protect() +// }) + + +const matchers = Object.keys(routeAccessMap).map((route) => ({ + matcher: createRouteMatcher([route]), + allowedRoles: routeAccessMap[route], +})); + +console.log(matchers); + +export default clerkMiddleware(async(auth, req) => { + // if (isProtectedRoute(req)) auth().protect() + + const { sessionClaims } = await auth(); + + const role = (sessionClaims?.metadata as { role?: string })?.role; + + for (const { matcher, allowedRoles } of matchers) { + if (matcher(req) && !allowedRoles.includes(role!)) { + return NextResponse.redirect(new URL(`/${role}`, req.url)); + } + } +}); +export const config = { + matcher: [ + // Skip Next.js internals and all static files, unless found in search params + "/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)", + // Always run for API routes + "/(api|trpc)(.*)", + ], +}; \ No newline at end of file From 651c9f419e3a2b8d6be6f96c42a5977f4ef1b7d0 Mon Sep 17 00:00:00 2001 From: Oksana-Tnt Date: Tue, 12 Nov 2024 18:18:48 +0100 Subject: [PATCH 3/3] Update EventForm.tsx --- src/components/forms/EventForm.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/forms/EventForm.tsx b/src/components/forms/EventForm.tsx index 5d7f9fba1..f3f7f2c38 100644 --- a/src/components/forms/EventForm.tsx +++ b/src/components/forms/EventForm.tsx @@ -45,6 +45,7 @@ const EventForm = ({ ); const onSubmit = handleSubmit((data) => { + console.log(data) formAction(data); });