diff --git a/src/components/admin/PageShell.jsx b/src/components/admin/PageShell.tsx similarity index 89% rename from src/components/admin/PageShell.jsx rename to src/components/admin/PageShell.tsx index de82471..d8a9f83 100644 --- a/src/components/admin/PageShell.jsx +++ b/src/components/admin/PageShell.tsx @@ -17,8 +17,17 @@ import { cn } from '@/lib/utils'; * Controls live in the page rather than in the global chrome, which is why the * Admin header only needs one 56px bar. */ -/** @param {any} props */ -export function AdminPage({ title, subtitle, meta, actions, tabs, children, className }) { +interface AdminPageProps { + title?: string; + subtitle?: string; + meta?: any; + actions?: any; + tabs?: any; + children: React.ReactNode; + className?: string; +} + +export function AdminPage({ title, subtitle, meta, actions, tabs, children, className }: AdminPageProps) { return (
@@ -57,8 +66,15 @@ export function AdminPage({ title, subtitle, meta, actions, tabs, children, clas * Sentence-case and small rather than a heavy all-caps overline: on a page with * four or five sections, loud labels compete with the data they introduce. */ -/** @param {any} props */ -export function SectionTitle({ id, title, meta, action, className }) { +interface SectionTitleProps { + id?: any; + title?: string; + meta?: any; + action?: any; + className?: string; +} + +export function SectionTitle({ id, title, meta, action, className }: SectionTitleProps) { return (
{/* The count wraps under the section name rather than competing with it @@ -91,8 +107,15 @@ export function SectionTitle({ id, title, meta, action, className }) { * primary action is pushed to the end, so every management page has the same * control geometry. */ -/** @param {any} props */ -export function Toolbar({ search = null, filters = null, actions = null, meta = null, className = '' }) { +interface ToolbarProps { + search?: any; + filters?: any; + actions?: any; + meta?: any; + className?: string; +} + +export function Toolbar({ search = null, filters = null, actions = null, meta = null, className = '' }: ToolbarProps) { return (
diff --git a/src/components/admin/RoleGlyph.jsx b/src/components/admin/RoleGlyph.tsx similarity index 84% rename from src/components/admin/RoleGlyph.jsx rename to src/components/admin/RoleGlyph.tsx index 351834f..84e16cc 100644 --- a/src/components/admin/RoleGlyph.jsx +++ b/src/components/admin/RoleGlyph.tsx @@ -1,3 +1,4 @@ +import type { ComponentType } from 'react'; import { Briefcase, ChefHat, ConciergeBell, HardHat, PackageSearch, ShieldCheck, Sparkles, Truck, Wine, } from 'lucide-react'; @@ -19,8 +20,10 @@ import { cn } from '@/lib/utils'; */ /** Category to icon. Matched loosely, so a new "Head Chef" still gets the hat. */ -/** @type {[RegExp, React.ComponentType][]} */ -const GLYPHS = [ +/* `[pattern, icon]` pairs. Without the tuple the element widens to the union of + both positions, so `pattern.test` and `` both stop working — the same + shape as `aiEngine`'s route table. */ +const GLYPHS: [RegExp, ComponentType][] = [ [/chef|cook|kitchen/i, ChefHat], [/bartend|bar|mixolog/i, Wine], [/server|wait|host|banquet|concierge/i, ConciergeBell], @@ -42,8 +45,15 @@ const SIZES = { lg: { box: 'h-14 w-14 rounded-xl', icon: 'h-6 w-6' }, }; -/** @param {any} props */ -export function RoleGlyph({ category, title, size = 'md', aiGenerated = false, className = '' }) { +interface RoleGlyphProps { + category?: any; + title?: string; + size?: any; + aiGenerated?: any; + className?: string; +} + +export function RoleGlyph({ category, title, size = 'md', aiGenerated = false, className = '' }: RoleGlyphProps) { const Icon = iconForRole(category, title); const s = SIZES[size] || SIZES.md; diff --git a/src/components/forge/AddSkillTraining.jsx b/src/components/forge/AddSkillTraining.tsx similarity index 95% rename from src/components/forge/AddSkillTraining.jsx rename to src/components/forge/AddSkillTraining.tsx index 3dcb474..ca72d5f 100644 --- a/src/components/forge/AddSkillTraining.jsx +++ b/src/components/forge/AddSkillTraining.tsx @@ -49,9 +49,21 @@ const EMPTY = { badge_reward: '', }; +interface AddSkillTrainingProps { + open?: boolean; + onOpenChange?: (...args: any[]) => void; + categories?: any; + onCreate?: (...args: any[]) => void; + onUpdate?: (...args: any[]) => void; + busy?: any; + prefill?: any; + startStep?: any; + courseId?: any; +} + export function AddSkillTraining({ open, onOpenChange, categories = [], onCreate, onUpdate, busy, prefill, startStep, courseId, -}) { +}: AddSkillTrainingProps) { const [step, setStep] = React.useState(0); const [form, setForm] = React.useState(EMPTY); const [confirming, setConfirming] = React.useState(false); @@ -409,7 +421,12 @@ export function AddSkillTraining({ /* ── Pieces ─────────────────────────────────────────────────────────────── */ /** Where the reader is, and how to step back to anything already answered. */ -function Stepper({ step, onStep }) { +interface StepperProps { + step?: any; + onStep?: (...args: any[]) => void; +} + +function Stepper({ step, onStep }: StepperProps) { return (
    {STEPS.map((s, i) => { @@ -443,7 +460,14 @@ function Stepper({ step, onStep }) { } /** One option in a small single-choice row. */ -function Choice({ active, onClick, className, children }) { +interface ChoiceProps { + active?: any; + onClick?: (...args: any[]) => void; + className?: string; + children: React.ReactNode; +} + +function Choice({ active, onClick, className, children }: ChoiceProps) { return (