From d11112c8e9e7f54d685c7013e014238c6aefbd93 Mon Sep 17 00:00:00 2001 From: priyanshuvish Date: Fri, 26 Sep 2025 19:45:02 +0530 Subject: [PATCH] Replace src folder with new version --- src/App.tsx | 355 +- ...50e43f238df3e08fcbf5d8f4899c233264e9f.png} | Bin ...20440d0fb3ca587ea6a7b2e63956e028f6f37.png} | Bin src/components/IpsativeQuestionEditor.tsx | 461 ++ src/components/LikertQuestionEditor.tsx | 425 ++ src/components/auth/ForgotPassword.tsx | 689 ++- src/components/auth/Login.tsx | 260 +- src/components/auth/TwoFactorAuth.tsx | 321 +- src/components/landing-pages/AuditDrawer.tsx | 198 + .../landing-pages/InternalLinkPicker.tsx | 186 + src/components/landing-pages/MediaPicker.tsx | 270 + src/components/landing-pages/PreviewModal.tsx | 191 + .../landing-pages/VersionHistory.tsx | 148 + src/components/layout/AuthenticatedLayout.tsx | 346 +- src/components/pages/AboutUsEditor.tsx | 553 ++ src/components/pages/Analytics.tsx | 2059 ++++++-- src/components/pages/ClassScheduler.tsx | 1308 ++++- src/components/pages/ContentManager.tsx | 2613 +++++++++- src/components/pages/CourseAssignment.tsx | 806 +++ src/components/pages/CourseBuilder.tsx | 1959 ++++++- src/components/pages/CourseBuilderFixed.tsx | 1762 +++++++ src/components/pages/Courses.tsx | 1041 +++- src/components/pages/Dashboard.tsx | 997 ++-- src/components/pages/Facilities.tsx | 177 - src/components/pages/Facilities360.tsx | 438 ++ src/components/pages/Facilities360Detail.tsx | 791 +++ src/components/pages/Facilities360New.tsx | 1002 ++++ src/components/pages/HomeEditor.tsx | 665 +++ src/components/pages/IndividualLearners.tsx | 1238 ++++- src/components/pages/LandingPages.tsx | 1427 +++++- src/components/pages/LandingPagesNew.tsx | 132 + src/components/pages/Leads.tsx | 1911 ++++++- src/components/pages/LeadsEnhanced.tsx | 1766 +++++++ src/components/pages/NewBlog.tsx | 422 ++ src/components/pages/NewFAQ.tsx | 481 ++ src/components/pages/NewOrganization.tsx | 1462 ++++-- src/components/pages/OpenProgramme.tsx | 1547 +++++- src/components/pages/Organizations.tsx | 1509 +++++- src/components/pages/Profile.tsx | 918 ++-- src/components/pages/ProfilerBuilder.tsx | 4167 ++++++++++++++- src/components/pages/ProfilerMaster.tsx | 591 +++ src/components/pages/ProfilerPreview.tsx | 627 +++ src/components/pages/Profilers.tsx | 1772 ++++++- src/components/pages/ProfilersEnhanced.tsx | 593 +++ src/components/pages/ProfilersUpdated.tsx | 2260 ++++++++ src/components/pages/ProgrammeAssignment.tsx | 809 +++ src/components/pages/ProgrammeComposer.tsx | 2135 +++++++- src/components/pages/Programmes.tsx | 618 ++- src/components/pages/ResetPassword.tsx | 510 +- src/components/pages/Roles.tsx | 1352 ++++- .../pages/SectionConfigurationManager.tsx | 953 ++++ src/components/pages/ServicesEditor.tsx | 811 +++ src/components/pages/Webinars.tsx | 1836 ++++++- src/components/pages/WebinarsEnhanced.tsx | 1777 +++++++ src/components/ui/button.tsx | 18 +- src/components/ui/dialog.tsx | 30 +- src/components/ui/dropdown-menu.tsx | 4 +- src/components/ui/sheet.tsx | 30 +- src/components/ui/sidebar.tsx | 6 +- src/data/mockData.ts | 4194 +++++++++++++++ src/global.d.ts | 25 +- src/index.css | 4556 +++++++++++++++++ src/main.tsx | 14 +- src/styles/globals.css | 37 +- vite.config.ts | 4 +- 65 files changed, 57577 insertions(+), 4986 deletions(-) rename src/assets/{klc-logo-blue.png => 1e150e43f238df3e08fcbf5d8f4899c233264e9f.png} (100%) rename src/assets/{klc-logo-white.png => af520440d0fb3ca587ea6a7b2e63956e028f6f37.png} (100%) create mode 100644 src/components/IpsativeQuestionEditor.tsx create mode 100644 src/components/LikertQuestionEditor.tsx create mode 100644 src/components/landing-pages/AuditDrawer.tsx create mode 100644 src/components/landing-pages/InternalLinkPicker.tsx create mode 100644 src/components/landing-pages/MediaPicker.tsx create mode 100644 src/components/landing-pages/PreviewModal.tsx create mode 100644 src/components/landing-pages/VersionHistory.tsx create mode 100644 src/components/pages/AboutUsEditor.tsx create mode 100644 src/components/pages/CourseAssignment.tsx create mode 100644 src/components/pages/CourseBuilderFixed.tsx delete mode 100644 src/components/pages/Facilities.tsx create mode 100644 src/components/pages/Facilities360.tsx create mode 100644 src/components/pages/Facilities360Detail.tsx create mode 100644 src/components/pages/Facilities360New.tsx create mode 100644 src/components/pages/HomeEditor.tsx create mode 100644 src/components/pages/LandingPagesNew.tsx create mode 100644 src/components/pages/LeadsEnhanced.tsx create mode 100644 src/components/pages/NewBlog.tsx create mode 100644 src/components/pages/NewFAQ.tsx create mode 100644 src/components/pages/ProfilerMaster.tsx create mode 100644 src/components/pages/ProfilerPreview.tsx create mode 100644 src/components/pages/ProfilersEnhanced.tsx create mode 100644 src/components/pages/ProfilersUpdated.tsx create mode 100644 src/components/pages/ProgrammeAssignment.tsx create mode 100644 src/components/pages/SectionConfigurationManager.tsx create mode 100644 src/components/pages/ServicesEditor.tsx create mode 100644 src/components/pages/WebinarsEnhanced.tsx create mode 100644 src/data/mockData.ts create mode 100644 src/index.css diff --git a/src/App.tsx b/src/App.tsx index d24671a..a0a0211 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,4 @@ -import React, { useEffect, useState } from "react"; -import { Routes, Route, Navigate, useNavigate } from "react-router-dom"; +import React, { useState, useEffect } from "react"; import { Login } from "./components/auth/Login"; import { ForgotPassword } from "./components/auth/ForgotPassword"; import { TwoFactorAuth } from "./components/auth/TwoFactorAuth"; @@ -10,25 +9,76 @@ import { IndividualLearners } from "./components/pages/IndividualLearners"; import { Organizations } from "./components/pages/Organizations"; import { NewOrganization } from "./components/pages/NewOrganization"; import { ContentManager } from "./components/pages/ContentManager"; +import { NewBlog } from "./components/pages/NewBlog"; +import { NewFAQ } from "./components/pages/NewFAQ"; import { Courses } from "./components/pages/Courses"; import { CourseBuilder } from "./components/pages/CourseBuilder"; -import { Profilers } from "./components/pages/Profilers"; +import { Profilers } from "./components/pages/ProfilersEnhanced"; import { ProfilerBuilder } from "./components/pages/ProfilerBuilder"; -import { LandingPages } from "./components/pages/LandingPages"; +import { ProfilerMaster } from "./components/pages/ProfilerMaster"; +import { ProfilerPreview } from "./components/pages/ProfilerPreview"; + +import { LandingPages } from "./components/pages/LandingPagesNew"; +import { HomeEditor } from "./components/pages/HomeEditor"; +import { ServicesEditor } from "./components/pages/ServicesEditor"; +import { AboutUsEditor } from "./components/pages/AboutUsEditor"; import { Webinars } from "./components/pages/Webinars"; import { Programmes } from "./components/pages/Programmes"; import { ProgrammeComposer } from "./components/pages/ProgrammeComposer"; +import { ProgrammeAssignment } from "./components/pages/ProgrammeAssignment"; +import { CourseAssignment } from "./components/pages/CourseAssignment"; import { ClassScheduler } from "./components/pages/ClassScheduler"; import { OpenProgramme } from "./components/pages/OpenProgramme"; -import { Facilities } from "./components/pages/Facilities"; +import { Facilities360 } from "./components/pages/Facilities360"; +import { Facilities360New } from "./components/pages/Facilities360New"; +import { Facilities360Detail } from "./components/pages/Facilities360Detail"; import { Leads } from "./components/pages/Leads"; import { Roles } from "./components/pages/Roles"; import { Analytics } from "./components/pages/Analytics"; import { Toaster } from "./components/ui/sonner"; -import { Settings } from "lucide-react"; +import { SESSION_CONFIG, AutoSaveData } 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" + | "/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"; export default function App() { - const [isAuthenticated, setIsAuthenticated] = useState(null); // null = loading + const [currentRoute, setCurrentRoute] = useState("/login"); + const [isAuthenticated, setIsAuthenticated] = useState(false); const [user, setUser] = useState({ name: "Admin User", email: "admin@klc.edu", @@ -37,150 +87,189 @@ export default function App() { lastLogin: "2024-01-15 14:30", }); - const navigate = useNavigate(); + // Auto-save state management + const [autoSaveData, setAutoSaveData] = useState<{ [key: string]: any }>({}); + // Simulate authentication check useEffect(() => { const authToken = localStorage.getItem("klc_auth_token"); - setIsAuthenticated(!!authToken); // true if token exists, false otherwise + if (authToken) { + setIsAuthenticated(true); + if (currentRoute === "/login") { + setCurrentRoute("/dashboard"); + } + } }, []); + // Auto-save functionality + const handleAutoSave = (formData: any) => { + const routeKey = `${currentRoute}_${user.email}`; + setAutoSaveData(prev => ({ + ...prev, + [routeKey]: formData + })); + + // Persist to localStorage + const autoSaveData: AutoSaveData = { + userId: user.email, + route: currentRoute, + formData, + timestamp: new Date().toISOString(), + isValid: true + }; + + localStorage.setItem(`autosave_${routeKey}`, JSON.stringify(autoSaveData)); + }; + + // Get auto-saved data for current route + const getAutoSavedData = () => { + const routeKey = `${currentRoute}_${user.email}`; + return autoSaveData[routeKey] || null; + }; + + // Clear auto-saved data + const clearAutoSavedData = (route?: string) => { + const routeKey = route ? `${route}_${user.email}` : `${currentRoute}_${user.email}`; + setAutoSaveData(prev => { + const newData = { ...prev }; + delete newData[routeKey]; + return newData; + }); + localStorage.removeItem(`autosave_${routeKey}`); + }; + + const navigate = (route: Route) => { + setCurrentRoute(route); + }; + const login = () => { localStorage.setItem("klc_auth_token", "mock_token"); + // Set longer session timeout + const expirationTime = Date.now() + SESSION_CONFIG.LOGOUT_TIMEOUT; + localStorage.setItem("klc_auth_expiration", expirationTime.toString()); + setIsAuthenticated(true); navigate("/dashboard"); }; 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"); }; - if (isAuthenticated === null) { - // while checking localStorage - return
Loading...
; - } + const renderPage = () => { + if (!isAuthenticated && !currentRoute.startsWith("/login")) { + return ; + } + + const commonProps = { + onNavigate: navigate, + onLogout: logout, + user, + formData: getAutoSavedData(), + onAutoSave: handleAutoSave, + onClearAutoSave: clearAutoSavedData + }; + + switch (currentRoute) { + case "/login": + return ; + case "/login/forget-password": + return ; + case "/login/2fa": + return ; + case "/dashboard": + return ; + case "/profile": + return ; + case "/profile/reset-password": + return ; + case "/users/individual": + return ; + case "/users/organizations": + return ; + case "/users/organizations/new": + return ; + case "/content": + return ; + case "/content/blogs/new": + return ; + case "/content/faqs/new": + return ; + case "/courses": + return ; + case "/courses/course-builder": + case "/courses/new": + return ; + case "/profilers": + return ; + case "/profilers/new": + return ; + case "/profilers/preview": + return navigate("/dashboard")} />; + case "/admin/profiler-master": + return ; + case "/landing-pages": + return ; + case "/landing-pages/edit/home": + return ; + case "/landing-pages/edit/services": + return ; + case "/landing-pages/edit/about-us": + return ; + case "/webinars": + return ; + case "/programmes": + return ; + case "/programmes/new": + return ; + case "/class-scheduler": + return ; + case "/open-programme": + return ; + case "/facilities-360": + return ; + case "/facilities-360/new": + return ; + case "/admin/leads": + return ; + case "/admin/roles": + return ; + case "/admin/analytics": + return ; + default: + // Handle dynamic routes + if (currentRoute.startsWith("/programmes/") && currentRoute.endsWith("/assign")) { + const programmeId = currentRoute.split("/")[2]; + return ; + } + if (currentRoute.startsWith("/courses/") && currentRoute.endsWith("/assign")) { + const courseId = currentRoute.split("/")[2]; + return ; + } + if (currentRoute.startsWith("/facilities-360/") && !currentRoute.endsWith("/new")) { + const tourId = currentRoute.split("/")[2]; + return ; + } + return ; + } + }; return ( -
- - {/* Public Routes */} - } /> - } - /> - } - /> - - {/* Protected Routes */} - {isAuthenticated ? ( - <> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - {/* } - /> */} - - ) : ( - // Redirect if not authenticated - } /> - )} - - {/* Default Redirects */} - } /> - } /> - - +
+ {renderPage()} +
); -} +} \ No newline at end of file diff --git a/src/assets/klc-logo-blue.png b/src/assets/1e150e43f238df3e08fcbf5d8f4899c233264e9f.png similarity index 100% rename from src/assets/klc-logo-blue.png rename to src/assets/1e150e43f238df3e08fcbf5d8f4899c233264e9f.png diff --git a/src/assets/klc-logo-white.png b/src/assets/af520440d0fb3ca587ea6a7b2e63956e028f6f37.png similarity index 100% rename from src/assets/klc-logo-white.png rename to src/assets/af520440d0fb3ca587ea6a7b2e63956e028f6f37.png diff --git a/src/components/IpsativeQuestionEditor.tsx b/src/components/IpsativeQuestionEditor.tsx new file mode 100644 index 0000000..4ff154f --- /dev/null +++ b/src/components/IpsativeQuestionEditor.tsx @@ -0,0 +1,461 @@ +import React, { useState, useEffect } from 'react'; +import { Button } from './ui/button'; +import { Input } from './ui/input'; +import { Label } from './ui/label'; +import { Textarea } from './ui/textarea'; +import { Badge } from './ui/badge'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from './ui/select'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from './ui/dialog'; +import { Checkbox } from './ui/checkbox'; +import { Alert, AlertDescription } from './ui/alert'; +import { AlertCircle, ExternalLink } from 'lucide-react'; +import { toast } from "sonner@2.0.3"; + +interface IpsativeItem { + selfText: string; + feedbackText: string; + subDimensionId: string; + order: number; +} + +interface IpsativeQuestion { + id?: string; + ipsative?: { + question: string; + allowZeroSum: boolean; + statementCount?: number; + items: IpsativeItem[]; + }; +} + +interface IpsativeQuestionEditorProps { + isOpen: boolean; + onClose: () => void; + question: IpsativeQuestion | null; + sectionDefaultN: number; // Section default statement count (1-5) + subDimensions: { id: string; name: string; description?: string }[]; + onSave: (questionData: IpsativeQuestion) => void; +} + +export function IpsativeQuestionEditor({ + isOpen, + onClose, + question, + sectionDefaultN, + subDimensions, + onSave +}: IpsativeQuestionEditorProps) { + // Question-level state + const [questionText, setQuestionText] = useState(''); + const [allowZeroSum, setAllowZeroSum] = useState(false); + const [statementCount, setStatementCount] = useState(sectionDefaultN); + const [items, setItems] = useState([]); + const [showSyncPrompt, setShowSyncPrompt] = useState(false); + const [previousSectionDefault, setPreviousSectionDefault] = useState(sectionDefaultN); + + // Initialize form when question or modal opens + useEffect(() => { + if (isOpen) { + if (question?.ipsative) { + setQuestionText(question.ipsative.question || ''); + setAllowZeroSum(question.ipsative.allowZeroSum || false); + setStatementCount(question.ipsative.statementCount || sectionDefaultN); + setItems(question.ipsative.items || []); + } else { + // New question - use section defaults + setQuestionText(''); + setAllowZeroSum(false); + setStatementCount(sectionDefaultN); + setItems(Array.from({ length: sectionDefaultN }, (_, i) => ({ + selfText: '', + feedbackText: '', + subDimensionId: '', + order: i + 1 + }))); + } + setPreviousSectionDefault(sectionDefaultN); + } + }, [isOpen, question, sectionDefaultN]); + + // Handle section default change while modal is open + useEffect(() => { + if (isOpen && sectionDefaultN !== previousSectionDefault) { + // Only show sync prompt if question's N equals the old section default + if (statementCount === previousSectionDefault) { + setShowSyncPrompt(true); + } + setPreviousSectionDefault(sectionDefaultN); + } + }, [sectionDefaultN, previousSectionDefault, statementCount, isOpen]); + + // Update statement count and manage items + const updateStatementCount = (newN: number) => { + const oldN = statementCount; + setStatementCount(newN); + + if (newN > oldN) { + // Add blank items for new statements + const newItems = [...items]; + for (let i = oldN; i < newN; i++) { + newItems.push({ + selfText: '', + feedbackText: '', + subDimensionId: '', + order: i + 1 + }); + } + setItems(newItems); + } else if (newN < oldN) { + // Trim items from the end + setItems(items.slice(0, newN)); + } + + // Live announcement for accessibility + const announcement = `Statements set to ${newN}`; + const liveRegion = document.createElement('div'); + liveRegion.setAttribute('aria-live', 'polite'); + liveRegion.setAttribute('aria-atomic', 'true'); + liveRegion.className = 'sr-only'; + liveRegion.textContent = announcement; + document.body.appendChild(liveRegion); + setTimeout(() => document.body.removeChild(liveRegion), 1000); + }; + + // Auto-fill order utility + const autoFillOrders = () => { + const updatedItems = items.map((item, index) => ({ + ...item, + order: index + 1 + })); + setItems(updatedItems); + toast.success(`Auto-filled orders 1…${statementCount}`); + }; + + // Update item field + const updateItem = (index: number, field: keyof IpsativeItem, value: string | number) => { + const updatedItems = [...items]; + updatedItems[index] = { ...updatedItems[index], [field]: value }; + setItems(updatedItems); + }; + + // Validation + const validateForm = () => { + if (!questionText.trim()) { + toast.error('Question is required'); + return false; + } + + if (statementCount < 1 || statementCount > 5) { + toast.error('Number of statements must be between 1 and 5'); + return false; + } + + // Validate each statement + for (let i = 0; i < statementCount; i++) { + const item = items[i]; + if (!item.subDimensionId) { + toast.error(`Sub Dimension is required for statement ${i + 1}`); + return false; + } + } + + // Validate unique orders + const orders = items.slice(0, statementCount).map(item => item.order); + const uniqueOrders = new Set(orders); + if (uniqueOrders.size !== orders.length) { + toast.error('Order values must be unique'); + return false; + } + + return true; + }; + + // Handle save + const handleSave = () => { + if (!validateForm()) return; + + const questionData: IpsativeQuestion = { + id: question?.id, + ipsative: { + question: questionText.trim(), + allowZeroSum, + statementCount, + items: items.slice(0, statementCount) + } + }; + + onSave(questionData); + onClose(); + }; + + // Handle sync prompt + const handleSyncToSectionDefault = () => { + updateStatementCount(sectionDefaultN); + setShowSyncPrompt(false); + toast.success(`Updated to section default: ${sectionDefaultN} statements`); + }; + + return ( + <> + + + {/* Sticky Header */} + + + {question?.id ? 'Edit Ipsative Question' : 'Add Ipsative Question'} + + + Configure forced-choice statements for this question + + + + {/* Scrollable Body */} +
+
+ + {/* Sub Dimensions Validation Banner */} + {(!subDimensions || subDimensions.length === 0) && ( + + + + Add Sub Dimensions in Profiler Master to continue. + + + + )} + + {/* Top Area */} +
+ {/* Question Text */} +
+ +