@burdenoff/microfe-crewfoundry 2026.1029.2 → 2026.1030.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/crewfoundry/lib/useFocusedRecordParam.d.ts +51 -0
  2. package/dist/crewfoundry/lib/useFocusedRecordParam.d.ts.map +1 -0
  3. package/dist/crewfoundry/lib/useFocusedRecordParam.js +38 -0
  4. package/dist/crewfoundry/lib/useFocusedRecordParam.js.map +1 -0
  5. package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.d.ts.map +1 -1
  6. package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js +119 -112
  7. package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js.map +1 -1
  8. package/dist/crewfoundry/pages/CompanyClientManagementPagePage.d.ts.map +1 -1
  9. package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js +78 -71
  10. package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js.map +1 -1
  11. package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.d.ts.map +1 -1
  12. package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js +88 -81
  13. package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js.map +1 -1
  14. package/dist/crewfoundry/pages/HiringCandidatesPagePage.d.ts.map +1 -1
  15. package/dist/crewfoundry/pages/HiringCandidatesPagePage.js +133 -126
  16. package/dist/crewfoundry/pages/HiringCandidatesPagePage.js.map +1 -1
  17. package/dist/crewfoundry/pages/HiringInterviewsPagePage.d.ts.map +1 -1
  18. package/dist/crewfoundry/pages/HiringInterviewsPagePage.js +150 -143
  19. package/dist/crewfoundry/pages/HiringInterviewsPagePage.js.map +1 -1
  20. package/dist/crewfoundry/pages/HrDepartmentsPagePage.d.ts.map +1 -1
  21. package/dist/crewfoundry/pages/HrDepartmentsPagePage.js +110 -103
  22. package/dist/crewfoundry/pages/HrDepartmentsPagePage.js.map +1 -1
  23. package/dist/crewfoundry/pages/HrLeaveManagementPagePage.d.ts.map +1 -1
  24. package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js +173 -162
  25. package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js.map +1 -1
  26. package/dist/crewfoundry/pages/HrPayrollPagePage.d.ts.map +1 -1
  27. package/dist/crewfoundry/pages/HrPayrollPagePage.js +169 -158
  28. package/dist/crewfoundry/pages/HrPayrollPagePage.js.map +1 -1
  29. package/dist/crewfoundry/pages/HrTimeTrackingPagePage.d.ts.map +1 -1
  30. package/dist/crewfoundry/pages/HrTimeTrackingPagePage.js +179 -163
  31. package/dist/crewfoundry/pages/HrTimeTrackingPagePage.js.map +1 -1
  32. package/dist/utils/pageContext.d.ts +43 -4
  33. package/dist/utils/pageContext.d.ts.map +1 -1
  34. package/dist/utils/pageContext.js +10 -3
  35. package/dist/utils/pageContext.js.map +1 -1
  36. package/package.json +3 -2
