diff --git a/package-lock.json b/package-lock.json index f5fde62..2b6b812 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-separator": "^1.1.0", - "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-tabs": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.4", "@tanstack/react-query": "^5.62.8", @@ -25,6 +25,7 @@ "@uploadthing/react": "^7.1.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "date-fns": "^3.6.0", "express": "^4.21.2", "http": "^0.0.1-security", "i18next": "^24.0.5", @@ -33,10 +34,10 @@ "next": "^15.0.4", "react": "18.2.0", "react-calendar": "^5.1.0", + "react-day-picker": "^8.10.1", "react-dom": "18.2.0", "react-hook-form": "^7.54.2", "react-hot-toast": "^2.4.1", - "react-i18next": "^15.1.3", "react-icons": "^5.3.0", "react-loading-skeleton": "^3.5.0", "react-router-dom": "^7.0.2", @@ -1559,6 +1560,24 @@ } } }, + "node_modules/@radix-ui/react-collection/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-compose-refs": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", @@ -1768,24 +1787,6 @@ } } }, - "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", - "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.1" - }, - "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-direction": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", @@ -1977,6 +1978,24 @@ } } }, + "node_modules/@radix-ui/react-menu/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-popper": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", @@ -2094,6 +2113,24 @@ } } }, + "node_modules/@radix-ui/react-primitive/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-roving-focus": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz", @@ -2183,6 +2220,24 @@ } } }, + "node_modules/@radix-ui/react-select/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-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.0.tgz", @@ -2207,11 +2262,12 @@ } }, "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==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", + "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", + "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0" + "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -2223,6 +2279,21 @@ } } }, + "node_modules/@radix-ui/react-slot/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "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-tabs": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.2.tgz", @@ -2378,24 +2449,6 @@ } } }, - "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-slot": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", - "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.1" - }, - "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-tooltip": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.4.tgz", @@ -2430,6 +2483,24 @@ } } }, + "node_modules/@radix-ui/react-tooltip/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-callback-ref": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", @@ -4331,6 +4402,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -6196,15 +6277,6 @@ "node": ">=10" } }, - "node_modules/html-parse-stringify": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", - "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", - "license": "MIT", - "dependencies": { - "void-elements": "3.1.0" - } - }, "node_modules/http": { "version": "0.0.1-security", "resolved": "https://registry.npmjs.org/http/-/http-0.0.1-security.tgz", @@ -6379,15 +6451,6 @@ "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==", - "license": "MIT", - "dependencies": { - "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", @@ -8769,6 +8832,20 @@ } } }, + "node_modules/react-day-picker": { + "version": "8.10.1", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.1.tgz", + "integrity": "sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==", + "license": "MIT", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/gpbl" + }, + "peerDependencies": { + "date-fns": "^2.28.0 || ^3.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -8813,28 +8890,6 @@ "react-dom": ">=16" } }, - "node_modules/react-i18next": { - "version": "15.1.3", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.1.3.tgz", - "integrity": "sha512-J11oA30FbM3NZegUZjn8ySK903z6PLBz/ZuBYyT1JMR0QPrW6PFXvl1WoUhortdGi9dM0m48/zJQlPskVZXgVw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.25.0", - "html-parse-stringify": "^3.0.1" - }, - "peerDependencies": { - "i18next": ">= 23.2.3", - "react": ">= 16.8.0" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, "node_modules/react-icons": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.3.0.tgz", @@ -8883,20 +8938,20 @@ } }, "node_modules/react-remove-scroll-bar": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", - "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", "license": "MIT", "dependencies": { - "react-style-singleton": "^2.2.1", + "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "engines": { "node": ">=10" }, "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -8992,21 +9047,20 @@ } }, "node_modules/react-style-singleton": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", - "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", "license": "MIT", "dependencies": { "get-nonce": "^1.0.0", - "invariant": "^2.2.4", "tslib": "^2.0.0" }, "engines": { "node": ">=10" }, "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -10822,9 +10876,9 @@ } }, "node_modules/use-callback-ref": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", - "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", "license": "MIT", "dependencies": { "tslib": "^2.0.0" @@ -10833,8 +10887,8 @@ "node": ">=10" }, "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -10948,15 +11002,6 @@ "d3-timer": "^3.0.1" } }, - "node_modules/void-elements": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", - "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/warning": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", diff --git a/package.json b/package.json index 35ab329..33398ea 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-separator": "^1.1.0", - "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-tabs": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.4", "@tanstack/react-query": "^5.62.8", @@ -32,6 +32,7 @@ "@uploadthing/react": "^7.1.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "date-fns": "^3.6.0", "express": "^4.21.2", "http": "^0.0.1-security", "i18next": "^24.0.5", @@ -40,10 +41,10 @@ "next": "^15.0.4", "react": "18.2.0", "react-calendar": "^5.1.0", + "react-day-picker": "^8.10.1", "react-dom": "18.2.0", "react-hook-form": "^7.54.2", "react-hot-toast": "^2.4.1", - "react-i18next": "^15.1.3", "react-icons": "^5.3.0", "react-loading-skeleton": "^3.5.0", "react-router-dom": "^7.0.2", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b1b694a..ac24076 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -52,6 +52,8 @@ model User { Department Department? @relation(fields: [departmentId], references: [id]) university University? @relation("UserToUniversity", fields: [universityId], references: [id]) + + Policy Policy[] } model University { @@ -77,6 +79,8 @@ model University { subjects Subject[] admin User @relation("universityAdmin", fields: [adminId], references: [id]) users User[] @relation("UserToUniversity") + + Policy Policy[] } model NonTeachingStaff { @@ -174,6 +178,8 @@ model Department { timeTable TimeTable[] User User[] classes Class[] + + Policy Policy[] } model Course { @@ -413,6 +419,7 @@ model Announcement { category String departmentId Int universityId Int + announcerName String createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @@ -450,10 +457,30 @@ model Event { budget Int organiserId String universityId Int - departmentId Int? + departmentId Int createdAt DateTime @default(now()) updatedAt DateTime @default(now()) department Department? @relation(fields: [departmentId], references: [id]) organiser User @relation(fields: [organiserId], references: [clerkId]) university University @relation(fields: [universityId], references: [id]) } + +model Policy { + id Int @id @default(autoincrement()) + title String + description String? + attachments Json[] + category String + effectiveDate String + expiryDate String? + departmentId Int + universityId Int + authorId String + authorName String + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) + + sharedBy User @relation(fields: [authorId], references: [clerkId]) + department Department @relation(fields: [departmentId], references: [id]) + university University @relation(fields: [universityId], references: [id]) +} diff --git a/prisma/seed.js b/prisma/seed.js index 105403d..dac8ca8 100755 --- a/prisma/seed.js +++ b/prisma/seed.js @@ -102,8 +102,8 @@ async function main() { const dummyPrincipalUser = await prisma.user.create({ // Principal data: { - id: "user_2qcpeeEw8rTPzWIKUgjppr0xWUo", - clerkId: "user_2qcpeeEw8rTPzWIKUgjppr0xWUo", + id: "user_2qrHhQZhnV8h24O5Yw52yYm3dws", + clerkId: "user_2qrHhQZhnV8h24O5Yw52yYm3dws", name: "Nilax Modi", email: "principal_uit@ku.edu.in", phone: "7894567898", @@ -123,8 +123,8 @@ async function main() { const dummyDeanUser = await prisma.user.create({ // Dean data: { - id: "user_2qVuNTsat2QYT68j069juHP3cqI", - clerkId: "user_2qVuNTsat2QYT68j069juHP3cqI", + id: "user_2qrHoqnzAhnRgTExHVB4qoCoU9Y", + clerkId: "user_2qrHoqnzAhnRgTExHVB4qoCoU9Y", name: "Mohak shah", email: "dean_uit@ku.edu.in", phone: "7894567898", @@ -497,4 +497,3 @@ main() process.exit(1) }) - diff --git a/src/app/(module)/announcements/_components/AnnouncementCard.tsx b/src/app/(module)/announcements/_components/AnnouncementCard.tsx index 2fa0d25..719a1e8 100644 --- a/src/app/(module)/announcements/_components/AnnouncementCard.tsx +++ b/src/app/(module)/announcements/_components/AnnouncementCard.tsx @@ -1,4 +1,4 @@ -import { useState } from "react" +import { useContext, useState } from "react" import { useRouter } from "next/navigation" import { ChevronDown, @@ -7,29 +7,21 @@ import { Edit, Loader2, Paperclip, - Trash2 + Trash2, + User } from "lucide-react" import toast from "react-hot-toast" - -// Format date function -const formatDate = (dateString: string) => { - return new Date(dateString).toLocaleDateString("en-US", { - year: "numeric", - month: "long", - day: "numeric" - }) -} +import { UserContext } from "@/context/user" export default function AnnouncementCard({ announcement, - refetch, - canCreateAnnouncement + refetch }: { announcement: any refetch: () => void - canCreateAnnouncement: boolean }) { const router = useRouter() + const { user } = useContext(UserContext) const [isExpanded, setIsExpanded] = useState(false) const [isDeleting, setIsDeleting] = useState(false) const [showConfirmDelete, setShowConfirmDelete] = useState(false) @@ -62,14 +54,18 @@ export default function AnnouncementCard({ } return ( -
+

