'use client' import React, { useState, useEffect, useCallback, useRef } from 'react' // ============================================================================= // TOAST // ============================================================================= export interface ToastMessage { id: number message: string type: 'success' | 'error' } let toastIdCounter = 0 export function useToast() { const [toasts, setToasts] = useState([]) const addToast = useCallback((message: string, type: 'success' | 'error' = 'success') => { const id = ++toastIdCounter setToasts((prev) => [...prev, { id, message, type }]) setTimeout(() => { setToasts((prev) => prev.filter((t) => t.id !== id)) }, 3500) }, []) return { toasts, addToast } } export function ToastContainer({ toasts }: { toasts: ToastMessage[] }) { if (toasts.length === 0) return null return (
{toasts.map((t) => (
{t.message}
))}
) } // ============================================================================= // LOADING SKELETON // ============================================================================= export function Skeleton({ className = '' }: { className?: string }) { return
} export function DashboardSkeleton() { return (
{Array.from({ length: 4 }).map((_, i) => ( ))}
{Array.from({ length: 3 }).map((_, i) => ( ))}
) } // ============================================================================= // MODAL // ============================================================================= export function Modal({ open, onClose, title, children, maxWidth = 'max-w-lg', }: { open: boolean; onClose: () => void; title: string children: React.ReactNode; maxWidth?: string }) { const overlayRef = useRef(null) useEffect(() => { if (!open) return const handleEsc = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose() } document.addEventListener('keydown', handleEsc) return () => document.removeEventListener('keydown', handleEsc) }, [open, onClose]) if (!open) return null return (
{ if (e.target === overlayRef.current) onClose() }}>

{title}

{children}
) } // ============================================================================= // STAT CARD, PROGRESS BARS, BADGE // ============================================================================= export function StatCard({ title, value, icon, accent = false, }: { title: string; value: string | number; icon: React.ReactNode; accent?: boolean }) { return (
{icon}

{title}

{value}

) } export function ComplianceBar({ score }: { score: number }) { const color = score < 40 ? 'bg-red-500' : score < 70 ? 'bg-yellow-500' : 'bg-green-500' const textColor = score < 40 ? 'text-red-700' : score < 70 ? 'text-yellow-700' : 'text-green-700' return (
{score}%
) } export function HoursBar({ used, budget }: { used: number; budget: number }) { const pct = budget > 0 ? Math.min((used / budget) * 100, 100) : 0 const over = used > budget return (
{used}h / {budget}h
) } export function Badge({ label, className = '' }: { label: string; className?: string }) { return ( {label} ) } // ============================================================================= // FORM COMPONENTS // ============================================================================= export function FormLabel({ children, htmlFor }: { children: React.ReactNode; htmlFor?: string }) { return } export function FormInput({ id, type = 'text', value, onChange, placeholder, required, min, max, step, }: { id?: string; type?: string; value: string | number; onChange: (val: string) => void placeholder?: string; required?: boolean; min?: string | number; max?: string | number; step?: string | number }) { return ( onChange(e.target.value)} placeholder={placeholder} required={required} min={min} max={max} step={step} className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500" /> ) } export function FormTextarea({ id, value, onChange, placeholder, rows = 3, }: { id?: string; value: string; onChange: (val: string) => void; placeholder?: string; rows?: number }) { return (