content tab convert as submenu
This commit is contained in:
49
src/App.tsx
49
src/App.tsx
@@ -1,4 +1,3 @@
|
|||||||
// In App.tsx - FIXED VERSION
|
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { Login } from "./components/auth/Login";
|
import { Login } from "./components/auth/Login";
|
||||||
import { ForgotPassword } from "./components/auth/ForgotPassword";
|
import { ForgotPassword } from "./components/auth/ForgotPassword";
|
||||||
@@ -50,6 +49,7 @@ import { EditPodcast } from "./components/pages/EditPodcast";
|
|||||||
import { EditCaseStudy } from "./components/pages/EditCaseStudy";
|
import { EditCaseStudy } from "./components/pages/EditCaseStudy";
|
||||||
import { EditKlcArchiveContent } from "./components/pages/EditKlcArchiveContent";
|
import { EditKlcArchiveContent } from "./components/pages/EditKlcArchiveContent";
|
||||||
|
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [currentRoute, setCurrentRoute] = useState<Route>("/login");
|
const [currentRoute, setCurrentRoute] = useState<Route>("/login");
|
||||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||||
@@ -269,7 +269,11 @@ export default function App() {
|
|||||||
return <Organizations {...commonProps} />;
|
return <Organizations {...commonProps} />;
|
||||||
case "/users/organizations/new":
|
case "/users/organizations/new":
|
||||||
return <NewOrganization {...commonProps} />;
|
return <NewOrganization {...commonProps} />;
|
||||||
|
|
||||||
|
// Updated content routes section
|
||||||
|
// In App.tsx - Update the content routes section
|
||||||
case "/content":
|
case "/content":
|
||||||
|
case "/content/profiler":
|
||||||
case "/content/blogs":
|
case "/content/blogs":
|
||||||
case "/content/faqs":
|
case "/content/faqs":
|
||||||
case "/content/webcasts":
|
case "/content/webcasts":
|
||||||
@@ -277,13 +281,28 @@ export default function App() {
|
|||||||
case "/content/reading-materials":
|
case "/content/reading-materials":
|
||||||
case "/content/podcasts":
|
case "/content/podcasts":
|
||||||
case "/content/case-studies":
|
case "/content/case-studies":
|
||||||
case "/content/klc-content-archive":
|
case "/content/klc-archives":
|
||||||
console.log('🏠 App.tsx - Rendering ContentManager with route:', currentRoute);
|
console.log('🏠 App.tsx - Rendering ContentManager with route:', currentRoute);
|
||||||
return <ContentManager {...commonProps} />;
|
return <ContentManager {...commonProps} currentRoute={currentRoute} />;
|
||||||
|
|
||||||
|
// Add these new routes for content creation
|
||||||
case "/content/blogs/new":
|
case "/content/blogs/new":
|
||||||
return <NewBlog {...commonProps} />;
|
return <NewBlog {...commonProps} />;
|
||||||
case "/content/faqs/new":
|
case "/content/faqs/new":
|
||||||
return <NewFAQ {...commonProps} />;
|
return <NewFAQ {...commonProps} />;
|
||||||
|
// case "/content/webcasts/new":
|
||||||
|
// return <NewWebcast {...commonProps} />;
|
||||||
|
// case "/content/training-materials/new":
|
||||||
|
// return <NewTrainingMaterial {...commonProps} />;
|
||||||
|
// case "/content/reading-materials/new":
|
||||||
|
// return <NewReadingMaterial {...commonProps} />;
|
||||||
|
// case "/content/podcasts/new":
|
||||||
|
// return <NewPodcast {...commonProps} />;
|
||||||
|
// case "/content/case-studies/new":
|
||||||
|
// return <NewCaseStudy {...commonProps} />;
|
||||||
|
// case "/content/klc-archives/new":
|
||||||
|
// return <NewKlcArchive {...commonProps} />;
|
||||||
|
|
||||||
case "/courses":
|
case "/courses":
|
||||||
return <Courses {...commonProps} />;
|
return <Courses {...commonProps} />;
|
||||||
case "/courses/course-builder":
|
case "/courses/course-builder":
|
||||||
@@ -356,22 +375,10 @@ export default function App() {
|
|||||||
const blogId = currentRoute.split("/").pop();
|
const blogId = currentRoute.split("/").pop();
|
||||||
return <EditBlog {...commonProps} blogId={blogId} />;
|
return <EditBlog {...commonProps} blogId={blogId} />;
|
||||||
}
|
}
|
||||||
if (currentRoute.startsWith("/content/case-studies/edit/")) {
|
|
||||||
const caseStudyId = currentRoute.split("/").pop();
|
|
||||||
return <EditCaseStudy {...commonProps} caseStudyId={caseStudyId} />;
|
|
||||||
}
|
|
||||||
if (currentRoute.startsWith("/content/klc-archive/edit/")) {
|
|
||||||
const archiveId = currentRoute.split("/").pop();
|
|
||||||
return <EditKlcArchiveContent {...commonProps} archiveId={archiveId} />;
|
|
||||||
}
|
|
||||||
if (currentRoute.startsWith("/content/webcasts/edit/")) {
|
if (currentRoute.startsWith("/content/webcasts/edit/")) {
|
||||||
const webcastId = currentRoute.split("/").pop();
|
const webcastId = currentRoute.split("/").pop();
|
||||||
return <EditWebcast {...commonProps} webcastId={webcastId} />;
|
return <EditWebcast {...commonProps} webcastId={webcastId} />;
|
||||||
}
|
}
|
||||||
if (currentRoute.startsWith("/content/podcasts/edit/")) {
|
|
||||||
const podcastId = currentRoute.split("/").pop();
|
|
||||||
return <EditPodcast {...commonProps} podcastId={podcastId} />;
|
|
||||||
}
|
|
||||||
if (currentRoute.startsWith("/content/training-materials/edit/")) {
|
if (currentRoute.startsWith("/content/training-materials/edit/")) {
|
||||||
const trainingMaterialId = currentRoute.split("/").pop();
|
const trainingMaterialId = currentRoute.split("/").pop();
|
||||||
return <EditTrainingMaterial {...commonProps} trainingMaterialId={trainingMaterialId} />;
|
return <EditTrainingMaterial {...commonProps} trainingMaterialId={trainingMaterialId} />;
|
||||||
@@ -380,6 +387,18 @@ export default function App() {
|
|||||||
const readingMaterialId = currentRoute.split("/").pop();
|
const readingMaterialId = currentRoute.split("/").pop();
|
||||||
return <EditReadingMaterial {...commonProps} readingMaterialId={readingMaterialId} />;
|
return <EditReadingMaterial {...commonProps} readingMaterialId={readingMaterialId} />;
|
||||||
}
|
}
|
||||||
|
if (currentRoute.startsWith("/content/podcasts/edit/")) {
|
||||||
|
const podcastId = currentRoute.split("/").pop();
|
||||||
|
return <EditPodcast {...commonProps} podcastId={podcastId} />;
|
||||||
|
}
|
||||||
|
if (currentRoute.startsWith("/content/case-studies/edit/")) {
|
||||||
|
const caseStudyId = currentRoute.split("/").pop();
|
||||||
|
return <EditCaseStudy {...commonProps} caseStudyId={caseStudyId} />;
|
||||||
|
}
|
||||||
|
if (currentRoute.startsWith("/content/klc-archive/edit/")) {
|
||||||
|
const archiveId = currentRoute.split("/").pop();
|
||||||
|
return <EditKlcArchiveContent {...commonProps} archiveId={archiveId} />;
|
||||||
|
}
|
||||||
if (currentRoute.startsWith("/programmes/") && currentRoute.endsWith("/assign")) {
|
if (currentRoute.startsWith("/programmes/") && currentRoute.endsWith("/assign")) {
|
||||||
const programmeId = currentRoute.split("/")[2];
|
const programmeId = currentRoute.split("/")[2];
|
||||||
return <ProgrammeAssignment programmeId={programmeId} {...commonProps} />;
|
return <ProgrammeAssignment programmeId={programmeId} {...commonProps} />;
|
||||||
|
|||||||
@@ -20,7 +20,13 @@ import {
|
|||||||
Webcam,
|
Webcam,
|
||||||
ClipboardList,
|
ClipboardList,
|
||||||
ContactRound,
|
ContactRound,
|
||||||
Cog
|
Cog,
|
||||||
|
Video,
|
||||||
|
BookOpen,
|
||||||
|
Mic,
|
||||||
|
Archive,
|
||||||
|
ChevronDown,
|
||||||
|
Target
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
@@ -103,7 +109,63 @@ const navigationItems: NavigationItem[] = [
|
|||||||
id: 'content',
|
id: 'content',
|
||||||
label: 'Content',
|
label: 'Content',
|
||||||
icon: FileText,
|
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',
|
id: 'courses',
|
||||||
@@ -159,7 +221,6 @@ const navigationItems: NavigationItem[] = [
|
|||||||
icon: Cog,
|
icon: Cog,
|
||||||
route: '/admin/profiler-master'
|
route: '/admin/profiler-master'
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
id: 'roles',
|
id: 'roles',
|
||||||
label: 'Roles',
|
label: 'Roles',
|
||||||
@@ -275,6 +336,7 @@ export function AuthenticatedLayout({
|
|||||||
}: AuthenticatedLayoutProps) {
|
}: AuthenticatedLayoutProps) {
|
||||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||||
const [localNotifications, setLocalNotifications] = useState(notifications);
|
const [localNotifications, setLocalNotifications] = useState(notifications);
|
||||||
|
const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
// Initialize auto-save and session timeout
|
// Initialize auto-save and session timeout
|
||||||
useAutoSave(formData, onAutoSave, currentRoute, user.email);
|
useAutoSave(formData, onAutoSave, currentRoute, user.email);
|
||||||
@@ -285,6 +347,24 @@ export function AuthenticatedLayout({
|
|||||||
setLocalNotifications(notifications);
|
setLocalNotifications(notifications);
|
||||||
}, [notifications]);
|
}, [notifications]);
|
||||||
|
|
||||||
|
// Auto-expand parent items when on child routes
|
||||||
|
useEffect(() => {
|
||||||
|
const newExpandedItems = new Set<string>();
|
||||||
|
|
||||||
|
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
|
// Notification handlers
|
||||||
const handleMarkAsRead = (notificationId: string) => {
|
const handleMarkAsRead = (notificationId: string) => {
|
||||||
setLocalNotifications(prev =>
|
setLocalNotifications(prev =>
|
||||||
@@ -328,8 +408,17 @@ export function AuthenticatedLayout({
|
|||||||
|
|
||||||
const isActiveRoute = (route: string) => {
|
const isActiveRoute = (route: string) => {
|
||||||
if (!currentRoute || !route) return false;
|
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 => {
|
const getActiveParent = (items: NavigationItem[]): string | null => {
|
||||||
@@ -346,11 +435,38 @@ export function AuthenticatedLayout({
|
|||||||
|
|
||||||
const activeParent = getActiveParent(navigationItems);
|
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 renderNavigationItem = (item: NavigationItem, isChild = false) => {
|
||||||
const isActive = isActiveRoute(item.route);
|
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 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 (
|
return (
|
||||||
<div key={item.id}>
|
<div key={item.id}>
|
||||||
<Button
|
<Button
|
||||||
@@ -358,20 +474,27 @@ export function AuthenticatedLayout({
|
|||||||
className={`w-full justify-start min-h-[44px] ${
|
className={`w-full justify-start min-h-[44px] ${
|
||||||
isActive
|
isActive
|
||||||
? 'bg-primary text-primary-foreground'
|
? 'bg-primary text-primary-foreground'
|
||||||
: isParentActive && !isChild
|
: expanded && !isChild
|
||||||
? 'bg-accent text-accent-foreground'
|
? 'bg-accent text-accent-foreground'
|
||||||
: 'hover:bg-accent hover:text-accent-foreground'
|
: 'hover:bg-accent hover:text-accent-foreground'
|
||||||
} ${isChild ? 'ml-4 text-sm' : ''}`}
|
} ${isChild ? 'ml-4 text-sm' : ''} ${hasChildren ? 'pr-2' : ''}`}
|
||||||
onClick={() => onNavigate(item.route)}
|
onClick={handleItemClick}
|
||||||
>
|
>
|
||||||
<Icon className={`${sidebarCollapsed ? 'mx-auto' : 'mr-2'} h-4 w-4 flex-shrink-0`} />
|
<Icon className={`${sidebarCollapsed ? 'mx-auto' : 'mr-2'} h-4 w-4 flex-shrink-0`} />
|
||||||
{!sidebarCollapsed && (
|
{!sidebarCollapsed && (
|
||||||
<span className="truncate">{item.label}</span>
|
<>
|
||||||
|
<span className="truncate flex-1 text-left">{item.label}</span>
|
||||||
|
{hasChildren && (
|
||||||
|
<ChevronDown className={`h-4 w-4 ml-auto transition-transform ${
|
||||||
|
expanded ? 'rotate-180' : ''
|
||||||
|
}`} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{/* Render children if parent is active and not collapsed */}
|
{/* Render children if dropdown is expanded and not collapsed */}
|
||||||
{item.children && isParentActive && !sidebarCollapsed && (
|
{item.children && expanded && !sidebarCollapsed && (
|
||||||
<div className="mt-1 space-y-1">
|
<div className="mt-1 space-y-1">
|
||||||
{item.children.map(child => renderNavigationItem(child, true))}
|
{item.children.map(child => renderNavigationItem(child, true))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { AuthenticatedLayout } from "../layout/AuthenticatedLayout";
|
import { AuthenticatedLayout } from "../layout/AuthenticatedLayout";
|
||||||
import { Card, CardContent } from "../ui/card";
|
import { Card, CardContent } from "../ui/card";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
|
|
||||||
import {
|
import {
|
||||||
Target,
|
Target,
|
||||||
FileText,
|
FileText,
|
||||||
@@ -30,34 +29,38 @@ interface ContentManagerProps {
|
|||||||
currentRoute?: Route;
|
currentRoute?: Route;
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentTabs = [
|
const contentTypes = [
|
||||||
{
|
{
|
||||||
id: "profiler",
|
id: "profiler",
|
||||||
label: "Profiler",
|
label: "Profiler",
|
||||||
icon: Target,
|
icon: Target,
|
||||||
canCreate: true,
|
canCreate: true,
|
||||||
primaryAction: "New Profiler"
|
primaryAction: "New Profiler",
|
||||||
|
route: "/content"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "blogs",
|
id: "blogs",
|
||||||
label: "Blogs",
|
label: "Blogs",
|
||||||
icon: FileText,
|
icon: FileText,
|
||||||
canCreate: true,
|
canCreate: true,
|
||||||
primaryAction: "New Blog"
|
primaryAction: "New Blog",
|
||||||
|
route: "/content/blogs"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "faq", // This is the tab ID - note it's "faq" without 's'
|
id: "faqs",
|
||||||
label: "FAQ",
|
label: "FAQs",
|
||||||
icon: HelpCircle,
|
icon: HelpCircle,
|
||||||
canCreate: true,
|
canCreate: true,
|
||||||
primaryAction: "New FAQ"
|
primaryAction: "New FAQ",
|
||||||
|
route: "/content/faqs"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "webcasts",
|
id: "webcasts",
|
||||||
label: "Webcasts",
|
label: "Webcasts",
|
||||||
icon: Video,
|
icon: Video,
|
||||||
canCreate: false,
|
canCreate: false,
|
||||||
primaryAction: "Upload Webcast"
|
primaryAction: "Upload Webcast",
|
||||||
|
route: "/content/webcasts"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "training-materials",
|
id: "training-materials",
|
||||||
@@ -65,6 +68,7 @@ const contentTabs = [
|
|||||||
icon: BookOpen,
|
icon: BookOpen,
|
||||||
canCreate: false,
|
canCreate: false,
|
||||||
primaryAction: "Upload File",
|
primaryAction: "Upload File",
|
||||||
|
route: "/content/training-materials",
|
||||||
hasInnerTabs: true,
|
hasInnerTabs: true,
|
||||||
innerTabs: ["Facilitator Manual", "Participant handouts", "To be printed (for KLC team)"]
|
innerTabs: ["Facilitator Manual", "Participant handouts", "To be printed (for KLC team)"]
|
||||||
},
|
},
|
||||||
@@ -73,28 +77,32 @@ const contentTabs = [
|
|||||||
label: "Reading Materials",
|
label: "Reading Materials",
|
||||||
icon: BookOpen,
|
icon: BookOpen,
|
||||||
canCreate: false,
|
canCreate: false,
|
||||||
primaryAction: "Upload Reading Material"
|
primaryAction: "Upload Reading Material",
|
||||||
|
route: "/content/reading-materials"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "podcasts",
|
id: "podcasts",
|
||||||
label: "Podcasts",
|
label: "Podcasts",
|
||||||
icon: Mic,
|
icon: Mic,
|
||||||
canCreate: false,
|
canCreate: false,
|
||||||
primaryAction: "Upload Podcast"
|
primaryAction: "Upload Podcast",
|
||||||
|
route: "/content/podcasts"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "case-studies",
|
id: "case-studies",
|
||||||
label: "Case Studies",
|
label: "Case Studies",
|
||||||
icon: FileText,
|
icon: FileText,
|
||||||
canCreate: false,
|
canCreate: false,
|
||||||
primaryAction: "Upload Case Study"
|
primaryAction: "Upload Case Study",
|
||||||
|
route: "/content/case-studies"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "klc-content-archive",
|
id: "klc-archives",
|
||||||
label: "KLC Content Archive",
|
label: "KLC Archives",
|
||||||
icon: FolderOpen,
|
icon: FolderOpen,
|
||||||
canCreate: false,
|
canCreate: false,
|
||||||
primaryAction: "Upload File",
|
primaryAction: "Upload File",
|
||||||
|
route: "/content/klc-archives",
|
||||||
hasInnerTabs: true,
|
hasInnerTabs: true,
|
||||||
innerTabs: [
|
innerTabs: [
|
||||||
"Leadership Lego Blocks",
|
"Leadership Lego Blocks",
|
||||||
@@ -117,165 +125,131 @@ export function ContentManager({
|
|||||||
}: ContentManagerProps) {
|
}: ContentManagerProps) {
|
||||||
console.log('📦 ContentManager received currentRoute:', currentRoute);
|
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 }>({
|
const [activeInnerTab, setActiveInnerTab] = useState<{ [key: string]: string }>({
|
||||||
"training-materials": "Facilitator Manual",
|
"training-materials": "Facilitator Manual",
|
||||||
"klc-content-archive": "Leadership Lego Blocks"
|
"klc-archives": "Leadership Lego Blocks"
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
const getCurrentContentType = () => {
|
||||||
console.log('🔄 Route changed, updating active tab. Current route:', currentRoute);
|
console.log('🔄 getCurrentContentType called with currentRoute:', 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);
|
|
||||||
|
|
||||||
// Navigate to specific routes when tabs are clicked
|
// Find exact route match first
|
||||||
switch (tabId) {
|
const exactMatch = contentTypes.find(type => currentRoute === type.route);
|
||||||
case 'blogs':
|
if (exactMatch) {
|
||||||
onNavigate('/content/blogs');
|
console.log('✅ Exact route match found:', exactMatch.id);
|
||||||
break;
|
return exactMatch;
|
||||||
case 'faq': // This should be 'faq' to match the tab ID
|
}
|
||||||
onNavigate('/content/faqs');
|
|
||||||
break;
|
// Find partial match for nested routes
|
||||||
case 'webcasts':
|
const partialMatch = contentTypes.find(type =>
|
||||||
onNavigate('/content/webcasts');
|
currentRoute.startsWith(type.route + '/') ||
|
||||||
break;
|
currentRoute.includes(type.id)
|
||||||
case 'training-materials':
|
);
|
||||||
onNavigate('/content/training-materials');
|
|
||||||
break;
|
if (partialMatch) {
|
||||||
case 'reading-materials':
|
console.log('✅ Partial route match found:', partialMatch.id);
|
||||||
onNavigate('/content/reading-materials');
|
return partialMatch;
|
||||||
break;
|
}
|
||||||
case 'podcasts':
|
|
||||||
onNavigate('/content/podcasts');
|
console.log('⚠️ No match found, defaulting to profiler');
|
||||||
break;
|
return contentTypes[0]; // Default to profiler
|
||||||
case 'case-studies':
|
};
|
||||||
onNavigate('/content/case-studies');
|
|
||||||
break;
|
const currentContentType = getCurrentContentType();
|
||||||
case 'klc-content-archive':
|
console.log('📌 Current content type:', currentContentType.label);
|
||||||
onNavigate('/content/klc-content-archive');
|
|
||||||
break;
|
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 <ProfilerTab onNavigate={onNavigate} user={user} />;
|
||||||
|
|
||||||
|
case "blogs":
|
||||||
|
return <BlogsTab onNavigate={onNavigate} user={user} />;
|
||||||
|
|
||||||
|
case "faqs":
|
||||||
|
return <FAQTab onNavigate={onNavigate} user={user} />;
|
||||||
|
|
||||||
|
case "webcasts":
|
||||||
|
return <WebcastsTab onNavigate={onNavigate} user={user} />;
|
||||||
|
|
||||||
|
case "training-materials":
|
||||||
|
return (
|
||||||
|
<TrainingMaterialsTab
|
||||||
|
onNavigate={onNavigate}
|
||||||
|
user={user}
|
||||||
|
activeInnerTab={activeInnerTab}
|
||||||
|
setActiveInnerTab={setActiveInnerTab}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case "reading-materials":
|
||||||
|
return <ReadingMaterialsTab onNavigate={onNavigate} user={user} />;
|
||||||
|
|
||||||
|
case "podcasts":
|
||||||
|
return <PodcastsTab onNavigate={onNavigate} user={user} />;
|
||||||
|
|
||||||
|
case "case-studies":
|
||||||
|
return <CaseStudiesTab onNavigate={onNavigate} user={user} />;
|
||||||
|
|
||||||
|
case "klc-archives":
|
||||||
|
return (
|
||||||
|
<KLCContentArchiveTab
|
||||||
|
onNavigate={onNavigate}
|
||||||
|
user={user}
|
||||||
|
activeInnerTab={activeInnerTab}
|
||||||
|
setActiveInnerTab={setActiveInnerTab}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
onNavigate('/content');
|
console.log('⚠️ Defaulting to profiler tab');
|
||||||
|
return <ProfilerTab onNavigate={onNavigate} user={user} />;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log('📌 Current active tab:', activeTab);
|
|
||||||
}, [activeTab]);
|
|
||||||
|
|
||||||
const breadcrumbs = [
|
|
||||||
{ label: "Content" }
|
|
||||||
];
|
|
||||||
|
|
||||||
const currentTab = contentTabs.find(tab => tab.id === activeTab);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthenticatedLayout
|
<AuthenticatedLayout
|
||||||
currentRoute="/content"
|
currentRoute={currentRoute}
|
||||||
onNavigate={onNavigate}
|
onNavigate={onNavigate}
|
||||||
onLogout={onLogout}
|
onLogout={onLogout}
|
||||||
user={user}
|
user={user}
|
||||||
breadcrumbs={breadcrumbs}
|
breadcrumbs={getBreadcrumbs()}
|
||||||
>
|
>
|
||||||
<div className="p-6 space-y-6 max-w-[1440px] mx-auto">
|
<div className="p-6 space-y-6 max-w-[1440px] mx-auto">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1>Content Manager</h1>
|
<h1 className="text-3xl font-bold tracking-tight">
|
||||||
<p className="text-muted-foreground">
|
{currentContentType.label}
|
||||||
Manage all content types and profiler submissions with approval workflow
|
</h1>
|
||||||
|
<p className="text-muted-foreground mt-2">
|
||||||
|
{currentContentType.id === "profiler"
|
||||||
|
? "Manage all content types and profiler submissions with approval workflow"
|
||||||
|
: `Manage and organize ${currentContentType.label.toLowerCase()}`
|
||||||
|
}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Tabs value={activeTab} onValueChange={handleTabChange}>
|
<Card>
|
||||||
<TabsList className="grid w-full grid-cols-9 h-auto p-1">
|
<CardContent className="p-6">
|
||||||
{contentTabs.map((tab) => {
|
{renderContent()}
|
||||||
const Icon = tab.icon;
|
</CardContent>
|
||||||
return (
|
</Card>
|
||||||
<TabsTrigger
|
|
||||||
key={tab.id}
|
|
||||||
value={tab.id}
|
|
||||||
className="flex flex-col gap-1 py-3 px-2 text-xs data-[state=active]:bg-[var(--color-brand-primary)] data-[state=active]:text-white focus-visible:ring-2 focus-visible:ring-[var(--color-brand-primary)] focus-visible:ring-opacity-50"
|
|
||||||
>
|
|
||||||
<Icon className="h-4 w-4" />
|
|
||||||
<span className="hidden sm:block">{tab.label}</span>
|
|
||||||
</TabsTrigger>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</TabsList>
|
|
||||||
<Card>
|
|
||||||
<CardContent className="p-6">
|
|
||||||
<TabsContent value="profiler">
|
|
||||||
<ProfilerTab onNavigate={onNavigate} user={user} />
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="blogs">
|
|
||||||
<BlogsTab onNavigate={onNavigate} user={user} />
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="faq"> {/* This matches the tab ID */}
|
|
||||||
<FAQTab onNavigate={onNavigate} user={user} />
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="webcasts">
|
|
||||||
<WebcastsTab onNavigate={onNavigate} user={user} />
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="training-materials">
|
|
||||||
<TrainingMaterialsTab
|
|
||||||
onNavigate={onNavigate}
|
|
||||||
user={user}
|
|
||||||
activeInnerTab={activeInnerTab}
|
|
||||||
setActiveInnerTab={setActiveInnerTab}
|
|
||||||
/>
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="reading-materials">
|
|
||||||
<ReadingMaterialsTab onNavigate={onNavigate} user={user} />
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="podcasts">
|
|
||||||
<PodcastsTab onNavigate={onNavigate} user={user} />
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="case-studies">
|
|
||||||
<CaseStudiesTab onNavigate={onNavigate} user={user} />
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="klc-content-archive">
|
|
||||||
<KLCContentArchiveTab
|
|
||||||
onNavigate={onNavigate}
|
|
||||||
user={user}
|
|
||||||
activeInnerTab={activeInnerTab}
|
|
||||||
setActiveInnerTab={setActiveInnerTab}
|
|
||||||
/>
|
|
||||||
</TabsContent>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
</div>
|
||||||
</AuthenticatedLayout>
|
</AuthenticatedLayout>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ export function UploadDrawer({
|
|||||||
const config = contentTypeConfig[contentType];
|
const config = contentTypeConfig[contentType];
|
||||||
const Icon = config.icon;
|
const Icon = config.icon;
|
||||||
|
|
||||||
|
// Commenting out file upload functionality since we're only using URL input
|
||||||
|
/*
|
||||||
const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const selectedFiles = event.target.files;
|
const selectedFiles = event.target.files;
|
||||||
if (!selectedFiles) return;
|
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) => {
|
const handleFileUrlChange = (url: string) => {
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({
|
||||||
...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 = () => {
|
const handleAddTag = () => {
|
||||||
if (tagInput.trim() && !formData.tags.includes(tagInput.trim())) {
|
if (tagInput.trim() && !formData.tags.includes(tagInput.trim())) {
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({
|
||||||
@@ -180,13 +183,13 @@ export function UploadDrawer({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if either files or fileUrl are provided
|
// Check if fileUrl is provided (files upload is commented out)
|
||||||
if (formData.files.length === 0 && !formData.fileUrl) {
|
if (!formData.fileUrl) {
|
||||||
toast.error("Please select at least one file or add a file URL");
|
toast.error("Please add a file URL");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate fileUrl if provided
|
// Validate fileUrl
|
||||||
if (formData.fileUrl) {
|
if (formData.fileUrl) {
|
||||||
try {
|
try {
|
||||||
new URL(formData.fileUrl);
|
new URL(formData.fileUrl);
|
||||||
@@ -210,10 +213,6 @@ export function UploadDrawer({
|
|||||||
if (formData.fileUrl) {
|
if (formData.fileUrl) {
|
||||||
// Use the provided URL directly
|
// Use the provided URL directly
|
||||||
uploadData.fileUrl = formData.fileUrl.trim();
|
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
|
// 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
|
// Function to upload file to your server and get back the file URL
|
||||||
const uploadFileToServer = async (file: File): Promise<string> => {
|
const uploadFileToServer = async (file: File): Promise<string> => {
|
||||||
// TODO: Implement your actual file upload logic here
|
// 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));
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
// Reset form when drawer closes
|
// Reset form when drawer closes
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -324,13 +326,13 @@ export function UploadDrawer({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* File Upload Section */}
|
{/* File Upload Section - ONLY URL INPUT */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<Label>
|
<Label>
|
||||||
File URL <span className="text-destructive">*</span>
|
File URL <span className="text-destructive">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
|
|
||||||
{/* File URL Input */}
|
{/* File URL Input - PRIMARY METHOD: User provides a direct URL to the file */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Link className="h-4 w-4 text-green-600" />
|
<Link className="h-4 w-4 text-green-600" />
|
||||||
@@ -341,12 +343,14 @@ export function UploadDrawer({
|
|||||||
className="flex-1"
|
className="flex-1"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
{/* Commenting out the "Or upload a file below" text */}
|
||||||
|
{/* <p className="text-xs text-muted-foreground">
|
||||||
Or upload a file below
|
Or upload a file below
|
||||||
</p>
|
</p> */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* File Upload as alternative */}
|
{/* COMMENTING OUT FILE UPLOAD SECTION - Only using URL input */}
|
||||||
|
{/*
|
||||||
<div className="border-2 border-dashed border-muted-foreground/25 rounded-lg p-6 text-center">
|
<div className="border-2 border-dashed border-muted-foreground/25 rounded-lg p-6 text-center">
|
||||||
{formData.files.length > 0 ? (
|
{formData.files.length > 0 ? (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
@@ -410,6 +414,7 @@ export function UploadDrawer({
|
|||||||
className="hidden"
|
className="hidden"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
*/}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tags */}
|
{/* Tags */}
|
||||||
@@ -465,7 +470,7 @@ export function UploadDrawer({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleUpload}
|
onClick={handleUpload}
|
||||||
disabled={isUploading || !formData.title.trim() || (!formData.fileUrl && formData.files.length === 0)}
|
disabled={isUploading || !formData.title.trim() || !formData.fileUrl}
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
style={{ backgroundColor: "var(--color-brand-primary)" }}
|
style={{ backgroundColor: "var(--color-brand-primary)" }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export type Route =
|
|||||||
| "/users/organizations"
|
| "/users/organizations"
|
||||||
| "/users/organizations/new"
|
| "/users/organizations/new"
|
||||||
| "/content"
|
| "/content"
|
||||||
|
| "/content/profiler"
|
||||||
| "/content/blogs"
|
| "/content/blogs"
|
||||||
| "/content/blogs/new"
|
| "/content/blogs/new"
|
||||||
| `/content/blogs/edit/${string}`
|
| `/content/blogs/edit/${string}`
|
||||||
@@ -19,16 +20,22 @@ export type Route =
|
|||||||
| `/content/faqs/edit/${string}`
|
| `/content/faqs/edit/${string}`
|
||||||
| `/content/faqs/view/${string}`
|
| `/content/faqs/view/${string}`
|
||||||
| "/content/webcasts"
|
| "/content/webcasts"
|
||||||
| "/content/training-materials"
|
| "/content/webcasts/new" // Add this
|
||||||
| "/content/reading-materials"
|
|
||||||
| "/content/podcasts"
|
|
||||||
| "/content/case-studies"
|
|
||||||
| "/content/klc-content-archive"
|
|
||||||
| `/content/webcasts/edit/${string}`
|
| `/content/webcasts/edit/${string}`
|
||||||
|
| "/content/training-materials"
|
||||||
|
| "/content/training-materials/new" // Add this
|
||||||
| `/content/training-materials/edit/${string}`
|
| `/content/training-materials/edit/${string}`
|
||||||
|
| "/content/reading-materials"
|
||||||
|
| "/content/reading-materials/new" // Add this
|
||||||
| `/content/reading-materials/edit/${string}`
|
| `/content/reading-materials/edit/${string}`
|
||||||
|
| "/content/podcasts"
|
||||||
|
| "/content/podcasts/new" // Add this
|
||||||
| `/content/podcasts/edit/${string}`
|
| `/content/podcasts/edit/${string}`
|
||||||
|
| "/content/case-studies"
|
||||||
|
| "/content/case-studies/new" // Add this
|
||||||
| `/content/case-studies/edit/${string}`
|
| `/content/case-studies/edit/${string}`
|
||||||
|
| "/content/klc-archives" // Changed from klc-content-archive
|
||||||
|
| "/content/klc-archives/new" // Add this
|
||||||
| `/content/klc-archive/edit/${string}`
|
| `/content/klc-archive/edit/${string}`
|
||||||
| "/courses"
|
| "/courses"
|
||||||
| "/courses/course-builder"
|
| "/courses/course-builder"
|
||||||
|
|||||||
Reference in New Issue
Block a user