From c08ecf6a4b463db4d3123fa029dd6726fb17c04e Mon Sep 17 00:00:00 2001 From: priyanshuvish Date: Mon, 10 Nov 2025 20:29:24 +0530 Subject: [PATCH] content tab convert as submenu --- src/App.tsx | 49 +++- src/components/layout/AuthenticatedLayout.tsx | 147 +++++++++- src/components/pages/ContentManager.tsx | 274 ++++++++---------- src/components/pages/shared/UploadDrawer.tsx | 47 +-- src/types/routes.ts | 17 +- 5 files changed, 331 insertions(+), 203 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 7a8fdae..a7672e9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,3 @@ -// In App.tsx - FIXED VERSION import React, { useState, useEffect } from "react"; import { Login } from "./components/auth/Login"; import { ForgotPassword } from "./components/auth/ForgotPassword"; @@ -50,6 +49,7 @@ 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"); const [isAuthenticated, setIsAuthenticated] = useState(false); @@ -269,7 +269,11 @@ export default function App() { return ; case "/users/organizations/new": return ; + + // Updated content routes section + // In App.tsx - Update the content routes section case "/content": + case "/content/profiler": case "/content/blogs": case "/content/faqs": case "/content/webcasts": @@ -277,13 +281,28 @@ export default function App() { case "/content/reading-materials": case "/content/podcasts": case "/content/case-studies": - case "/content/klc-content-archive": + case "/content/klc-archives": console.log('🏠 App.tsx - Rendering ContentManager with route:', currentRoute); - return ; + return ; + + // Add these new routes for content creation case "/content/blogs/new": return ; case "/content/faqs/new": return ; + // case "/content/webcasts/new": + // return ; + // case "/content/training-materials/new": + // return ; + // case "/content/reading-materials/new": + // return ; + // case "/content/podcasts/new": + // return ; + // case "/content/case-studies/new": + // return ; + // case "/content/klc-archives/new": + // return ; + case "/courses": return ; case "/courses/course-builder": @@ -356,22 +375,10 @@ export default function App() { 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 ; @@ -380,6 +387,18 @@ export default function App() { const readingMaterialId = currentRoute.split("/").pop(); return ; } + if (currentRoute.startsWith("/content/podcasts/edit/")) { + const podcastId = 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("/programmes/") && currentRoute.endsWith("/assign")) { const programmeId = currentRoute.split("/")[2]; return ; diff --git a/src/components/layout/AuthenticatedLayout.tsx b/src/components/layout/AuthenticatedLayout.tsx index 8c6d27a..179bb55 100644 --- a/src/components/layout/AuthenticatedLayout.tsx +++ b/src/components/layout/AuthenticatedLayout.tsx @@ -20,7 +20,13 @@ import { Webcam, ClipboardList, ContactRound, - Cog + Cog, + Video, + BookOpen, + Mic, + Archive, + ChevronDown, + Target } from 'lucide-react'; import { Breadcrumb, @@ -103,7 +109,63 @@ const navigationItems: NavigationItem[] = [ id: 'content', label: 'Content', icon: FileText, - route: '/content' + route: '/content', + children: [ + { + id: 'profiler', + label: 'Profiler', + icon: Target, + route: '/content' + }, + { + id: 'blogs', + label: 'Blogs', + icon: FileText, + route: '/content/blogs' + }, + { + id: 'faqs', + label: 'FAQs', + icon: FileText, + route: '/content/faqs' + }, + { + id: 'webcasts', + label: 'Webcasts', + icon: Video, + route: '/content/webcasts' + }, + { + id: 'training-materials', + label: 'Training Materials', + icon: BookOpen, + route: '/content/training-materials' + }, + { + id: 'reading-materials', + label: 'Reading Materials', + icon: BookOpen, + route: '/content/reading-materials' + }, + { + id: 'podcasts', + label: 'Podcasts', + icon: Mic, + route: '/content/podcasts' + }, + { + id: 'case-studies', + label: 'Case Studies', + icon: ClipboardList, + route: '/content/case-studies' + }, + { + id: 'klc-archives', + label: 'KLC Archives', + icon: Archive, + route: '/content/klc-archives' + } + ] }, { id: 'courses', @@ -159,7 +221,6 @@ const navigationItems: NavigationItem[] = [ icon: Cog, route: '/admin/profiler-master' }, - { id: 'roles', label: 'Roles', @@ -275,6 +336,7 @@ export function AuthenticatedLayout({ }: AuthenticatedLayoutProps) { const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const [localNotifications, setLocalNotifications] = useState(notifications); + const [expandedItems, setExpandedItems] = useState>(new Set()); // Initialize auto-save and session timeout useAutoSave(formData, onAutoSave, currentRoute, user.email); @@ -285,6 +347,24 @@ export function AuthenticatedLayout({ setLocalNotifications(notifications); }, [notifications]); + // Auto-expand parent items when on child routes + useEffect(() => { + const newExpandedItems = new Set(); + + navigationItems.forEach(item => { + if (item.children) { + const isChildActive = item.children.some(child => + isActiveRoute(child.route) + ); + if (isChildActive) { + newExpandedItems.add(item.id); + } + } + }); + + setExpandedItems(newExpandedItems); + }, [currentRoute]); + // Notification handlers const handleMarkAsRead = (notificationId: string) => { setLocalNotifications(prev => @@ -328,8 +408,17 @@ export function AuthenticatedLayout({ const isActiveRoute = (route: string) => { if (!currentRoute || !route) return false; - return currentRoute === route || - (route === '/users/individual' && currentRoute.startsWith('/users/')); + + // Exact match + if (currentRoute === route) return true; + + // User routes + if (route === '/users/individual' && currentRoute.startsWith('/users/')) return true; + + // Content routes - match both parent and children + if (route === '/content' && currentRoute.startsWith('/content/')) return true; + + return false; }; const getActiveParent = (items: NavigationItem[]): string | null => { @@ -346,11 +435,38 @@ export function AuthenticatedLayout({ const activeParent = getActiveParent(navigationItems); + const toggleExpanded = (itemId: string) => { + const newExpandedItems = new Set(expandedItems); + if (newExpandedItems.has(itemId)) { + newExpandedItems.delete(itemId); + } else { + newExpandedItems.add(itemId); + } + setExpandedItems(newExpandedItems); + }; + + const isExpanded = (itemId: string) => expandedItems.has(itemId); + const renderNavigationItem = (item: NavigationItem, isChild = false) => { const isActive = isActiveRoute(item.route); - const isParentActive = activeParent === item.id; + const hasChildren = item.children && item.children.length > 0; + const expanded = isExpanded(item.id); const Icon = item.icon; + const handleItemClick = () => { + if (hasChildren && !isChild) { + // Toggle dropdown for parent items + toggleExpanded(item.id); + // If not active and has children, navigate to first child + if (!isActive && item.children && item.children.length > 0) { + onNavigate(item.children[0].route); + } + } else { + // Navigate directly for child items or items without children + onNavigate(item.route); + } + }; + return (
- {/* Render children if parent is active and not collapsed */} - {item.children && isParentActive && !sidebarCollapsed && ( + {/* Render children if dropdown is expanded and not collapsed */} + {item.children && expanded && !sidebarCollapsed && (
{item.children.map(child => renderNavigationItem(child, true))}
diff --git a/src/components/pages/ContentManager.tsx b/src/components/pages/ContentManager.tsx index 9440ecb..819b1f0 100644 --- a/src/components/pages/ContentManager.tsx +++ b/src/components/pages/ContentManager.tsx @@ -2,7 +2,6 @@ import React, { useState, useEffect } from "react"; import { AuthenticatedLayout } from "../layout/AuthenticatedLayout"; import { Card, CardContent } from "../ui/card"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs"; import { Target, FileText, @@ -30,34 +29,38 @@ interface ContentManagerProps { currentRoute?: Route; } -const contentTabs = [ +const contentTypes = [ { id: "profiler", label: "Profiler", icon: Target, canCreate: true, - primaryAction: "New Profiler" + primaryAction: "New Profiler", + route: "/content" }, { id: "blogs", label: "Blogs", icon: FileText, canCreate: true, - primaryAction: "New Blog" + primaryAction: "New Blog", + route: "/content/blogs" }, { - id: "faq", // This is the tab ID - note it's "faq" without 's' - label: "FAQ", + id: "faqs", + label: "FAQs", icon: HelpCircle, canCreate: true, - primaryAction: "New FAQ" + primaryAction: "New FAQ", + route: "/content/faqs" }, { id: "webcasts", label: "Webcasts", icon: Video, canCreate: false, - primaryAction: "Upload Webcast" + primaryAction: "Upload Webcast", + route: "/content/webcasts" }, { id: "training-materials", @@ -65,6 +68,7 @@ const contentTabs = [ icon: BookOpen, canCreate: false, primaryAction: "Upload File", + route: "/content/training-materials", hasInnerTabs: true, innerTabs: ["Facilitator Manual", "Participant handouts", "To be printed (for KLC team)"] }, @@ -73,28 +77,32 @@ const contentTabs = [ label: "Reading Materials", icon: BookOpen, canCreate: false, - primaryAction: "Upload Reading Material" + primaryAction: "Upload Reading Material", + route: "/content/reading-materials" }, { id: "podcasts", label: "Podcasts", icon: Mic, canCreate: false, - primaryAction: "Upload Podcast" + primaryAction: "Upload Podcast", + route: "/content/podcasts" }, { id: "case-studies", label: "Case Studies", icon: FileText, canCreate: false, - primaryAction: "Upload Case Study" + primaryAction: "Upload Case Study", + route: "/content/case-studies" }, { - id: "klc-content-archive", - label: "KLC Content Archive", + id: "klc-archives", + label: "KLC Archives", icon: FolderOpen, canCreate: false, primaryAction: "Upload File", + route: "/content/klc-archives", hasInnerTabs: true, innerTabs: [ "Leadership Lego Blocks", @@ -117,165 +125,131 @@ export function ContentManager({ }: ContentManagerProps) { console.log('📦 ContentManager received currentRoute:', currentRoute); - const getInitialTab = () => { - console.log('🔄 getInitialTab called with currentRoute:', currentRoute); - - // Map routes to tab IDs correctly - if (currentRoute.includes('/content/blogs')) return 'blogs'; - if (currentRoute.includes('/content/faqs')) return 'faq'; // This should be 'faq' to match the tab ID - if (currentRoute.includes('/content/webcasts')) return 'webcasts'; - if (currentRoute.includes('/content/training-materials')) return 'training-materials'; - if (currentRoute.includes('/content/reading-materials')) return 'reading-materials'; - if (currentRoute.includes('/content/podcasts')) return 'podcasts'; - if (currentRoute.includes('/content/case-studies')) return 'case-studies'; - if (currentRoute.includes('/content/klc-content-archive')) return 'klc-content-archive'; - - console.log('📌 Defaulting to profiler tab'); - return 'profiler'; - }; - - const [activeTab, setActiveTab] = useState(getInitialTab()); const [activeInnerTab, setActiveInnerTab] = useState<{ [key: string]: string }>({ "training-materials": "Facilitator Manual", - "klc-content-archive": "Leadership Lego Blocks" + "klc-archives": "Leadership Lego Blocks" }); - useEffect(() => { - console.log('🔄 Route changed, updating active tab. Current route:', currentRoute); - const newTab = getInitialTab(); - console.log('📌 Setting active tab to:', newTab); - setActiveTab(newTab); - }, [currentRoute]); - - const handleTabChange = (tabId: string) => { - console.log('🔄 Tab changed to:', tabId); - setActiveTab(tabId); + const getCurrentContentType = () => { + console.log('🔄 getCurrentContentType called with currentRoute:', currentRoute); - // Navigate to specific routes when tabs are clicked - switch (tabId) { - case 'blogs': - onNavigate('/content/blogs'); - break; - case 'faq': // This should be 'faq' to match the tab ID - onNavigate('/content/faqs'); - break; - case 'webcasts': - onNavigate('/content/webcasts'); - break; - case 'training-materials': - onNavigate('/content/training-materials'); - break; - case 'reading-materials': - onNavigate('/content/reading-materials'); - break; - case 'podcasts': - onNavigate('/content/podcasts'); - break; - case 'case-studies': - onNavigate('/content/case-studies'); - break; - case 'klc-content-archive': - onNavigate('/content/klc-content-archive'); - break; + // Find exact route match first + const exactMatch = contentTypes.find(type => currentRoute === type.route); + if (exactMatch) { + console.log('✅ Exact route match found:', exactMatch.id); + return exactMatch; + } + + // Find partial match for nested routes + const partialMatch = contentTypes.find(type => + currentRoute.startsWith(type.route + '/') || + currentRoute.includes(type.id) + ); + + if (partialMatch) { + console.log('✅ Partial route match found:', partialMatch.id); + return partialMatch; + } + + console.log('⚠️ No match found, defaulting to profiler'); + return contentTypes[0]; // Default to profiler + }; + + const currentContentType = getCurrentContentType(); + console.log('📌 Current content type:', currentContentType.label); + + const getBreadcrumbs = () => { + const breadcrumbs = [ + { label: "Content", href: "/content" } + ]; + + // Add specific content type to breadcrumbs if not on the main content page + if (currentRoute !== "/content") { + breadcrumbs.push({ label: currentContentType.label }); + } + + return breadcrumbs; + }; + + const renderContent = () => { + console.log('🎨 Rendering content for:', currentContentType.id); + + switch (currentContentType.id) { + case "profiler": + return ; + + case "blogs": + return ; + + case "faqs": + return ; + + case "webcasts": + return ; + + case "training-materials": + return ( + + ); + + case "reading-materials": + return ; + + case "podcasts": + return ; + + case "case-studies": + return ; + + case "klc-archives": + return ( + + ); + default: - onNavigate('/content'); + console.log('⚠️ Defaulting to profiler tab'); + return ; } }; - useEffect(() => { - console.log('📌 Current active tab:', activeTab); - }, [activeTab]); - - const breadcrumbs = [ - { label: "Content" } - ]; - - const currentTab = contentTabs.find(tab => tab.id === activeTab); - return (
-

Content Manager

-

- Manage all content types and profiler submissions with approval workflow +

+ {currentContentType.label} +

+

+ {currentContentType.id === "profiler" + ? "Manage all content types and profiler submissions with approval workflow" + : `Manage and organize ${currentContentType.label.toLowerCase()}` + }

- - - {contentTabs.map((tab) => { - const Icon = tab.icon; - return ( - - - {tab.label} - - ); - })} - - - - - - - - - - - - {/* This matches the tab ID */} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + {renderContent()} + +
); diff --git a/src/components/pages/shared/UploadDrawer.tsx b/src/components/pages/shared/UploadDrawer.tsx index 14166ff..f7ca80a 100644 --- a/src/components/pages/shared/UploadDrawer.tsx +++ b/src/components/pages/shared/UploadDrawer.tsx @@ -100,6 +100,8 @@ export function UploadDrawer({ const config = contentTypeConfig[contentType]; const Icon = config.icon; + // Commenting out file upload functionality since we're only using URL input + /* const handleFileSelect = (event: React.ChangeEvent) => { const selectedFiles = event.target.files; if (!selectedFiles) return; @@ -123,6 +125,14 @@ export function UploadDrawer({ } }; + const handleRemoveFile = (index: number) => { + setFormData(prev => ({ + ...prev, + files: prev.files.filter((_, i) => i !== index) + })); + }; + */ + const handleFileUrlChange = (url: string) => { setFormData(prev => ({ ...prev, @@ -138,13 +148,6 @@ export function UploadDrawer({ } }; - const handleRemoveFile = (index: number) => { - setFormData(prev => ({ - ...prev, - files: prev.files.filter((_, i) => i !== index) - })); - }; - const handleAddTag = () => { if (tagInput.trim() && !formData.tags.includes(tagInput.trim())) { setFormData(prev => ({ @@ -180,13 +183,13 @@ export function UploadDrawer({ return; } - // Check if either files or fileUrl are provided - if (formData.files.length === 0 && !formData.fileUrl) { - toast.error("Please select at least one file or add a file URL"); + // Check if fileUrl is provided (files upload is commented out) + if (!formData.fileUrl) { + toast.error("Please add a file URL"); return; } - // Validate fileUrl if provided + // Validate fileUrl if (formData.fileUrl) { try { new URL(formData.fileUrl); @@ -210,10 +213,6 @@ export function UploadDrawer({ if (formData.fileUrl) { // Use the provided URL directly uploadData.fileUrl = formData.fileUrl.trim(); - } else if (formData.files.length > 0) { - // If files are uploaded, simulate upload and get URL - const fileUrl = await uploadFileToServer(formData.files[0]); - uploadData.fileUrl = fileUrl; } // Validate that we have a fileUrl @@ -246,6 +245,8 @@ export function UploadDrawer({ } }; + // Commenting out file upload server function since we're only using URL input + /* // Function to upload file to your server and get back the file URL const uploadFileToServer = async (file: File): Promise => { // TODO: Implement your actual file upload logic here @@ -268,6 +269,7 @@ export function UploadDrawer({ const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }; + */ // Reset form when drawer closes React.useEffect(() => { @@ -324,13 +326,13 @@ export function UploadDrawer({ />
- {/* File Upload Section */} + {/* File Upload Section - ONLY URL INPUT */}
- {/* File URL Input */} + {/* File URL Input - PRIMARY METHOD: User provides a direct URL to the file */}
@@ -341,12 +343,14 @@ export function UploadDrawer({ className="flex-1" />
-

+ {/* Commenting out the "Or upload a file below" text */} + {/*

Or upload a file below -

+

*/}
- {/* File Upload as alternative */} + {/* COMMENTING OUT FILE UPLOAD SECTION - Only using URL input */} + {/*
{formData.files.length > 0 ? (
@@ -410,6 +414,7 @@ export function UploadDrawer({ className="hidden" />
+ */}
{/* Tags */} @@ -465,7 +470,7 @@ export function UploadDrawer({