From 729d1d8f50d8201b05376c85fbcffe3f22a0776e Mon Sep 17 00:00:00 2001 From: priyanshuvish Date: Wed, 29 Oct 2025 19:21:35 +0530 Subject: [PATCH] content manager api integrated --- package-lock.json | 100 + package.json | 2 + src/App.tsx | 138 +- src/components/auth/ForgotPassword.tsx | 3 +- src/components/auth/Login.tsx | 3 +- src/components/auth/TwoFactorAuth.tsx | 3 +- src/components/layout/AuthenticatedLayout.tsx | 2 +- src/components/pages/AboutUsEditor.tsx | 3 +- src/components/pages/Analytics.tsx | 3 +- src/components/pages/ClassScheduler.tsx | 5 +- src/components/pages/ContentManager.tsx | 2481 +---------------- src/components/pages/CourseAssignment.tsx | 5 +- src/components/pages/Courses.tsx | 3 +- src/components/pages/Dashboard.tsx | 3 +- src/components/pages/EditBlog.tsx | 597 ++++ src/components/pages/EditCaseStudy.tsx | 397 +++ src/components/pages/EditFAQ.tsx | 431 +++ .../pages/EditKlcArchiveContent.tsx | 383 +++ src/components/pages/EditPodcast.tsx | 397 +++ src/components/pages/EditReadingMaterial.tsx | 374 +++ src/components/pages/EditTrainingMaterial.tsx | 381 +++ src/components/pages/EditWebcast.tsx | 380 +++ src/components/pages/Facilities360.tsx | 5 +- src/components/pages/Facilities360Detail.tsx | 5 +- src/components/pages/Facilities360New.tsx | 3 +- src/components/pages/HomeEditor.tsx | 3 +- src/components/pages/IndividualLearners.tsx | 3 +- src/components/pages/LandingPagesNew.tsx | 3 +- src/components/pages/Leads.tsx | 5 +- src/components/pages/NewBlog.tsx | 175 +- src/components/pages/NewFAQ.tsx | 155 +- src/components/pages/NewOrganization.tsx | 3 +- src/components/pages/OpenProgramme.tsx | 3 +- src/components/pages/Organizations.tsx | 3 +- src/components/pages/Profile.tsx | 3 +- src/components/pages/ProfilerApproval.tsx | 5 +- src/components/pages/ProfilerBuilder.tsx | 5 +- src/components/pages/ProfilerMaster.tsx | 3 +- src/components/pages/ProfilersEnhanced.tsx | 3 +- src/components/pages/ProgrammeAssignment.tsx | 5 +- src/components/pages/ProgrammeComposer.tsx | 3 +- src/components/pages/Programmes.tsx | 5 +- src/components/pages/ResetPassword.tsx | 3 +- src/components/pages/Roles.tsx | 5 +- src/components/pages/ServicesEditor.tsx | 3 +- src/components/pages/ViewBlog.tsx | 253 ++ src/components/pages/ViewFAQ.tsx | 477 ++++ src/components/pages/Webinars.tsx | 3 +- .../pages/shared/BulkUploadDrawer.tsx | 300 ++ src/components/pages/shared/ContentTable.tsx | 708 +++++ src/components/pages/shared/UploadDrawer.tsx | 489 ++++ src/components/pages/tabs/BlogsTab.tsx | 204 ++ src/components/pages/tabs/CaseStudiesTab.tsx | 158 ++ src/components/pages/tabs/FAQTab.tsx | 206 ++ .../pages/tabs/KLCContentArchiveTab.tsx | 218 ++ src/components/pages/tabs/PodcastsTab.tsx | 156 ++ src/components/pages/tabs/ProfilerTab.tsx | 215 ++ .../pages/tabs/ReadingMaterialsTab.tsx | 153 + .../pages/tabs/TrainingMaterialsTab.tsx | 255 ++ src/components/pages/tabs/WebcastsTab.tsx | 160 ++ src/global.d.ts | 12 + src/main.tsx | 16 +- src/store/baseQuery.ts | 17 + src/store/services/contentManager.service.ts | 429 +++ src/store/store.ts | 20 + src/styles/globals.css | 5 + src/types/routes.ts | 44 + 67 files changed, 8458 insertions(+), 2543 deletions(-) create mode 100644 src/components/pages/EditBlog.tsx create mode 100644 src/components/pages/EditCaseStudy.tsx create mode 100644 src/components/pages/EditFAQ.tsx create mode 100644 src/components/pages/EditKlcArchiveContent.tsx create mode 100644 src/components/pages/EditPodcast.tsx create mode 100644 src/components/pages/EditReadingMaterial.tsx create mode 100644 src/components/pages/EditTrainingMaterial.tsx create mode 100644 src/components/pages/EditWebcast.tsx create mode 100644 src/components/pages/ViewBlog.tsx create mode 100644 src/components/pages/ViewFAQ.tsx create mode 100644 src/components/pages/shared/BulkUploadDrawer.tsx create mode 100644 src/components/pages/shared/ContentTable.tsx create mode 100644 src/components/pages/shared/UploadDrawer.tsx create mode 100644 src/components/pages/tabs/BlogsTab.tsx create mode 100644 src/components/pages/tabs/CaseStudiesTab.tsx create mode 100644 src/components/pages/tabs/FAQTab.tsx create mode 100644 src/components/pages/tabs/KLCContentArchiveTab.tsx create mode 100644 src/components/pages/tabs/PodcastsTab.tsx create mode 100644 src/components/pages/tabs/ProfilerTab.tsx create mode 100644 src/components/pages/tabs/ReadingMaterialsTab.tsx create mode 100644 src/components/pages/tabs/TrainingMaterialsTab.tsx create mode 100644 src/components/pages/tabs/WebcastsTab.tsx create mode 100644 src/store/baseQuery.ts create mode 100644 src/store/services/contentManager.service.ts create mode 100644 src/store/store.ts create mode 100644 src/types/routes.ts diff --git a/package-lock.json b/package-lock.json index b3ac5c5..26df0f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "@radix-ui/react-toggle": "^1.1.2", "@radix-ui/react-toggle-group": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.8", + "@reduxjs/toolkit": "^2.9.0", "@tailwindcss/postcss": "^4.1.12", "class-variance-authority": "^0.7.1", "clsx": "*", @@ -47,6 +48,7 @@ "react-day-picker": "^8.10.1", "react-dom": "^18.3.1", "react-hook-form": "^7.55.0", + "react-redux": "^9.2.0", "react-resizable-panels": "^2.1.7", "react-router-dom": "^7.8.2", "recharts": "^2.15.2", @@ -1941,6 +1943,32 @@ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "license": "MIT" }, + "node_modules/@reduxjs/toolkit": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.9.0.tgz", + "integrity": "sha512-fSfQlSRu9Z5yBkvsNhYF2rPS8cGXn/TZVrlwN1948QyZ8xMZ0JvP50S2acZNaf+o63u6aEeMjipFyksjIcWrog==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -2242,6 +2270,18 @@ "win32" ] }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@swc/core": { "version": "1.13.5", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.5.tgz", @@ -2829,6 +2869,12 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@vitejs/plugin-react-swc": { "version": "3.11.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.11.0.tgz", @@ -3225,6 +3271,16 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, + "node_modules/immer": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.3.tgz", + "integrity": "sha512-tmjF/k8QDKydUlm3mZU+tjM6zeq9/fFpPqH9SzWmBnVVKsPBg/V66qsMwb3/Bo90cgUN+ghdVBess+hPsxUyRw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/input-otp": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.2.tgz", @@ -3721,6 +3777,29 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "license": "MIT" }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-remove-scroll": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", @@ -3901,6 +3980,27 @@ "decimal.js-light": "^2.4.1" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, "node_modules/rollup": { "version": "4.50.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.0.tgz", diff --git a/package.json b/package.json index cb2633b..b279f85 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@radix-ui/react-toggle": "^1.1.2", "@radix-ui/react-toggle-group": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.8", + "@reduxjs/toolkit": "^2.9.0", "@tailwindcss/postcss": "^4.1.12", "class-variance-authority": "^0.7.1", "clsx": "*", @@ -42,6 +43,7 @@ "react-day-picker": "^8.10.1", "react-dom": "^18.3.1", "react-hook-form": "^7.55.0", + "react-redux": "^9.2.0", "react-resizable-panels": "^2.1.7", "react-router-dom": "^7.8.2", "recharts": "^2.15.2", diff --git a/src/App.tsx b/src/App.tsx index d630129..edf3507 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -39,44 +39,17 @@ import { Analytics } from "./components/pages/Analytics"; import { Toaster } from "./components/ui/sonner"; import { SESSION_CONFIG, AutoSaveData, mockNotifications, Notification, mockApprovalTasks, ApprovalTask } from "./data/mockData"; -type Route = - | "/login" - | "/login/forget-password" - | "/login/2fa" - | "/dashboard" - | "/profile" - | "/profile/reset-password" - | "/users/individual" - | "/users/organizations" - | "/users/organizations/new" - | "/content" - | "/content/blogs/new" - | "/content/faqs/new" - | "/courses" - | "/courses/course-builder" - | "/courses/new" - | `/courses/${string}/assign` - | "/profilers" - | "/profilers/new" - | "/profilers/preview" - | "/profiler-approval" - | "/landing-pages" - | "/landing-pages/edit/home" - | "/landing-pages/edit/services" - | "/landing-pages/edit/about-us" - | "/webinars" - | "/programmes" - | "/programmes/new" - | `/programmes/${string}/assign` - | "/class-scheduler" - | "/open-programme" - | "/facilities-360" - | "/facilities-360/new" - | `/facilities-360/${string}` - | "/admin/leads" - | "/admin/roles" - | "/admin/analytics" - | "/admin/profiler-master"; +import { Route } from "./types/routes"; +import { ViewFAQ } from "./components/pages/ViewFAQ"; +import { ViewBlog } from "./components/pages/ViewBlog"; +import { EditFAQ } from "./components/pages/EditFAQ"; +import { EditBlog } from "./components/pages/EditBlog"; +import { EditWebcast, WebcastEditPage } from "./components/pages/EditWebcast"; +import { EditTrainingMaterial } from "./components/pages/EditTrainingMaterial"; +import { EditReadingMaterial } from "./components/pages/EditReadingMaterial"; +import { EditPodcast } from "./components/pages/EditPodcast"; +import { EditCaseStudy } from "./components/pages/EditCaseStudy"; +import { EditKlcArchiveContent } from "./components/pages/EditKlcArchiveContent"; export default function App() { const [currentRoute, setCurrentRoute] = useState("/login"); @@ -114,7 +87,7 @@ export default function App() { ...prev, [routeKey]: formData })); - + // Persist to localStorage const autoSaveData: AutoSaveData = { userId: user.email, @@ -123,7 +96,7 @@ export default function App() { timestamp: new Date().toISOString(), isValid: true }; - + localStorage.setItem(`autosave_${routeKey}`, JSON.stringify(autoSaveData)); }; @@ -146,7 +119,7 @@ export default function App() { // Notification handlers const handleMarkNotificationAsRead = (notificationId: string) => { - setNotifications(prev => + setNotifications(prev => prev.map(n => n.id === notificationId ? { ...n, read: true } : n) ); }; @@ -250,7 +223,7 @@ export default function App() { // Set longer session timeout const expirationTime = Date.now() + SESSION_CONFIG.LOGOUT_TIMEOUT; localStorage.setItem("klc_auth_expiration", expirationTime.toString()); - + setIsAuthenticated(true); navigate("/dashboard"); }; @@ -258,19 +231,21 @@ export default function App() { const logout = () => { localStorage.removeItem("klc_auth_token"); localStorage.removeItem("klc_auth_expiration"); - + // Clear all auto-saved data for this user Object.keys(localStorage).forEach(key => { if (key.startsWith(`autosave_`) && key.includes(user.email)) { localStorage.removeItem(key); } }); - + setIsAuthenticated(false); setAutoSaveData({}); navigate("/login"); }; + // In your App.tsx, update the renderPage function: + const renderPage = () => { if (!isAuthenticated && !currentRoute.startsWith("/login")) { return ; @@ -314,6 +289,22 @@ export default function App() { return ; case "/content/faqs/new": return ; + case "/content/faqs": + return ; + case "/content/blogs": + return ; + + // ADD THESE ROUTES FOR WEBCASTS AND TRAINING MATERIALS + case "/content/webcasts": + return ; + case "/content/training-materials": + return ; + case "/content/reading-materials": + return ; + case "/content/podcasts": + return ; + + case "/courses": return ; case "/courses/course-builder": @@ -326,7 +317,6 @@ export default function App() { case "/profilers/preview": return navigate("/dashboard")} />; case "/profiler-approval": - // Get the approval task from sessionStorage const taskData = sessionStorage.getItem('currentApprovalTask'); const approvalTask = taskData ? JSON.parse(taskData) : approvalTasks[0]; return ( @@ -371,7 +361,62 @@ export default function App() { case "/admin/analytics": return ; default: - // Handle dynamic routes + // Handle dynamic routes - UPDATE THIS SECTION: + if (currentRoute.startsWith("/content/faqs/view/")) { + const faqId = currentRoute.split("/").pop(); + console.log('🔄 Rendering ViewFAQ with ID:', faqId); + return ; + } + if (currentRoute.startsWith("/content/blogs/view/")) { + const blogId = currentRoute.split("/").pop(); + console.log('🔄 Rendering ViewBlog with ID:', blogId); + return ; + } + if (currentRoute.startsWith("/content/faqs/edit/")) { + const faqId = currentRoute.split("/").pop(); + return ; + } + if (currentRoute.startsWith("/content/blogs/edit/")) { + const blogId = currentRoute.split("/").pop(); + return ; + } + + if (currentRoute.startsWith("/content/case-studies/edit/")) { + const caseStudyId = currentRoute.split("/").pop(); + return ; + } + + if (currentRoute.startsWith("/content/klc-archive/edit/")) { + const archiveId = currentRoute.split("/").pop(); + return ; + } + + if (currentRoute.startsWith("/content/webcasts/edit/")) { + const webcastId = currentRoute.split("/").pop(); + return ; + } + if (currentRoute.startsWith("/content/podcasts/edit/")) { + const podcastId = currentRoute.split("/").pop(); + return ; + } + if (currentRoute.startsWith("/content/training-materials/edit/")) { + const trainingMaterialId = currentRoute.split("/").pop(); + return ; + } + if (currentRoute.startsWith("/content/reading-materials/edit/")) { + const readingMaterialId = currentRoute.split("/").pop(); + return ; + } + + + if (currentRoute.startsWith("/content/faqs/edit/")) { + const faqId = currentRoute.split("/").pop(); + return ; + } + if (currentRoute.startsWith("/content/blogs/edit/")) { + const blogId = currentRoute.split("/").pop(); + return ; + } if (currentRoute.startsWith("/programmes/") && currentRoute.endsWith("/assign")) { const programmeId = currentRoute.split("/")[2]; return ; @@ -384,6 +429,7 @@ export default function App() { const tourId = currentRoute.split("/")[2]; return ; } + console.log('🚨 Route not found, falling back to dashboard:', currentRoute); return ; } }; diff --git a/src/components/auth/ForgotPassword.tsx b/src/components/auth/ForgotPassword.tsx index 021eb11..d05e535 100644 --- a/src/components/auth/ForgotPassword.tsx +++ b/src/components/auth/ForgotPassword.tsx @@ -6,9 +6,10 @@ import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'; import { Alert, AlertDescription } from '../ui/alert'; import { Eye, EyeOff, CheckCircle2, Loader2, AlertCircle } from 'lucide-react'; import klcLogoLight from 'figma:asset/1e150e43f238df3e08fcbf5d8f4899c233264e9f.png'; +import { Route } from '../../types/routes'; interface ForgotPasswordProps { - onNavigate: (route: string) => void; + onNavigate: (route: Route) => void; } type Step = 'request' | 'verify' | 'newPassword' | 'done'; diff --git a/src/components/auth/Login.tsx b/src/components/auth/Login.tsx index 8071164..b41b68b 100644 --- a/src/components/auth/Login.tsx +++ b/src/components/auth/Login.tsx @@ -6,10 +6,11 @@ import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'; import { Alert, AlertDescription } from '../ui/alert'; import { Eye, EyeOff, AlertCircle, Loader2 } from 'lucide-react'; import klcLogoLight from 'figma:asset/1e150e43f238df3e08fcbf5d8f4899c233264e9f.png'; +import { Route } from '../../types/routes'; interface LoginProps { onLogin: () => void; - onNavigate: (route: string) => void; + onNavigate: (route: Route) => void; } interface FormErrors { diff --git a/src/components/auth/TwoFactorAuth.tsx b/src/components/auth/TwoFactorAuth.tsx index b9bf816..5cd0dfd 100644 --- a/src/components/auth/TwoFactorAuth.tsx +++ b/src/components/auth/TwoFactorAuth.tsx @@ -6,10 +6,11 @@ import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'; import { Alert, AlertDescription } from '../ui/alert'; import { AlertCircle, Loader2 } from 'lucide-react'; import klcLogoLight from 'figma:asset/1e150e43f238df3e08fcbf5d8f4899c233264e9f.png'; +import { Route } from '../../types/routes'; interface TwoFactorAuthProps { onLogin: () => void; - onNavigate: (route: string) => void; + onNavigate: (route: Route) => void; } export function TwoFactorAuth({ onLogin, onNavigate }: TwoFactorAuthProps) { diff --git a/src/components/layout/AuthenticatedLayout.tsx b/src/components/layout/AuthenticatedLayout.tsx index c67e1a3..8c6d27a 100644 --- a/src/components/layout/AuthenticatedLayout.tsx +++ b/src/components/layout/AuthenticatedLayout.tsx @@ -38,7 +38,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from '../ui/dropdown-menu'; -import { toast } from "sonner@2.0.3"; +import { toast } from "sonner"; import klcLogoDark from 'figma:asset/af520440d0fb3ca587ea6a7b2e63956e028f6f37.png'; import { SESSION_CONFIG, AutoSaveData, mockNotifications } from '../../data/mockData'; diff --git a/src/components/pages/AboutUsEditor.tsx b/src/components/pages/AboutUsEditor.tsx index 534bebb..7ee73ee 100644 --- a/src/components/pages/AboutUsEditor.tsx +++ b/src/components/pages/AboutUsEditor.tsx @@ -22,9 +22,10 @@ import { MediaPicker } from '../landing-pages/MediaPicker'; import { PreviewModal } from '../landing-pages/PreviewModal'; import { VersionHistory } from '../landing-pages/VersionHistory'; import { AuditDrawer } from '../landing-pages/AuditDrawer'; +import { Route } from '../../types/routes'; interface AboutUsEditorProps { - onNavigate: (route: string) => void; + onNavigate: (route: Route) => void; onLogout: () => void; user: any; } diff --git a/src/components/pages/Analytics.tsx b/src/components/pages/Analytics.tsx index 8245a4e..b606d0b 100644 --- a/src/components/pages/Analytics.tsx +++ b/src/components/pages/Analytics.tsx @@ -67,9 +67,10 @@ import { Clock, CheckCircle2 } from 'lucide-react'; +import { Route } from '../../types/routes'; interface AnalyticsProps { - onNavigate: (route: string) => void; + onNavigate: (route: Route) => void; onLogout: () => void; user: any; } diff --git a/src/components/pages/ClassScheduler.tsx b/src/components/pages/ClassScheduler.tsx index 909bb84..edf1697 100644 --- a/src/components/pages/ClassScheduler.tsx +++ b/src/components/pages/ClassScheduler.tsx @@ -46,7 +46,7 @@ import { } from '../ui/dialog'; import { Checkbox } from '../ui/checkbox'; import { Separator } from '../ui/separator'; -import { toast } from "sonner@2.0.3"; +import { toast } from "sonner"; import { Calendar, Download, @@ -66,9 +66,10 @@ import { Check, Eye } from 'lucide-react'; +import { Route } from '../../types/routes'; interface ClassSchedulerProps { - onNavigate: (route: string) => void; + onNavigate: (route: Route) => void; onLogout: () => void; user: any; pickerMode?: boolean; diff --git a/src/components/pages/ContentManager.tsx b/src/components/pages/ContentManager.tsx index 7c4b589..15c06b1 100644 --- a/src/components/pages/ContentManager.tsx +++ b/src/components/pages/ContentManager.tsx @@ -1,100 +1,25 @@ -import React, { useState, useRef } from "react"; +import React, { useState } from "react"; import { AuthenticatedLayout } from "../layout/AuthenticatedLayout"; +import { Card, CardContent } from "../ui/card"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs"; import { - Card, - CardContent, - CardHeader, - CardTitle, -} from "../ui/card"; -import { Button } from "../ui/button"; -import { Input } from "../ui/input"; -import { Badge } from "../ui/badge"; -import { Label } from "../ui/label"; -import { Textarea } from "../ui/textarea"; -import { Progress } from "../ui/progress"; -import { - Tabs, - TabsContent, - TabsList, - TabsTrigger, -} from "../ui/tabs"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "../ui/table"; -import { - Sheet, - SheetContent, - SheetDescription, - SheetHeader, - SheetTitle, -} from "../ui/sheet"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from "../ui/dialog"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "../ui/select"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "../ui/dropdown-menu"; -import { Checkbox } from "../ui/checkbox"; -import { Separator } from "../ui/separator"; -import { - Search, - Plus, - Upload, - Filter, - MoreHorizontal, - Eye, - Edit, - Archive, - RotateCcw, - FileText, - Calendar, - ExternalLink, Target, + FileText, HelpCircle, Video, BookOpen, Mic, - - FolderOpen, - Settings, - Clock, - Download, - CheckCircle, - XCircle, - AlertCircle, - FileSpreadsheet, - ThumbsUp, - ThumbsDown, - MessageSquare, - Send, - History, - X, - Loader2, - ChevronLeft, - ChevronRight + FolderOpen } from "lucide-react"; -import { toast } from "sonner@2.0.3"; -import { mockProfilers } from "../../data/mockData"; +import { ProfilerTab } from "./tabs/ProfilerTab"; +import { BlogsTab } from "./tabs/BlogsTab"; +import { FAQTab } from "./tabs/FAQTab"; +import { WebcastsTab } from "./tabs/WebcastsTab"; +import { TrainingMaterialsTab } from "./tabs/TrainingMaterialsTab"; +import { ReadingMaterialsTab } from "./tabs/ReadingMaterialsTab"; +import { KLCContentArchiveTab } from "./tabs/KLCContentArchiveTab"; +import { CaseStudiesTab } from "./tabs/CaseStudiesTab"; +import { PodcastsTab } from "./tabs/PodcastsTab"; interface ContentManagerProps { onNavigate: (route: string) => void; @@ -102,91 +27,75 @@ interface ContentManagerProps { user: any; } -interface BulkUploadResult { - total: number; - success: number; - failed: number; - profilers: Array<{ - code: string; - title: string; - sectionsCount: number; - questionsCount: number; - issues: string[]; - }>; - errors: Array<{ row: number; error: string; }>; -} - -// Content type definitions const contentTabs = [ - { - id: "profiler", - label: "Profiler", - icon: Target, + { + id: "profiler", + label: "Profiler", + icon: Target, canCreate: true, primaryAction: "New Profiler" }, - { - id: "blogs", - label: "Blogs", - icon: FileText, + { + id: "blogs", + label: "Blogs", + icon: FileText, canCreate: true, primaryAction: "New Blog" }, - { - id: "faq", - label: "FAQ", - icon: HelpCircle, + { + id: "faq", + label: "FAQ", + icon: HelpCircle, canCreate: true, primaryAction: "New FAQ" }, - { - id: "webcasts", - label: "Webcasts", - icon: Video, + { + id: "webcasts", + label: "Webcasts", + icon: Video, canCreate: false, primaryAction: "Upload Webcast" }, - { - id: "training-materials", - label: "Training Materials", - icon: BookOpen, + { + id: "training-materials", + label: "Training Materials", + icon: BookOpen, canCreate: false, primaryAction: "Upload File", hasInnerTabs: true, innerTabs: ["Facilitator Manual", "Participant handouts", "To be printed (for KLC team)"] }, - { - id: "reading-materials", - label: "Reading Materials", - icon: BookOpen, + { + id: "reading-materials", + label: "Reading Materials", + icon: BookOpen, canCreate: false, primaryAction: "Upload Reading Material" }, - { - id: "podcasts", - label: "Podcasts", - icon: Mic, + { + id: "podcasts", + label: "Podcasts", + icon: Mic, canCreate: false, primaryAction: "Upload Podcast" }, - - { - id: "case-studies", - label: "Case Studies", - icon: FileText, + { + id: "case-studies", + label: "Case Studies", + icon: FileText, canCreate: false, primaryAction: "Upload Case Study" }, - { - id: "klc-content-archive", - label: "KLC Content Archive", - icon: FolderOpen, + { + id: "klc-content-archive", + label: "KLC Content Archive", + icon: FolderOpen, canCreate: false, primaryAction: "Upload File", hasInnerTabs: true, innerTabs: [ "Leadership Lego Blocks", - "Management Development Lego blocks", + "Management Development Lego blocks", "Consulting Lego Blocks", "Business Development", "KLC - facility-related", @@ -197,2191 +106,22 @@ const contentTabs = [ } ]; -// Enhanced profiler data with approval workflow -const enhancedProfilerData = (mockProfilers || []).map((profiler, index) => ({ - id: `prof_${profiler.id}`, - title: profiler.name || profiler.title || "Untitled Profiler", - type: index % 3 === 0 ? "Individual Self-Assessment" : - index % 3 === 1 ? "360 Feedback (self + multi-rater)" : "Group survey/poll", - status: index % 5 === 0 ? "Draft" : - index % 5 === 1 ? "In Review" : - index % 5 === 2 ? "Changes Requested" : - index % 5 === 3 ? "Approved" : "Published", - version: `v${Math.floor(Math.random() * 3) + 1}`, - updated: profiler.lastModified || new Date().toISOString(), - owner: profiler.createdBy || profiler.owner || "Unknown", - sectionsCount: Math.floor(Math.random() * 8) + 3, - questionsCount: { - likert: Math.floor(Math.random() * 20) + 5, - ipsative: Math.floor(Math.random() * 10) + 2, - trueFalse: Math.floor(Math.random() * 8), - matching: Math.floor(Math.random() * 5), - descriptive: Math.floor(Math.random() * 3) - }, - tags: profiler.tags || ["Leadership", "Assessment", "360 Feedback"].slice(0, Math.floor(Math.random() * 3) + 1), - raterGroups: index % 3 === 1 ? ["Self", "Manager", "Peer", "Direct Report"] : [], - anonymousGroups: index % 3 === 1 ? ["Manager", "Peer", "Direct Report"] : [], - randomizeSections: Math.random() > 0.5, - randomizeQuestions: Math.random() > 0.5, - keepStemSetsTogether: Math.random() > 0.5 -})); - -// Mock data for other content types -const mockContent = { - profiler: enhancedProfilerData, - blogs: [ - { - id: 1, - title: "Strategic Leadership in Digital Transformation", - type: "Blog", - tags: ["Leadership", "Digital"], - status: "Published", - version: "v1", - updated: "2024-01-15 09:20", - owner: "Prof. Sharma" - }, - { - id: 2, - title: "Building High-Performance Teams", - type: "Blog", - tags: ["Teams", "Performance"], - status: "Draft", - version: "v1", - updated: "2024-01-14 16:45", - owner: "Dr. Kumar" - } - ], - faq: [ - { - id: 1, - title: "How to enroll in leadership programs?", - type: "FAQ", - tags: ["Enrollment"], - status: "Published", - version: "v1", - updated: "2024-01-10 11:30", - owner: "Admin User" - } - ] -}; - -const profilerTypeOptions = [ - { value: "all", label: "All Types" }, - { value: "individual", label: "Individual Self-Assessment" }, - { value: "360-feedback", label: "360 Feedback (self + multi-rater)" }, - { value: "group-survey", label: "Group survey/poll" } -]; - -const profilerStatusOptions = [ - { value: "all", label: "All Status" }, - { value: "draft", label: "Draft" }, - { value: "in-review", label: "In Review" }, - { value: "changes-requested", label: "Changes Requested" }, - { value: "approved", label: "Approved" }, - { value: "published", label: "Published" }, - { value: "archived", label: "Archived" } -]; - -const ownerOptions = [ - { value: "all", label: "All Owners" }, - { value: "dr-rajesh", label: "Dr. Rajesh Mehta" }, - { value: "prof-sunita", label: "Prof. Sunita Agarwal" }, - { value: "dr-amit", label: "Dr. Amit Sharma" } -]; - export function ContentManager({ onNavigate, onLogout, user, }: ContentManagerProps) { const [activeTab, setActiveTab] = useState("profiler"); - const [activeInnerTab, setActiveInnerTab] = useState<{[key: string]: string}>({ + const [activeInnerTab, setActiveInnerTab] = useState<{ [key: string]: string }>({ "training-materials": "Facilitator Manual", "klc-content-archive": "Leadership Lego Blocks" }); - - // Search and filters - const [searchTerm, setSearchTerm] = useState(""); - const [typeFilter, setTypeFilter] = useState("all"); - const [statusFilter, setStatusFilter] = useState("all"); - const [ownerFilter, setOwnerFilter] = useState("all"); - const [dateFrom, setDateFrom] = useState(""); - const [dateTo, setDateTo] = useState(""); - - // Selection and interactions - const [selectedItems, setSelectedItems] = useState([]); - - // Sheet and dialog states - const [isEditSheetOpen, setIsEditSheetOpen] = useState(false); - const [isAuditSheetOpen, setIsAuditSheetOpen] = useState(false); - const [isBulkUploadOpen, setIsBulkUploadOpen] = useState(false); - const [isPreviewSheetOpen, setIsPreviewSheetOpen] = useState(false); - const [isApprovalDialogOpen, setIsApprovalDialogOpen] = useState(false); - - // Data states - const [editingItem, setEditingItem] = useState(null); - const [previewItem, setPreviewItem] = useState(null); - const [approvalItem, setApprovalItem] = useState(null); - const [bulkUploadResult, setBulkUploadResult] = useState(null); - const [bulkUploadProgress, setBulkUploadProgress] = useState(0); - const [uploadedFile, setUploadedFile] = useState(null); - const [autoSubmitEnabled, setAutoSubmitEnabled] = useState(false); - - // Form states - const [approvalComments, setApprovalComments] = useState(""); - const [formData, setFormData] = useState({ - title: "", - description: "", - question: "", - answer: "", - category: "", - tags: "", - file: null as File | null, - // Bulk upload for webcasts - files: [] as File[], - // Blog specific fields - bannerImage: null as File | null, - bannerImageAlt: "", - bodyContent: "", - seoTitle: "", - seoDescription: "", - slug: "" - }); - - const fileInputRef = useRef(null); const breadcrumbs = [ { label: "Content" } ]; const currentTab = contentTabs.find(tab => tab.id === activeTab); - const hasInnerTabs = currentTab?.hasInnerTabs; - const currentInnerTab = hasInnerTabs ? activeInnerTab[activeTab] : null; - - // Get content for current tab - const getCurrentContent = () => { - return (mockContent as any)[activeTab] || []; - }; - - // Filter profilers specifically - const getFilteredProfilers = () => { - if (activeTab !== "profiler") return getCurrentContent(); - - return enhancedProfilerData.filter(profiler => { - const searchLower = searchTerm.toLowerCase(); - const matchesSearch = (profiler.title || '').toLowerCase().includes(searchLower) || - (profiler.tags || []).some(tag => (tag || '').toLowerCase().includes(searchLower)); - const matchesType = typeFilter === "all" || - (typeFilter === "individual" && profiler.type === "Individual Self-Assessment") || - (typeFilter === "360-feedback" && profiler.type === "360 Feedback (self + multi-rater)") || - (typeFilter === "group-survey" && profiler.type === "Group survey/poll"); - const matchesStatus = statusFilter === "all" || - (profiler.status || '').toLowerCase().replace(" ", "-") === statusFilter; - const ownerLower = ownerFilter.replace("-", " ").toLowerCase(); - const matchesOwner = ownerFilter === "all" || - (profiler.owner || '').toLowerCase().includes(ownerLower); - - return matchesSearch && matchesType && matchesStatus && matchesOwner; - }); - }; - - const getStatusBadgeVariant = (status: string) => { - switch (status) { - case "Published": return "default"; - case "Approved": return "default"; - case "In Review": return "secondary"; - case "Draft": return "secondary"; - case "Changes Requested": return "outline"; - case "Archived": return "destructive"; - default: return "secondary"; - } - }; - - const getStatusIcon = (status: string) => { - switch (status) { - case "Published": - case "Approved": return ; - case "In Review": return ; - case "Changes Requested": return ; - default: return null; - } - }; - - const handleRowSelection = (id: string, checked: boolean) => { - if (checked) { - setSelectedItems([...selectedItems, id]); - } else { - setSelectedItems(selectedItems.filter(item => item !== id)); - } - }; - - const handleSelectAll = (checked: boolean) => { - if (checked) { - const allIds = getFilteredProfilers().map((item: any) => item.id); - setSelectedItems(allIds); - } else { - setSelectedItems([]); - } - }; - - // Bulk upload functionality - const handleBulkUploadFile = () => { - fileInputRef.current?.click(); - }; - - const handleFileUpload = async (event: React.ChangeEvent) => { - const file = event.target.files?.[0]; - if (!file) return; - - if (!file.name.endsWith('.xlsx') && !file.name.endsWith('.csv')) { - toast.error("Please upload a valid Excel (.xlsx) or CSV file"); - return; - } - - setUploadedFile(file); - setBulkUploadProgress(0); - setBulkUploadResult(null); - - // Simulate file processing - const processFile = () => { - const interval = setInterval(() => { - setBulkUploadProgress(prev => { - if (prev >= 100) { - clearInterval(interval); - // Simulate processing results - const mockResult: BulkUploadResult = { - total: 3, - success: 2, - failed: 1, - profilers: [ - { - code: "LEAD001", - title: "Leadership Assessment 2024", - sectionsCount: 5, - questionsCount: 45, - issues: [] - }, - { - code: "TEAM002", - title: "Team Dynamics Profiler", - sectionsCount: 4, - questionsCount: 32, - issues: [] - }, - { - code: "COMM003", - title: "Communication Skills Assessment", - sectionsCount: 0, - questionsCount: 0, - issues: ["Missing required sections", "Invalid question types"] - } - ], - errors: [ - { row: 45, error: "Missing required field: SectionName" }, - { row: 67, error: "Invalid QuestionType: MultipleSelect" } - ] - }; - setBulkUploadResult(mockResult); - toast.success(`Bulk upload completed. ${mockResult.success}/${mockResult.total} profilers processed successfully.`); - return 100; - } - return prev + 10; - }); - }, 200); - }; - - processFile(); - }; - - const downloadTemplate = () => { - toast.success("Bulk upload template downloaded"); - }; - - // Approval workflow functions - const handleSubmitForApproval = (profiler: any) => { - toast.success(`"${profiler.title}" submitted for approval`); - }; - - const handleApprove = (profiler: any) => { - setApprovalItem(profiler); - setApprovalComments(""); - setIsApprovalDialogOpen(true); - }; - - const handleRequestChanges = (profiler: any) => { - setApprovalItem(profiler); - setApprovalComments(""); - setIsApprovalDialogOpen(true); - }; - - const submitApproval = (approved: boolean) => { - if (!approvalItem) return; - - const action = approved ? "approved" : "requested changes for"; - toast.success(`Profiler ${action} successfully`); - setIsApprovalDialogOpen(false); - setApprovalItem(null); - setApprovalComments(""); - }; - - const handleEdit = (item?: any) => { - // Navigate to dedicated pages for blogs and FAQs - if (!item) { - if (activeTab === "blogs") { - onNavigate("/content/blogs/new"); - return; - } else if (activeTab === "faq") { - onNavigate("/content/faqs/new"); - return; - } - } - - if (item) { - setEditingItem(item); - setFormData({ - title: item.title || "", - description: item.description || "", - question: item.question || "", - answer: item.answer || "", - category: item.category || "", - tags: item.tags?.join(", ") || "", - file: null, - // Bulk upload for webcasts - files: [], - // Blog specific fields - bannerImage: null, - bannerImageAlt: item.bannerImageAlt || "", - bodyContent: item.bodyContent || "", - seoTitle: item.seoTitle || "", - seoDescription: item.seoDescription || "", - slug: item.slug || "" - }); - } else { - setEditingItem(null); - setFormData({ - title: "", - description: "", - question: "", - answer: "", - category: "", - tags: "", - file: null, - // Bulk upload for webcasts - files: [], - // Blog specific fields - bannerImage: null, - bannerImageAlt: "", - bodyContent: "", - seoTitle: "", - seoDescription: "", - slug: "" - }); - } - setIsEditSheetOpen(true); - }; - - const handleSave = () => { - if (!formData.title) { - toast.error("Title is required"); - return; - } - - if (activeTab === "faq" && !formData.question) { - toast.error("Question is required"); - return; - } - - if (activeTab === "faq" && !formData.answer) { - toast.error("Answer is required"); - return; - } - - // Blog-specific validations - if (activeTab === "blogs") { - if (!formData.bannerImage && !editingItem) { - toast.error("Blog banner image is required"); - return; - } - if (!formData.bannerImageAlt) { - toast.error("Banner image alt text is required"); - return; - } - if (!formData.bodyContent) { - toast.error("Blog body content is required"); - return; - } - if (!formData.slug) { - toast.error("Blog slug is required"); - return; - } - } - - // Special handling for webcast bulk upload - if (activeTab === "webcasts" && formData.files.length > 0) { - // Validate that each file has metadata - const filesWithMetadata = formData.files.map((file, index) => { - const titleInput = document.getElementById(`file-title-${index}`) as HTMLInputElement; - const descriptionInput = document.getElementById(`file-description-${index}`) as HTMLTextAreaElement; - const tagsInput = document.getElementById(`file-tags-${index}`) as HTMLInputElement; - - return { - file, - title: titleInput?.value || file.name.replace(/\.[^/.]+$/, ""), - description: descriptionInput?.value || "", - tags: tagsInput?.value || "" - }; - }); - - // Simulate bulk upload - toast.success(`${formData.files.length} webcast(s) uploaded successfully. You can now edit their details individually.`); - - // Reset form data - setFormData({ - title: "", - description: "", - question: "", - answer: "", - category: "", - tags: "", - file: null, - files: [], - bannerImage: null, - bannerImageAlt: "", - bodyContent: "", - seoTitle: "", - seoDescription: "", - slug: "" - }); - setIsEditSheetOpen(false); - return; - } - - if (!currentTab?.canCreate && !formData.file && formData.files.length === 0) { - toast.error("File is required for upload"); - return; - } - - // Simulate save - if (editingItem) { - toast.success("Changes saved successfully"); - } else { - if (currentTab?.canCreate) { - toast.success("Content created successfully"); - } else { - toast.success("File uploaded successfully. v1 created."); - } - } - - // Reset form data - setFormData({ - title: "", - description: "", - question: "", - answer: "", - category: "", - tags: "", - file: null, - files: [], - bannerImage: null, - bannerImageAlt: "", - bodyContent: "", - seoTitle: "", - seoDescription: "", - slug: "" - }); - - setIsEditSheetOpen(false); - setEditingItem(null); - }; - - const handlePublish = (item: any) => { - toast.success(`"${item.title}" published successfully`); - }; - - const handleArchive = (item: any) => { - toast.success(`"${item.title}" archived. You can restore it anytime.`); - }; - - const handleProfilerBuilder = (item?: any) => { - if (item) { - onNavigate(`/profilers/new?id=${item.id}`); - } else { - onNavigate("/profilers/new"); - } - }; - - const handlePreview = (item: any) => { - setPreviewItem(item); - setIsPreviewSheetOpen(true); - }; - - const handleAudit = (item: any) => { - setIsAuditSheetOpen(true); - }; - - const handleBulkAction = (action: string) => { - const count = selectedItems.length; - switch (action) { - case "submit-approval": - toast.success(`${count} profilers submitted for approval`); - break; - case "approve": - toast.success(`${count} profilers approved`); - break; - case "request-changes": - toast.success(`${count} profilers marked for changes`); - break; - case "publish": - toast.success(`${count} profilers published`); - break; - case "unpublish": - toast.success(`${count} profilers unpublished`); - break; - case "archive": - toast.success(`${count} profilers archived`); - break; - case "restore": - toast.success(`${count} profilers restored`); - break; - case "export": - toast.success(`${count} profilers exported`); - break; - } - setSelectedItems([]); - }; - - const renderProfilerToolbar = () => ( -
- {/* Search */} -
-
- - setSearchTerm(e.target.value)} - className="pl-10 min-h-[44px] focus-visible:ring-2 focus-visible:ring-[var(--color-brand-primary)] focus-visible:ring-opacity-50" - /> -
-
- - {/* Type Filter */} - - - {/* Status Filter */} - - - {/* Owner Filter */} - - - {/* Date Range */} -
- setDateFrom(e.target.value)} - className="w-[140px] min-h-[44px]" - placeholder="From" - /> - to - setDateTo(e.target.value)} - className="w-[140px] min-h-[44px]" - placeholder="To" - /> -
- - {/* Actions */} -
- - - -
-
- ); - - const renderStandardToolbar = () => ( -
- {/* Search */} -
-
- - setSearchTerm(e.target.value)} - className="pl-10 min-h-[44px] focus-visible:ring-2 focus-visible:ring-[var(--color-brand-primary)] focus-visible:ring-opacity-50" - /> -
-
- - {/* Filters */} - - - - - - - {/* Primary Actions */} -
- {currentTab && ( - - )} -
-
- ); - - const renderToolbar = () => { - return activeTab === "profiler" ? renderProfilerToolbar() : renderStandardToolbar(); - }; - - const renderBulkActions = () => { - if (selectedItems.length === 0) return null; - - const isProfiler = activeTab === "profiler"; - - return ( -
-
- {selectedItems.length} selected -
- {isProfiler && ( - <> - - - - - )} - - - - - {isProfiler && ( - - )} - -
-
-
- ); - }; - - const renderInnerTabs = () => { - if (!hasInnerTabs || !currentTab?.innerTabs) return null; - - return ( -
-
- {currentTab.innerTabs.map((innerTab) => ( - - ))} -
-
- ); - }; - - const renderProfilerTable = () => { - const content = getFilteredProfilers(); - - if (content.length === 0) { - return ( -
- -

No profilers yet — create one or bulk upload.

-
- - -
-
- ); - } - - return ( -
- - - - - 0} - onCheckedChange={handleSelectAll} - aria-label="Select all profilers" - /> - - Title - Type - Status - Version - Updated - Owner - Actions - - - - {content.map((profiler: any) => ( - - - handleRowSelection(profiler.id, checked as boolean)} - aria-label={`Select ${profiler.title}`} - /> - - -
-
{profiler.title}
-
- {profiler.tags.map((tag: string) => ( - - {tag} - - ))} -
-
-
- - - {profiler.type} - - - -
- {getStatusIcon(profiler.status)} - - {profiler.status} - -
-
- - {profiler.version} - - - {profiler.updated} - - {profiler.owner} - - - - - - - handleProfilerBuilder(profiler)}> - - Open in Builder - - {(profiler.status === "Draft" || profiler.status === "Changes Requested") && ( - handleSubmitForApproval(profiler)}> - - Submit for Approval - - )} - {profiler.status === "In Review" && user.role === "Super Admin" && ( - <> - handleApprove(profiler)}> - - Approve - - handleRequestChanges(profiler)}> - - Request Changes - - - )} - handlePublish(profiler)}> - - {profiler.status === "Published" ? "Unpublish" : "Publish"} - - handleArchive(profiler)}> - - Archive - - {}}> - - Restore - - handlePreview(profiler)}> - - Preview - - handleAudit(profiler)}> - - Audit - - - - -
- ))} -
-
- - {/* Pagination Footer */} -
-
- Showing {content.length} of {enhancedProfilerData.length} profilers -
-
- - -
-
-
- ); - }; - - const renderStandardTable = () => { - const content = getCurrentContent(); - - if (content.length === 0) { - return renderEmptyState(); - } - - return ( -
- - - - - 0} - onCheckedChange={handleSelectAll} - aria-label="Select all items" - /> - - Title - Type - Tags - Status - Version - Updated - Owner - Actions - - - - {content.map((item: any) => ( - - - handleRowSelection(item.id.toString(), checked as boolean)} - aria-label={`Select ${item.title}`} - /> - - {item.title} - {item.type} - -
- {item.tags?.map((tag: string) => ( - - {tag} - - ))} -
-
- - - {item.status} - - - - {item.version} - - - {item.updated} - - {item.owner} - - - - - - - {}}> - - Preview - - handleEdit(item)}> - - Edit - - handlePublish(item)}> - - {item.status === "Published" ? "Unpublish" : "Publish"} - - handleArchive(item)}> - - Archive - - setIsAuditSheetOpen(true)}> - - Audit - - - - -
- ))} -
-
-
- ); - }; - - const renderTable = () => { - return activeTab === "profiler" ? renderProfilerTable() : renderStandardTable(); - }; - - const renderEmptyState = () => { - const Icon = currentTab?.icon || FileText; - const isCreate = currentTab?.canCreate; - - let emptyMessage = ""; - let ctaText = ""; - - if (activeTab === "blogs") { - emptyMessage = "No blog posts yet — create your first one."; - ctaText = "New Blog"; - } else if (activeTab === "faq") { - emptyMessage = "No FAQs yet — add a question."; - ctaText = "New FAQ"; - } else if (activeTab === "profiler") { - emptyMessage = "No profilers yet — create your first one."; - ctaText = "New Profiler"; - } else { - emptyMessage = `No files yet — upload to get started.`; - ctaText = currentTab?.primaryAction || "Upload File"; - } - - return ( -
- -

{emptyMessage}

- -
- ); - }; - - const renderBulkUploadDrawer = () => ( - - - - Bulk Upload Profilers - - Upload multiple profilers using Excel or CSV files. One file can define multiple profilers. - - - -
- {/* Step 1: Download Template */} -
-

Step 1: Download Template

-
-
- -
-

Bulk Upload Template

-

- CSV/XLSX with required columns -

-
-
- -
- -
-

Required columns:

-
    -
  • ProfilerCode, ProfilerTitle, ProfilerType
  • -
  • SectionName, SectionOrder, QuestionOrder
  • -
  • QuestionType, QuestionPrompt
  • -
  • LikertScale, LeftAnchor, RightAnchor
  • -
  • IpsativeMode, Statement1, Statement2
  • -
  • MatchingLeft, MatchingRight
  • -
  • RaterGroups, AnonymousGroups (for 360)
  • -
  • Tags, RandomizeSections, RandomizeQuestions
  • -
-
-
- - - - {/* Step 2: Upload File */} -
-

Step 2: Upload File

-
- {uploadedFile ? ( -
- -

{uploadedFile.name}

-

- {(uploadedFile.size / 1024).toFixed(1)} KB -

- {bulkUploadProgress > 0 && bulkUploadProgress < 100 && ( -
- -

- Processing... {bulkUploadProgress}% -

-
- )} -
- ) : ( -
- -
-

Upload your file

-

- Supports .xlsx and .csv files -

-
- -
- )} -
-
- - {/* Step 3: Validation Results */} - {bulkUploadResult && ( - <> - -
-

Step 3: Validation Results

- -
-
-

{bulkUploadResult.total}

-

Total

-
-
-

{bulkUploadResult.success}

-

Valid

-
-
-

{bulkUploadResult.failed}

-

Issues

-
-
- - {/* Profiler Groups */} -
-

Profiler Groups:

- {bulkUploadResult.profilers.map((profiler, index) => ( -
0 ? 'bg-red-50 border-red-200' : 'bg-green-50 border-green-200' - }`}> -
-
-

{profiler.code}: {profiler.title}

-

- {profiler.sectionsCount} sections, {profiler.questionsCount} questions -

-
- {profiler.issues.length > 0 ? ( - - ) : ( - - )} -
- {profiler.issues.length > 0 && ( -
    - {profiler.issues.map((issue, i) => ( -
  • {issue}
  • - ))} -
- )} -
- ))} -
- - {bulkUploadResult.errors.length > 0 && ( -
-
-

Row Errors:

- -
-
- {bulkUploadResult.errors.map((error, index) => ( -
- Row {error.row}: {error.error} -
- ))} -
-
- )} -
- - )} - - {/* Step 4: Create Profilers */} - {bulkUploadResult && bulkUploadResult.success > 0 && ( - <> - -
-

Step 4: Create Profilers

- -
- setAutoSubmitEnabled(checked as boolean)} - /> - -
- -
- - -
-
- - )} -
- - -
-
- ); - - const renderPreviewDrawer = () => ( - - - - {previewItem?.title} - - Profiler preview and summary - - - - {previewItem && ( -
- {/* Basic Info */} -
-
- Type: -
- {previewItem.type} -
-
- -
- Status: -
- {getStatusIcon(previewItem.status)} - - {previewItem.status} - -
-
- -
- Sections: -
{previewItem.sectionsCount} sections
-
-
- - {/* Questions Summary */} -
-

Questions by Type

-
-
- Likert Scale: - {previewItem.questionsCount.likert} -
-
- Ipsative: - {previewItem.questionsCount.ipsative} -
-
- True/False: - {previewItem.questionsCount.trueFalse} -
-
- Matching: - {previewItem.questionsCount.matching} -
-
- Descriptive: - {previewItem.questionsCount.descriptive} -
-
-
- - {/* Randomization Settings */} -
-

Randomization Settings

-
-
- Randomize Sections: - - {previewItem.randomizeSections ? "Yes" : "No"} - -
-
- Randomize Questions: - - {previewItem.randomizeQuestions ? "Yes" : "No"} - -
-
- Keep Stem Sets Together: - - {previewItem.keepStemSetsTogether ? "Yes" : "No"} - -
-
-
- - {/* 360 Feedback Settings */} - {previewItem.type === "360 Feedback (self + multi-rater)" && ( -
-

360 Feedback Settings

-
-
- Rater Groups: -
- {previewItem.raterGroups.map((group: string) => ( - - {group} - - ))} -
-
-
- Anonymous Groups: -
- {previewItem.anonymousGroups.map((group: string) => ( - - {group} - - ))} -
-
-
-
- )} - - {/* Metadata */} -
-
- Version: -
- {previewItem.version} -
-
-
- Last Updated: -
{previewItem.updated}
-
-
- Owner: -
{previewItem.owner}
-
-
- - {/* Action Buttons */} -
- - -
- {(previewItem.status === "Draft" || previewItem.status === "Changes Requested") && ( - - )} - - {previewItem.status === "In Review" && user.role === "Super Admin" && ( - <> - - - - )} -
- - -
-
- )} -
-
- ); - - const renderApprovalDialog = () => ( - - - - Review Profiler - - {approvalItem ? `Review "${approvalItem.title}"` : "Review profiler submission"} - - - -
- {approvalItem && ( -
-
- -

{approvalItem.title}

-
-
- -

{approvalItem.type}

-
-
- -

{approvalItem.owner}

-
-
- )} - -
- -