{announcement.title}

+
+ + {announcement.announcerName} +
- {formatDate(announcement.createdAt)} + {`${new Date(announcement.updatedAt).toLocaleString()}`}
@@ -123,7 +119,7 @@ export default function AnnouncementCard({
)} - {canCreateAnnouncement && ( + {announcement.announcerId === user?.id && (
+ )} +
+ + {/* Timeline Section */} +
+
+
+ + Timeline + + +
+ +
+
+
+ + Effective From + +
+ + {formatDate(policy.effectiveDate)} + +
+
+
+ + {policy.expiryDate && ( + <> +
+
+
+ + Expires On + + + {formatDate(policy.expiryDate)} + +
+
+ + )} + + {policy.expiryDate && ( +
+ {new Date(policy.expiryDate) > new Date() ? ( + + Active + + ) : ( + + Expired + + )} +
+ )} +
+
+
+ + {/* Rest of the component remains the same */} + {/* Attachments Section */} + {policy.attachments?.length > 0 && ( +
+
+ + Attachments ({policy.attachments.length}) +
+
+ {policy.attachments.map((attachment: any, index: any) => ( + + {attachment.fileName} + + ))} +
+
+ )} +
+ + {/* Actions Section */} + {policy.authorId === user?.id && ( +
+
+ + + {showConfirmDelete ? ( +
+ + +
+ ) : ( + + )} +
+
+ )} +
+ ) +} diff --git a/src/app/(module)/policy/form/page.tsx b/src/app/(module)/policy/form/page.tsx new file mode 100644 index 0000000..fb1dc4d --- /dev/null +++ b/src/app/(module)/policy/form/page.tsx @@ -0,0 +1,370 @@ +"use client" + +import React, { useCallback, useContext, useEffect, useState } from "react" +import { Loader2 } from "lucide-react" +import { UploadthingUploader } from "@/components/(commnon)/UploadthingUploader" +import { UserContext } from "@/context/user" +import { useUploadThing } from "@/utils/uploadthing" +import { UploadedFile } from "@/types/globals" +import { useRouter, useSearchParams } from "next/navigation" +import toast from "react-hot-toast" + +interface FileWithPreview extends File { + preview?: string + existing?: boolean + url?: string +} + +export default function CreateOrUpdatePolicy() { + const searchParams = useSearchParams() + const policyId = searchParams.get("policyId") + const { user } = useContext(UserContext) + const [title, setTitle] = useState("") + const [description, setDescription] = useState("") + const [files, setFiles] = useState([]) + const [category, setCategory] = useState("academic") + const [effectiveDate, setEffectiveDate] = useState("") + const [expiryDate, setExpiryDate] = useState("") + const [uploading, setUploading] = useState(false) + const [isSubmitting, setIsSubmitting] = useState(false) + const [isLoading, setIsLoading] = useState(false) + const router = useRouter() + + useEffect(() => { + const fetchPolicyDetails = async () => { + if (!policyId) return + + setIsLoading(true) + try { + const response = await fetch( + `/api/policy?route=findOne&&policyId=${policyId}` + ) + if (!response.ok) { + throw new Error("Failed to fetch the policy details") + } + + const data = await response.json() + setTitle(data.title) + setDescription(data.description) + setCategory(data.category) + setEffectiveDate(data.effectiveDate) + setExpiryDate(data.expiryDate) + + // Handle existing attachments + if (data.attachments?.length > 0) { + const existingFiles = data.attachments.map((attachment: any) => ({ + name: attachment.fileName, + url: attachment.url, + preview: attachment.url, + existing: true + })) + setFiles(existingFiles) + } + } catch (error) { + if (error) toast.error("Failed to fetch the policy details") + } finally { + setIsLoading(false) + } + } + + fetchPolicyDetails() + }, [policyId]) + + const { startUpload, routeConfig } = useUploadThing("attachmentsUploader", { + onClientUploadComplete: () => { + setUploading(false) + files.forEach((file) => { + if (file.preview) { + URL.revokeObjectURL(file.preview) + } + }) + }, + onUploadError: () => { + toast.error("Failed to upload file") + setUploading(false) + }, + onUploadBegin: () => { + setUploading(true) + } + }) + + const generatePreview = (file: File) => { + const fileWithPreview = file as File & { preview?: string } + if (file.type.startsWith("image/")) { + fileWithPreview.preview = URL.createObjectURL(file) + } + return fileWithPreview + } + + const onDrop = useCallback( + async (acceptedFiles: File[]) => { + const uniqueFiles = acceptedFiles.filter((file) => { + return !files.some((existingFile) => existingFile.name === file.name) + }) + const filesWithPreviews = uniqueFiles.map(generatePreview) + setFiles((prevFiles) => [...prevFiles, ...filesWithPreviews]) + }, + [files] + ) + + const removeFile = (indexToRemove: number) => { + setFiles((prevFiles) => { + const newFiles = prevFiles.filter((_, index) => index !== indexToRemove) + if (prevFiles[indexToRemove].preview) { + URL.revokeObjectURL(prevFiles[indexToRemove].preview!) + } + return newFiles + }) + } + + useEffect(() => { + return () => { + files.forEach((file) => { + if (file.preview) { + URL.revokeObjectURL(file.preview) + } + }) + } + }, [files]) + + const handleFormSubmit = async (e: React.FormEvent) => { + e.preventDefault() + setIsSubmitting(true) + + try { + let uploadedFileData: UploadedFile[] = [] + + // Handle new file uploads + const newFiles = files.filter((file) => !file.existing) + if (newFiles.length > 0) { + const uploadedFiles = await startUpload(newFiles) + + if (uploadedFiles) { + uploadedFileData = uploadedFiles.map((file) => ({ + url: file.url, + fileType: file.name.split(".").pop()?.toLowerCase() || "", + fileName: file.name + })) + } + } + + // Keep existing files in the upload data + const existingFiles = files + .filter((file) => file.existing) + .map((file) => ({ + url: file.url, + fileName: file.name, + fileType: file.name.split(".").pop()?.toLowerCase() || "" + })) + + const formData = { + title, + description, + category, + departmentId: user?.departmentId, + universityId: user?.universityId, + authorId: user?.id, + authorName: user?.name, + effectiveDate, + expiryDate, + attachments: [...existingFiles, ...uploadedFileData] + } + + const response = await fetch( + `/api/policy${policyId ? `?id=${policyId}` : ""}`, + { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(formData) + } + ) + + if (response.ok) { + toast.success(`Policy ${policyId ? "Edit" : "Save"} successfully`) + setTitle("") + setDescription("") + setFiles([]) + setCategory("academic") + setEffectiveDate("") + setExpiryDate("") + router.push("/policy") + } else { + toast.error("Failed to save the policy") + console.log("Failed to save the policy", response) + } + } catch (error) { + if (error) toast.error("An error occurred while saving the policy") + } finally { + setIsSubmitting(false) + } + } + + if (isLoading) { + return ( +
+ +
+ ) + } + + return ( +
+
+

+ {policyId ? "Edit" : "Create"} Policy +

+
+
+ + setTitle(e.target.value)} + className="w-full px-4 py-2 rounded-lg border border-Secondary focus:outline-none focus:ring-2 focus:ring-ColorThree transition-all duration-200" + required + /> +
+ +
+ + +
+
+ +
+
+
+ +
+ +