diff --git a/src/components/ds/toast.ts b/src/components/ds/toast.ts index 2e74641..cd065e8 100644 --- a/src/components/ds/toast.ts +++ b/src/components/ds/toast.ts @@ -39,7 +39,7 @@ const withDefaults = (options: Partial = {}): Partial) { return hotToast.success(message, { duration: DURATIONS.success, iconTheme: { primary: 'hsl(142 71% 36%)', secondary: '#FFFFFF' }, @@ -47,7 +47,7 @@ export const toast = { }); }, - error(message, options) { + error(message: any, options?: Partial) { return hotToast.error(message, { duration: DURATIONS.error, iconTheme: { primary: 'hsl(0 84% 60%)', secondary: '#FFFFFF' }, @@ -55,11 +55,11 @@ export const toast = { }); }, - info(message, options) { + info(message: any, options?: Partial) { return hotToast(message, { duration: DURATIONS.info, ...withDefaults(options) }); }, - loading(message, options) { + loading(message: any, options?: Partial) { return hotToast.loading(message, { duration: DURATIONS.loading, ...withDefaults(options) }); }, @@ -68,7 +68,7 @@ export const toast = { * error. Preferred over a manual loading/dismiss pair, which leaks a spinner * whenever the promise rejects. */ - promise(promise, messages, options) { + promise(promise: any, messages: any, options?: Partial) { return hotToast.promise( promise, { diff --git a/src/components/krow/AIInterviewModal.jsx b/src/components/krow/AIInterviewModal.tsx similarity index 98% rename from src/components/krow/AIInterviewModal.jsx rename to src/components/krow/AIInterviewModal.tsx index c41b493..e0288a0 100644 --- a/src/components/krow/AIInterviewModal.jsx +++ b/src/components/krow/AIInterviewModal.tsx @@ -1,3 +1,4 @@ +import type { JobApplication, JobPosting } from '@/types/entities'; import React, { useState, useRef, useEffect, useCallback } from 'react'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; @@ -10,7 +11,15 @@ import { cn } from '@/lib/utils'; const TOTAL_SECONDS = 180; const QUESTION_COUNT = 5; -export default function AIInterviewModal({ open, onClose, application, job, conductor = 'KROW' }) { +interface AIInterviewModalProps { + open: boolean; + onClose?: () => void; + application: JobApplication; + job: JobPosting; + conductor?: string; +} + +export default function AIInterviewModal({ open, onClose, application, job, conductor = 'KROW' }: AIInterviewModalProps) { const [messages, setMessages] = useState([]); const [phase, setPhase] = useState('intro'); // intro | active | evaluating | done const [isListening, setIsListening] = useState(false); @@ -40,7 +49,7 @@ export default function AIInterviewModal({ open, onClose, application, job, cond }, [messages]); const speak = useCallback((text) => { - return new Promise((resolve) => { + return new Promise((resolve) => { if (!ttsEnabled || !window.speechSynthesis) { resolve(); return; @@ -409,7 +418,7 @@ export default function AIInterviewModal({ open, onClose, application, job, cond {evaluation.category_scores && Object.entries(evaluation.category_scores).map(([key, val]) => (
{key.replace(/_/g, ' ')}
-
{val}/100
+
{val as React.ReactNode}/100
))} diff --git a/src/components/krow/ActivityCharts.jsx b/src/components/krow/ActivityCharts.tsx similarity index 95% rename from src/components/krow/ActivityCharts.jsx rename to src/components/krow/ActivityCharts.tsx index b0e2226..eb10d51 100644 --- a/src/components/krow/ActivityCharts.jsx +++ b/src/components/krow/ActivityCharts.tsx @@ -1,3 +1,4 @@ +import type { UserActivity } from '@/types/entities'; import React, { useMemo } from 'react'; import { ResponsiveContainer, @@ -27,7 +28,11 @@ function last7Days() { return days; } -export default function ActivityCharts({ activities }) { +interface ActivityChartsProps { + activities: UserActivity[]; +} + +export default function ActivityCharts({ activities }: ActivityChartsProps) { const timeSeries = useMemo(() => { const days = last7Days(); return days.map((day) => { diff --git a/src/components/krow/ActivityLogTable.jsx b/src/components/krow/ActivityLogTable.tsx similarity index 96% rename from src/components/krow/ActivityLogTable.jsx rename to src/components/krow/ActivityLogTable.tsx index 5e3ca0c..f244998 100644 --- a/src/components/krow/ActivityLogTable.jsx +++ b/src/components/krow/ActivityLogTable.tsx @@ -1,3 +1,4 @@ +import type { UserActivity } from '@/types/entities'; import React, { useState } from 'react'; import { Search } from 'lucide-react'; @@ -36,7 +37,11 @@ function formatTime(dateStr) { }); } -export default function ActivityLogTable({ activities }) { +interface ActivityLogTableProps { + activities: UserActivity[]; +} + +export default function ActivityLogTable({ activities }: ActivityLogTableProps) { const [filter, setFilter] = useState('all'); const [search, setSearch] = useState(''); diff --git a/src/components/krow/ActivityMetrics.jsx b/src/components/krow/ActivityMetrics.tsx similarity index 89% rename from src/components/krow/ActivityMetrics.jsx rename to src/components/krow/ActivityMetrics.tsx index b8776eb..8f0bc05 100644 --- a/src/components/krow/ActivityMetrics.jsx +++ b/src/components/krow/ActivityMetrics.tsx @@ -1,7 +1,12 @@ +import type { UserActivity } from '@/types/entities'; import React from 'react'; import { Users, LogIn, Activity, UserCheck } from 'lucide-react'; -export default function ActivityMetrics({ activities }) { +interface ActivityMetricsProps { + activities: UserActivity[]; +} + +export default function ActivityMetrics({ activities }: ActivityMetricsProps) { const now = Date.now(); const dayAgo = now - 24 * 60 * 60 * 1000; diff --git a/src/components/krow/AddToPositionModal.jsx b/src/components/krow/AddToPositionModal.tsx similarity index 94% rename from src/components/krow/AddToPositionModal.jsx rename to src/components/krow/AddToPositionModal.tsx index 0af75c0..d490ae7 100644 --- a/src/components/krow/AddToPositionModal.jsx +++ b/src/components/krow/AddToPositionModal.tsx @@ -1,3 +1,5 @@ +import type { ProfileView } from './types'; +import type { JobApplication, Staff } from '@/types/entities'; import React, { useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Badge, Button, EmptyState, Modal, StatusBadge, toast } from '@/components/ds'; @@ -22,7 +24,15 @@ import { remainingFor } from '@/lib/hiringRecords'; * database (`UNIQUE (job_posting_id, email)`), so positions they are already up * for are shown as such rather than offered and then rejected. */ -export default function AddToPositionModal({ open, onOpenChange, profile, existing = [], staff = [] }) { +interface AddToPositionModalProps { + open: boolean; + onOpenChange?: (open: boolean) => void; + profile: ProfileView; + existing?: JobApplication[]; + staff?: Staff[]; +} + +export default function AddToPositionModal({ open, onOpenChange, profile, existing = [], staff = [] }: AddToPositionModalProps) { const navigate = useNavigate(); const { data: postings = [], isLoading } = useJobPostings(); const createApplication = useCreateApplication(); diff --git a/src/components/krow/AnalyticsMetrics.jsx b/src/components/krow/AnalyticsMetrics.tsx similarity index 88% rename from src/components/krow/AnalyticsMetrics.jsx rename to src/components/krow/AnalyticsMetrics.tsx index 858ea19..65499ec 100644 --- a/src/components/krow/AnalyticsMetrics.jsx +++ b/src/components/krow/AnalyticsMetrics.tsx @@ -1,7 +1,14 @@ +import type { AIInterview, JobApplication, Staff } from '@/types/entities'; import React from 'react'; import { Users, TrendingUp, Brain, Mic, UserCheck, Target } from 'lucide-react'; -export default function AnalyticsMetrics({ applications, interviews: _interviews, staff }) { +interface AnalyticsMetricsProps { + applications: JobApplication[]; + interviews: AIInterview[]; + staff: Staff[]; +} + +export default function AnalyticsMetrics({ applications, interviews: _interviews, staff }: AnalyticsMetricsProps) { const totalApplicants = applications.length; const scored = applications.filter(a => a.ai_score > 0); const avgScore = scored.length ? Math.round(scored.reduce((s, a) => s + a.ai_score, 0) / scored.length) : 0; diff --git a/src/components/krow/ApplyIdentityCard.jsx b/src/components/krow/ApplyIdentityCard.tsx similarity index 89% rename from src/components/krow/ApplyIdentityCard.jsx rename to src/components/krow/ApplyIdentityCard.tsx index 5e9c67e..93c9087 100644 --- a/src/components/krow/ApplyIdentityCard.jsx +++ b/src/components/krow/ApplyIdentityCard.tsx @@ -1,3 +1,5 @@ +import type { ProfileView } from './types'; +import type { JobPosting } from '@/types/entities'; import React from 'react'; import { Mic, Clock, Gauge, Sparkles, Fingerprint, BadgeCheck, Briefcase, ChevronRight, AlertCircle } from 'lucide-react'; import { Image } from '@/components/ui/image'; @@ -15,7 +17,15 @@ function krowCode(profile) { return 'KROWID'; } -export default function ApplyIdentityCard({ profile, job, onStart, submitting, onBuildIdentity }) { +interface ApplyIdentityCardProps { + profile: ProfileView; + job: JobPosting; + onStart?: () => void; + submitting: boolean; + onBuildIdentity?: () => void; +} + +export default function ApplyIdentityCard({ profile, job, onStart, submitting, onBuildIdentity }: ApplyIdentityCardProps) { const ready = profile && profile.full_name; const completeness = Math.min(100, Math.round(profile?.profile_completion || 0)); const skills = profile?.skills || []; @@ -131,7 +141,15 @@ export default function ApplyIdentityCard({ profile, job, onStart, submitting, o ); } -function Stat({ label, value, icon }) { +/** Props for the local `Stat` helper in this file. Optional throughout: + the JavaScript validated none of them and callers omit freely. */ +interface StatProps { + label?: any; + value?: any; + icon?: any; +} + +function Stat({ label, value, icon }: StatProps) { return (
{icon}
@@ -141,7 +159,14 @@ function Stat({ label, value, icon }) { ); } -function Row({ label, children }) { +/** Props for the local `Row` helper in this file. Optional throughout: + the JavaScript validated none of them and callers omit freely. */ +interface RowProps { + label?: any; + children?: any; +} + +function Row({ label, children }: RowProps) { return (

{label.toUpperCase()}

diff --git a/src/components/krow/CandidateCard.jsx b/src/components/krow/CandidateCard.tsx similarity index 95% rename from src/components/krow/CandidateCard.jsx rename to src/components/krow/CandidateCard.tsx index 98bee04..d4dd9d6 100644 --- a/src/components/krow/CandidateCard.jsx +++ b/src/components/krow/CandidateCard.tsx @@ -1,3 +1,4 @@ +import type { JobApplication } from '@/types/entities'; import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { cn } from '@/lib/utils'; @@ -48,7 +49,13 @@ function getScoreColor(score) { return '#EF4444'; } -function ScoreRing({ score }) { +/** Props for the local `ScoreRing` helper in this file. Optional throughout: + the JavaScript validated none of them and callers omit freely. */ +interface ScoreRingProps { + score?: any; +} + +function ScoreRing({ score }: ScoreRingProps) { const color = getScoreColor(score); const radius = 24; const circumference = 2 * Math.PI * radius; @@ -67,8 +74,20 @@ function ScoreRing({ score }) { ); } -/** @param {any} props */ -export default function CandidateCard({ application, jobTitle, rank, onAction, onMessage, onCall, onSchedule, onDecline, onDelete, onHire }) { +interface CandidateCardProps { + application: JobApplication; + jobTitle?: string; + rank: number; + onAction?: (...args: any[]) => void; + onMessage?: (...args: any[]) => void; + onCall?: (...args: any[]) => void; + onSchedule?: (...args: any[]) => void; + onDecline?: (...args: any[]) => void; + onDelete?: (...args: any[]) => void; + onHire?: (...args: any[]) => void; +} + +export default function CandidateCard({ application, jobTitle, rank, onAction, onMessage, onCall, onSchedule, onDecline, onDelete, onHire }: CandidateCardProps) { const navigate = useNavigate(); const position = jobTitle || application.job_title || ''; const status = application.status || 'applied'; diff --git a/src/components/krow/CandidateExpandedDetails.jsx b/src/components/krow/CandidateExpandedDetails.tsx similarity index 97% rename from src/components/krow/CandidateExpandedDetails.jsx rename to src/components/krow/CandidateExpandedDetails.tsx index 935cc9f..e889bfe 100644 --- a/src/components/krow/CandidateExpandedDetails.jsx +++ b/src/components/krow/CandidateExpandedDetails.tsx @@ -1,3 +1,4 @@ +import type { JobApplication } from '@/types/entities'; import React from 'react'; import { Award, BarChart3, Clock, ScrollText, Calendar, User, Users, MessageSquare, CalendarCheck, Dumbbell, Crown, Briefcase, Target, Shield, Check, AlertTriangle } from 'lucide-react'; import InterviewResults from '@/components/krow/InterviewResults'; @@ -21,7 +22,6 @@ const METRIC_CONFIG = [ { key: 'employer_requirements', label: 'Req Match', Icon: Check }, ]; -/** @param {any} props */ function _MetricPill({ label, value, Icon }) { return ( @@ -32,7 +32,11 @@ function _MetricPill({ label, value, Icon }) { ); } -export default function CandidateExpandedDetails({ application }) { +interface CandidateExpandedDetailsProps { + application: JobApplication; +} + +export default function CandidateExpandedDetails({ application }: CandidateExpandedDetailsProps) { const breakdown = application.score_breakdown || {}; const strengths = application.ai_strengths || []; const gaps = application.ai_gaps || []; diff --git a/src/components/krow/CertificationManager.jsx b/src/components/krow/CertificationManager.tsx similarity index 100% rename from src/components/krow/CertificationManager.jsx rename to src/components/krow/CertificationManager.tsx diff --git a/src/components/krow/HiringAnalytics.jsx b/src/components/krow/HiringAnalytics.tsx similarity index 94% rename from src/components/krow/HiringAnalytics.jsx rename to src/components/krow/HiringAnalytics.tsx index 5542d06..f5feaea 100644 --- a/src/components/krow/HiringAnalytics.jsx +++ b/src/components/krow/HiringAnalytics.tsx @@ -1,9 +1,15 @@ +import type { JobApplication, Staff } from '@/types/entities'; import React, { useMemo } from 'react'; import { ResponsiveContainer, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Cell } from 'recharts'; const TIER_COLORS = { Skilled: '#0838E0', 'Cross-Trained': '#333F48', Beginner: '#D1E0D7' }; -export default function HiringAnalytics({ applications, staff }) { +interface HiringAnalyticsProps { + applications: JobApplication[]; + staff: Staff[]; +} + +export default function HiringAnalytics({ applications, staff }: HiringAnalyticsProps) { const byTier = useMemo(() => { const tiers = ['Skilled', 'Cross-Trained', 'Beginner']; return tiers.map(tier => ({ @@ -14,7 +20,7 @@ export default function HiringAnalytics({ applications, staff }) { }, [staff]); const byRole = useMemo(() => { - const counts = {}; + const counts: Record = {}; staff.forEach(s => { const role = s.role || 'Unassigned'; counts[role] = (counts[role] || 0) + 1; diff --git a/src/components/krow/ImpactMetrics.jsx b/src/components/krow/ImpactMetrics.tsx similarity index 95% rename from src/components/krow/ImpactMetrics.jsx rename to src/components/krow/ImpactMetrics.tsx index dd065d5..776945d 100644 --- a/src/components/krow/ImpactMetrics.jsx +++ b/src/components/krow/ImpactMetrics.tsx @@ -1,8 +1,15 @@ +import type { AIInterview, JobApplication, Staff } from '@/types/entities'; import React from 'react'; import { motion } from 'framer-motion'; import { Clock, DollarSign, Award, Zap, Scale, Heart } from 'lucide-react'; -export default function ImpactMetrics({ applications, interviews, staff: _staff }) { +interface ImpactMetricsProps { + applications: JobApplication[]; + interviews: AIInterview[]; + staff: Staff[]; +} + +export default function ImpactMetrics({ applications, interviews, staff: _staff }: ImpactMetricsProps) { const hired = applications.filter(a => a.status === 'hired'); /* Named for what it is: the applications the AI actually scored. Elsewhere in the product "screened" means the stage (status <> 'applied'), which is a diff --git a/src/components/krow/InterviewAnalytics.jsx b/src/components/krow/InterviewAnalytics.tsx similarity index 96% rename from src/components/krow/InterviewAnalytics.jsx rename to src/components/krow/InterviewAnalytics.tsx index d0caedd..770f500 100644 --- a/src/components/krow/InterviewAnalytics.jsx +++ b/src/components/krow/InterviewAnalytics.tsx @@ -1,10 +1,16 @@ +import type { AIInterview, JobApplication } from '@/types/entities'; import React, { useMemo } from 'react'; import { ResponsiveContainer, PieChart, Pie, Cell, Tooltip, Legend, BarChart, Bar, XAxis, YAxis, CartesianGrid } from 'recharts'; import { Mic, CheckCircle2, Clock, AlertTriangle } from 'lucide-react'; const VERDICT_COLORS = { hire: '#0838E0', maybe: '#F9E547', no: '#333F48' }; -export default function InterviewAnalytics({ applications, interviews }) { +interface InterviewAnalyticsProps { + applications: JobApplication[]; + interviews: AIInterview[]; +} + +export default function InterviewAnalytics({ applications, interviews }: InterviewAnalyticsProps) { const stats = useMemo(() => { const started = applications.filter(a => a.status === 'interview' || a.interview_id).length; const completed = interviews.length; diff --git a/src/components/krow/InterviewResults.jsx b/src/components/krow/InterviewResults.tsx similarity index 95% rename from src/components/krow/InterviewResults.jsx rename to src/components/krow/InterviewResults.tsx index 701c7cc..d8c5521 100644 --- a/src/components/krow/InterviewResults.jsx +++ b/src/components/krow/InterviewResults.tsx @@ -1,3 +1,4 @@ +import type { JobApplication } from '@/types/entities'; import React from 'react'; import { useInterviews } from '@/lib/krowHooks'; import { MessageSquare, CheckCircle2, AlertTriangle, Star, FileText } from 'lucide-react'; @@ -32,11 +33,15 @@ function getCategoryColor(val) { return '#EF4444'; } -export default function InterviewResults({ application }) { +interface InterviewResultsProps { + application: JobApplication; +} + +export default function InterviewResults({ application }: InterviewResultsProps) { const { data: interviews = [], isLoading } = useInterviews(); const interview = interviews .filter((i) => i.application_id === application.id) - .sort((a, b) => new Date(b.created_date) - new Date(a.created_date))[0]; + .sort((a, b) => (new Date(b.created_date) as any) - (new Date(a.created_date) as any))[0]; if (isLoading) { return ( @@ -79,7 +84,7 @@ export default function InterviewResults({ application }) { const color = getCategoryColor(val); return (
-
{val}
+
{val as React.ReactNode}
diff --git a/src/components/krow/InviteApplicantsModal.jsx b/src/components/krow/InviteApplicantsModal.tsx similarity index 97% rename from src/components/krow/InviteApplicantsModal.jsx rename to src/components/krow/InviteApplicantsModal.tsx index b146fab..2323943 100644 --- a/src/components/krow/InviteApplicantsModal.jsx +++ b/src/components/krow/InviteApplicantsModal.tsx @@ -1,10 +1,17 @@ +import type { JobPosting } from '@/types/entities'; import React, { useState, useMemo } from 'react'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Copy, Check, Share2, Mail, Link2, Smartphone, Users } from 'lucide-react'; import { toast } from 'react-hot-toast'; -export default function InviteApplicantsModal({ open, onClose, job }) { +interface InviteApplicantsModalProps { + open: boolean; + onClose?: () => void; + job: JobPosting; +} + +export default function InviteApplicantsModal({ open, onClose, job }: InviteApplicantsModalProps) { const [copied, setCopied] = useState(false); const [emailRecipients, setEmailRecipients] = useState(''); const [sending, setSending] = useState(false); diff --git a/src/components/krow/Journey.jsx b/src/components/krow/Journey.tsx similarity index 98% rename from src/components/krow/Journey.jsx rename to src/components/krow/Journey.tsx index 06baa9b..96c4cd3 100644 --- a/src/components/krow/Journey.jsx +++ b/src/components/krow/Journey.tsx @@ -40,7 +40,12 @@ const READING_LINE = 0.35; * more code and less exact at the boundaries. Reads are throttled to one per * frame and every step is a single `getBoundingClientRect`. */ -export function Journey({ children, className }) { +interface JourneyProps { + children: React.ReactNode; + className?: string; +} + +export function Journey({ children, className }: JourneyProps) { const [active, setActive] = useState(0); const steps = useRef(new Map()); diff --git a/src/components/krow/KROWCandidateLeaderboard.jsx b/src/components/krow/KROWCandidateLeaderboard.tsx similarity index 95% rename from src/components/krow/KROWCandidateLeaderboard.jsx rename to src/components/krow/KROWCandidateLeaderboard.tsx index 0860aea..4b554f4 100644 --- a/src/components/krow/KROWCandidateLeaderboard.jsx +++ b/src/components/krow/KROWCandidateLeaderboard.tsx @@ -1,3 +1,4 @@ +import type { JobApplication } from '@/types/entities'; import React from 'react'; import { cn } from '@/lib/utils'; @@ -15,7 +16,11 @@ function getTier(score) { return 3; } -export default function KROWCandidateLeaderboard({ applications }) { +interface KROWCandidateLeaderboardProps { + applications: JobApplication[]; +} + +export default function KROWCandidateLeaderboard({ applications }: KROWCandidateLeaderboardProps) { const screened = applications.filter(a => a.ai_score > 0 || a.status !== 'applied'); const counts = TIERS.map((_, i) => screened.filter(a => getTier(a.ai_score || 0) === i).length); const total = counts.reduce((a, b) => a + b, 0) || 1; diff --git a/src/components/krow/KROWJobPostingCard.jsx b/src/components/krow/KROWJobPostingCard.tsx similarity index 94% rename from src/components/krow/KROWJobPostingCard.jsx rename to src/components/krow/KROWJobPostingCard.tsx index 5e28def..364ed28 100644 --- a/src/components/krow/KROWJobPostingCard.jsx +++ b/src/components/krow/KROWJobPostingCard.tsx @@ -1,3 +1,4 @@ +import type { JobPosting } from '@/types/entities'; import React from 'react'; import { useNavigate } from 'react-router-dom'; import { cn } from '@/lib/utils'; @@ -17,7 +18,12 @@ function getCategoryAbbr(category) { return words.map(w => w[0]).join('').toUpperCase().slice(0, 2); } -export default function KROWJobPostingCard({ posting, stats }) { +interface KROWJobPostingCardProps { + posting: JobPosting; + stats: any; +} + +export default function KROWJobPostingCard({ posting, stats }: KROWJobPostingCardProps) { const navigate = useNavigate(); const applied = stats?.applied ?? 0; const screened = stats?.screened ?? 0; diff --git a/src/components/krow/KrowIdPreview.jsx b/src/components/krow/KrowIdPreview.tsx similarity index 100% rename from src/components/krow/KrowIdPreview.jsx rename to src/components/krow/KrowIdPreview.tsx diff --git a/src/components/krow/KrowIdentityCard.jsx b/src/components/krow/KrowIdentityCard.tsx similarity index 90% rename from src/components/krow/KrowIdentityCard.jsx rename to src/components/krow/KrowIdentityCard.tsx index 8cd3fe1..82bde33 100644 --- a/src/components/krow/KrowIdentityCard.jsx +++ b/src/components/krow/KrowIdentityCard.tsx @@ -2,7 +2,11 @@ import { Camera, Fingerprint, Sparkles } from 'lucide-react'; // A living identity card that reflects — rather than describes — the resume-less // KROW Identity. It fills in as the worker adds a face, skills, languages, etc. -export default function KrowIdentityCard({ form }) { +interface KrowIdentityCardProps { + form: any; +} + +export default function KrowIdentityCard({ form }: KrowIdentityCardProps) { const parts = [ { key: 'face', filled: !!form.selfie_url }, { key: 'name', filled: !!form.full_name.trim() }, @@ -62,7 +66,14 @@ export default function KrowIdentityCard({ form }) { ); } -function SlotRow({ label, items }) { +/** Props for the local `SlotRow` helper in this file. Optional throughout: + the JavaScript validated none of them and callers omit freely. */ +interface SlotRowProps { + label?: any; + items?: any; +} + +function SlotRow({ label, items }: SlotRowProps) { const filled = (items || []).length > 0; return (
diff --git a/src/components/krow/MatchedCandidates.jsx b/src/components/krow/MatchedCandidates.tsx similarity index 93% rename from src/components/krow/MatchedCandidates.jsx rename to src/components/krow/MatchedCandidates.tsx index 237831e..8e23748 100644 --- a/src/components/krow/MatchedCandidates.jsx +++ b/src/components/krow/MatchedCandidates.tsx @@ -1,3 +1,5 @@ +import type { ProfileView, CourseView } from './types'; +import type { JobApplication, JobPosting } from '@/types/entities'; import React, { useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { ArrowRight, Check, GraduationCap, Minus, Sparkles, X } from 'lucide-react'; @@ -33,7 +35,13 @@ const TONE = { }; /** One requirement, met or not, with the level on each side of the comparison. */ -function RequirementRow({ line }) { +/** Props for the local `RequirementRow` helper in this file. Optional throughout: + the JavaScript validated none of them and callers omit freely. */ +interface RequirementRowProps { + line?: any; +} + +function RequirementRow({ line }: RequirementRowProps) { const Icon = line.met ? Check : line.close ? Minus : X; const tone = line.met ? 'text-[#059669]' : line.close ? 'text-[#D97706]' : 'text-[#DC2626]'; @@ -51,7 +59,18 @@ function RequirementRow({ line }) { ); } -function CandidateRow({ row, job, onView, onInterview, busy, attaches }) { +/** Props for the local `CandidateRow` helper in this file. Optional throughout: + the JavaScript validated none of them and callers omit freely. */ +interface CandidateRowProps { + row?: any; + job?: any; + onView?: any; + onInterview?: any; + busy?: any; + attaches?: any; +} + +function CandidateRow({ row, job, onView, onInterview, busy, attaches }: CandidateRowProps) { const [open, setOpen] = useState(false); /* Identity is the candidate record; the worker profile is enrichment that most candidates do not have. Nothing here reads a profile field without @@ -192,7 +211,16 @@ function CandidateRow({ row, job, onView, onInterview, busy, attaches }) { ); } -export function MatchedCandidates({ job, profiles = [], courses = [], applications = [], onInterview, busy }) { +interface MatchedCandidatesProps { + job: JobPosting; + profiles?: ProfileView[]; + courses?: CourseView[]; + applications?: JobApplication[]; + onInterview?: (...args: any[]) => void; + busy: boolean; +} + +export function MatchedCandidates({ job, profiles = [], courses = [], applications = [], onInterview, busy }: MatchedCandidatesProps) { const navigate = useNavigate(); const [showAll, setShowAll] = useState(false); const positionSkills = usePageSkills('positions'); @@ -241,7 +269,7 @@ export function MatchedCandidates({ job, profiles = [], courses = [], applicatio /* A position with no skill requirements can still be matched — on the requirements it does state — so this no longer replaces the whole section. It is said once, above the list, and the list still renders. */ - const hasSkillRequirements = Boolean(job?.skill_requirements?.length); + const hasSkillRequirements = Boolean((job?.skill_requirements as any[] | undefined)?.length); const shown = showAll ? ranked : ranked.slice(0, 5); @@ -286,7 +314,7 @@ export function MatchedCandidates({ job, profiles = [], courses = [], applicatio