@@ -1 +1 @@
1
- {"version":3,"file":"HiringInterviewsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HiringInterviewsPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { Calendar, Clock, Plus, Star, Video } from 'lucide-react';\nimport { useCrewFoundryStore, type Interview } from '../store/crewfoundryStore';\nimport { FormModal, type FieldConfig, type FormValues } from '../components/forms/FormModal';\nimport { getText, newClientId } from '../components/forms/formValues';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { RatingStars, getInitials } from '../components/viz';\n\nconst INTERVIEW_TYPE_OPTIONS: FieldConfig['options'] = [\n { label: 'Phone', value: 'phone' },\n { label: 'Video', value: 'video' },\n { label: 'Onsite', value: 'onsite' },\n { label: 'Panel', value: 'panel' },\n { label: 'Technical', value: 'technical' },\n];\n\n// Per-competency scorecard. CrewInterview has only a single `feedback` (String)\n// + `rating` (Float), and the gateway strips `rating` from update inputs — so the\n// four competency scores are persisted by encoding them into `feedback` behind a\n// compact machine marker, then parsed back out on load. The written notes follow\n// the marker line; the overall rating is derived from the competency scores.\nconst COMPETENCIES: { key: string; label: string }[] = [\n { key: 'technical', label: 'Technical Skills' },\n { key: 'problem', label: 'Problem Solving' },\n { key: 'communication', label: 'Communication' },\n { key: 'culture', label: 'Culture Fit' },\n];\n\nconst SCORECARD_MARKER = '@@scorecard';\n\ninterface ParsedScorecard {\n scores: Record<string, number>;\n notes: string;\n}\n\nfunction parseScorecard(feedback?: string | null): ParsedScorecard {\n if (!feedback) {\n return { scores: {}, notes: '' };\n }\n if (!feedback.startsWith(`${SCORECARD_MARKER}:`)) {\n return { scores: {}, notes: feedback };\n }\n const newline = feedback.indexOf('\\n');\n const header = newline === -1 ? feedback : feedback.slice(0, newline);\n const notes = newline === -1 ? '' : feedback.slice(newline + 1);\n const scores: Record<string, number> = {};\n const body = header.slice(SCORECARD_MARKER.length + 1);\n for (const pair of body.split(',')) {\n const [key, raw] = pair.split('=');\n const value = Number(raw);\n if (key && !Number.isNaN(value)) {\n scores[key] = value;\n }\n }\n return { scores, notes };\n}\n\nfunction serializeScorecard(scores: Record<string, number>, notes: string): string {\n const entries = COMPETENCIES.filter((c) => (scores[c.key] ?? 0) > 0).map(\n (c) => `${c.key}=${scores[c.key]}`\n );\n if (entries.length === 0) {\n return notes;\n }\n return `${SCORECARD_MARKER}:${entries.join(',')}\\n${notes}`;\n}\n\nfunction overallScore(scores: Record<string, number>): number {\n const values = COMPETENCIES.map((c) => scores[c.key]).filter(\n (v): v is number => typeof v === 'number' && v > 0\n );\n if (values.length === 0) {\n return 0;\n }\n return Math.round((values.reduce((a, b) => a + b, 0) / values.length) * 10) / 10;\n}\n\nfunction InitialsAvatar({ name, size = 40 }: { name: string; size?: number }) {\n return (\n <span\n className=\"inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full border border-border-subtle bg-bg-sunken font-medium text-text-secondary\"\n style={{ width: size, height: size, fontSize: Math.round(size * 0.36) }}\n aria-hidden=\"true\"\n >\n {getInitials(name)}\n </span>\n );\n}\n\n/** Interactive 1–max star input for a single competency. */\nfunction StarInput({\n value,\n onChange,\n label,\n max = 5,\n}: {\n value: number;\n onChange: (value: number) => void;\n label: string;\n max?: number;\n}) {\n return (\n <div className=\"flex items-center gap-0.5\" role=\"radiogroup\" aria-label={label}>\n {Array.from({ length: max }, (_, i) => i + 1).map((n) => (\n <button\n key={n}\n type=\"button\"\n role=\"radio\"\n aria-checked={value === n}\n aria-label={`${n} out of ${max}`}\n onClick={() => onChange(value === n ? 0 : n)}\n className=\"rounded p-0.5 outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <Star\n className=\"h-5 w-5\"\n strokeWidth={1.5}\n style={{\n color: n <= value ? 'var(--color-status-warning-text)' : 'var(--color-border-strong)',\n }}\n fill={n <= value ? 'currentColor' : 'none'}\n />\n </button>\n ))}\n </div>\n );\n}\n\nconst TYPE_BADGE: Record<Interview['type'], 'default' | 'secondary' | 'outline'> = {\n technical: 'default',\n panel: 'secondary',\n onsite: 'secondary',\n video: 'outline',\n phone: 'outline',\n};\n\nfunction formatDay(dateStr: string): string {\n const date = new Date(dateStr);\n if (Number.isNaN(date.getTime())) {\n return dateStr || 'Unscheduled';\n }\n return date.toLocaleDateString(undefined, {\n weekday: 'short',\n month: 'short',\n day: 'numeric',\n });\n}\n\nexport function HiringInterviewsPagePage() {\n const { showToast } = useGlobalUi();\n const { interviews, candidates, addInterview, updateInterview } = useCrewFoundryStore();\n const [scheduleOpen, setScheduleOpen] = useState(false);\n const [selectedId, setSelectedId] = useState<string | null>(null);\n const [saving, setSaving] = useState(false);\n\n const scheduled = interviews.filter((i) => i.status === 'scheduled');\n const completed = interviews.filter((i) => i.status === 'completed');\n\n // Interviews sorted chronologically, grouped by day for the schedule list.\n const dayGroups = useMemo(() => {\n const sorted = [...interviews].sort((a, b) => {\n const da = new Date(a.date).getTime();\n const db = new Date(b.date).getTime();\n if (Number.isNaN(da) && Number.isNaN(db)) return 0;\n if (Number.isNaN(da)) return 1;\n if (Number.isNaN(db)) return -1;\n return da - db;\n });\n const groups: { label: string; items: Interview[] }[] = [];\n for (const interview of sorted) {\n const label = formatDay(interview.date);\n const last = groups[groups.length - 1];\n if (last && last.label === label) {\n last.items.push(interview);\n } else {\n groups.push({ label, items: [interview] });\n }\n }\n return groups;\n }, [interviews]);\n\n // Effective selection is derived (no effect): honour an explicit click, else\n // default to the first scheduled interview, else the first interview overall.\n const activeId = selectedId ?? scheduled[0]?.id ?? interviews[0]?.id ?? null;\n const selected = activeId ? (interviews.find((i) => i.id === activeId) ?? null) : null;\n\n const thisWeekCount = useMemo(() => {\n const now = new Date();\n const startOfWeek = new Date(now);\n startOfWeek.setDate(now.getDate() - ((now.getDay() + 6) % 7));\n startOfWeek.setHours(0, 0, 0, 0);\n const endOfWeek = new Date(startOfWeek);\n endOfWeek.setDate(startOfWeek.getDate() + 7);\n return interviews.filter((i) => {\n const date = new Date(i.date);\n return !Number.isNaN(date.getTime()) && date >= startOfWeek && date < endOfWeek;\n }).length;\n }, [interviews]);\n\n const scheduleFields = useMemo<FieldConfig[]>(\n () => [\n {\n name: 'candidateId',\n label: 'Candidate',\n type: 'select',\n required: true,\n placeholder: candidates.length ? 'Select a candidate…' : 'No candidates available',\n options: candidates.map((c) => ({\n label: `${c.firstName} ${c.lastName} — ${c.position}`,\n value: c.id,\n })),\n },\n { name: 'interviewerName', label: 'Interviewer', type: 'text', required: true },\n { name: 'date', label: 'Date', type: 'date', required: true },\n { name: 'time', label: 'Time', type: 'text', required: true, placeholder: 'e.g. 10:30 AM' },\n {\n name: 'type',\n label: 'Type',\n type: 'select',\n required: true,\n options: INTERVIEW_TYPE_OPTIONS,\n },\n ],\n [candidates]\n );\n\n const handleSchedule = async (values: FormValues) => {\n const candidate = candidates.find((c) => c.id === getText(values, 'candidateId'));\n if (!candidate) {\n throw new Error('Please select a candidate');\n }\n await addInterview({\n id: newClientId(),\n candidateId: candidate.id,\n candidateName: `${candidate.firstName} ${candidate.lastName}`,\n position: candidate.position,\n interviewerName: getText(values, 'interviewerName'),\n date: getText(values, 'date'),\n time: getText(values, 'time'),\n type: values.type as Interview['type'],\n status: 'scheduled',\n });\n showToast('Interview scheduled', 'success');\n };\n\n const handleSaveScorecard = async (submit: boolean, feedback: string) => {\n if (!selected) {\n return;\n }\n setSaving(true);\n try {\n await updateInterview(selected.id, {\n status: submit ? 'completed' : selected.status,\n feedback,\n });\n showToast(submit ? 'Scorecard submitted' : 'Draft saved', 'success');\n } catch {\n showToast('Could not save the scorecard', 'error');\n } finally {\n setSaving(false);\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Interviews</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {scheduled.length} upcoming · {completed.length} completed\n </p>\n <PagePurpose className=\"mt-1\">\n Schedule interviews and score candidates on a live scorecard — capture competency\n ratings and feedback the moment the conversation ends.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => setScheduleOpen(true)}>\n <Plus className=\"mr-1 h-4 w-4\" />\n Schedule Interview\n </Button>\n </div>\n\n <div className=\"grid gap-4 sm:grid-cols-3\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Scheduled</CardTitle>\n <Calendar className=\"h-4 w-4 text-status-info-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{scheduled.length}</div>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Completed</CardTitle>\n <Clock className=\"h-4 w-4 text-status-success-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{completed.length}</div>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">This Week</CardTitle>\n <Video className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{thisWeekCount}</div>\n </CardContent>\n </GlassCard>\n </div>\n\n <div className=\"grid gap-6 lg:grid-cols-3\">\n {/* Scheduled / all interviews list */}\n <Card className=\"lg:col-span-2\">\n <CardHeader>\n <CardTitle>Scheduled Interviews</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {interviews.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No interviews scheduled\"\n description=\"Schedule an interview to start tracking your pipeline conversations.\"\n />\n ) : (\n dayGroups.map((group) => (\n <div key={group.label} className=\"space-y-2\">\n <div className=\"sticky top-0 rounded bg-bg-sunken px-3 py-1 text-xs font-medium text-text-secondary\">\n {group.label}\n </div>\n {group.items.map((interview) => {\n const isSelected = interview.id === activeId;\n const parsed = parseScorecard(interview.feedback);\n const overall = overallScore(parsed.scores) || interview.rating || 0;\n return (\n <button\n key={interview.id}\n type=\"button\"\n onClick={() => setSelectedId(interview.id)}\n aria-pressed={isSelected}\n className={`flex w-full flex-wrap items-center justify-between gap-3 rounded-[var(--radius-input)] border p-4 text-left transition-colors ${\n isSelected\n ? 'border-action-primary-bg bg-action-primary-bg/10'\n : 'border-border-subtle hover:bg-bg-sunken'\n }`}\n >\n <div className=\"flex min-w-0 items-center gap-3\">\n <InitialsAvatar name={interview.candidateName} />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {interview.candidateName}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">\n {interview.position}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">\n Interviewer: {interview.interviewerName}\n </p>\n </div>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <div className=\"text-right\">\n <Badge variant={TYPE_BADGE[interview.type]} className=\"text-[10px]\">\n {formatEnumLabel(interview.type)}\n </Badge>\n <p className=\"mt-1 text-xs text-text-secondary\">{interview.time}</p>\n </div>\n {interview.status === 'completed' && overall > 0 && (\n <RatingStars value={overall} size={12} />\n )}\n <Badge\n variant={interview.status === 'completed' ? 'default' : 'secondary'}\n className=\"text-[10px]\"\n >\n {formatEnumLabel(interview.status)}\n </Badge>\n </div>\n </button>\n );\n })}\n </div>\n ))\n )}\n </CardContent>\n </Card>\n\n {/* Persistent scorecard panel */}\n <div className=\"lg:sticky lg:top-4 lg:self-start\">\n <Card>\n <CardHeader>\n <CardTitle>Interview Scorecard</CardTitle>\n {selected && (\n <p className=\"text-sm text-text-secondary\">\n {formatEnumLabel(selected.type)} interview for {selected.candidateName}\n </p>\n )}\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {!selected ? (\n <p className=\"text-sm text-text-muted\">\n Select an interview to open its scorecard.\n </p>\n ) : (\n <ScorecardEditor\n key={selected.id}\n interview={selected}\n saving={saving}\n onSave={handleSaveScorecard}\n />\n )}\n </CardContent>\n </Card>\n </div>\n </div>\n\n <FormModal\n open={scheduleOpen}\n onOpenChange={setScheduleOpen}\n title=\"Schedule Interview\"\n description=\"Set up a conversation with a candidate in your pipeline.\"\n fields={scheduleFields}\n submitLabel=\"Schedule\"\n onSubmit={handleSchedule}\n />\n </div>\n );\n}\n\ninterface ScorecardEditorProps {\n interview: Interview;\n saving: boolean;\n onSave: (submit: boolean, feedback: string) => void;\n}\n\n/**\n * Editable scorecard for one interview. Re-mounted (via `key={interview.id}`)\n * whenever the selection changes, so its local state initializes from the\n * interview's persisted feedback without a synchronizing effect.\n */\nfunction ScorecardEditor({ interview, saving, onSave }: ScorecardEditorProps) {\n const initial = parseScorecard(interview.feedback);\n const [scores, setScores] = useState<Record<string, number>>(initial.scores);\n const [notes, setNotes] = useState(initial.notes);\n\n const overall = overallScore(scores);\n\n return (\n <>\n <div className=\"grid grid-cols-2 gap-3 rounded-[var(--radius-input)] bg-bg-sunken p-3 text-xs\">\n <div>\n <p className=\"text-text-muted\">Candidate</p>\n <p className=\"font-medium text-text-primary\">{interview.candidateName}</p>\n <p className=\"mt-1 text-text-muted\">Position</p>\n <p className=\"font-medium text-text-primary\">{interview.position}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Date</p>\n <p className=\"font-medium text-text-primary\">{interview.date}</p>\n <p className=\"mt-1 text-text-muted\">Time</p>\n <p className=\"font-medium text-text-primary\">{interview.time}</p>\n </div>\n </div>\n\n <div className=\"space-y-3\">\n {COMPETENCIES.map((competency) => (\n <div key={competency.key} className=\"flex items-center justify-between gap-2\">\n <span className=\"text-sm font-medium text-text-primary\">{competency.label}</span>\n <StarInput\n label={competency.label}\n value={scores[competency.key] ?? 0}\n onChange={(value) => setScores((prev) => ({ ...prev, [competency.key]: value }))}\n />\n </div>\n ))}\n </div>\n\n <div className=\"flex items-center justify-between border-t border-border-subtle pt-3 text-sm\">\n <span className=\"font-medium text-text-primary\">Overall</span>\n {overall > 0 ? (\n <RatingStars value={overall} size={16} showValue />\n ) : (\n <span className=\"text-xs text-text-muted\">Not scored</span>\n )}\n </div>\n\n <div className=\"space-y-1.5\">\n <label htmlFor=\"scorecard-feedback\" className=\"text-sm font-medium\">\n Feedback\n </label>\n <Textarea\n id=\"scorecard-feedback\"\n rows={5}\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n placeholder=\"Summarize strengths, concerns, and a recommendation…\"\n />\n </div>\n\n <div className=\"flex gap-2\">\n <Button\n className=\"flex-1\"\n disabled={saving}\n onClick={() => onSave(true, serializeScorecard(scores, notes))}\n >\n Submit\n </Button>\n <Button\n variant=\"outline\"\n className=\"flex-1\"\n disabled={saving}\n onClick={() => onSave(false, serializeScorecard(scores, notes))}\n >\n Save Draft\n </Button>\n </div>\n </>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"HiringInterviewsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HiringInterviewsPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { Calendar, Clock, Plus, Star, Video } from 'lucide-react';\nimport { useCrewFoundryStore, type Interview } from '../store/crewfoundryStore';\nimport { FormModal, type FieldConfig, type FormValues } from '../components/forms/FormModal';\nimport { getText, newClientId } from '../components/forms/formValues';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { useFocusedRecordParam } from '../lib/useFocusedRecordParam';\nimport { RatingStars, getInitials } from '../components/viz';\n\nconst INTERVIEW_TYPE_OPTIONS: FieldConfig['options'] = [\n { label: 'Phone', value: 'phone' },\n { label: 'Video', value: 'video' },\n { label: 'Onsite', value: 'onsite' },\n { label: 'Panel', value: 'panel' },\n { label: 'Technical', value: 'technical' },\n];\n\n// Per-competency scorecard. CrewInterview has only a single `feedback` (String)\n// + `rating` (Float), and the gateway strips `rating` from update inputs — so the\n// four competency scores are persisted by encoding them into `feedback` behind a\n// compact machine marker, then parsed back out on load. The written notes follow\n// the marker line; the overall rating is derived from the competency scores.\nconst COMPETENCIES: { key: string; label: string }[] = [\n { key: 'technical', label: 'Technical Skills' },\n { key: 'problem', label: 'Problem Solving' },\n { key: 'communication', label: 'Communication' },\n { key: 'culture', label: 'Culture Fit' },\n];\n\nconst SCORECARD_MARKER = '@@scorecard';\n\ninterface ParsedScorecard {\n scores: Record<string, number>;\n notes: string;\n}\n\nfunction parseScorecard(feedback?: string | null): ParsedScorecard {\n if (!feedback) {\n return { scores: {}, notes: '' };\n }\n if (!feedback.startsWith(`${SCORECARD_MARKER}:`)) {\n return { scores: {}, notes: feedback };\n }\n const newline = feedback.indexOf('\\n');\n const header = newline === -1 ? feedback : feedback.slice(0, newline);\n const notes = newline === -1 ? '' : feedback.slice(newline + 1);\n const scores: Record<string, number> = {};\n const body = header.slice(SCORECARD_MARKER.length + 1);\n for (const pair of body.split(',')) {\n const [key, raw] = pair.split('=');\n const value = Number(raw);\n if (key && !Number.isNaN(value)) {\n scores[key] = value;\n }\n }\n return { scores, notes };\n}\n\nfunction serializeScorecard(scores: Record<string, number>, notes: string): string {\n const entries = COMPETENCIES.filter((c) => (scores[c.key] ?? 0) > 0).map(\n (c) => `${c.key}=${scores[c.key]}`\n );\n if (entries.length === 0) {\n return notes;\n }\n return `${SCORECARD_MARKER}:${entries.join(',')}\\n${notes}`;\n}\n\nfunction overallScore(scores: Record<string, number>): number {\n const values = COMPETENCIES.map((c) => scores[c.key]).filter(\n (v): v is number => typeof v === 'number' && v > 0\n );\n if (values.length === 0) {\n return 0;\n }\n return Math.round((values.reduce((a, b) => a + b, 0) / values.length) * 10) / 10;\n}\n\nfunction InitialsAvatar({ name, size = 40 }: { name: string; size?: number }) {\n return (\n <span\n className=\"inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full border border-border-subtle bg-bg-sunken font-medium text-text-secondary\"\n style={{ width: size, height: size, fontSize: Math.round(size * 0.36) }}\n aria-hidden=\"true\"\n >\n {getInitials(name)}\n </span>\n );\n}\n\n/** Interactive 1–max star input for a single competency. */\nfunction StarInput({\n value,\n onChange,\n label,\n max = 5,\n}: {\n value: number;\n onChange: (value: number) => void;\n label: string;\n max?: number;\n}) {\n return (\n <div className=\"flex items-center gap-0.5\" role=\"radiogroup\" aria-label={label}>\n {Array.from({ length: max }, (_, i) => i + 1).map((n) => (\n <button\n key={n}\n type=\"button\"\n role=\"radio\"\n aria-checked={value === n}\n aria-label={`${n} out of ${max}`}\n onClick={() => onChange(value === n ? 0 : n)}\n className=\"rounded p-0.5 outline-none transition-transform hover:scale-110 focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <Star\n className=\"h-5 w-5\"\n strokeWidth={1.5}\n style={{\n color: n <= value ? 'var(--color-status-warning-text)' : 'var(--color-border-strong)',\n }}\n fill={n <= value ? 'currentColor' : 'none'}\n />\n </button>\n ))}\n </div>\n );\n}\n\nconst TYPE_BADGE: Record<Interview['type'], 'default' | 'secondary' | 'outline'> = {\n technical: 'default',\n panel: 'secondary',\n onsite: 'secondary',\n video: 'outline',\n phone: 'outline',\n};\n\nfunction formatDay(dateStr: string): string {\n const date = new Date(dateStr);\n if (Number.isNaN(date.getTime())) {\n return dateStr || 'Unscheduled';\n }\n return date.toLocaleDateString(undefined, {\n weekday: 'short',\n month: 'short',\n day: 'numeric',\n });\n}\n\nexport function HiringInterviewsPagePage() {\n const { showToast } = useGlobalUi();\n const { interviews, candidates, addInterview, updateInterview } = useCrewFoundryStore();\n const [scheduleOpen, setScheduleOpen] = useState(false);\n const [selectedId, setSelectedId] = useState<string | null>(null);\n const [saving, setSaving] = useState(false);\n\n const scheduled = interviews.filter((i) => i.status === 'scheduled');\n const completed = interviews.filter((i) => i.status === 'completed');\n\n // Interviews sorted chronologically, grouped by day for the schedule list.\n const dayGroups = useMemo(() => {\n const sorted = [...interviews].sort((a, b) => {\n const da = new Date(a.date).getTime();\n const db = new Date(b.date).getTime();\n if (Number.isNaN(da) && Number.isNaN(db)) return 0;\n if (Number.isNaN(da)) return 1;\n if (Number.isNaN(db)) return -1;\n return da - db;\n });\n const groups: { label: string; items: Interview[] }[] = [];\n for (const interview of sorted) {\n const label = formatDay(interview.date);\n const last = groups[groups.length - 1];\n if (last && last.label === label) {\n last.items.push(interview);\n } else {\n groups.push({ label, items: [interview] });\n }\n }\n return groups;\n }, [interviews]);\n\n // Effective selection is derived (no effect): honour an explicit click, else\n // default to the first scheduled interview, else the first interview overall.\n const activeId = selectedId ?? scheduled[0]?.id ?? interviews[0]?.id ?? null;\n const selected = activeId ? (interviews.find((i) => i.id === activeId) ?? null) : null;\n\n // The interview in the detail pane is the one \"this interview\" refers to:\n // keep it in `?id=` for the assistant's page context and for links.\n useFocusedRecordParam({\n records: interviews,\n activeId: selected?.id ?? null,\n onRestore: (interview) => setSelectedId(interview.id),\n });\n\n const thisWeekCount = useMemo(() => {\n const now = new Date();\n const startOfWeek = new Date(now);\n startOfWeek.setDate(now.getDate() - ((now.getDay() + 6) % 7));\n startOfWeek.setHours(0, 0, 0, 0);\n const endOfWeek = new Date(startOfWeek);\n endOfWeek.setDate(startOfWeek.getDate() + 7);\n return interviews.filter((i) => {\n const date = new Date(i.date);\n return !Number.isNaN(date.getTime()) && date >= startOfWeek && date < endOfWeek;\n }).length;\n }, [interviews]);\n\n const scheduleFields = useMemo<FieldConfig[]>(\n () => [\n {\n name: 'candidateId',\n label: 'Candidate',\n type: 'select',\n required: true,\n placeholder: candidates.length ? 'Select a candidate…' : 'No candidates available',\n options: candidates.map((c) => ({\n label: `${c.firstName} ${c.lastName} — ${c.position}`,\n value: c.id,\n })),\n },\n { name: 'interviewerName', label: 'Interviewer', type: 'text', required: true },\n { name: 'date', label: 'Date', type: 'date', required: true },\n { name: 'time', label: 'Time', type: 'text', required: true, placeholder: 'e.g. 10:30 AM' },\n {\n name: 'type',\n label: 'Type',\n type: 'select',\n required: true,\n options: INTERVIEW_TYPE_OPTIONS,\n },\n ],\n [candidates]\n );\n\n const handleSchedule = async (values: FormValues) => {\n const candidate = candidates.find((c) => c.id === getText(values, 'candidateId'));\n if (!candidate) {\n throw new Error('Please select a candidate');\n }\n await addInterview({\n id: newClientId(),\n candidateId: candidate.id,\n candidateName: `${candidate.firstName} ${candidate.lastName}`,\n position: candidate.position,\n interviewerName: getText(values, 'interviewerName'),\n date: getText(values, 'date'),\n time: getText(values, 'time'),\n type: values.type as Interview['type'],\n status: 'scheduled',\n });\n showToast('Interview scheduled', 'success');\n };\n\n const handleSaveScorecard = async (submit: boolean, feedback: string) => {\n if (!selected) {\n return;\n }\n setSaving(true);\n try {\n await updateInterview(selected.id, {\n status: submit ? 'completed' : selected.status,\n feedback,\n });\n showToast(submit ? 'Scorecard submitted' : 'Draft saved', 'success');\n } catch {\n showToast('Could not save the scorecard', 'error');\n } finally {\n setSaving(false);\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Interviews</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {scheduled.length} upcoming · {completed.length} completed\n </p>\n <PagePurpose className=\"mt-1\">\n Schedule interviews and score candidates on a live scorecard — capture competency\n ratings and feedback the moment the conversation ends.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => setScheduleOpen(true)}>\n <Plus className=\"mr-1 h-4 w-4\" />\n Schedule Interview\n </Button>\n </div>\n\n <div className=\"grid gap-4 sm:grid-cols-3\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Scheduled</CardTitle>\n <Calendar className=\"h-4 w-4 text-status-info-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{scheduled.length}</div>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Completed</CardTitle>\n <Clock className=\"h-4 w-4 text-status-success-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{completed.length}</div>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">This Week</CardTitle>\n <Video className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{thisWeekCount}</div>\n </CardContent>\n </GlassCard>\n </div>\n\n <div className=\"grid gap-6 lg:grid-cols-3\">\n {/* Scheduled / all interviews list */}\n <Card className=\"lg:col-span-2\">\n <CardHeader>\n <CardTitle>Scheduled Interviews</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {interviews.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No interviews scheduled\"\n description=\"Schedule an interview to start tracking your pipeline conversations.\"\n />\n ) : (\n dayGroups.map((group) => (\n <div key={group.label} className=\"space-y-2\">\n <div className=\"sticky top-0 rounded bg-bg-sunken px-3 py-1 text-xs font-medium text-text-secondary\">\n {group.label}\n </div>\n {group.items.map((interview) => {\n const isSelected = interview.id === activeId;\n const parsed = parseScorecard(interview.feedback);\n const overall = overallScore(parsed.scores) || interview.rating || 0;\n return (\n <button\n key={interview.id}\n type=\"button\"\n onClick={() => setSelectedId(interview.id)}\n aria-pressed={isSelected}\n className={`flex w-full flex-wrap items-center justify-between gap-3 rounded-[var(--radius-input)] border p-4 text-left transition-colors ${\n isSelected\n ? 'border-action-primary-bg bg-action-primary-bg/10'\n : 'border-border-subtle hover:bg-bg-sunken'\n }`}\n >\n <div className=\"flex min-w-0 items-center gap-3\">\n <InitialsAvatar name={interview.candidateName} />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {interview.candidateName}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">\n {interview.position}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">\n Interviewer: {interview.interviewerName}\n </p>\n </div>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <div className=\"text-right\">\n <Badge variant={TYPE_BADGE[interview.type]} className=\"text-[10px]\">\n {formatEnumLabel(interview.type)}\n </Badge>\n <p className=\"mt-1 text-xs text-text-secondary\">{interview.time}</p>\n </div>\n {interview.status === 'completed' && overall > 0 && (\n <RatingStars value={overall} size={12} />\n )}\n <Badge\n variant={interview.status === 'completed' ? 'default' : 'secondary'}\n className=\"text-[10px]\"\n >\n {formatEnumLabel(interview.status)}\n </Badge>\n </div>\n </button>\n );\n })}\n </div>\n ))\n )}\n </CardContent>\n </Card>\n\n {/* Persistent scorecard panel */}\n <div className=\"lg:sticky lg:top-4 lg:self-start\">\n <Card>\n <CardHeader>\n <CardTitle>Interview Scorecard</CardTitle>\n {selected && (\n <p className=\"text-sm text-text-secondary\">\n {formatEnumLabel(selected.type)} interview for {selected.candidateName}\n </p>\n )}\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {!selected ? (\n <p className=\"text-sm text-text-muted\">\n Select an interview to open its scorecard.\n </p>\n ) : (\n <ScorecardEditor\n key={selected.id}\n interview={selected}\n saving={saving}\n onSave={handleSaveScorecard}\n />\n )}\n </CardContent>\n </Card>\n </div>\n </div>\n\n <FormModal\n open={scheduleOpen}\n onOpenChange={setScheduleOpen}\n title=\"Schedule Interview\"\n description=\"Set up a conversation with a candidate in your pipeline.\"\n fields={scheduleFields}\n submitLabel=\"Schedule\"\n onSubmit={handleSchedule}\n />\n </div>\n );\n}\n\ninterface ScorecardEditorProps {\n interview: Interview;\n saving: boolean;\n onSave: (submit: boolean, feedback: string) => void;\n}\n\n/**\n * Editable scorecard for one interview. Re-mounted (via `key={interview.id}`)\n * whenever the selection changes, so its local state initializes from the\n * interview's persisted feedback without a synchronizing effect.\n */\nfunction ScorecardEditor({ interview, saving, onSave }: ScorecardEditorProps) {\n const initial = parseScorecard(interview.feedback);\n const [scores, setScores] = useState<Record<string, number>>(initial.scores);\n const [notes, setNotes] = useState(initial.notes);\n\n const overall = overallScore(scores);\n\n return (\n <>\n <div className=\"grid grid-cols-2 gap-3 rounded-[var(--radius-input)] bg-bg-sunken p-3 text-xs\">\n <div>\n <p className=\"text-text-muted\">Candidate</p>\n <p className=\"font-medium text-text-primary\">{interview.candidateName}</p>\n <p className=\"mt-1 text-text-muted\">Position</p>\n <p className=\"font-medium text-text-primary\">{interview.position}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Date</p>\n <p className=\"font-medium text-text-primary\">{interview.date}</p>\n <p className=\"mt-1 text-text-muted\">Time</p>\n <p className=\"font-medium text-text-primary\">{interview.time}</p>\n </div>\n </div>\n\n <div className=\"space-y-3\">\n {COMPETENCIES.map((competency) => (\n <div key={competency.key} className=\"flex items-center justify-between gap-2\">\n <span className=\"text-sm font-medium text-text-primary\">{competency.label}</span>\n <StarInput\n label={competency.label}\n value={scores[competency.key] ?? 0}\n onChange={(value) => setScores((prev) => ({ ...prev, [competency.key]: value }))}\n />\n </div>\n ))}\n </div>\n\n <div className=\"flex items-center justify-between border-t border-border-subtle pt-3 text-sm\">\n <span className=\"font-medium text-text-primary\">Overall</span>\n {overall > 0 ? (\n <RatingStars value={overall} size={16} showValue />\n ) : (\n <span className=\"text-xs text-text-muted\">Not scored</span>\n )}\n </div>\n\n <div className=\"space-y-1.5\">\n <label htmlFor=\"scorecard-feedback\" className=\"text-sm font-medium\">\n Feedback\n </label>\n <Textarea\n id=\"scorecard-feedback\"\n rows={5}\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n placeholder=\"Summarize strengths, concerns, and a recommendation…\"\n />\n </div>\n\n <div className=\"flex gap-2\">\n <Button\n className=\"flex-1\"\n disabled={saving}\n onClick={() => onSave(true, serializeScorecard(scores, notes))}\n >\n Submit\n </Button>\n <Button\n variant=\"outline\"\n className=\"flex-1\"\n disabled={saving}\n onClick={() => onSave(false, serializeScorecard(scores, notes))}\n >\n Save Draft\n </Button>\n </div>\n </>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"HrDepartmentsPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/HrDepartmentsPagePage.tsx"],"names":[],"mappings":"AAqDA,wBAAgB,qBAAqB,gCAuSpC"}
1
+ {"version":3,"file":"HrDepartmentsPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/HrDepartmentsPagePage.tsx"],"names":[],"mappings":"AAsDA,wBAAgB,qBAAqB,gCA6SpC"}
@@ -5,14 +5,15 @@ import { OrgTree as r } from "../components/viz/OrgTree.js";
5
5
  import { AvatarStack as i } from "../components/viz/AvatarStack.js";
