/** * Shared layout components for consistent page structure. */ import React, { useState } from "react"; import { Link, useLocation, useNavigate } from "react-router-dom"; import { Briefcase, Home, LucideIcon, Menu, Settings, Shield } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger, } from "@/components/ui/sheet"; import { cn } from "@/lib/utils"; // ============================================================================ // Page Header // ============================================================================ const navLinks = [ { to: "/", label: "Dashboard", icon: Home }, { to: "/visa-sponsors", label: "Visa Sponsors", icon: Shield }, { to: "/ukvisajobs", label: "UK Visa Jobs", icon: Briefcase }, { to: "/settings", label: "Settings", icon: Settings }, ]; interface PageHeaderProps { icon: LucideIcon; title: string; subtitle: string; badge?: string; statusIndicator?: React.ReactNode; actions?: React.ReactNode; } export const PageHeader: React.FC = ({ icon: Icon, title, subtitle, badge, statusIndicator, actions, }) => { const location = useLocation(); const navigate = useNavigate(); const [navOpen, setNavOpen] = useState(false); const handleNavClick = (to: string) => { if (location.pathname === to) { setNavOpen(false); return; } setNavOpen(false); setTimeout(() => navigate(to), 150); }; return (
JobOps
{title}
{subtitle}
{badge && ( {badge} )} {statusIndicator}
{actions}
); }; // ============================================================================ // Status Indicator (Pipeline running, Updating, etc.) // ============================================================================ interface StatusIndicatorProps { label: string; variant?: "amber" | "emerald" | "sky"; } export const StatusIndicator: React.FC = ({ label, variant = "amber" }) => { const colorMap = { amber: "border-amber-500/30 bg-amber-500/10 text-amber-200", emerald: "border-emerald-500/30 bg-emerald-500/10 text-emerald-200", sky: "border-sky-500/30 bg-sky-500/10 text-sky-200", }; const dotMap = { amber: "bg-amber-400", emerald: "bg-emerald-400", sky: "bg-sky-400", }; return ( {label} ); }; // ============================================================================ // Split Layout (List + Detail panels) // ============================================================================ interface SplitLayoutProps { children: React.ReactNode; className?: string; } export const SplitLayout: React.FC = ({ children, className }) => (
{children}
); // ============================================================================ // List Panel (left side of split) // ============================================================================ interface ListPanelProps { children: React.ReactNode; header?: React.ReactNode; footer?: React.ReactNode; className?: string; } export const ListPanel: React.FC = ({ children, header, footer, className }) => (
{header &&
{header}
}
{children}
{footer &&
{footer}
}
); // ============================================================================ // List Item (clickable row in list) // ============================================================================ interface ListItemProps { selected?: boolean; onClick?: () => void; children: React.ReactNode; className?: string; } export const ListItem: React.FC = ({ selected, onClick, children, className }) => ( ); // ============================================================================ // Detail Panel (right side of split) // ============================================================================ interface DetailPanelProps { children: React.ReactNode; className?: string; sticky?: boolean; } export const DetailPanel: React.FC = ({ children, className, sticky = true }) => (
{children}
); // ============================================================================ // Empty State // ============================================================================ interface EmptyStateProps { icon?: LucideIcon; title: string; description?: string; action?: React.ReactNode; } export const EmptyState: React.FC = ({ icon: Icon, title, description, action }) => (
{Icon && }
{title}
{description &&

{description}

} {action &&
{action}
}
); // ============================================================================ // Score Meter // ============================================================================ interface ScoreMeterProps { score: number | null; showLabel?: boolean; } const getScoreTokens = (score: number) => { if (score >= 90) return { bar: "bg-emerald-500/80" }; if (score >= 70) return { bar: "bg-amber-500/80" }; if (score >= 50) return { bar: "bg-orange-500/80" }; return { bar: "bg-rose-500/80" }; }; export const ScoreMeter: React.FC = ({ score, showLabel = true }) => { if (score == null) { return Not scored; } const tokens = getScoreTokens(score); return (
{showLabel && {score}%}
); }; // ============================================================================ // Full Height Split Layout (for pages like VisaSponsors that use full viewport) // ============================================================================ interface FullHeightSplitProps { sidebar: React.ReactNode; sidebarWidth?: string; children: React.ReactNode; } export const FullHeightSplit: React.FC = ({ sidebar, sidebarWidth = "lg:w-[420px]", children, }) => (
{sidebar}
{children}
); // ============================================================================ // Section Card (for forms, stats, etc.) // ============================================================================ interface SectionCardProps { children: React.ReactNode; className?: string; } export const SectionCard: React.FC = ({ children, className }) => (
{children}
); // ============================================================================ // Page Main Content Wrapper // ============================================================================ interface PageMainProps { children: React.ReactNode; className?: string; } export const PageMain: React.FC = ({ children, className }) => (
{children}
);