6
6
  import "../components/viz/index.js";
7
7
  import { FormModal as a, useFormModal as o } from "../components/forms/FormModal.js";
8
- import { ConfirmDialog as s } from "../components/forms/ConfirmDialog.js";
9
- import { getNumber as c, getText as l, newClientId as u } from "../components/forms/formValues.js";
10
- import { useCallback as d, useMemo as f, useState as p } from "react";
11
- import { Building2 as m, DollarSign as h, MapPin as g, Network as _, Pencil as v, Plus as y, Trash2 as b, Users as x } from "lucide-react";
12
- import { jsx as S, jsxs as C } from "react/jsx-runtime";
13
- import { Badge as w, Button as T, Card as E, CardContent as D, CardHeader as O, CardTitle as k, GlassCard as A, GradientText as j, PagePurpose as M } from "@burdenoff/fe-libs/ui";
8
+ import { useFocusedRecordParam as s } from "../lib/useFocusedRecordParam.js";
9
+ import { ConfirmDialog as c } from "../components/forms/ConfirmDialog.js";
10
+ import { getNumber as l, getText as u, newClientId as d } from "../components/forms/formValues.js";
11
+ import { useCallback as f, useMemo as p, useState as m } from "react";
12
+ import { Building2 as h, DollarSign as g, MapPin as _, Network as v, Pencil as y, Plus as b, Trash2 as x, Users as S } from "lucide-react";
13
+ import { jsx as C, jsxs as w } from "react/jsx-runtime";
14
+ import { Badge as T, Button as E, Card as D, CardContent as O, CardHeader as k, CardTitle as A, GlassCard as j, GradientText as M, PagePurpose as N } from "@burdenoff/fe-libs/ui";
14
15
  //#region src/crewfoundry/pages/HrDepartmentsPagePage.tsx
15
- var N = (e) => `${e.firstName} ${e.lastName}`.trim(), P = [
16
+ var P = (e) => `${e.firstName} ${e.lastName}`.trim(), F = [
16
17
  {
17
18
  name: "name",
18
19
  label: "Department name",
@@ -51,25 +52,31 @@ var N = (e) => `${e.firstName} ${e.lastName}`.trim(), P = [
51
52
  value: "restructuring"
52
53
  }]
53
54
  }
54
- ], F = (e) => ({
55
+ ], I = (e) => ({
55
56
  name: e.name,
56
57
  headName: e.headName,
57
58
  location: e.location,
58
59
  budget: e.budget,
59
60
  status: e.status
60
61
  });
61
- function I() {
62
- let { departments: I, employees: L, teams: R, addDepartment: z, updateDepartment: B, removeDepartment: V } = e(), H = o(), [U, W] = p(null), G = I.reduce((e, t) => e + t.budget, 0), K = I.reduce((e, t) => e + t.employeeCount, 0), q = H.context, J = d((e) => L.find((t) => t.id === e.headId) || L.find((t) => N(t) === e.headName), [L]), Y = f(() => {
63
- if (I.length === 0) return null;
64
- let e = L.find((e) => !e.managerId && /chief|ceo|founder|president|head of company/i.test(e.position)), t = I.map((e) => {
65
- let t = J(e), n = R.filter((t) => t.department === e.name), r;
62
+ function L() {
63
+ let { departments: L, employees: R, teams: z, addDepartment: B, updateDepartment: V, removeDepartment: H } = e(), U = o();
64
+ s({
65
+ records: L,
66
+ activeId: U.open ? U.context?.id ?? null : null,
67
+ onRestore: U.openModal
68
+ });
69
+ let [W, G] = m(null), K = L.reduce((e, t) => e + t.budget, 0), q = L.reduce((e, t) => e + t.employeeCount, 0), J = U.context, Y = f((e) => R.find((t) => t.id === e.headId) || R.find((t) => P(t) === e.headName), [R]), X = p(() => {
70
+ if (L.length === 0) return null;
71
+ let e = R.find((e) => !e.managerId && /chief|ceo|founder|president|head of company/i.test(e.position)), t = L.map((e) => {
72
+ let t = Y(e), n = z.filter((t) => t.department === e.name), r;
66
73
  return r = n.length > 0 ? n.slice(0, 4).map((e) => ({
67
74
  id: e.id,
68
75
  name: e.name,
69
76
  role: `${e.memberCount} member${e.memberCount === 1 ? "" : "s"}`
70
- })) : L.filter((n) => n.department === e.name && n.id !== t?.id).slice(0, 3).map((e) => ({
77
+ })) : R.filter((n) => n.department === e.name && n.id !== t?.id).slice(0, 3).map((e) => ({
71
78
  id: e.id,
72
- name: N(e),
79
+ name: P(e),
73
80
  role: e.position,
74
81
  avatarUrl: e.avatar
75
82
  })), {
@@ -82,175 +89,175 @@ function I() {
82
89
  });
83
90
  return {
84
91
  id: e?.id ?? "org-root",
85
- name: e ? N(e) : "Organization",
92
+ name: e ? P(e) : "Organization",
86
93
  role: e?.position ?? "Company",
87
94
  avatarUrl: e?.avatar,
88
95
  children: t
89
96
  };
90
97
  }, [
91
- I,
92
98
  L,
93
99
  R,
94
- J
100
+ z,
101
+ Y
95
102
  ]);
96
- return /* @__PURE__ */ C("div", {
103
+ return /* @__PURE__ */ w("div", {
97
104
  className: "space-y-6 p-6",
98
105
  children: [
99
- /* @__PURE__ */ C("div", {
106
+ /* @__PURE__ */ w("div", {
100
107
  className: "flex items-center justify-between",
101
- children: [/* @__PURE__ */ C("div", { children: [
102
- /* @__PURE__ */ S("h1", {
108
+ children: [/* @__PURE__ */ w("div", { children: [
109
+ /* @__PURE__ */ C("h1", {
103
110
  className: "text-2xl font-semibold",
104
- children: /* @__PURE__ */ S(j, { children: "Departments" })
111
+ children: /* @__PURE__ */ C(M, { children: "Departments" })
105
112
  }),
106
- /* @__PURE__ */ S("p", {
113
+ /* @__PURE__ */ C("p", {
107
114
  className: "text-sm text-text-secondary",
108
115
  children: "Organizational structure and department management"
109
116
  }),
110
- /* @__PURE__ */ S(M, {
117
+ /* @__PURE__ */ C(N, {
111
118
  className: "mt-1",
112
119
  children: "Organize your company into departments — set up structure, assign heads, and see how headcount is distributed across the org."
113
120
  })
114
- ] }), /* @__PURE__ */ C(T, {
121
+ ] }), /* @__PURE__ */ w(E, {
115
122
  size: "sm",
116
- onClick: () => H.openModal(),
117
- children: [/* @__PURE__ */ S(y, { className: "h-4 w-4 mr-1" }), "Add Department"]
123
+ onClick: () => U.openModal(),
124
+ children: [/* @__PURE__ */ C(b, { className: "h-4 w-4 mr-1" }), "Add Department"]
118
125
  })]
119
126
  }),
120
- /* @__PURE__ */ C("div", {
127
+ /* @__PURE__ */ w("div", {
121
128
  className: "grid gap-4 md:grid-cols-3",
122
129
  children: [
123
- /* @__PURE__ */ S(n, {
130
+ /* @__PURE__ */ C(n, {
124
131
  label: "Total Departments",
125
- value: I.length,
126
- icon: /* @__PURE__ */ S(m, { className: "h-4 w-4" })
132
+ value: L.length,
133
+ icon: /* @__PURE__ */ C(h, { className: "h-4 w-4" })
127
134
  }),
128
- /* @__PURE__ */ S(n, {
135
+ /* @__PURE__ */ C(n, {
129
136
  label: "Total Headcount",
130
- value: K,
131
- icon: /* @__PURE__ */ S(x, { className: "h-4 w-4" })
137
+ value: q,
138
+ icon: /* @__PURE__ */ C(S, { className: "h-4 w-4" })
132
139
  }),
133
- /* @__PURE__ */ S(n, {
140
+ /* @__PURE__ */ C(n, {
134
141
  label: "Total Budget",
135
- value: `$${(G / 1e6).toFixed(1)}M`,
136
- icon: /* @__PURE__ */ S(h, { className: "h-4 w-4" })
142
+ value: `$${(K / 1e6).toFixed(1)}M`,
143
+ icon: /* @__PURE__ */ C(g, { className: "h-4 w-4" })
137
144
  })
138
145
  ]
139
146
  }),
140
- /* @__PURE__ */ S("div", {
147
+ /* @__PURE__ */ C("div", {
141
148
  className: "grid gap-4 md:grid-cols-2 xl:grid-cols-3",
142
- children: I.map((e) => {
143
- let n = L.filter((t) => t.department === e.name), r = e.employeeCount || n.length, a = n.length > 0 ? Math.round(n.reduce((e, t) => e + t.salary, 0) / n.length) : 0, o = n.length > 0 ? n.reduce((e, t) => e + t.performanceScore, 0) / n.length : 0, s = Math.round(o / 5 * 100), c = J(e), l = s >= 80 ? "bg-status-success-text" : s >= 60 ? "bg-status-warning-text" : "bg-status-error-text";
144
- return /* @__PURE__ */ C(A, {
149
+ children: L.map((e) => {
150
+ let n = R.filter((t) => t.department === e.name), r = e.employeeCount || n.length, a = n.length > 0 ? Math.round(n.reduce((e, t) => e + t.salary, 0) / n.length) : 0, o = n.length > 0 ? n.reduce((e, t) => e + t.performanceScore, 0) / n.length : 0, s = Math.round(o / 5 * 100), c = Y(e), l = s >= 80 ? "bg-status-success-text" : s >= 60 ? "bg-status-warning-text" : "bg-status-error-text";
151
+ return /* @__PURE__ */ w(j, {
145
152
  treatment: "glass",
146
153
  className: "border-border-seam",
147
- children: [/* @__PURE__ */ S(O, { children: /* @__PURE__ */ C("div", {
154
+ children: [/* @__PURE__ */ C(k, { children: /* @__PURE__ */ w("div", {
148
155
  className: "flex items-start justify-between gap-2",
149
- children: [/* @__PURE__ */ C("div", {
156
+ children: [/* @__PURE__ */ w("div", {
150
157
  className: "min-w-0",
151
- children: [/* @__PURE__ */ S(k, {
158
+ children: [/* @__PURE__ */ C(A, {
152
159
  className: "truncate text-lg",
153
160
  children: e.name
154
- }), /* @__PURE__ */ C("div", {
161
+ }), /* @__PURE__ */ w("div", {
155
162
  className: "mt-2 flex items-center gap-2",
156
- children: [/* @__PURE__ */ S(i, {
163
+ children: [/* @__PURE__ */ C(i, {
157
164
  people: [{
158
165
  name: e.headName || "Unassigned",
159
166
  avatarUrl: c?.avatar
160
167
  }],
161
168
  size: 28
162
- }), /* @__PURE__ */ C("span", {
169
+ }), /* @__PURE__ */ w("span", {
163
170
  className: "truncate text-sm text-text-secondary",
164
- children: [/* @__PURE__ */ S("span", {
171
+ children: [/* @__PURE__ */ C("span", {
165
172
  className: "font-medium text-text-primary",
166
173
  children: e.headName
167
174
  }), e.headName ? ", Head" : "No head assigned"]
168
175
  })]
169
176
  })]
170
- }), /* @__PURE__ */ C("div", {
177
+ }), /* @__PURE__ */ w("div", {
171
178
  className: "flex items-center gap-1",
172
179
  children: [
173
- /* @__PURE__ */ S(w, {
180
+ /* @__PURE__ */ C(T, {
174
181
  variant: e.status === "active" ? "default" : "secondary",
175
182
  children: t(e.status)
176
183
  }),
177
- /* @__PURE__ */ S(T, {
184
+ /* @__PURE__ */ C(E, {
178
185
  variant: "ghost",
179
186
  size: "sm",
180
187
  className: "h-8 w-8 p-0",
181
188
  "aria-label": `Edit ${e.name}`,
182
- onClick: () => H.openModal(e),
183
- children: /* @__PURE__ */ S(v, { className: "h-4 w-4" })
189
+ onClick: () => U.openModal(e),
190
+ children: /* @__PURE__ */ C(y, { className: "h-4 w-4" })
184
191
  }),
185
- /* @__PURE__ */ S(T, {
192
+ /* @__PURE__ */ C(E, {
186
193
  variant: "ghost",
187
194
  size: "sm",
188
195
  className: "h-8 w-8 p-0",
189
196
  "aria-label": `Delete ${e.name}`,
190
- onClick: () => W(e),
191
- children: /* @__PURE__ */ S(b, { className: "h-4 w-4 text-status-error-text" })
197
+ onClick: () => G(e),
198
+ children: /* @__PURE__ */ C(x, { className: "h-4 w-4 text-status-error-text" })
192
199
  })
193
200
  ]
194
201
  })]
195
- }) }), /* @__PURE__ */ C(D, {
202
+ }) }), /* @__PURE__ */ w(O, {
196
203
  className: "space-y-4",
197
204
  children: [
198
- /* @__PURE__ */ C("div", {
205
+ /* @__PURE__ */ w("div", {
199
206
  className: "flex items-end justify-between gap-3",
200
- children: [/* @__PURE__ */ C("div", { children: [/* @__PURE__ */ S("p", {
207
+ children: [/* @__PURE__ */ w("div", { children: [/* @__PURE__ */ C("p", {
201
208
  className: "text-3xl font-bold leading-none tabular-nums",
202
209
  children: r
203
- }), /* @__PURE__ */ S("p", {
210
+ }), /* @__PURE__ */ C("p", {
204
211
  className: "mt-1 text-xs text-text-secondary",
205
212
  children: "Employees"
206
- })] }), /* @__PURE__ */ C("div", {
213
+ })] }), /* @__PURE__ */ w("div", {
207
214
  className: "text-right",
208
- children: [/* @__PURE__ */ C("p", {
215
+ children: [/* @__PURE__ */ w("p", {
209
216
  className: "text-lg font-semibold tabular-nums",
210
217
  children: [
211
218
  "$",
212
219
  (e.budget / 1e6).toFixed(1),
213
220
  "M"
214
221
  ]
215
- }), /* @__PURE__ */ S("p", {
222
+ }), /* @__PURE__ */ C("p", {
216
223
  className: "text-xs text-text-secondary",
217
224
  children: "Annual budget"
218
225
  })]
219
226
  })]
220
227
  }),
221
- /* @__PURE__ */ C("div", {
228
+ /* @__PURE__ */ w("div", {
222
229
  className: "space-y-1",
223
- children: [/* @__PURE__ */ C("div", {
230
+ children: [/* @__PURE__ */ w("div", {
224
231
  className: "flex items-center justify-between text-xs text-text-secondary",
225
- children: [/* @__PURE__ */ S("span", { children: "Avg performance" }), /* @__PURE__ */ C("span", {
232
+ children: [/* @__PURE__ */ C("span", { children: "Avg performance" }), /* @__PURE__ */ w("span", {
226
233
  className: "font-medium text-text-primary tabular-nums",
227
234
  children: [s, "%"]
228
235
  })]
229
- }), /* @__PURE__ */ S("div", {
236
+ }), /* @__PURE__ */ C("div", {
230
237
  className: "h-2 overflow-hidden rounded-full bg-bg-sunken",
231
- children: /* @__PURE__ */ S("div", {
238
+ children: /* @__PURE__ */ C("div", {
232
239
  className: `h-2 rounded-full ${l}`,
233
240
  style: { width: `${s}%` }
234
241
  })
235
242
  })]
236
243
  }),
237
- /* @__PURE__ */ C("div", {
244
+ /* @__PURE__ */ w("div", {
238
245
  className: "flex items-center justify-between text-xs text-text-secondary",
239
- children: [/* @__PURE__ */ C("span", {
246
+ children: [/* @__PURE__ */ w("span", {
240
247
  className: "flex items-center gap-1",
241
- children: [/* @__PURE__ */ S(g, { className: "h-3 w-3" }), e.location]
242
- }), /* @__PURE__ */ C("span", { children: [
248
+ children: [/* @__PURE__ */ C(_, { className: "h-3 w-3" }), e.location]
249
+ }), /* @__PURE__ */ w("span", { children: [
243
250
  "Cost/Head:",
244
251
  " ",
245
252
  r > 0 ? `$${Math.round(e.budget / r / 1e3)}K` : "—"
246
253
  ] })]
247
254
  }),
248
- a > 0 && /* @__PURE__ */ C("p", {
255
+ a > 0 && /* @__PURE__ */ w("p", {
249
256
  className: "text-xs text-text-secondary",
250
257
  children: [
251
258
  "Avg salary",
252
259
  " ",
253
- /* @__PURE__ */ C("span", {
260
+ /* @__PURE__ */ w("span", {
254
261
  className: "font-medium text-text-primary",
255
262
  children: ["$", a.toLocaleString()]
256
263
  })
@@ -261,55 +268,55 @@ function I() {
261
268
  }, e.id);
262
269
  })
263
270
  }),
264
- Y && /* @__PURE__ */ C(E, { children: [/* @__PURE__ */ S(O, { children: /* @__PURE__ */ C(k, {
271
+ X && /* @__PURE__ */ w(D, { children: [/* @__PURE__ */ C(k, { children: /* @__PURE__ */ w(A, {
265
272
  className: "flex items-center gap-2 text-lg",
266
- children: [/* @__PURE__ */ S(_, { className: "h-4 w-4 text-text-secondary" }), "Org structure"]
267
- }) }), /* @__PURE__ */ S(D, { children: /* @__PURE__ */ S("div", {
273
+ children: [/* @__PURE__ */ C(v, { className: "h-4 w-4 text-text-secondary" }), "Org structure"]
274
+ }) }), /* @__PURE__ */ C(O, { children: /* @__PURE__ */ C("div", {
268
275
  className: "overflow-x-auto pb-2",
269
- children: /* @__PURE__ */ S("div", {
276
+ children: /* @__PURE__ */ C("div", {
270
277
  className: "min-w-fit",
271
- children: /* @__PURE__ */ S(r, { root: Y })
278
+ children: /* @__PURE__ */ C(r, { root: X })
272
279
  })
273
280
  }) })] }),
274
- /* @__PURE__ */ S(a, {
275
- open: H.open,
276
- onOpenChange: H.onOpenChange,
277
- title: q ? "Edit department" : "Add department",
278
- description: q ? "Update this department’s details." : "Create a new department in your organization.",
279
- fields: P,
280
- initialValues: q ? F(q) : void 0,
281
- submitLabel: q ? "Save changes" : "Add department",
281
+ /* @__PURE__ */ C(a, {
282
+ open: U.open,
283
+ onOpenChange: U.onOpenChange,
284
+ title: J ? "Edit department" : "Add department",
285
+ description: J ? "Update this department’s details." : "Create a new department in your organization.",
286
+ fields: F,
287
+ initialValues: J ? I(J) : void 0,
288
+ submitLabel: J ? "Save changes" : "Add department",
282
289
  onSubmit: async (e) => {
283
290
  let t = {
284
- id: q?.id ?? u(),
285
- name: l(e, "name"),
286
- headId: q?.headId ?? "",
287
- headName: l(e, "headName"),
288
- employeeCount: q?.employeeCount ?? 0,
289
- budget: c(e, "budget"),
290
- location: l(e, "location"),
291
- status: l(e, "status")
291
+ id: J?.id ?? d(),
292
+ name: u(e, "name"),
293
+ headId: J?.headId ?? "",
294
+ headName: u(e, "headName"),
295
+ employeeCount: J?.employeeCount ?? 0,
296
+ budget: l(e, "budget"),
297
+ location: u(e, "location"),
298
+ status: u(e, "status")
292
299
  };
293
- q ? await B(q.id, t) : await z(t);
300
+ J ? await V(J.id, t) : await B(t);
294
301
  }
295
302
  }),
296
- /* @__PURE__ */ S(s, {
297
- open: U !== null,
303
+ /* @__PURE__ */ C(c, {
304
+ open: W !== null,
298
305
  onOpenChange: (e) => {
299
- e || W(null);
306
+ e || G(null);
300
307
  },
301
308
  title: "Delete department?",
302
- description: U ? `${U.name} will be removed. This cannot be undone.` : void 0,
309
+ description: W ? `${W.name} will be removed. This cannot be undone.` : void 0,
303
310
  confirmLabel: "Delete",
304
311
  destructive: !0,
305
312
  onConfirm: async () => {
306
- U && (await V(U.id), W(null));
313
+ W && (await H(W.id), G(null));
307
314
  }
308
315
  })
309
316
  ]
310
317
  });
311
318
  }
312
319
  //#endregion
313
- export { I as HrDepartmentsPagePage };
320
+ export { L as HrDepartmentsPagePage };
314
321
 
315
322
  //# sourceMappingURL=HrDepartmentsPagePage.js.map