@burdenoff/microfe-crewfoundry 2026.830.1 → 2026.1013.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.
- package/dist/crewfoundry/components/forms/FormModal.d.ts.map +1 -1
- package/dist/crewfoundry/components/forms/FormModal.js.map +1 -1
- package/dist/crewfoundry/pages/HiringCreateJobPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/HiringCreateJobPagePage.js +0 -4
- package/dist/crewfoundry/pages/HiringCreateJobPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringJobDetailsPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js +0 -4
- package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/store/crewfoundryStore.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormModal.d.ts","sourceRoot":"","sources":["../../../../src/crewfoundry/components/forms/FormModal.tsx"],"names":[],"mappings":"AAmBA,MAAM,MAAM,aAAa,
|
|
1
|
+
{"version":3,"file":"FormModal.d.ts","sourceRoot":"","sources":["../../../../src/crewfoundry/components/forms/FormModal.tsx"],"names":[],"mappings":"AAmBA,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,QAAQ,GACR,MAAM,GACN,OAAO,GACP,UAAU,GACV,QAAQ,GACR,MAAM,GACN,QAAQ,CAAC;AAEb,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,CAAC;AAC7D,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAEnD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,aAAa,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB,wFAAwF;IACxF,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,aAAa,CAAC,EAAE,UAAU,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACjD;AAkED,wBAAgB,SAAS,CAAC,EACxB,IAAI,EACJ,YAAY,EACZ,KAAK,EACL,WAAW,EACX,MAAM,EACN,aAAa,EACb,WAAW,EACX,QAAQ,GACT,EAAE,cAAc,+BAsRhB;AAGD,MAAM,WAAW,kBAAkB,CAAC,QAAQ;IAC1C,IAAI,EAAE,OAAO,CAAC;IACd,0EAA0E;IAC1E,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,CAAC,OAAO,CAAC,EAAE,QAAQ,KAAK,IAAI,CAAC;IACxC,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACvC;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,QAAQ,GAAG,IAAI,KAAK,kBAAkB,CAAC,QAAQ,CAAC,CAmB5E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormModal.js","names":[],"sources":["../../../../src/crewfoundry/components/forms/FormModal.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport {\n Button,\n Input,\n Label,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n Switch,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { X } from 'lucide-react';\nimport { useTr } from '../../../shared/hooks/useTr';\n\n// ── Public API ──────────────────────────────────────────────────\nexport type FormFieldType =\n 'text' | 'number' | 'date' | 'email' | 'textarea' | 'select' | 'tags' | 'switch';\n\nexport type FormValue = string | number | boolean | string[];\nexport type FormValues = Record<string, FormValue>;\n\nexport interface FieldOption {\n label: string;\n value: string;\n}\n\nexport interface FieldConfig {\n name: string;\n label: string;\n type: FormFieldType;\n required?: boolean;\n placeholder?: string;\n options?: FieldOption[];\n /** Column span in the 2-col grid on md+ (defaults to 1; textarea/tags default to 2). */\n colSpan?: 1 | 2;\n}\n\nexport interface FormModalProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n title: string;\n description?: string;\n fields: FieldConfig[];\n initialValues?: FormValues;\n submitLabel?: string;\n onSubmit: (values: FormValues) => Promise<void>;\n}\n\n// ── Internal state helpers ──────────────────────────────────────\n// Text-like inputs are held as strings for controlled inputs; switches as\n// booleans; tags as string[]. Values are coerced back to their public shapes\n// on submit.\ntype InternalValue = string | boolean | string[];\ntype InternalValues = Record<string, InternalValue>;\n\nconst buildInitialState = (fields: FieldConfig[], initialValues?: FormValues): InternalValues => {\n const state: InternalValues = {};\n for (const field of fields) {\n const raw = initialValues?.[field.name];\n if (field.type === 'switch') {\n state[field.name] = Boolean(raw);\n } else if (field.type === 'tags') {\n if (Array.isArray(raw)) {\n state[field.name] = raw.map((item) => String(item));\n } else if (typeof raw === 'string' && raw.length > 0) {\n state[field.name] = raw\n .split(',')\n .map((item) => item.trim())\n .filter(Boolean);\n } else {\n state[field.name] = [];\n }\n } else {\n state[field.name] = raw === undefined || raw === null ? '' : String(raw);\n }\n }\n return state;\n};\n\nconst isEmptyValue = (field: FieldConfig, value: InternalValue): boolean => {\n if (field.type === 'switch') {\n return false;\n }\n if (field.type === 'tags') {\n return !Array.isArray(value) || value.length === 0;\n }\n return typeof value !== 'string' || value.trim() === '';\n};\n\nconst toOutput = (fields: FieldConfig[], state: InternalValues): FormValues => {\n const output: FormValues = {};\n for (const field of fields) {\n const value = state[field.name];\n if (field.type === 'switch') {\n output[field.name] = Boolean(value);\n } else if (field.type === 'tags') {\n output[field.name] = Array.isArray(value) ? value : [];\n } else if (field.type === 'number') {\n const raw = typeof value === 'string' ? value.trim() : '';\n output[field.name] = raw === '' ? '' : Number(raw);\n } else {\n output[field.name] = typeof value === 'string' ? value : '';\n }\n }\n return output;\n};\n\n// Native <select> styled with the same semantic tokens as fe-libs Input.\nconst SELECT_CLASS =\n 'flex h-10 w-full rounded-input border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary transition-colors duration-200 focus-visible:border-border-focus focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)] focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)] disabled:cursor-not-allowed disabled:opacity-50';\n\n// ── Component ───────────────────────────────────────────────────\nexport function FormModal({\n open,\n onOpenChange,\n title,\n description,\n fields,\n initialValues,\n submitLabel,\n onSubmit,\n}: FormModalProps) {\n const { showToast } = useGlobalUi();\n const tr = useTr();\n const resolvedSubmitLabel = submitLabel ?? tr('formModal.submitLabel', 'Save');\n const [values, setValues] = useState<InternalValues>(() =>\n buildInitialState(fields, initialValues)\n );\n const [tagDrafts, setTagDrafts] = useState<Record<string, string>>({});\n const [submitting, setSubmitting] = useState(false);\n\n // Keep the latest field config / initial values reachable from the reset\n // effect without making them effect dependencies (page callers rarely\n // memoize these, and depending on them would loop or clobber edits).\n const fieldsRef = useRef(fields);\n const initialValuesRef = useRef(initialValues);\n const wasOpenRef = useRef(false);\n\n // Sync the latest field config / initial values into refs after each commit\n // (never during render). Declared before the reset effect so that on a rising\n // edge of `open` the refs are refreshed first, then read by the reset below.\n useEffect(() => {\n fieldsRef.current = fields;\n initialValuesRef.current = initialValues;\n }, [fields, initialValues]);\n\n // Reset the working copy on each rising edge of `open` so edit vs create\n // contexts never leak into each other.\n useEffect(() => {\n if (open && !wasOpenRef.current) {\n setValues(buildInitialState(fieldsRef.current, initialValuesRef.current));\n setTagDrafts({});\n setSubmitting(false);\n }\n wasOpenRef.current = open;\n }, [open]);\n\n const setFieldValue = useCallback((name: string, value: InternalValue) => {\n setValues((prev) => ({ ...prev, [name]: value }));\n }, []);\n\n const commitTag = useCallback((name: string) => {\n setTagDrafts((prevDrafts) => {\n const draft = (prevDrafts[name] ?? '').trim();\n if (draft) {\n setValues((prev) => {\n const current = Array.isArray(prev[name]) ? (prev[name] as string[]) : [];\n if (current.includes(draft)) {\n return prev;\n }\n return { ...prev, [name]: [...current, draft] };\n });\n }\n return { ...prevDrafts, [name]: '' };\n });\n }, []);\n\n const removeTag = useCallback((name: string, tag: string) => {\n setValues((prev) => {\n const current = Array.isArray(prev[name]) ? (prev[name] as string[]) : [];\n return { ...prev, [name]: current.filter((item) => item !== tag) };\n });\n }, []);\n\n const handleSubmit = useCallback(\n async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n if (submitting) {\n return;\n }\n\n // Fold any un-committed tag draft into its field before validating.\n const staged: InternalValues = { ...values };\n for (const field of fields) {\n if (field.type === 'tags') {\n const draft = (tagDrafts[field.name] ?? '').trim();\n if (draft) {\n const current = Array.isArray(staged[field.name])\n ? (staged[field.name] as string[])\n : [];\n if (!current.includes(draft)) {\n staged[field.name] = [...current, draft];\n }\n }\n }\n }\n\n const missing = fields.find(\n (field) => field.required && isEmptyValue(field, staged[field.name])\n );\n if (missing) {\n showToast(\n tr('formModal.fieldRequired', '{{label}} is required', { label: missing.label }),\n 'warning'\n );\n return;\n }\n\n setSubmitting(true);\n try {\n await onSubmit(toOutput(fields, staged));\n onOpenChange(false);\n } catch (error) {\n showToast(\n error instanceof Error\n ? error.message\n : tr('formModal.submitError', 'Something went wrong'),\n 'error'\n );\n } finally {\n setSubmitting(false);\n }\n },\n [fields, onOpenChange, onSubmit, showToast, submitting, tagDrafts, tr, values]\n );\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"max-h-[85vh] overflow-y-auto sm:max-w-[560px]\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>{title}</ResponsiveDialogTitle>\n {description ? (\n <ResponsiveDialogDescription>{description}</ResponsiveDialogDescription>\n ) : null}\n </ResponsiveDialogHeader>\n\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n {fields.map((field) => {\n const fieldId = `formmodal-${field.name}`;\n const spanTwo =\n field.colSpan === 2 ||\n (field.colSpan === undefined &&\n (field.type === 'textarea' || field.type === 'tags'));\n const value = values[field.name];\n\n return (\n <div\n key={field.name}\n className={`flex flex-col gap-1.5 ${spanTwo ? 'md:col-span-2' : ''}`}\n >\n {field.type === 'switch' ? (\n <div className=\"flex items-center justify-between gap-3 rounded-input border border-border-default bg-bg-surface px-3 py-2\">\n <Label htmlFor={fieldId}>\n {field.label}\n {field.required ? <span className=\"text-status-error-fg\"> *</span> : null}\n </Label>\n <Switch\n id={fieldId}\n checked={Boolean(value)}\n onCheckedChange={(checked) => setFieldValue(field.name, checked)}\n disabled={submitting}\n />\n </div>\n ) : (\n <>\n <Label htmlFor={fieldId}>\n {field.label}\n {field.required ? <span className=\"text-status-error-fg\"> *</span> : null}\n </Label>\n\n {field.type === 'textarea' ? (\n <Textarea\n id={fieldId}\n value={typeof value === 'string' ? value : ''}\n placeholder={field.placeholder}\n disabled={submitting}\n onChange={(event) => setFieldValue(field.name, event.target.value)}\n />\n ) : field.type === 'select' ? (\n <select\n id={fieldId}\n className={SELECT_CLASS}\n value={typeof value === 'string' ? value : ''}\n disabled={submitting}\n onChange={(event) => setFieldValue(field.name, event.target.value)}\n >\n <option value=\"\" disabled>\n {field.placeholder ?? tr('formModal.selectPlaceholder', 'Select…')}\n </option>\n {(field.options ?? []).map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n ) : field.type === 'tags' ? (\n <div className=\"flex min-h-10 flex-wrap items-center gap-1.5 rounded-input border border-border-default bg-bg-surface px-2 py-1.5\">\n {(Array.isArray(value) ? value : []).map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center gap-1 rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-primary\"\n >\n {tag}\n <button\n type=\"button\"\n aria-label={tr('formModal.removeTag', 'Remove {{tag}}', { tag })}\n className=\"text-text-secondary hover:text-text-primary\"\n onClick={() => removeTag(field.name, tag)}\n disabled={submitting}\n >\n <X className=\"h-3 w-3\" />\n </button>\n </span>\n ))}\n <input\n id={fieldId}\n className=\"min-w-[6rem] flex-1 bg-transparent text-sm text-text-primary outline-none placeholder:text-text-placeholder\"\n value={tagDrafts[field.name] ?? ''}\n placeholder={\n field.placeholder ??\n tr('formModal.tagsPlaceholder', 'Add and press Enter')\n }\n disabled={submitting}\n onChange={(event) =>\n setTagDrafts((prev) => ({\n ...prev,\n [field.name]: event.target.value,\n }))\n }\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ',') {\n event.preventDefault();\n commitTag(field.name);\n } else if (\n event.key === 'Backspace' &&\n (tagDrafts[field.name] ?? '') === ''\n ) {\n const current = Array.isArray(value) ? value : [];\n if (current.length > 0) {\n removeTag(field.name, current[current.length - 1]);\n }\n }\n }}\n onBlur={() => commitTag(field.name)}\n />\n </div>\n ) : (\n <Input\n id={fieldId}\n type={\n field.type === 'number'\n ? 'number'\n : field.type === 'date'\n ? 'date'\n : field.type === 'email'\n ? 'email'\n : 'text'\n }\n value={typeof value === 'string' ? value : ''}\n placeholder={field.placeholder}\n disabled={submitting}\n onChange={(event) => setFieldValue(field.name, event.target.value)}\n />\n )}\n </>\n )}\n </div>\n );\n })}\n </div>\n\n <ResponsiveDialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n disabled={submitting}\n >\n {tr('formModal.cancel', 'Cancel')}\n </Button>\n <Button type=\"submit\" disabled={submitting}>\n {submitting ? tr('formModal.saving', 'Saving…') : resolvedSubmitLabel}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\n// ── Optional open/close helper ──────────────────────────────────\nexport interface UseFormModalResult<TContext> {\n open: boolean;\n /** The row/context passed to openModal (e.g. the entity being edited). */\n context: TContext | null;\n openModal: (context?: TContext) => void;\n closeModal: () => void;\n onOpenChange: (open: boolean) => void;\n}\n\n/**\n * Tiny helper for pages that toggle a single FormModal and want to remember\n * which row is being edited. `context` is null for a create flow.\n */\nexport function useFormModal<TContext = void>(): UseFormModalResult<TContext> {\n const [open, setOpen] = useState(false);\n const [context, setContext] = useState<TContext | null>(null);\n\n const openModal = useCallback((next?: TContext) => {\n setContext(next ?? null);\n setOpen(true);\n }, []);\n\n const closeModal = useCallback(() => setOpen(false), []);\n\n const onOpenChange = useCallback((nextOpen: boolean) => {\n setOpen(nextOpen);\n if (!nextOpen) {\n setContext(null);\n }\n }, []);\n\n return { open, context, openModal, closeModal, onOpenChange };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"FormModal.js","names":[],"sources":["../../../../src/crewfoundry/components/forms/FormModal.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport {\n Button,\n Input,\n Label,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n Switch,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { X } from 'lucide-react';\nimport { useTr } from '../../../shared/hooks/useTr';\n\n// ── Public API ──────────────────────────────────────────────────\nexport type FormFieldType =\n | 'text'\n | 'number'\n | 'date'\n | 'email'\n | 'textarea'\n | 'select'\n | 'tags'\n | 'switch';\n\nexport type FormValue = string | number | boolean | string[];\nexport type FormValues = Record<string, FormValue>;\n\nexport interface FieldOption {\n label: string;\n value: string;\n}\n\nexport interface FieldConfig {\n name: string;\n label: string;\n type: FormFieldType;\n required?: boolean;\n placeholder?: string;\n options?: FieldOption[];\n /** Column span in the 2-col grid on md+ (defaults to 1; textarea/tags default to 2). */\n colSpan?: 1 | 2;\n}\n\nexport interface FormModalProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n title: string;\n description?: string;\n fields: FieldConfig[];\n initialValues?: FormValues;\n submitLabel?: string;\n onSubmit: (values: FormValues) => Promise<void>;\n}\n\n// ── Internal state helpers ──────────────────────────────────────\n// Text-like inputs are held as strings for controlled inputs; switches as\n// booleans; tags as string[]. Values are coerced back to their public shapes\n// on submit.\ntype InternalValue = string | boolean | string[];\ntype InternalValues = Record<string, InternalValue>;\n\nconst buildInitialState = (fields: FieldConfig[], initialValues?: FormValues): InternalValues => {\n const state: InternalValues = {};\n for (const field of fields) {\n const raw = initialValues?.[field.name];\n if (field.type === 'switch') {\n state[field.name] = Boolean(raw);\n } else if (field.type === 'tags') {\n if (Array.isArray(raw)) {\n state[field.name] = raw.map((item) => String(item));\n } else if (typeof raw === 'string' && raw.length > 0) {\n state[field.name] = raw\n .split(',')\n .map((item) => item.trim())\n .filter(Boolean);\n } else {\n state[field.name] = [];\n }\n } else {\n state[field.name] = raw === undefined || raw === null ? '' : String(raw);\n }\n }\n return state;\n};\n\nconst isEmptyValue = (field: FieldConfig, value: InternalValue): boolean => {\n if (field.type === 'switch') {\n return false;\n }\n if (field.type === 'tags') {\n return !Array.isArray(value) || value.length === 0;\n }\n return typeof value !== 'string' || value.trim() === '';\n};\n\nconst toOutput = (fields: FieldConfig[], state: InternalValues): FormValues => {\n const output: FormValues = {};\n for (const field of fields) {\n const value = state[field.name];\n if (field.type === 'switch') {\n output[field.name] = Boolean(value);\n } else if (field.type === 'tags') {\n output[field.name] = Array.isArray(value) ? value : [];\n } else if (field.type === 'number') {\n const raw = typeof value === 'string' ? value.trim() : '';\n output[field.name] = raw === '' ? '' : Number(raw);\n } else {\n output[field.name] = typeof value === 'string' ? value : '';\n }\n }\n return output;\n};\n\n// Native <select> styled with the same semantic tokens as fe-libs Input.\nconst SELECT_CLASS =\n 'flex h-10 w-full rounded-input border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary transition-colors duration-200 focus-visible:border-border-focus focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)] focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)] disabled:cursor-not-allowed disabled:opacity-50';\n\n// ── Component ───────────────────────────────────────────────────\nexport function FormModal({\n open,\n onOpenChange,\n title,\n description,\n fields,\n initialValues,\n submitLabel,\n onSubmit,\n}: FormModalProps) {\n const { showToast } = useGlobalUi();\n const tr = useTr();\n const resolvedSubmitLabel = submitLabel ?? tr('formModal.submitLabel', 'Save');\n const [values, setValues] = useState<InternalValues>(() =>\n buildInitialState(fields, initialValues)\n );\n const [tagDrafts, setTagDrafts] = useState<Record<string, string>>({});\n const [submitting, setSubmitting] = useState(false);\n\n // Keep the latest field config / initial values reachable from the reset\n // effect without making them effect dependencies (page callers rarely\n // memoize these, and depending on them would loop or clobber edits).\n const fieldsRef = useRef(fields);\n const initialValuesRef = useRef(initialValues);\n const wasOpenRef = useRef(false);\n\n // Sync the latest field config / initial values into refs after each commit\n // (never during render). Declared before the reset effect so that on a rising\n // edge of `open` the refs are refreshed first, then read by the reset below.\n useEffect(() => {\n fieldsRef.current = fields;\n initialValuesRef.current = initialValues;\n }, [fields, initialValues]);\n\n // Reset the working copy on each rising edge of `open` so edit vs create\n // contexts never leak into each other.\n useEffect(() => {\n if (open && !wasOpenRef.current) {\n setValues(buildInitialState(fieldsRef.current, initialValuesRef.current));\n setTagDrafts({});\n setSubmitting(false);\n }\n wasOpenRef.current = open;\n }, [open]);\n\n const setFieldValue = useCallback((name: string, value: InternalValue) => {\n setValues((prev) => ({ ...prev, [name]: value }));\n }, []);\n\n const commitTag = useCallback((name: string) => {\n setTagDrafts((prevDrafts) => {\n const draft = (prevDrafts[name] ?? '').trim();\n if (draft) {\n setValues((prev) => {\n const current = Array.isArray(prev[name]) ? (prev[name] as string[]) : [];\n if (current.includes(draft)) {\n return prev;\n }\n return { ...prev, [name]: [...current, draft] };\n });\n }\n return { ...prevDrafts, [name]: '' };\n });\n }, []);\n\n const removeTag = useCallback((name: string, tag: string) => {\n setValues((prev) => {\n const current = Array.isArray(prev[name]) ? (prev[name] as string[]) : [];\n return { ...prev, [name]: current.filter((item) => item !== tag) };\n });\n }, []);\n\n const handleSubmit = useCallback(\n async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n if (submitting) {\n return;\n }\n\n // Fold any un-committed tag draft into its field before validating.\n const staged: InternalValues = { ...values };\n for (const field of fields) {\n if (field.type === 'tags') {\n const draft = (tagDrafts[field.name] ?? '').trim();\n if (draft) {\n const current = Array.isArray(staged[field.name])\n ? (staged[field.name] as string[])\n : [];\n if (!current.includes(draft)) {\n staged[field.name] = [...current, draft];\n }\n }\n }\n }\n\n const missing = fields.find(\n (field) => field.required && isEmptyValue(field, staged[field.name])\n );\n if (missing) {\n showToast(\n tr('formModal.fieldRequired', '{{label}} is required', { label: missing.label }),\n 'warning'\n );\n return;\n }\n\n setSubmitting(true);\n try {\n await onSubmit(toOutput(fields, staged));\n onOpenChange(false);\n } catch (error) {\n showToast(\n error instanceof Error\n ? error.message\n : tr('formModal.submitError', 'Something went wrong'),\n 'error'\n );\n } finally {\n setSubmitting(false);\n }\n },\n [fields, onOpenChange, onSubmit, showToast, submitting, tagDrafts, tr, values]\n );\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"max-h-[85vh] overflow-y-auto sm:max-w-[560px]\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>{title}</ResponsiveDialogTitle>\n {description ? (\n <ResponsiveDialogDescription>{description}</ResponsiveDialogDescription>\n ) : null}\n </ResponsiveDialogHeader>\n\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n {fields.map((field) => {\n const fieldId = `formmodal-${field.name}`;\n const spanTwo =\n field.colSpan === 2 ||\n (field.colSpan === undefined &&\n (field.type === 'textarea' || field.type === 'tags'));\n const value = values[field.name];\n\n return (\n <div\n key={field.name}\n className={`flex flex-col gap-1.5 ${spanTwo ? 'md:col-span-2' : ''}`}\n >\n {field.type === 'switch' ? (\n <div className=\"flex items-center justify-between gap-3 rounded-input border border-border-default bg-bg-surface px-3 py-2\">\n <Label htmlFor={fieldId}>\n {field.label}\n {field.required ? <span className=\"text-status-error-fg\"> *</span> : null}\n </Label>\n <Switch\n id={fieldId}\n checked={Boolean(value)}\n onCheckedChange={(checked) => setFieldValue(field.name, checked)}\n disabled={submitting}\n />\n </div>\n ) : (\n <>\n <Label htmlFor={fieldId}>\n {field.label}\n {field.required ? <span className=\"text-status-error-fg\"> *</span> : null}\n </Label>\n\n {field.type === 'textarea' ? (\n <Textarea\n id={fieldId}\n value={typeof value === 'string' ? value : ''}\n placeholder={field.placeholder}\n disabled={submitting}\n onChange={(event) => setFieldValue(field.name, event.target.value)}\n />\n ) : field.type === 'select' ? (\n <select\n id={fieldId}\n className={SELECT_CLASS}\n value={typeof value === 'string' ? value : ''}\n disabled={submitting}\n onChange={(event) => setFieldValue(field.name, event.target.value)}\n >\n <option value=\"\" disabled>\n {field.placeholder ?? tr('formModal.selectPlaceholder', 'Select…')}\n </option>\n {(field.options ?? []).map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n ) : field.type === 'tags' ? (\n <div className=\"flex min-h-10 flex-wrap items-center gap-1.5 rounded-input border border-border-default bg-bg-surface px-2 py-1.5\">\n {(Array.isArray(value) ? value : []).map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center gap-1 rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-primary\"\n >\n {tag}\n <button\n type=\"button\"\n aria-label={tr('formModal.removeTag', 'Remove {{tag}}', { tag })}\n className=\"text-text-secondary hover:text-text-primary\"\n onClick={() => removeTag(field.name, tag)}\n disabled={submitting}\n >\n <X className=\"h-3 w-3\" />\n </button>\n </span>\n ))}\n <input\n id={fieldId}\n className=\"min-w-[6rem] flex-1 bg-transparent text-sm text-text-primary outline-none placeholder:text-text-placeholder\"\n value={tagDrafts[field.name] ?? ''}\n placeholder={\n field.placeholder ??\n tr('formModal.tagsPlaceholder', 'Add and press Enter')\n }\n disabled={submitting}\n onChange={(event) =>\n setTagDrafts((prev) => ({\n ...prev,\n [field.name]: event.target.value,\n }))\n }\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ',') {\n event.preventDefault();\n commitTag(field.name);\n } else if (\n event.key === 'Backspace' &&\n (tagDrafts[field.name] ?? '') === ''\n ) {\n const current = Array.isArray(value) ? value : [];\n if (current.length > 0) {\n removeTag(field.name, current[current.length - 1]);\n }\n }\n }}\n onBlur={() => commitTag(field.name)}\n />\n </div>\n ) : (\n <Input\n id={fieldId}\n type={\n field.type === 'number'\n ? 'number'\n : field.type === 'date'\n ? 'date'\n : field.type === 'email'\n ? 'email'\n : 'text'\n }\n value={typeof value === 'string' ? value : ''}\n placeholder={field.placeholder}\n disabled={submitting}\n onChange={(event) => setFieldValue(field.name, event.target.value)}\n />\n )}\n </>\n )}\n </div>\n );\n })}\n </div>\n\n <ResponsiveDialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n disabled={submitting}\n >\n {tr('formModal.cancel', 'Cancel')}\n </Button>\n <Button type=\"submit\" disabled={submitting}>\n {submitting ? tr('formModal.saving', 'Saving…') : resolvedSubmitLabel}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\n// ── Optional open/close helper ──────────────────────────────────\nexport interface UseFormModalResult<TContext> {\n open: boolean;\n /** The row/context passed to openModal (e.g. the entity being edited). */\n context: TContext | null;\n openModal: (context?: TContext) => void;\n closeModal: () => void;\n onOpenChange: (open: boolean) => void;\n}\n\n/**\n * Tiny helper for pages that toggle a single FormModal and want to remember\n * which row is being edited. `context` is null for a create flow.\n */\nexport function useFormModal<TContext = void>(): UseFormModalResult<TContext> {\n const [open, setOpen] = useState(false);\n const [context, setContext] = useState<TContext | null>(null);\n\n const openModal = useCallback((next?: TContext) => {\n setContext(next ?? null);\n setOpen(true);\n }, []);\n\n const closeModal = useCallback(() => setOpen(false), []);\n\n const onOpenChange = useCallback((nextOpen: boolean) => {\n setOpen(nextOpen);\n if (!nextOpen) {\n setContext(null);\n }\n }, []);\n\n return { open, context, openModal, closeModal, onOpenChange };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HiringCreateJobPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/HiringCreateJobPagePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"HiringCreateJobPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/HiringCreateJobPagePage.tsx"],"names":[],"mappings":"AA2CA,wBAAgB,uBAAuB,gCAmUtC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HiringCreateJobPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HiringCreateJobPagePage.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from 'react';\nimport {\n Button,\n GlassCard,\n GradientText,\n Input,\n PagePurpose,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { Briefcase, ChevronRight, Plus, Save, X } from 'lucide-react';\nimport { useNavigate } from 'react-router';\nimport { useCrewFoundryStore, type JobPosting } from '../store/crewfoundryStore';\nimport { newClientId } from '../components/forms/formValues';\n\n// Native <select> styled with the same semantic tokens as fe-libs Input.\nconst SELECT_CLASS =\n 'flex h-10 w-full rounded-input border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary transition-colors duration-200 focus-visible:border-border-focus focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50';\n\nconst FIELD_LABEL_CLASS = 'text-sm font-medium text-text-primary';\n\nconst JOB_TYPE_OPTIONS: { label: string; value: JobPosting['type'] }[] = [\n { label: 'Full time', value: 'full_time' },\n { label: 'Part time', value: 'part_time' },\n { label: 'Contract', value: 'contract' },\n { label: 'Remote', value: 'remote' },\n];\n\nconst EXPERIENCE_OPTIONS: { label: string; value: JobPosting['experienceLevel'] }[] = [\n { label: 'Entry', value: 'entry' },\n { label: 'Mid', value: 'mid' },\n { label: 'Senior', value: 'senior' },\n { label: 'Lead', value: 'lead' },\n { label: 'Executive', value: 'executive' },\n];\n\nfunction FieldLabel({ htmlFor, children }: { htmlFor: string; children: ReactNode }) {\n return (\n <label className={FIELD_LABEL_CLASS} htmlFor={htmlFor}>\n {children}\n </label>\n );\n}\n\nexport function HiringCreateJobPagePage() {\n const navigate = useNavigate();\n const { showToast } = useGlobalUi();\n const addJobPosting = useCrewFoundryStore((state) => state.addJobPosting);\n const departments = useCrewFoundryStore((state) => state.departments);\n\n const [title, setTitle] = useState('');\n const [department, setDepartment] = useState('');\n const [location, setLocation] = useState('');\n const [description, setDescription] = useState('');\n const [type, setType] = useState<JobPosting['type']>('full_time');\n const [experienceLevel, setExperienceLevel] = useState<JobPosting['experienceLevel']>('mid');\n const [salaryMin, setSalaryMin] = useState('');\n const [salaryMax, setSalaryMax] = useState('');\n const [deadline, setDeadline] = useState('');\n const [requirements, setRequirements] = useState<string[]>(['']);\n const [benefits, setBenefits] = useState<string[]>(['']);\n const [submitting, setSubmitting] = useState(false);\n\n const departmentNames = useMemo(\n () => Array.from(new Set(departments.map((d) => d.name).filter(Boolean))).sort(),\n [departments]\n );\n\n const addRequirement = () => setRequirements((prev) => [...prev, '']);\n const addBenefit = () => setBenefits((prev) => [...prev, '']);\n const updateRequirement = (idx: number, value: string) =>\n setRequirements((prev) => prev.map((item, i) => (i === idx ? value : item)));\n const updateBenefit = (idx: number, value: string) =>\n setBenefits((prev) => prev.map((item, i) => (i === idx ? value : item)));\n const removeRequirement = (idx: number) =>\n setRequirements((prev) => prev.filter((_, i) => i !== idx));\n const removeBenefit = (idx: number) => setBenefits((prev) => prev.filter((_, i) => i !== idx));\n\n const submit = async (status: JobPosting['status']) => {\n if (submitting) {\n return;\n }\n if (!title.trim()) {\n showToast('Job Title is required', 'warning');\n return;\n }\n if (!department.trim()) {\n showToast('Department is required', 'warning');\n return;\n }\n const today = new Date().toISOString().slice(0, 10);\n const job: JobPosting = {\n id: newClientId(),\n title: title.trim(),\n department: department.trim(),\n location: location.trim(),\n type,\n experienceLevel,\n salaryMin: salaryMin.trim() === '' ? 0 : Number(salaryMin),\n salaryMax: salaryMax.trim() === '' ? 0 : Number(salaryMax),\n status,\n applicants: 0,\n postedDate: today,\n deadline: deadline.trim() === '' ? today : deadline,\n description: description.trim(),\n requirements: requirements.map((item) => item.trim()).filter(Boolean),\n benefits: benefits.map((item) => item.trim()).filter(Boolean),\n };\n\n setSubmitting(true);\n try {\n await addJobPosting(job);\n showToast(status === 'draft' ? 'Draft saved' : 'Job published', 'success');\n navigate('../hiring/jobs-page');\n } catch (error) {\n showToast(error instanceof Error ? error.message : 'Unable to save job posting', 'error');\n } finally {\n setSubmitting(false);\n }\n };\n\n const renderChipRows = (\n items: string[],\n update: (idx: number, value: string) => void,\n remove: (idx: number) => void,\n add: () => void,\n kind: 'Requirement' | 'Benefit',\n placeholder: string\n ) => (\n <div className=\"space-y-2\">\n {items.map((value, idx) => (\n <div key={idx} className=\"relative\">\n <Input\n className=\"pr-9\"\n placeholder={placeholder}\n value={value}\n onChange={(e) => update(idx, e.target.value)}\n />\n <button\n type=\"button\"\n aria-label={`Remove ${kind.toLowerCase()} ${idx + 1}`}\n onClick={() => remove(idx)}\n className=\"absolute right-2.5 top-1/2 -translate-y-1/2 rounded-sm p-0.5 text-text-secondary transition-colors hover:text-text-primary focus-visible:outline-none focus-visible:text-text-primary\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n ))}\n <Button variant=\"link\" size=\"sm\" className=\"h-auto p-0\" onClick={add} type=\"button\">\n <Plus className=\"h-4 w-4 mr-1\" />\n Add {kind}\n </Button>\n </div>\n );\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"space-y-2\">\n <nav\n aria-label=\"Breadcrumb\"\n className=\"flex items-center gap-1 text-sm text-text-secondary\"\n >\n <button\n type=\"button\"\n onClick={() => navigate('../hiring/jobs-page')}\n className=\"rounded-sm transition-colors hover:text-text-primary focus-visible:outline-none focus-visible:text-text-primary\"\n >\n Hiring\n </button>\n <ChevronRight className=\"h-3.5 w-3.5\" />\n <span className=\"text-text-primary\">Post New Job</span>\n </nav>\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Create Job Posting</GradientText>\n </h1>\n <PagePurpose className=\"mt-1\">\n Create a new job posting — define the role, requirements, and compensation here to start\n attracting and collecting applicants.\n </PagePurpose>\n </div>\n </div>\n\n <GlassCard treatment=\"glass\" className=\"max-w-5xl p-6\">\n <div className=\"mb-6 flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <Briefcase className=\"h-5 w-5\" />\n Job details\n </div>\n\n <div className=\"space-y-5\">\n <div className=\"grid grid-cols-1 gap-5 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-title\">Job Title</FieldLabel>\n <Input\n id=\"job-title\"\n placeholder=\"e.g. Senior Frontend Developer\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n />\n </div>\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-type\">Employment Type</FieldLabel>\n <select\n id=\"job-type\"\n className={SELECT_CLASS}\n value={type}\n onChange={(e) => setType(e.target.value as JobPosting['type'])}\n >\n {JOB_TYPE_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-department\">Department</FieldLabel>\n <Input\n id=\"job-department\"\n list=\"crewfoundry-department-options\"\n placeholder=\"e.g. Engineering\"\n value={department}\n onChange={(e) => setDepartment(e.target.value)}\n />\n <datalist id=\"crewfoundry-department-options\">\n {departmentNames.map((name) => (\n <option key={name} value={name} />\n ))}\n </datalist>\n </div>\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-experience\">Experience Level</FieldLabel>\n <select\n id=\"job-experience\"\n className={SELECT_CLASS}\n value={experienceLevel}\n onChange={(e) =>\n setExperienceLevel(e.target.value as JobPosting['experienceLevel'])\n }\n >\n {EXPERIENCE_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-location\">Location</FieldLabel>\n <Input\n id=\"job-location\"\n placeholder=\"e.g. San Francisco, CA\"\n value={location}\n onChange={(e) => setLocation(e.target.value)}\n />\n </div>\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-salary-min\">Salary Min</FieldLabel>\n <div className=\"relative\">\n <span className=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-sm text-text-secondary\">\n $\n </span>\n <Input\n id=\"job-salary-min\"\n type=\"number\"\n className=\"pl-7\"\n placeholder=\"120,000\"\n value={salaryMin}\n onChange={(e) => setSalaryMin(e.target.value)}\n />\n </div>\n </div>\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-salary-max\">Salary Max</FieldLabel>\n <div className=\"relative\">\n <span className=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-sm text-text-secondary\">\n $\n </span>\n <Input\n id=\"job-salary-max\"\n type=\"number\"\n className=\"pl-7\"\n placeholder=\"180,000\"\n value={salaryMax}\n onChange={(e) => setSalaryMax(e.target.value)}\n />\n </div>\n </div>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-deadline\">Application Deadline</FieldLabel>\n <Input\n id=\"job-deadline\"\n type=\"date\"\n value={deadline}\n onChange={(e) => setDeadline(e.target.value)}\n />\n </div>\n </div>\n\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-description\">Description</FieldLabel>\n <Textarea\n id=\"job-description\"\n placeholder=\"Describe the role, responsibilities, and ideal candidate...\"\n rows={4}\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 border-t border-border-subtle pt-5 md:grid-cols-2\">\n <div className=\"space-y-3\">\n <div>\n <div className={FIELD_LABEL_CLASS}>Requirements</div>\n <p className=\"text-xs text-text-secondary\">\n List the key requirements for this role\n </p>\n </div>\n {renderChipRows(\n requirements,\n updateRequirement,\n removeRequirement,\n addRequirement,\n 'Requirement',\n 'e.g. 5+ years with React'\n )}\n </div>\n <div className=\"space-y-3\">\n <div>\n <div className={FIELD_LABEL_CLASS}>Benefits</div>\n <p className=\"text-xs text-text-secondary\">What does this position offer?</p>\n </div>\n {renderChipRows(\n benefits,\n updateBenefit,\n removeBenefit,\n addBenefit,\n 'Benefit',\n 'e.g. Flexible remote work'\n )}\n </div>\n </div>\n\n <div className=\"flex flex-col-reverse justify-end gap-2 border-t border-border-subtle pt-5 sm:flex-row\">\n <Button variant=\"outline\" disabled={submitting} onClick={() => submit('draft')}>\n Save as Draft\n </Button>\n <Button disabled={submitting} onClick={() => submit('active')}>\n <Save className=\"h-4 w-4 mr-1\" />\n {submitting ? 'Saving…' : 'Publish Job'}\n </Button>\n </div>\n </div>\n </GlassCard>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HiringCreateJobPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HiringCreateJobPagePage.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from 'react';\nimport {\n Button,\n GlassCard,\n GradientText,\n Input,\n PagePurpose,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { Briefcase, ChevronRight, Plus, Save, X } from 'lucide-react';\nimport { useNavigate } from 'react-router';\nimport { useCrewFoundryStore, type JobPosting } from '../store/crewfoundryStore';\nimport { newClientId } from '../components/forms/formValues';\n\n// Native <select> styled with the same semantic tokens as fe-libs Input.\nconst SELECT_CLASS =\n 'flex h-10 w-full rounded-input border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary transition-colors duration-200 focus-visible:border-border-focus focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50';\n\nconst FIELD_LABEL_CLASS = 'text-sm font-medium text-text-primary';\n\nconst JOB_TYPE_OPTIONS: { label: string; value: JobPosting['type'] }[] = [\n { label: 'Full time', value: 'full_time' },\n { label: 'Part time', value: 'part_time' },\n { label: 'Contract', value: 'contract' },\n];\n\nconst EXPERIENCE_OPTIONS: { label: string; value: JobPosting['experienceLevel'] }[] = [\n { label: 'Entry', value: 'entry' },\n { label: 'Mid', value: 'mid' },\n { label: 'Senior', value: 'senior' },\n { label: 'Lead', value: 'lead' },\n { label: 'Executive', value: 'executive' },\n];\n\nfunction FieldLabel({ htmlFor, children }: { htmlFor: string; children: ReactNode }) {\n return (\n <label className={FIELD_LABEL_CLASS} htmlFor={htmlFor}>\n {children}\n </label>\n );\n}\n\nexport function HiringCreateJobPagePage() {\n const navigate = useNavigate();\n const { showToast } = useGlobalUi();\n const addJobPosting = useCrewFoundryStore((state) => state.addJobPosting);\n const departments = useCrewFoundryStore((state) => state.departments);\n\n const [title, setTitle] = useState('');\n const [department, setDepartment] = useState('');\n const [location, setLocation] = useState('');\n const [description, setDescription] = useState('');\n const [type, setType] = useState<JobPosting['type']>('full_time');\n const [experienceLevel, setExperienceLevel] = useState<JobPosting['experienceLevel']>('mid');\n const [salaryMin, setSalaryMin] = useState('');\n const [salaryMax, setSalaryMax] = useState('');\n const [deadline, setDeadline] = useState('');\n const [requirements, setRequirements] = useState<string[]>(['']);\n const [benefits, setBenefits] = useState<string[]>(['']);\n const [submitting, setSubmitting] = useState(false);\n\n const departmentNames = useMemo(\n () => Array.from(new Set(departments.map((d) => d.name).filter(Boolean))).sort(),\n [departments]\n );\n\n const addRequirement = () => setRequirements((prev) => [...prev, '']);\n const addBenefit = () => setBenefits((prev) => [...prev, '']);\n const updateRequirement = (idx: number, value: string) =>\n setRequirements((prev) => prev.map((item, i) => (i === idx ? value : item)));\n const updateBenefit = (idx: number, value: string) =>\n setBenefits((prev) => prev.map((item, i) => (i === idx ? value : item)));\n const removeRequirement = (idx: number) =>\n setRequirements((prev) => prev.filter((_, i) => i !== idx));\n const removeBenefit = (idx: number) => setBenefits((prev) => prev.filter((_, i) => i !== idx));\n\n const submit = async (status: JobPosting['status']) => {\n if (submitting) {\n return;\n }\n if (!title.trim()) {\n showToast('Job Title is required', 'warning');\n return;\n }\n if (!department.trim()) {\n showToast('Department is required', 'warning');\n return;\n }\n const today = new Date().toISOString().slice(0, 10);\n const job: JobPosting = {\n id: newClientId(),\n title: title.trim(),\n department: department.trim(),\n location: location.trim(),\n type,\n experienceLevel,\n salaryMin: salaryMin.trim() === '' ? 0 : Number(salaryMin),\n salaryMax: salaryMax.trim() === '' ? 0 : Number(salaryMax),\n status,\n applicants: 0,\n postedDate: today,\n deadline: deadline.trim() === '' ? today : deadline,\n description: description.trim(),\n requirements: requirements.map((item) => item.trim()).filter(Boolean),\n benefits: benefits.map((item) => item.trim()).filter(Boolean),\n };\n\n setSubmitting(true);\n try {\n await addJobPosting(job);\n showToast(status === 'draft' ? 'Draft saved' : 'Job published', 'success');\n navigate('../hiring/jobs-page');\n } catch (error) {\n showToast(error instanceof Error ? error.message : 'Unable to save job posting', 'error');\n } finally {\n setSubmitting(false);\n }\n };\n\n const renderChipRows = (\n items: string[],\n update: (idx: number, value: string) => void,\n remove: (idx: number) => void,\n add: () => void,\n kind: 'Requirement' | 'Benefit',\n placeholder: string\n ) => (\n <div className=\"space-y-2\">\n {items.map((value, idx) => (\n <div key={idx} className=\"relative\">\n <Input\n className=\"pr-9\"\n placeholder={placeholder}\n value={value}\n onChange={(e) => update(idx, e.target.value)}\n />\n <button\n type=\"button\"\n aria-label={`Remove ${kind.toLowerCase()} ${idx + 1}`}\n onClick={() => remove(idx)}\n className=\"absolute right-2.5 top-1/2 -translate-y-1/2 rounded-sm p-0.5 text-text-secondary transition-colors hover:text-text-primary focus-visible:outline-none focus-visible:text-text-primary\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n ))}\n <Button variant=\"link\" size=\"sm\" className=\"h-auto p-0\" onClick={add} type=\"button\">\n <Plus className=\"h-4 w-4 mr-1\" />\n Add {kind}\n </Button>\n </div>\n );\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"space-y-2\">\n <nav\n aria-label=\"Breadcrumb\"\n className=\"flex items-center gap-1 text-sm text-text-secondary\"\n >\n <button\n type=\"button\"\n onClick={() => navigate('../hiring/jobs-page')}\n className=\"rounded-sm transition-colors hover:text-text-primary focus-visible:outline-none focus-visible:text-text-primary\"\n >\n Hiring\n </button>\n <ChevronRight className=\"h-3.5 w-3.5\" />\n <span className=\"text-text-primary\">Post New Job</span>\n </nav>\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Create Job Posting</GradientText>\n </h1>\n <PagePurpose className=\"mt-1\">\n Create a new job posting — define the role, requirements, and compensation here to start\n attracting and collecting applicants.\n </PagePurpose>\n </div>\n </div>\n\n <GlassCard treatment=\"glass\" className=\"max-w-5xl p-6\">\n <div className=\"mb-6 flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <Briefcase className=\"h-5 w-5\" />\n Job details\n </div>\n\n <div className=\"space-y-5\">\n <div className=\"grid grid-cols-1 gap-5 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-title\">Job Title</FieldLabel>\n <Input\n id=\"job-title\"\n placeholder=\"e.g. Senior Frontend Developer\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n />\n </div>\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-type\">Employment Type</FieldLabel>\n <select\n id=\"job-type\"\n className={SELECT_CLASS}\n value={type}\n onChange={(e) => setType(e.target.value as JobPosting['type'])}\n >\n {JOB_TYPE_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-department\">Department</FieldLabel>\n <Input\n id=\"job-department\"\n list=\"crewfoundry-department-options\"\n placeholder=\"e.g. Engineering\"\n value={department}\n onChange={(e) => setDepartment(e.target.value)}\n />\n <datalist id=\"crewfoundry-department-options\">\n {departmentNames.map((name) => (\n <option key={name} value={name} />\n ))}\n </datalist>\n </div>\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-experience\">Experience Level</FieldLabel>\n <select\n id=\"job-experience\"\n className={SELECT_CLASS}\n value={experienceLevel}\n onChange={(e) =>\n setExperienceLevel(e.target.value as JobPosting['experienceLevel'])\n }\n >\n {EXPERIENCE_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-location\">Location</FieldLabel>\n <Input\n id=\"job-location\"\n placeholder=\"e.g. San Francisco, CA\"\n value={location}\n onChange={(e) => setLocation(e.target.value)}\n />\n </div>\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-salary-min\">Salary Min</FieldLabel>\n <div className=\"relative\">\n <span className=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-sm text-text-secondary\">\n $\n </span>\n <Input\n id=\"job-salary-min\"\n type=\"number\"\n className=\"pl-7\"\n placeholder=\"120,000\"\n value={salaryMin}\n onChange={(e) => setSalaryMin(e.target.value)}\n />\n </div>\n </div>\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-salary-max\">Salary Max</FieldLabel>\n <div className=\"relative\">\n <span className=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-sm text-text-secondary\">\n $\n </span>\n <Input\n id=\"job-salary-max\"\n type=\"number\"\n className=\"pl-7\"\n placeholder=\"180,000\"\n value={salaryMax}\n onChange={(e) => setSalaryMax(e.target.value)}\n />\n </div>\n </div>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-deadline\">Application Deadline</FieldLabel>\n <Input\n id=\"job-deadline\"\n type=\"date\"\n value={deadline}\n onChange={(e) => setDeadline(e.target.value)}\n />\n </div>\n </div>\n\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-description\">Description</FieldLabel>\n <Textarea\n id=\"job-description\"\n placeholder=\"Describe the role, responsibilities, and ideal candidate...\"\n rows={4}\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 border-t border-border-subtle pt-5 md:grid-cols-2\">\n <div className=\"space-y-3\">\n <div>\n <div className={FIELD_LABEL_CLASS}>Requirements</div>\n <p className=\"text-xs text-text-secondary\">\n List the key requirements for this role\n </p>\n </div>\n {renderChipRows(\n requirements,\n updateRequirement,\n removeRequirement,\n addRequirement,\n 'Requirement',\n 'e.g. 5+ years with React'\n )}\n </div>\n <div className=\"space-y-3\">\n <div>\n <div className={FIELD_LABEL_CLASS}>Benefits</div>\n <p className=\"text-xs text-text-secondary\">What does this position offer?</p>\n </div>\n {renderChipRows(\n benefits,\n updateBenefit,\n removeBenefit,\n addBenefit,\n 'Benefit',\n 'e.g. Flexible remote work'\n )}\n </div>\n </div>\n\n <div className=\"flex flex-col-reverse justify-end gap-2 border-t border-border-subtle pt-5 sm:flex-row\">\n <Button variant=\"outline\" disabled={submitting} onClick={() => submit('draft')}>\n Save as Draft\n </Button>\n <Button disabled={submitting} onClick={() => submit('active')}>\n <Save className=\"h-4 w-4 mr-1\" />\n {submitting ? 'Saving…' : 'Publish Job'}\n </Button>\n </div>\n </div>\n </GlassCard>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HiringJobDetailsPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/HiringJobDetailsPagePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"HiringJobDetailsPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/HiringJobDetailsPagePage.tsx"],"names":[],"mappings":"AAiHA,wBAAgB,wBAAwB,gCAsbvC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HiringJobDetailsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HiringJobDetailsPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n Spinner,\n} from '@burdenoff/fe-libs/ui';\nimport { useState } from 'react';\nimport {\n Briefcase,\n Calendar,\n CheckCircle,\n MapPin,\n Pencil,\n Share2,\n Sparkles,\n Trash2,\n Users,\n XCircle,\n} from 'lucide-react';\nimport { useNavigate, useSearchParams } from 'react-router';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useCrewFoundryStore, type Candidate, type JobPosting } from '../store/crewfoundryStore';\nimport { FormModal, type FieldConfig, type FormValues } from '../components/forms/FormModal';\nimport { ConfirmDialog } from '../components/forms/ConfirmDialog';\nimport { getNumber, getTags, getText } from '../components/forms/formValues';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport {\n HBarFunnel,\n RatingStars,\n SkillBar,\n STATUS,\n getInitials,\n type LabeledValue,\n} from '../components/viz';\n\nconst JOB_FIELDS: FieldConfig[] = [\n { name: 'title', label: 'Job Title', type: 'text', required: true, colSpan: 2 },\n { name: 'department', label: 'Department', type: 'text', required: true },\n { name: 'location', label: 'Location', type: 'text', required: true },\n {\n name: 'type',\n label: 'Employment Type',\n type: 'select',\n options: [\n { label: 'Full time', value: 'full_time' },\n { label: 'Part time', value: 'part_time' },\n { label: 'Contract', value: 'contract' },\n { label: 'Remote', value: 'remote' },\n ],\n },\n {\n name: 'experienceLevel',\n label: 'Experience Level',\n type: 'select',\n options: [\n { label: 'Entry', value: 'entry' },\n { label: 'Mid', value: 'mid' },\n { label: 'Senior', value: 'senior' },\n { label: 'Lead', value: 'lead' },\n { label: 'Executive', value: 'executive' },\n ],\n },\n {\n name: 'status',\n label: 'Status',\n type: 'select',\n options: [\n { label: 'Draft', value: 'draft' },\n { label: 'Active', value: 'active' },\n { label: 'Paused', value: 'paused' },\n { label: 'Closed', value: 'closed' },\n { label: 'Filled', value: 'filled' },\n ],\n },\n { name: 'salaryMin', label: 'Salary Min ($)', type: 'number' },\n { name: 'salaryMax', label: 'Salary Max ($)', type: 'number' },\n { name: 'deadline', label: 'Application Deadline', type: 'date' },\n { name: 'description', label: 'Description', type: 'textarea', colSpan: 2 },\n { name: 'requirements', label: 'Requirements', type: 'tags' },\n { name: 'benefits', label: 'Benefits', type: 'tags' },\n];\n\nconst STAGE_BADGE: Record<\n Candidate['status'],\n 'default' | 'secondary' | 'destructive' | 'outline'\n> = {\n new: 'outline',\n screening: 'secondary',\n interview: 'secondary',\n assessment: 'secondary',\n offer: 'default',\n hired: 'default',\n rejected: 'destructive',\n};\n\nfunction InitialsAvatar({ name, size = 32 }: { 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\nexport function HiringJobDetailsPagePage() {\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const { showToast } = useGlobalUi();\n const {\n jobPostings,\n candidates,\n updateJobPosting,\n removeJobPosting,\n initialized,\n jobPostingMatches,\n jobPostingMatchesLoading,\n fetchJobPostingMatches,\n } = useCrewFoundryStore();\n const jobId = searchParams.get('id');\n const job = jobId ? jobPostings.find((j) => j.id === jobId) : jobPostings[0];\n const [editOpen, setEditOpen] = useState(false);\n const [deleteOpen, setDeleteOpen] = useState(false);\n\n if (!initialized && !job) {\n return (\n <div className=\"flex min-h-[60vh] items-center justify-center p-6\">\n <Spinner size=\"lg\" />\n </div>\n );\n }\n\n if (!job) {\n return (\n <div className=\"p-6\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No job posting found\"\n description=\"This job posting is no longer available.\"\n />\n </div>\n );\n }\n\n // Applicants for this role. CrewCandidate has no jobId link, so match on the\n // (normalized) position title — the only real linkage the schema exposes.\n const titleKey = job.title.trim().toLowerCase();\n const jobCandidates = candidates.filter((c) => c.position.trim().toLowerCase() === titleKey);\n const countBy = (status: Candidate['status']) =>\n jobCandidates.filter((c) => c.status === status).length;\n\n const interviewCount = countBy('interview');\n\n // Real-data hiring funnel: \"Applied\" = everyone who applied to this role, then\n // the count currently at each downstream stage (offer folds in hired).\n const funnelStages: LabeledValue[] = [\n { label: 'Applied', value: jobCandidates.length },\n { label: 'Screening', value: countBy('screening') },\n { label: 'Interview', value: interviewCount },\n { label: 'Assessment', value: countBy('assessment') },\n { label: 'Offer', value: countBy('offer') + countBy('hired') },\n ];\n\n const editInitial: FormValues = {\n title: job.title,\n department: job.department,\n location: job.location,\n type: job.type,\n experienceLevel: job.experienceLevel,\n status: job.status,\n salaryMin: job.salaryMin,\n salaryMax: job.salaryMax,\n deadline: job.deadline,\n description: job.description,\n requirements: job.requirements,\n benefits: job.benefits,\n };\n\n const handleEditSubmit = async (values: FormValues) => {\n const updates: Partial<JobPosting> = {\n title: getText(values, 'title'),\n department: getText(values, 'department'),\n location: getText(values, 'location'),\n type: values.type as JobPosting['type'],\n experienceLevel: values.experienceLevel as JobPosting['experienceLevel'],\n status: values.status as JobPosting['status'],\n salaryMin: getNumber(values, 'salaryMin'),\n salaryMax: getNumber(values, 'salaryMax'),\n deadline: getText(values, 'deadline'),\n description: getText(values, 'description'),\n requirements: getTags(values, 'requirements'),\n benefits: getTags(values, 'benefits'),\n };\n await updateJobPosting(job.id, updates);\n showToast('Job posting updated', 'success');\n };\n\n const handleDelete = async () => {\n await removeJobPosting(job.id);\n showToast('Job posting deleted', 'success');\n navigate('../hiring/jobs-page');\n };\n\n const handleShare = async () => {\n try {\n await navigator.clipboard.writeText(window.location.href);\n showToast('Job link copied to clipboard', 'success');\n } catch {\n showToast('Could not copy the job link', 'error');\n }\n };\n\n const viewApplications = () =>\n navigate(`../hiring/applications-page?position=${encodeURIComponent(job.title)}`);\n\n // BOFF-4195 — AI-assisted candidate matching (deterministic skill-overlap +\n // experience-fit scoring; see wspace-crewfoundry-svc matching.service.ts).\n const matches = jobPostingMatches[job.id];\n const matchesLoaded = matches !== undefined;\n const scoreTone = (score: number): string =>\n score >= 75 ? STATUS.success : score >= 45 ? STATUS.warning : STATUS.danger;\n const handleGenerateMatches = async () => {\n try {\n await fetchJobPostingMatches(job.id);\n } catch {\n showToast('Unable to generate candidate matches', 'error');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardContent className=\"flex flex-col gap-4 p-6 lg:flex-row lg:items-start lg:justify-between\">\n <div className=\"min-w-0\">\n <div className=\"mb-1 flex flex-wrap items-center gap-2\">\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>{job.title}</GradientText>\n </h1>\n <Badge variant={job.status === 'active' ? 'default' : 'secondary'}>\n {formatEnumLabel(job.status)}\n </Badge>\n </div>\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <Briefcase className=\"h-3.5 w-3.5\" />\n {job.department}\n </span>\n <span className=\"flex items-center gap-1\">\n <MapPin className=\"h-3.5 w-3.5\" />\n {job.location}\n </span>\n <span>{formatEnumLabel(job.type)}</span>\n </div>\n <div className=\"mt-2 flex flex-wrap items-center gap-2 text-xs text-text-secondary\">\n <Badge variant=\"secondary\" className=\"text-[10px]\">\n {jobCandidates.length} applicants\n </Badge>\n {interviewCount > 0 && (\n <Badge variant=\"secondary\" className=\"text-[10px]\">\n {interviewCount} in interview\n </Badge>\n )}\n <span className=\"flex items-center gap-1\">\n <Calendar className=\"h-3 w-3\" />\n Posted {job.postedDate}\n </span>\n </div>\n <PagePurpose className=\"mt-3\">\n Everything about this role in one place — review applicants, requirements, and\n compensation to decide who to interview and keep the posting current.\n </PagePurpose>\n </div>\n\n <div className=\"flex shrink-0 flex-col items-start gap-3 lg:items-end\">\n <div className=\"text-2xl font-bold tabular-nums text-text-primary\">\n ${(job.salaryMin / 1000).toFixed(0)}k – ${(job.salaryMax / 1000).toFixed(0)}k\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={handleShare}>\n <Share2 className=\"mr-1 h-4 w-4\" />\n Share\n </Button>\n <Button variant=\"outline\" size=\"sm\" onClick={() => setEditOpen(true)}>\n <Pencil className=\"mr-1 h-4 w-4\" />\n Edit\n </Button>\n <Button\n variant=\"destructive\"\n size=\"sm\"\n aria-label=\"Delete job posting\"\n onClick={() => setDeleteOpen(true)}\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n <Button size=\"sm\" onClick={viewApplications}>\n <Users className=\"mr-1 h-4 w-4\" />\n View Applications\n </Button>\n </div>\n </div>\n </CardContent>\n </GlassCard>\n\n {/* Two-column body */}\n <div className=\"grid gap-6 lg:grid-cols-3\">\n {/* Left: description / requirements / benefits */}\n <div className=\"space-y-6 lg:col-span-2\">\n <Card>\n <CardHeader>\n <CardTitle>Description</CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm leading-relaxed text-text-secondary\">{job.description}</p>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Requirements</CardTitle>\n </CardHeader>\n <CardContent>\n {job.requirements.length === 0 ? (\n <p className=\"text-sm text-text-muted\">No requirements listed.</p>\n ) : (\n <ul className=\"space-y-2\">\n {job.requirements.map((req, idx) => (\n <li key={idx} className=\"flex items-start gap-2 text-sm\">\n <CheckCircle className=\"mt-0.5 h-4 w-4 flex-shrink-0 text-status-success-text\" />\n {req}\n </li>\n ))}\n </ul>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Benefits</CardTitle>\n </CardHeader>\n <CardContent>\n {job.benefits.length === 0 ? (\n <p className=\"text-sm text-text-muted\">No benefits listed.</p>\n ) : (\n <div className=\"flex flex-wrap gap-2\">\n {job.benefits.map((ben, idx) => (\n <Badge key={idx} variant=\"outline\" className=\"rounded-full px-3 py-1 text-xs\">\n {ben}\n </Badge>\n ))}\n </div>\n )}\n </CardContent>\n </Card>\n\n {/* BOFF-4195 — AI-assisted candidate matching (deterministic, explainable) */}\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between gap-3\">\n <div>\n <CardTitle>Suggested Matches</CardTitle>\n <p className=\"mt-1 text-xs text-text-muted\">\n Rule-based scoring — skill overlap (70%) + experience fit (30%) against this\n role's requirements. Not an AI-generated summary.\n </p>\n </div>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={handleGenerateMatches}\n disabled={jobPostingMatchesLoading}\n >\n <Sparkles className=\"mr-1 h-4 w-4\" />\n {matchesLoaded ? 'Refresh matches' : 'Generate matches'}\n </Button>\n </CardHeader>\n <CardContent>\n {jobPostingMatchesLoading ? (\n <div className=\"flex items-center justify-center py-6\">\n <Spinner size=\"md\" />\n </div>\n ) : !matchesLoaded ? (\n <p className=\"text-sm text-text-muted\">\n Click "Generate matches" to rank the candidate pool against this\n posting's requirements and experience level.\n </p>\n ) : matches.length === 0 ? (\n <p className=\"text-sm text-text-muted\">\n No candidates in the pool right now (excluding already-hired/rejected).\n </p>\n ) : (\n <ul className=\"space-y-4\">\n {matches.map((match) => (\n <li\n key={match.candidateId}\n className=\"rounded-[var(--radius-card)] border border-border-subtle bg-bg-surface p-3\"\n >\n <div className=\"flex items-center gap-2\">\n <InitialsAvatar\n name={`${match.candidate.firstName} ${match.candidate.lastName}`}\n size={28}\n />\n <span className=\"min-w-0 flex-1 truncate text-sm font-medium text-text-primary\">\n {match.candidate.firstName} {match.candidate.lastName}\n </span>\n <Badge\n variant={STAGE_BADGE[match.candidate.status]}\n className=\"shrink-0 text-[9px]\"\n >\n {formatEnumLabel(match.candidate.status)}\n </Badge>\n </div>\n\n <div className=\"mt-2\">\n <SkillBar\n label=\"Overall match\"\n value={match.score}\n levelLabel={`${match.score}%`}\n color={scoreTone(match.score)}\n height={6}\n />\n </div>\n <div className=\"mt-2 grid grid-cols-2 gap-2 text-xs text-text-secondary\">\n <span>Skills: {match.skillScore}%</span>\n <span>Experience: {match.experienceScore}%</span>\n </div>\n\n {(match.matchedSkills.length > 0 || match.missingSkills.length > 0) && (\n <div className=\"mt-2 flex flex-wrap gap-1\">\n {match.matchedSkills.map((skill) => (\n <Badge\n key={`m-${skill}`}\n variant=\"secondary\"\n className=\"gap-1 text-[10px]\"\n >\n <CheckCircle className=\"h-3 w-3 text-status-success-text\" />\n {skill}\n </Badge>\n ))}\n {match.missingSkills.map((skill) => (\n <Badge\n key={`x-${skill}`}\n variant=\"outline\"\n className=\"gap-1 text-[10px] text-text-muted\"\n >\n <XCircle className=\"h-3 w-3 text-status-error-text\" />\n {skill}\n </Badge>\n ))}\n </div>\n )}\n\n {/* Rationale is always visible — the ranking is never a black box. */}\n <p className=\"mt-2 text-xs leading-relaxed text-text-muted\">\n {match.rationale}\n </p>\n </li>\n ))}\n </ul>\n )}\n </CardContent>\n </Card>\n </div>\n\n {/* Right rail: candidates + funnel */}\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Candidates</CardTitle>\n </CardHeader>\n <CardContent>\n {jobCandidates.length === 0 ? (\n <p className=\"text-sm text-text-muted\">No applicants matched to this role yet.</p>\n ) : (\n <ul className=\"max-h-96 space-y-1 overflow-y-auto pr-1\">\n {jobCandidates.map((candidate) => (\n <li\n key={candidate.id}\n className=\"flex items-center gap-2 rounded-[var(--radius-input)] px-2 py-2 hover:bg-bg-sunken\"\n >\n <InitialsAvatar\n name={`${candidate.firstName} ${candidate.lastName}`}\n size={30}\n />\n <span className=\"min-w-0 flex-1 truncate text-sm font-medium text-text-primary\">\n {candidate.firstName} {candidate.lastName}\n </span>\n <Badge\n variant={STAGE_BADGE[candidate.status]}\n className=\"shrink-0 text-[9px]\"\n >\n {formatEnumLabel(candidate.status)}\n </Badge>\n <RatingStars value={candidate.rating} size={12} className=\"shrink-0\" />\n </li>\n ))}\n </ul>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Hiring funnel</CardTitle>\n </CardHeader>\n <CardContent>\n {jobCandidates.length === 0 ? (\n <p className=\"text-sm text-text-muted\">\n The funnel appears once candidates apply to this role.\n </p>\n ) : (\n <HBarFunnel\n stages={funnelStages}\n valueFormatter={(value) => String(value)}\n showConversion\n />\n )}\n </CardContent>\n </Card>\n </div>\n </div>\n\n <FormModal\n open={editOpen}\n onOpenChange={setEditOpen}\n title=\"Edit Job Posting\"\n description=\"Update the role details, compensation, and status.\"\n fields={JOB_FIELDS}\n initialValues={editInitial}\n submitLabel=\"Save changes\"\n onSubmit={handleEditSubmit}\n />\n <ConfirmDialog\n open={deleteOpen}\n onOpenChange={setDeleteOpen}\n title=\"Delete job posting\"\n description={`Permanently remove “${job.title}”? This cannot be undone.`}\n confirmLabel=\"Delete\"\n destructive\n onConfirm={handleDelete}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA0CA,IAAM,IAA4B;CAChC;EAAE,MAAM;EAAS,OAAO;EAAa,MAAM;EAAQ,UAAU;EAAM,SAAS;EAAG;CAC/E;EAAE,MAAM;EAAc,OAAO;EAAc,MAAM;EAAQ,UAAU;EAAM;CACzE;EAAE,MAAM;EAAY,OAAO;EAAY,MAAM;EAAQ,UAAU;EAAM;CACrE;EACE,MAAM;EACN,OAAO;EACP,MAAM;EACN,SAAS;GACP;IAAE,OAAO;IAAa,OAAO;IAAa;GAC1C;IAAE,OAAO;IAAa,OAAO;IAAa;GAC1C;IAAE,OAAO;IAAY,OAAO;IAAY;GACxC;IAAE,OAAO;IAAU,OAAO;IAAU;GACrC;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,MAAM;EACN,SAAS;GACP;IAAE,OAAO;IAAS,OAAO;IAAS;GAClC;IAAE,OAAO;IAAO,OAAO;IAAO;GAC9B;IAAE,OAAO;IAAU,OAAO;IAAU;GACpC;IAAE,OAAO;IAAQ,OAAO;IAAQ;GAChC;IAAE,OAAO;IAAa,OAAO;IAAa;GAC3C;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,MAAM;EACN,SAAS;GACP;IAAE,OAAO;IAAS,OAAO;IAAS;GAClC;IAAE,OAAO;IAAU,OAAO;IAAU;GACpC;IAAE,OAAO;IAAU,OAAO;IAAU;GACpC;IAAE,OAAO;IAAU,OAAO;IAAU;GACpC;IAAE,OAAO;IAAU,OAAO;IAAU;GACrC;EACF;CACD;EAAE,MAAM;EAAa,OAAO;EAAkB,MAAM;EAAU;CAC9D;EAAE,MAAM;EAAa,OAAO;EAAkB,MAAM;EAAU;CAC9D;EAAE,MAAM;EAAY,OAAO;EAAwB,MAAM;EAAQ;CACjE;EAAE,MAAM;EAAe,OAAO;EAAe,MAAM;EAAY,SAAS;EAAG;CAC3E;EAAE,MAAM;EAAgB,OAAO;EAAgB,MAAM;EAAQ;CAC7D;EAAE,MAAM;EAAY,OAAO;EAAY,MAAM;EAAQ;CACtD,EAEK,IAGF;CACF,KAAK;CACL,WAAW;CACX,WAAW;CACX,YAAY;CACZ,OAAO;CACP,OAAO;CACP,UAAU;CACX;AAED,SAAS,EAAe,EAAE,SAAM,UAAO,MAAuC;AAC5E,QACE,kBAAC,QAAD;EACE,WAAU;EACV,OAAO;GAAE,OAAO;GAAM,QAAQ;GAAM,UAAU,KAAK,MAAM,IAAO,IAAK;GAAE;EACvE,eAAY;YAEX,EAAY,EAAK;EACb,CAAA;;AAIX,SAAgB,IAA2B;CACzC,IAAM,IAAW,GAAa,EACxB,CAAC,KAAgB,IAAiB,EAClC,EAAE,iBAAc,GAAa,EAC7B,EACJ,gBACA,eACA,qBACA,qBACA,gBACA,sBACA,6BACA,8BACE,GAAqB,EACnB,IAAQ,EAAa,IAAI,KAAK,EAC9B,IAAM,IAAQ,EAAY,MAAM,MAAM,EAAE,OAAO,EAAM,GAAG,EAAY,IACpE,CAAC,GAAU,KAAe,EAAS,GAAM,EACzC,CAAC,IAAY,KAAiB,EAAS,GAAM;AAEnD,KAAI,CAAC,KAAe,CAAC,EACnB,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAS,MAAK,MAAO,CAAA;EACjB,CAAA;AAIV,KAAI,CAAC,EACH,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD;GACE,cAAa;GACb,OAAM;GACN,aAAY;GACZ,CAAA;EACE,CAAA;CAMV,IAAM,KAAW,EAAI,MAAM,MAAM,CAAC,aAAa,EACzC,IAAgB,EAAW,QAAQ,MAAM,EAAE,SAAS,MAAM,CAAC,aAAa,KAAK,GAAS,EACtF,KAAW,MACf,EAAc,QAAQ,MAAM,EAAE,WAAW,EAAO,CAAC,QAE7C,IAAiB,EAAQ,YAAY,EAIrC,KAA+B;EACnC;GAAE,OAAO;GAAW,OAAO,EAAc;GAAQ;EACjD;GAAE,OAAO;GAAa,OAAO,EAAQ,YAAY;GAAE;EACnD;GAAE,OAAO;GAAa,OAAO;GAAgB;EAC7C;GAAE,OAAO;GAAc,OAAO,EAAQ,aAAa;GAAE;EACrD;GAAE,OAAO;GAAS,OAAO,EAAQ,QAAQ,GAAG,EAAQ,QAAQ;GAAE;EAC/D,EAEK,KAA0B;EAC9B,OAAO,EAAI;EACX,YAAY,EAAI;EAChB,UAAU,EAAI;EACd,MAAM,EAAI;EACV,iBAAiB,EAAI;EACrB,QAAQ,EAAI;EACZ,WAAW,EAAI;EACf,WAAW,EAAI;EACf,UAAU,EAAI;EACd,aAAa,EAAI;EACjB,cAAc,EAAI;EAClB,UAAU,EAAI;EACf,EAEK,KAAmB,OAAO,MAAuB;EACrD,IAAM,IAA+B;GACnC,OAAO,EAAQ,GAAQ,QAAQ;GAC/B,YAAY,EAAQ,GAAQ,aAAa;GACzC,UAAU,EAAQ,GAAQ,WAAW;GACrC,MAAM,EAAO;GACb,iBAAiB,EAAO;GACxB,QAAQ,EAAO;GACf,WAAW,EAAU,GAAQ,YAAY;GACzC,WAAW,EAAU,GAAQ,YAAY;GACzC,UAAU,EAAQ,GAAQ,WAAW;GACrC,aAAa,EAAQ,GAAQ,cAAc;GAC3C,cAAc,EAAQ,GAAQ,eAAe;GAC7C,UAAU,EAAQ,GAAQ,WAAW;GACtC;AAED,EADA,MAAM,EAAiB,EAAI,IAAI,EAAQ,EACvC,EAAU,uBAAuB,UAAU;IAGvC,KAAe,YAAY;AAG/B,EAFA,MAAM,EAAiB,EAAI,GAAG,EAC9B,EAAU,uBAAuB,UAAU,EAC3C,EAAS,sBAAsB;IAG3B,KAAc,YAAY;AAC9B,MAAI;AAEF,GADA,MAAM,UAAU,UAAU,UAAU,OAAO,SAAS,KAAK,EACzD,EAAU,gCAAgC,UAAU;UAC9C;AACN,KAAU,+BAA+B,QAAQ;;IAI/C,WACJ,EAAS,wCAAwC,mBAAmB,EAAI,MAAM,GAAG,EAI7E,IAAU,EAAkB,EAAI,KAChC,IAAgB,MAAY,KAAA,GAC5B,MAAa,MACjB,KAAS,KAAK,EAAO,UAAU,KAAS,KAAK,EAAO,UAAU,EAAO;AASvE,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,IAAD;IAAW,WAAU;IAAQ,MAAA;IAAK,WAAU;cAC1C,kBAAC,GAAD;KAAa,WAAU;eAAvB,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,MAAD;SAAI,WAAU;mBACZ,kBAAC,GAAD,EAAA,UAAe,EAAI,OAAqB,CAAA;SACrC,CAAA,EACL,kBAAC,GAAD;SAAO,SAAS,EAAI,WAAW,WAAW,YAAY;mBACnD,EAAgB,EAAI,OAAO;SACtB,CAAA,CACJ;;OACN,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,QAAD;UAAM,WAAU;oBAAhB,CACE,kBAAC,IAAD,EAAW,WAAU,eAAgB,CAAA,EACpC,EAAI,WACA;;SACP,kBAAC,QAAD;UAAM,WAAU;oBAAhB,CACE,kBAAC,IAAD,EAAQ,WAAU,eAAgB,CAAA,EACjC,EAAI,SACA;;SACP,kBAAC,QAAD,EAAA,UAAO,EAAgB,EAAI,KAAK,EAAQ,CAAA;SACpC;;OACN,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,GAAD;UAAO,SAAQ;UAAY,WAAU;oBAArC,CACG,EAAc,QAAO,cAChB;;SACP,IAAiB,KAChB,kBAAC,GAAD;UAAO,SAAQ;UAAY,WAAU;oBAArC,CACG,GAAe,gBACV;;SAEV,kBAAC,QAAD;UAAM,WAAU;oBAAhB;WACE,kBAAC,IAAD,EAAU,WAAU,WAAY,CAAA;;WACxB,EAAI;WACP;;SACH;;OACN,kBAAC,GAAD;QAAa,WAAU;kBAAO;QAGhB,CAAA;OACV;SAEN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf;QAAmE;SAC9D,EAAI,YAAY,KAAM,QAAQ,EAAE;QAAC;SAAO,EAAI,YAAY,KAAM,QAAQ,EAAE;QAAC;QACxE;UACN,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,GAAD;SAAQ,SAAQ;SAAU,MAAK;SAAK,SAAS;mBAA7C,CACE,kBAAC,GAAD,EAAQ,WAAU,gBAAiB,CAAA,EAAA,QAE5B;;QACT,kBAAC,GAAD;SAAQ,SAAQ;SAAU,MAAK;SAAK,eAAe,EAAY,GAAK;mBAApE,CACE,kBAAC,GAAD,EAAQ,WAAU,gBAAiB,CAAA,EAAA,OAE5B;;QACT,kBAAC,GAAD;SACE,SAAQ;SACR,MAAK;SACL,cAAW;SACX,eAAe,EAAc,GAAK;mBAElC,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;SACvB,CAAA;QACT,kBAAC,GAAD;SAAQ,MAAK;SAAK,SAAS;mBAA3B,CACE,kBAAC,GAAD,EAAO,WAAU,gBAAiB,CAAA,EAAA,oBAE3B;;QACL;SACF;QACM;;IACJ,CAAA;GAGZ,kBAAC,OAAD;IAAK,WAAU;cAAf,CAEE,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,eAAuB,CAAA,EACvB,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,KAAD;OAAG,WAAU;iBAA+C,EAAI;OAAgB,CAAA,EACpE,CAAA,CACT,EAAA,CAAA;MAEP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,gBAAwB,CAAA,EACxB,CAAA,EACb,kBAAC,GAAD,EAAA,UACG,EAAI,aAAa,WAAW,IAC3B,kBAAC,KAAD;OAAG,WAAU;iBAA0B;OAA2B,CAAA,GAElE,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAI,aAAa,KAAK,GAAK,MAC1B,kBAAC,MAAD;QAAc,WAAU;kBAAxB,CACE,kBAAC,GAAD,EAAa,WAAU,yDAA0D,CAAA,EAChF,EACE;UAHI,EAGJ,CACL;OACC,CAAA,EAEK,CAAA,CACT,EAAA,CAAA;MAEP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,YAAoB,CAAA,EACpB,CAAA,EACb,kBAAC,GAAD,EAAA,UACG,EAAI,SAAS,WAAW,IACvB,kBAAC,KAAD;OAAG,WAAU;iBAA0B;OAAuB,CAAA,GAE9D,kBAAC,OAAD;OAAK,WAAU;iBACZ,EAAI,SAAS,KAAK,GAAK,MACtB,kBAAC,GAAD;QAAiB,SAAQ;QAAU,WAAU;kBAC1C;QACK,EAFI,EAEJ,CACR;OACE,CAAA,EAEI,CAAA,CACT,EAAA,CAAA;MAGP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;OAAY,WAAU;iBAAtB,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAW,qBAA6B,CAAA,EACxC,kBAAC,KAAD;QAAG,WAAU;kBAA+B;QAGxC,CAAA,CACA,EAAA,CAAA,EACN,kBAAC,GAAD;QACE,MAAK;QACL,SAAQ;QACR,SApJgB,YAAY;AACxC,aAAI;AACF,gBAAM,EAAuB,EAAI,GAAG;iBAC9B;AACN,YAAU,wCAAwC,QAAQ;;;QAiJhD,UAAU;kBAJZ,CAME,kBAAC,GAAD,EAAU,WAAU,gBAAiB,CAAA,EACpC,IAAgB,oBAAoB,mBAC9B;UACE;UACb,kBAAC,GAAD,EAAA,UACG,IACC,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD,EAAS,MAAK,MAAO,CAAA;OACjB,CAAA,GACH,IAKD,EAAQ,WAAW,IACrB,kBAAC,KAAD;OAAG,WAAU;iBAA0B;OAEnC,CAAA,GAEJ,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAQ,KAAK,MACZ,kBAAC,MAAD;QAEE,WAAU;kBAFZ;SAIE,kBAAC,OAAD;UAAK,WAAU;oBAAf;WACE,kBAAC,GAAD;YACE,MAAM,GAAG,EAAM,UAAU,UAAU,GAAG,EAAM,UAAU;YACtD,MAAM;YACN,CAAA;WACF,kBAAC,QAAD;YAAM,WAAU;sBAAhB;aACG,EAAM,UAAU;aAAU;aAAE,EAAM,UAAU;aACxC;;WACP,kBAAC,GAAD;YACE,SAAS,EAAY,EAAM,UAAU;YACrC,WAAU;sBAET,EAAgB,EAAM,UAAU,OAAO;YAClC,CAAA;WACJ;;SAEN,kBAAC,OAAD;UAAK,WAAU;oBACb,kBAAC,IAAD;WACE,OAAM;WACN,OAAO,EAAM;WACb,YAAY,GAAG,EAAM,MAAM;WAC3B,OAAO,GAAU,EAAM,MAAM;WAC7B,QAAQ;WACR,CAAA;UACE,CAAA;SACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,QAAD,EAAA,UAAA;WAAM;WAAS,EAAM;WAAW;WAAQ,EAAA,CAAA,EACxC,kBAAC,QAAD,EAAA,UAAA;WAAM;WAAa,EAAM;WAAgB;WAAQ,EAAA,CAAA,CAC7C;;UAEJ,EAAM,cAAc,SAAS,KAAK,EAAM,cAAc,SAAS,MAC/D,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACG,EAAM,cAAc,KAAK,MACxB,kBAAC,GAAD;WAEE,SAAQ;WACR,WAAU;qBAHZ,CAKE,kBAAC,GAAD,EAAa,WAAU,oCAAqC,CAAA,EAC3D,EACK;aAND,KAAK,IAMJ,CACR,EACD,EAAM,cAAc,KAAK,MACxB,kBAAC,GAAD;WAEE,SAAQ;WACR,WAAU;qBAHZ,CAKE,kBAAC,GAAD,EAAS,WAAU,kCAAmC,CAAA,EACrD,EACK;aAND,KAAK,IAMJ,CACR,CACE;;SAIR,kBAAC,KAAD;UAAG,WAAU;oBACV,EAAM;UACL,CAAA;SACD;UA9DE,EAAM,YA8DR,CACL;OACC,CAAA,GA5EL,kBAAC,KAAD;OAAG,WAAU;iBAA0B;OAGnC,CAAA,EA2EM,CAAA,CACT,EAAA,CAAA;MACH;QAGN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,cAAsB,CAAA,EACtB,CAAA,EACb,kBAAC,GAAD,EAAA,UACG,EAAc,WAAW,IACxB,kBAAC,KAAD;MAAG,WAAU;gBAA0B;MAA2C,CAAA,GAElF,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAc,KAAK,MAClB,kBAAC,MAAD;OAEE,WAAU;iBAFZ;QAIE,kBAAC,GAAD;SACE,MAAM,GAAG,EAAU,UAAU,GAAG,EAAU;SAC1C,MAAM;SACN,CAAA;QACF,kBAAC,QAAD;SAAM,WAAU;mBAAhB;UACG,EAAU;UAAU;UAAE,EAAU;UAC5B;;QACP,kBAAC,GAAD;SACE,SAAS,EAAY,EAAU;SAC/B,WAAU;mBAET,EAAgB,EAAU,OAAO;SAC5B,CAAA;QACR,kBAAC,GAAD;SAAa,OAAO,EAAU;SAAQ,MAAM;SAAI,WAAU;SAAa,CAAA;QACpE;SAjBE,EAAU,GAiBZ,CACL;MACC,CAAA,EAEK,CAAA,CACT,EAAA,CAAA,EAEP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,iBAAyB,CAAA,EACzB,CAAA,EACb,kBAAC,GAAD,EAAA,UACG,EAAc,WAAW,IACxB,kBAAC,KAAD;MAAG,WAAU;gBAA0B;MAEnC,CAAA,GAEJ,kBAAC,GAAD;MACE,QAAQ;MACR,iBAAiB,MAAU,OAAO,EAAM;MACxC,gBAAA;MACA,CAAA,EAEQ,CAAA,CACT,EAAA,CAAA,CACH;OACF;;GAEN,kBAAC,IAAD;IACE,MAAM;IACN,cAAc;IACd,OAAM;IACN,aAAY;IACZ,QAAQ;IACR,eAAe;IACf,aAAY;IACZ,UAAU;IACV,CAAA;GACF,kBAAC,IAAD;IACE,MAAM;IACN,cAAc;IACd,OAAM;IACN,aAAa,uBAAuB,EAAI,MAAM;IAC9C,cAAa;IACb,aAAA;IACA,WAAW;IACX,CAAA;GACE"}
|
|
1
|
+
{"version":3,"file":"HiringJobDetailsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HiringJobDetailsPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n Spinner,\n} from '@burdenoff/fe-libs/ui';\nimport { useState } from 'react';\nimport {\n Briefcase,\n Calendar,\n CheckCircle,\n MapPin,\n Pencil,\n Share2,\n Sparkles,\n Trash2,\n Users,\n XCircle,\n} from 'lucide-react';\nimport { useNavigate, useSearchParams } from 'react-router';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useCrewFoundryStore, type Candidate, type JobPosting } from '../store/crewfoundryStore';\nimport { FormModal, type FieldConfig, type FormValues } from '../components/forms/FormModal';\nimport { ConfirmDialog } from '../components/forms/ConfirmDialog';\nimport { getNumber, getTags, getText } from '../components/forms/formValues';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport {\n HBarFunnel,\n RatingStars,\n SkillBar,\n STATUS,\n getInitials,\n type LabeledValue,\n} from '../components/viz';\n\nconst JOB_FIELDS: FieldConfig[] = [\n { name: 'title', label: 'Job Title', type: 'text', required: true, colSpan: 2 },\n { name: 'department', label: 'Department', type: 'text', required: true },\n { name: 'location', label: 'Location', type: 'text', required: true },\n {\n name: 'type',\n label: 'Employment Type',\n type: 'select',\n options: [\n { label: 'Full time', value: 'full_time' },\n { label: 'Part time', value: 'part_time' },\n { label: 'Contract', value: 'contract' },\n ],\n },\n {\n name: 'experienceLevel',\n label: 'Experience Level',\n type: 'select',\n options: [\n { label: 'Entry', value: 'entry' },\n { label: 'Mid', value: 'mid' },\n { label: 'Senior', value: 'senior' },\n { label: 'Lead', value: 'lead' },\n { label: 'Executive', value: 'executive' },\n ],\n },\n {\n name: 'status',\n label: 'Status',\n type: 'select',\n options: [\n { label: 'Draft', value: 'draft' },\n { label: 'Active', value: 'active' },\n { label: 'Paused', value: 'paused' },\n { label: 'Closed', value: 'closed' },\n { label: 'Filled', value: 'filled' },\n ],\n },\n { name: 'salaryMin', label: 'Salary Min ($)', type: 'number' },\n { name: 'salaryMax', label: 'Salary Max ($)', type: 'number' },\n { name: 'deadline', label: 'Application Deadline', type: 'date' },\n { name: 'description', label: 'Description', type: 'textarea', colSpan: 2 },\n { name: 'requirements', label: 'Requirements', type: 'tags' },\n { name: 'benefits', label: 'Benefits', type: 'tags' },\n];\n\nconst STAGE_BADGE: Record<\n Candidate['status'],\n 'default' | 'secondary' | 'destructive' | 'outline'\n> = {\n new: 'outline',\n screening: 'secondary',\n interview: 'secondary',\n assessment: 'secondary',\n offer: 'default',\n hired: 'default',\n rejected: 'destructive',\n};\n\nfunction InitialsAvatar({ name, size = 32 }: { 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\nexport function HiringJobDetailsPagePage() {\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const { showToast } = useGlobalUi();\n const {\n jobPostings,\n candidates,\n updateJobPosting,\n removeJobPosting,\n initialized,\n jobPostingMatches,\n jobPostingMatchesLoading,\n fetchJobPostingMatches,\n } = useCrewFoundryStore();\n const jobId = searchParams.get('id');\n const job = jobId ? jobPostings.find((j) => j.id === jobId) : jobPostings[0];\n const [editOpen, setEditOpen] = useState(false);\n const [deleteOpen, setDeleteOpen] = useState(false);\n\n if (!initialized && !job) {\n return (\n <div className=\"flex min-h-[60vh] items-center justify-center p-6\">\n <Spinner size=\"lg\" />\n </div>\n );\n }\n\n if (!job) {\n return (\n <div className=\"p-6\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No job posting found\"\n description=\"This job posting is no longer available.\"\n />\n </div>\n );\n }\n\n // Applicants for this role. CrewCandidate has no jobId link, so match on the\n // (normalized) position title — the only real linkage the schema exposes.\n const titleKey = job.title.trim().toLowerCase();\n const jobCandidates = candidates.filter((c) => c.position.trim().toLowerCase() === titleKey);\n const countBy = (status: Candidate['status']) =>\n jobCandidates.filter((c) => c.status === status).length;\n\n const interviewCount = countBy('interview');\n\n // Real-data hiring funnel: \"Applied\" = everyone who applied to this role, then\n // the count currently at each downstream stage (offer folds in hired).\n const funnelStages: LabeledValue[] = [\n { label: 'Applied', value: jobCandidates.length },\n { label: 'Screening', value: countBy('screening') },\n { label: 'Interview', value: interviewCount },\n { label: 'Assessment', value: countBy('assessment') },\n { label: 'Offer', value: countBy('offer') + countBy('hired') },\n ];\n\n const editInitial: FormValues = {\n title: job.title,\n department: job.department,\n location: job.location,\n type: job.type,\n experienceLevel: job.experienceLevel,\n status: job.status,\n salaryMin: job.salaryMin,\n salaryMax: job.salaryMax,\n deadline: job.deadline,\n description: job.description,\n requirements: job.requirements,\n benefits: job.benefits,\n };\n\n const handleEditSubmit = async (values: FormValues) => {\n const updates: Partial<JobPosting> = {\n title: getText(values, 'title'),\n department: getText(values, 'department'),\n location: getText(values, 'location'),\n type: values.type as JobPosting['type'],\n experienceLevel: values.experienceLevel as JobPosting['experienceLevel'],\n status: values.status as JobPosting['status'],\n salaryMin: getNumber(values, 'salaryMin'),\n salaryMax: getNumber(values, 'salaryMax'),\n deadline: getText(values, 'deadline'),\n description: getText(values, 'description'),\n requirements: getTags(values, 'requirements'),\n benefits: getTags(values, 'benefits'),\n };\n await updateJobPosting(job.id, updates);\n showToast('Job posting updated', 'success');\n };\n\n const handleDelete = async () => {\n await removeJobPosting(job.id);\n showToast('Job posting deleted', 'success');\n navigate('../hiring/jobs-page');\n };\n\n const handleShare = async () => {\n try {\n await navigator.clipboard.writeText(window.location.href);\n showToast('Job link copied to clipboard', 'success');\n } catch {\n showToast('Could not copy the job link', 'error');\n }\n };\n\n const viewApplications = () =>\n navigate(`../hiring/applications-page?position=${encodeURIComponent(job.title)}`);\n\n // BOFF-4195 — AI-assisted candidate matching (deterministic skill-overlap +\n // experience-fit scoring; see wspace-crewfoundry-svc matching.service.ts).\n const matches = jobPostingMatches[job.id];\n const matchesLoaded = matches !== undefined;\n const scoreTone = (score: number): string =>\n score >= 75 ? STATUS.success : score >= 45 ? STATUS.warning : STATUS.danger;\n const handleGenerateMatches = async () => {\n try {\n await fetchJobPostingMatches(job.id);\n } catch {\n showToast('Unable to generate candidate matches', 'error');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardContent className=\"flex flex-col gap-4 p-6 lg:flex-row lg:items-start lg:justify-between\">\n <div className=\"min-w-0\">\n <div className=\"mb-1 flex flex-wrap items-center gap-2\">\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>{job.title}</GradientText>\n </h1>\n <Badge variant={job.status === 'active' ? 'default' : 'secondary'}>\n {formatEnumLabel(job.status)}\n </Badge>\n </div>\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <Briefcase className=\"h-3.5 w-3.5\" />\n {job.department}\n </span>\n <span className=\"flex items-center gap-1\">\n <MapPin className=\"h-3.5 w-3.5\" />\n {job.location}\n </span>\n <span>{formatEnumLabel(job.type)}</span>\n </div>\n <div className=\"mt-2 flex flex-wrap items-center gap-2 text-xs text-text-secondary\">\n <Badge variant=\"secondary\" className=\"text-[10px]\">\n {jobCandidates.length} applicants\n </Badge>\n {interviewCount > 0 && (\n <Badge variant=\"secondary\" className=\"text-[10px]\">\n {interviewCount} in interview\n </Badge>\n )}\n <span className=\"flex items-center gap-1\">\n <Calendar className=\"h-3 w-3\" />\n Posted {job.postedDate}\n </span>\n </div>\n <PagePurpose className=\"mt-3\">\n Everything about this role in one place — review applicants, requirements, and\n compensation to decide who to interview and keep the posting current.\n </PagePurpose>\n </div>\n\n <div className=\"flex shrink-0 flex-col items-start gap-3 lg:items-end\">\n <div className=\"text-2xl font-bold tabular-nums text-text-primary\">\n ${(job.salaryMin / 1000).toFixed(0)}k – ${(job.salaryMax / 1000).toFixed(0)}k\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={handleShare}>\n <Share2 className=\"mr-1 h-4 w-4\" />\n Share\n </Button>\n <Button variant=\"outline\" size=\"sm\" onClick={() => setEditOpen(true)}>\n <Pencil className=\"mr-1 h-4 w-4\" />\n Edit\n </Button>\n <Button\n variant=\"destructive\"\n size=\"sm\"\n aria-label=\"Delete job posting\"\n onClick={() => setDeleteOpen(true)}\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n <Button size=\"sm\" onClick={viewApplications}>\n <Users className=\"mr-1 h-4 w-4\" />\n View Applications\n </Button>\n </div>\n </div>\n </CardContent>\n </GlassCard>\n\n {/* Two-column body */}\n <div className=\"grid gap-6 lg:grid-cols-3\">\n {/* Left: description / requirements / benefits */}\n <div className=\"space-y-6 lg:col-span-2\">\n <Card>\n <CardHeader>\n <CardTitle>Description</CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm leading-relaxed text-text-secondary\">{job.description}</p>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Requirements</CardTitle>\n </CardHeader>\n <CardContent>\n {job.requirements.length === 0 ? (\n <p className=\"text-sm text-text-muted\">No requirements listed.</p>\n ) : (\n <ul className=\"space-y-2\">\n {job.requirements.map((req, idx) => (\n <li key={idx} className=\"flex items-start gap-2 text-sm\">\n <CheckCircle className=\"mt-0.5 h-4 w-4 flex-shrink-0 text-status-success-text\" />\n {req}\n </li>\n ))}\n </ul>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Benefits</CardTitle>\n </CardHeader>\n <CardContent>\n {job.benefits.length === 0 ? (\n <p className=\"text-sm text-text-muted\">No benefits listed.</p>\n ) : (\n <div className=\"flex flex-wrap gap-2\">\n {job.benefits.map((ben, idx) => (\n <Badge key={idx} variant=\"outline\" className=\"rounded-full px-3 py-1 text-xs\">\n {ben}\n </Badge>\n ))}\n </div>\n )}\n </CardContent>\n </Card>\n\n {/* BOFF-4195 — AI-assisted candidate matching (deterministic, explainable) */}\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between gap-3\">\n <div>\n <CardTitle>Suggested Matches</CardTitle>\n <p className=\"mt-1 text-xs text-text-muted\">\n Rule-based scoring — skill overlap (70%) + experience fit (30%) against this\n role's requirements. Not an AI-generated summary.\n </p>\n </div>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={handleGenerateMatches}\n disabled={jobPostingMatchesLoading}\n >\n <Sparkles className=\"mr-1 h-4 w-4\" />\n {matchesLoaded ? 'Refresh matches' : 'Generate matches'}\n </Button>\n </CardHeader>\n <CardContent>\n {jobPostingMatchesLoading ? (\n <div className=\"flex items-center justify-center py-6\">\n <Spinner size=\"md\" />\n </div>\n ) : !matchesLoaded ? (\n <p className=\"text-sm text-text-muted\">\n Click "Generate matches" to rank the candidate pool against this\n posting's requirements and experience level.\n </p>\n ) : matches.length === 0 ? (\n <p className=\"text-sm text-text-muted\">\n No candidates in the pool right now (excluding already-hired/rejected).\n </p>\n ) : (\n <ul className=\"space-y-4\">\n {matches.map((match) => (\n <li\n key={match.candidateId}\n className=\"rounded-[var(--radius-card)] border border-border-subtle bg-bg-surface p-3\"\n >\n <div className=\"flex items-center gap-2\">\n <InitialsAvatar\n name={`${match.candidate.firstName} ${match.candidate.lastName}`}\n size={28}\n />\n <span className=\"min-w-0 flex-1 truncate text-sm font-medium text-text-primary\">\n {match.candidate.firstName} {match.candidate.lastName}\n </span>\n <Badge\n variant={STAGE_BADGE[match.candidate.status]}\n className=\"shrink-0 text-[9px]\"\n >\n {formatEnumLabel(match.candidate.status)}\n </Badge>\n </div>\n\n <div className=\"mt-2\">\n <SkillBar\n label=\"Overall match\"\n value={match.score}\n levelLabel={`${match.score}%`}\n color={scoreTone(match.score)}\n height={6}\n />\n </div>\n <div className=\"mt-2 grid grid-cols-2 gap-2 text-xs text-text-secondary\">\n <span>Skills: {match.skillScore}%</span>\n <span>Experience: {match.experienceScore}%</span>\n </div>\n\n {(match.matchedSkills.length > 0 || match.missingSkills.length > 0) && (\n <div className=\"mt-2 flex flex-wrap gap-1\">\n {match.matchedSkills.map((skill) => (\n <Badge\n key={`m-${skill}`}\n variant=\"secondary\"\n className=\"gap-1 text-[10px]\"\n >\n <CheckCircle className=\"h-3 w-3 text-status-success-text\" />\n {skill}\n </Badge>\n ))}\n {match.missingSkills.map((skill) => (\n <Badge\n key={`x-${skill}`}\n variant=\"outline\"\n className=\"gap-1 text-[10px] text-text-muted\"\n >\n <XCircle className=\"h-3 w-3 text-status-error-text\" />\n {skill}\n </Badge>\n ))}\n </div>\n )}\n\n {/* Rationale is always visible — the ranking is never a black box. */}\n <p className=\"mt-2 text-xs leading-relaxed text-text-muted\">\n {match.rationale}\n </p>\n </li>\n ))}\n </ul>\n )}\n </CardContent>\n </Card>\n </div>\n\n {/* Right rail: candidates + funnel */}\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Candidates</CardTitle>\n </CardHeader>\n <CardContent>\n {jobCandidates.length === 0 ? (\n <p className=\"text-sm text-text-muted\">No applicants matched to this role yet.</p>\n ) : (\n <ul className=\"max-h-96 space-y-1 overflow-y-auto pr-1\">\n {jobCandidates.map((candidate) => (\n <li\n key={candidate.id}\n className=\"flex items-center gap-2 rounded-[var(--radius-input)] px-2 py-2 hover:bg-bg-sunken\"\n >\n <InitialsAvatar\n name={`${candidate.firstName} ${candidate.lastName}`}\n size={30}\n />\n <span className=\"min-w-0 flex-1 truncate text-sm font-medium text-text-primary\">\n {candidate.firstName} {candidate.lastName}\n </span>\n <Badge\n variant={STAGE_BADGE[candidate.status]}\n className=\"shrink-0 text-[9px]\"\n >\n {formatEnumLabel(candidate.status)}\n </Badge>\n <RatingStars value={candidate.rating} size={12} className=\"shrink-0\" />\n </li>\n ))}\n </ul>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Hiring funnel</CardTitle>\n </CardHeader>\n <CardContent>\n {jobCandidates.length === 0 ? (\n <p className=\"text-sm text-text-muted\">\n The funnel appears once candidates apply to this role.\n </p>\n ) : (\n <HBarFunnel\n stages={funnelStages}\n valueFormatter={(value) => String(value)}\n showConversion\n />\n )}\n </CardContent>\n </Card>\n </div>\n </div>\n\n <FormModal\n open={editOpen}\n onOpenChange={setEditOpen}\n title=\"Edit Job Posting\"\n description=\"Update the role details, compensation, and status.\"\n fields={JOB_FIELDS}\n initialValues={editInitial}\n submitLabel=\"Save changes\"\n onSubmit={handleEditSubmit}\n />\n <ConfirmDialog\n open={deleteOpen}\n onOpenChange={setDeleteOpen}\n title=\"Delete job posting\"\n description={`Permanently remove “${job.title}”? This cannot be undone.`}\n confirmLabel=\"Delete\"\n destructive\n onConfirm={handleDelete}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crewfoundryStore.js","names":[],"sources":["../../../src/crewfoundry/store/crewfoundryStore.ts"],"sourcesContent":["import { create } from 'zustand';\nimport type { OverviewMetric, WorkItem } from '../types';\n\n// ── Domain Types ────────────────────────────────────────────────\nexport interface Employee {\n id: string;\n firstName: string;\n lastName: string;\n email: string;\n department: string;\n position: string;\n status: 'active' | 'inactive' | 'on_leave' | 'terminated';\n type: 'full_time' | 'part_time' | 'contract' | 'intern';\n salary: number;\n startDate: string;\n avatar?: string;\n location: string;\n managerId?: string;\n phone: string;\n performanceScore: number;\n /** Real per-employee skill tags (backend default `[]`). */\n skills: string[];\n /** Exit date for terminated employees; ISO string or null while employed. */\n terminationDate?: string | null;\n}\n\nexport interface Department {\n id: string;\n name: string;\n headId: string;\n headName: string;\n employeeCount: number;\n budget: number;\n location: string;\n status: 'active' | 'restructuring';\n}\n\nexport interface JobPosting {\n id: string;\n title: string;\n department: string;\n location: string;\n type: 'full_time' | 'part_time' | 'contract' | 'remote';\n experienceLevel: 'entry' | 'mid' | 'senior' | 'lead' | 'executive';\n salaryMin: number;\n salaryMax: number;\n status: 'draft' | 'active' | 'paused' | 'closed' | 'filled';\n applicants: number;\n postedDate: string;\n deadline: string;\n description: string;\n requirements: string[];\n benefits: string[];\n}\n\nexport interface Candidate {\n id: string;\n firstName: string;\n lastName: string;\n email: string;\n phone: string;\n position: string;\n status: 'new' | 'screening' | 'interview' | 'assessment' | 'offer' | 'hired' | 'rejected';\n appliedDate: string;\n resumeUrl?: string;\n rating: number;\n source: string;\n experience: number;\n skills: string[];\n currentCompany: string;\n notes: string;\n /** Date the candidate was hired; ISO string or null until hired. */\n hiredDate?: string | null;\n}\n\nexport interface Interview {\n id: string;\n candidateId: string;\n candidateName: string;\n position: string;\n interviewerName: string;\n date: string;\n time: string;\n type: 'phone' | 'video' | 'onsite' | 'panel' | 'technical';\n status: 'scheduled' | 'completed' | 'cancelled' | 'no_show';\n feedback?: string;\n rating?: number;\n}\n\nexport interface LearningCourse {\n id: string;\n title: string;\n category: string;\n instructor: string;\n duration: string;\n level: 'beginner' | 'intermediate' | 'advanced';\n enrolled: number;\n completed: number;\n rating: number;\n status: 'active' | 'draft' | 'archived';\n thumbnail?: string;\n description: string;\n modules: number;\n skills: string[];\n}\n\nexport interface LearningPath {\n id: string;\n title: string;\n description: string;\n courses: string[];\n duration: string;\n level: string;\n enrolled: number;\n completionRate: number;\n}\n\nexport interface MarketplaceService {\n id: string;\n title: string;\n provider: string;\n category: string;\n price: number;\n priceType: 'hourly' | 'fixed' | 'monthly';\n rating: number;\n reviews: number;\n description: string;\n deliveryTime: string;\n status: 'active' | 'paused' | 'pending';\n skills: string[];\n}\n\nexport interface Announcement {\n id: string;\n title: string;\n content: string;\n author: string;\n authorRole: string;\n category: 'general' | 'hr' | 'it' | 'emergency' | 'event';\n priority: 'low' | 'medium' | 'high' | 'urgent';\n isPinned: boolean;\n createdAt: string;\n viewCount: number;\n tags: string[];\n}\n\nexport interface Team {\n id: string;\n name: string;\n description: string;\n leadId: string;\n leadName: string;\n members: string[];\n memberCount: number;\n department: string;\n status: 'active' | 'forming' | 'disbanded';\n projectCount: number;\n createdAt: string;\n}\n\nexport interface Project {\n id: string;\n name: string;\n description: string;\n status: 'planning' | 'active' | 'on_hold' | 'completed' | 'cancelled';\n priority: 'low' | 'medium' | 'high' | 'critical';\n startDate: string;\n endDate: string;\n progress: number;\n budget: number;\n spent: number;\n teamId: string;\n teamName: string;\n managerId: string;\n managerName: string;\n tasks: number;\n completedTasks: number;\n}\n\nexport interface PayrollRecord {\n id: string;\n employeeId: string;\n employeeName: string;\n department: string;\n period: string;\n grossPay: number;\n deductions: number;\n netPay: number;\n status: 'draft' | 'processing' | 'approved' | 'paid' | 'failed';\n payDate: string;\n paymentMethod: string;\n}\n\nexport interface LeaveRequest {\n id: string;\n employeeId: string;\n employeeName: string;\n department: string;\n type: 'annual' | 'sick' | 'personal' | 'maternity' | 'paternity' | 'unpaid';\n startDate: string;\n endDate: string;\n days: number;\n status: 'pending' | 'approved' | 'rejected' | 'cancelled';\n reason: string;\n approverName?: string;\n}\n\nexport interface TimeEntry {\n id: string;\n employeeId: string;\n employeeName: string;\n date: string;\n clockIn: string;\n clockOut: string;\n totalHours: number;\n overtime: number;\n project: string;\n status: 'pending' | 'approved' | 'rejected';\n /** Whether the logged time is billable to a client (backend default `true`). */\n billable: boolean;\n}\n\nexport interface Notification {\n id: string;\n title: string;\n message: string;\n type: 'info' | 'success' | 'warning' | 'error';\n category: string;\n read: boolean;\n createdAt: string;\n actionUrl?: string;\n}\n\nexport interface Invoice {\n id: string;\n number: string;\n clientName: string;\n amount: number;\n status: 'draft' | 'sent' | 'paid' | 'overdue' | 'cancelled';\n issueDate: string;\n dueDate: string;\n items: { description: string; amount: number }[];\n}\n\nexport interface Vendor {\n id: string;\n name: string;\n category: string;\n contactPerson: string;\n email: string;\n phone: string;\n status: 'active' | 'inactive' | 'pending';\n contractValue: number;\n rating: number;\n}\n\nexport interface Client {\n id: string;\n name: string;\n industry: string;\n contactPerson: string;\n email: string;\n phone: string;\n status: 'active' | 'prospect' | 'churned';\n revenue: number;\n projects: number;\n since: string;\n}\n\n// BOFF-3987 — employee profile Documents tab. The binary + canonical file\n// metadata live in wspace-files-svc; this is just the (employee, file)\n// association crewfoundry owns.\nexport interface CrewEmployeeDocument {\n id: string;\n employeeId: string;\n fileId: string;\n title: string;\n category?: string | null;\n uploadedBy: string;\n uploadedAt: string;\n}\n\n// BOFF-3983 — requisition-to-hire onboarding tracker (mock cf-30).\nexport type OnboardingStage = 'offer' | 'paperwork' | 'it_setup' | 'training' | 'day_one';\nexport type OnboardingStatus = 'not_started' | 'in_progress' | 'completed' | 'cancelled';\n\nexport interface CrewOnboardingTask {\n id: string;\n onboardingId: string;\n stage: OnboardingStage;\n title: string;\n assigneeActorId?: string | null;\n assigneeName?: string | null;\n dueDate?: string | null;\n completedAt?: string | null;\n sortOrder: number;\n}\n\nexport interface CrewOnboarding {\n id: string;\n candidateId?: string | null;\n employeeId?: string | null;\n fullName: string;\n position: string;\n department?: string | null;\n startDate?: string | null;\n status: OnboardingStatus;\n currentStage: OnboardingStage;\n createdAt: string;\n tasks: CrewOnboardingTask[];\n totalTaskCount: number;\n completedTaskCount: number;\n progressPercent: number;\n}\n\n// BOFF-4195 — AI-assisted candidate/worker matching engine. Deterministic,\n// rule-based (skill overlap 70% + experience fit 30%) — the backend never\n// fabricates an AI summary; `rationale` is a plain-language breakdown of the\n// two sub-scores so the ranking is never a black box.\nexport interface CrewCandidateMatch {\n candidateId: string;\n jobPostingId: string;\n candidate: Candidate;\n score: number;\n skillScore: number;\n experienceScore: number;\n matchedSkills: string[];\n missingSkills: string[];\n rationale: string;\n}\n\n// Workspace skills supply/demand, computed server-side by\n// `Query.crewSkillsGap` (wspace-crewfoundry-svc analytics service):\n// employeesWithSkill — ACTIVE employees carrying that skill tag\n// openJobsRequiringSkill — non-CLOSED job postings listing it as a requirement\n// shortage — max(0, demand − supply)\n// Rows arrive already sorted by shortage desc, then demand desc. Skills are\n// free-text tags matched case-insensitively; `skill` is the first-seen casing.\nexport interface CrewSkillsGapRow {\n skill: string;\n employeesWithSkill: number;\n openJobsRequiringSkill: number;\n shortage: number;\n}\n\n// BOFF-4193 — native gross-to-net payroll calculation engine (US/IN/GB\n// reference countries). NOT a licensed EOR/statutory-filing platform — see\n// wspace-crewfoundry-svc/src/modules/workforce/services/payroll/README.md\n// for the full scope note. `country`/`status` are kept in the exact\n// UPPER_SNAKE casing the schema's enums use (unlike most entities in this\n// store, which get lower-cased on read) since they're small closed sets\n// displayed as badges, not compared against lower_snake literals elsewhere.\nexport interface CrewPayrollRun {\n id: string;\n country: 'US' | 'IN' | 'GB';\n currency: string;\n period: string;\n payDate?: string | null;\n status: 'DRAFT' | 'CALCULATED' | 'APPROVED' | 'CLOSED';\n employeeCount: number;\n totalGrossPay: number;\n totalNetPay: number;\n calculatedAt?: string | null;\n approvedAt?: string | null;\n approvedBy?: string | null;\n notes?: string | null;\n}\n\nexport interface CrewPayslipTaxBracket {\n label: string;\n rate: number;\n bandFrom: number;\n bandTo?: number | null;\n taxableInBand: number;\n taxOnBand: number;\n}\n\nexport interface CrewPayslipContributionLine {\n label: string;\n rate?: number | null;\n base: number;\n amount: number;\n}\n\nexport interface CrewPayslip {\n id: string;\n payrollRunId: string;\n employeeId: string;\n employeeName: string;\n country: 'US' | 'IN' | 'GB';\n currency: string;\n period: string;\n annualGrossSalary: number;\n grossPay: number;\n taxableIncome: number;\n incomeTax: number;\n employeeSocialContrib: number;\n employerSocialContrib: number;\n otherDeductions: number;\n netPay: number;\n incomeTaxBrackets: CrewPayslipTaxBracket[];\n employeeContributions: CrewPayslipContributionLine[];\n employerContributions: CrewPayslipContributionLine[];\n otherDeductionLines: CrewPayslipContributionLine[];\n assumptions: string[];\n}\n\nexport interface CreateCrewPayrollRunEmployeeInput {\n employeeId: string;\n employeeName: string;\n annualGrossSalary: number;\n}\n\nexport interface ExternalPaymentMethod {\n id: string;\n type: 'bank' | 'card';\n name: string;\n bank: string;\n last4: string;\n status: 'primary' | 'active';\n expiry?: string;\n}\n\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport type { Gateway } from '@burdenoff/fe-libs/shared/config';\n\ntype InitParams = {\n workspaceId?: string;\n authToken?: string;\n apiGatewayUrl?: string;\n actorId?: string;\n};\n\ntype GraphqlResult<T> = {\n data: T | null;\n errors?: Array<{ message: string }>;\n};\n\nconst BOOTSTRAP_QUERY = `\n query CrewfoundryBootstrap {\n crewfoundryBootstrap {\n employees { id firstName lastName email department position status type salary startDate avatar location managerId phone performanceScore skills terminationDate }\n departments { id name headId headName employeeCount budget location status }\n jobPostings { id title department location type experienceLevel salaryMin salaryMax status applicants postedDate deadline description requirements benefits }\n candidates { id firstName lastName email phone position status appliedDate resumeUrl rating source experience skills currentCompany notes hiredDate }\n interviews { id candidateId candidateName position interviewerName date time type status feedback rating }\n announcements { id title content author authorRole category priority isPinned createdAt viewCount tags }\n teams { id name description leadId leadName members memberCount department status projectCount createdAt }\n projects { id name description status priority startDate endDate progress budget spent teamId teamName managerId managerName tasks completedTasks }\n leaveRequests { id employeeId employeeName department type startDate endDate days status reason approverName }\n timeEntries { id employeeId employeeName date clockIn clockOut totalHours overtime project status billable }\n vendors { id name category contactPerson email phone status contractValue rating }\n clients { id name industry contactPerson email phone status revenue projects since }\n }\n }\n`;\n\n// Payroll (BOFF-4887) is deliberately NOT part of crewfoundryBootstrap — it is\n// gated by the dedicated `crewfoundry-payroll` RBAC resource rather than the\n// general `crewfoundry:read` the bootstrap query uses, so a Workforce\n// Coordinator (or any role with only general workforce access) must not be\n// able to read gross/net pay and deductions just by loading the dashboard.\n// Fetched separately via `runOverlap` below (best-effort — a FORBIDDEN\n// response from a role without payroll access degrades to an empty list\n// rather than failing the whole bootstrap load).\nconst CREW_PAYROLL_RECORDS_QUERY = `\n query CrewPayrollRecords {\n crewPayrollRecords { id employeeId employeeName department period grossPay deductions netPay status payDate paymentMethod }\n }\n`;\n\ntype CrewPayrollRecordsResponse = {\n crewPayrollRecords: PayrollRecord[];\n};\n\nconst WORKSPACE_BOOTSTRAP_WITH_OVERLAP_QUERY = `\n query CrewfoundryWorkspaceBootstrapWithOverlap(\n $workspaceId: ID!\n $projectPagination: PaginationInput\n $groupFirst: Int\n $conversationFirst: Int\n ) {\n crewfoundryBootstrap {\n employees { id firstName lastName email department position status type salary startDate avatar location managerId phone performanceScore skills terminationDate }\n departments { id name headId headName employeeCount budget location status }\n jobPostings { id title department location type experienceLevel salaryMin salaryMax status applicants postedDate deadline description requirements benefits }\n candidates { id firstName lastName email phone position status appliedDate resumeUrl rating source experience skills currentCompany notes hiredDate }\n interviews { id candidateId candidateName position interviewerName date time type status feedback rating }\n announcements { id title content author authorRole category priority isPinned createdAt viewCount tags }\n teams { id name description leadId leadName members memberCount department status projectCount createdAt }\n projects { id name description status priority startDate endDate progress budget spent teamId teamName managerId managerName tasks completedTasks }\n leaveRequests { id employeeId employeeName department type startDate endDate days status reason approverName }\n timeEntries { id employeeId employeeName date clockIn clockOut totalHours overtime project status billable }\n vendors { id name category contactPerson email phone status contractValue rating }\n clients { id name industry contactPerson email phone status revenue projects since }\n }\n projects(filter: { workspaceId: $workspaceId }, pagination: $projectPagination) {\n items {\n id\n name\n key\n creatorId\n status\n config\n archived\n createdAt\n updatedAt\n }\n }\n groups(first: $groupFirst) {\n edges {\n node {\n id\n name\n description\n groupType\n status\n memberCount\n ownerId\n createdAt\n }\n }\n }\n conversations(first: $conversationFirst) {\n edges {\n node {\n id\n title\n description\n state\n unreadCount\n memberCount\n updatedAt\n }\n }\n }\n }\n`;\n\nconst OVERLAP_BILLING_QUERY = `\n query CrewfoundryOverlapBilling {\n getBillingAccountsByOrg {\n id\n name\n isDefault\n paymentMethods {\n id\n type\n brand\n last4\n isDefault\n expiryMonth\n expiryYear\n }\n }\n }\n`;\n\nconst CREATE_EMPLOYEE_MUTATION = `mutation CreateCrewEmployee($input: CreateCrewEmployeeInput!) { createCrewEmployee(input: $input) { id firstName lastName email department position status type salary startDate avatar location managerId phone performanceScore skills terminationDate } }`;\nconst UPDATE_EMPLOYEE_MUTATION = `mutation UpdateCrewEmployee($id: ID!, $input: UpdateCrewEmployeeInput!) { updateCrewEmployee(id: $id, input: $input) { id firstName lastName email department position status type salary startDate avatar location managerId phone performanceScore skills terminationDate } }`;\nconst REMOVE_EMPLOYEE_MUTATION = `mutation RemoveCrewEmployee($id: ID!) { removeCrewEmployee(id: $id) }`;\nconst CREATE_JOB_MUTATION = `mutation CreateCrewJobPosting($input: CreateCrewJobPostingInput!) { createCrewJobPosting(input: $input) { id title department location type experienceLevel salaryMin salaryMax status applicants postedDate deadline description requirements benefits } }`;\nconst UPDATE_JOB_MUTATION = `mutation UpdateCrewJobPosting($id: ID!, $input: UpdateCrewJobPostingInput!) { updateCrewJobPosting(id: $id, input: $input) { id title department location type experienceLevel salaryMin salaryMax status applicants postedDate deadline description requirements benefits } }`;\nconst CREATE_CANDIDATE_MUTATION = `mutation CreateCrewCandidate($input: CreateCrewCandidateInput!) { createCrewCandidate(input: $input) { id firstName lastName email phone position status appliedDate resumeUrl rating source experience skills currentCompany notes hiredDate } }`;\nconst UPDATE_CANDIDATE_MUTATION = `mutation UpdateCrewCandidate($id: ID!, $input: UpdateCrewCandidateInput!) { updateCrewCandidate(id: $id, input: $input) { id firstName lastName email phone position status appliedDate resumeUrl rating source experience skills currentCompany notes hiredDate } }`;\nconst CREATE_ANNOUNCEMENT_MUTATION = `mutation CreateCrewAnnouncement($input: CreateCrewAnnouncementInput!) { createCrewAnnouncement(input: $input) { id title content author authorRole category priority isPinned createdAt viewCount tags } }`;\nconst CREATE_LEAVE_MUTATION = `mutation CreateCrewLeaveRequest($input: CreateCrewLeaveRequestInput!) { createCrewLeaveRequest(input: $input) { id employeeId employeeName department type startDate endDate days status reason approverName } }`;\nconst UPDATE_LEAVE_MUTATION = `mutation UpdateCrewLeaveRequest($id: ID!, $input: UpdateCrewLeaveRequestInput!) { updateCrewLeaveRequest(id: $id, input: $input) { id employeeId employeeName department type startDate endDate days status reason approverName } }`;\n// Extra removes / updates for existing entities (delete + edit buttons).\nconst REMOVE_JOB_MUTATION = `mutation RemoveCrewJobPosting($id: ID!) { removeCrewJobPosting(id: $id) }`;\nconst REMOVE_CANDIDATE_MUTATION = `mutation RemoveCrewCandidate($id: ID!) { removeCrewCandidate(id: $id) }`;\nconst UPDATE_ANNOUNCEMENT_MUTATION = `mutation UpdateCrewAnnouncement($id: ID!, $input: UpdateCrewAnnouncementInput!) { updateCrewAnnouncement(id: $id, input: $input) { id title content author authorRole category priority isPinned createdAt viewCount tags } }`;\nconst REMOVE_ANNOUNCEMENT_MUTATION = `mutation RemoveCrewAnnouncement($id: ID!) { removeCrewAnnouncement(id: $id) }`;\n\n// Interview\nconst CREATE_INTERVIEW_MUTATION = `mutation CreateCrewInterview($input: CreateCrewInterviewInput!) { createCrewInterview(input: $input) { id candidateId candidateName position interviewerName date time type status feedback rating } }`;\nconst UPDATE_INTERVIEW_MUTATION = `mutation UpdateCrewInterview($id: ID!, $input: UpdateCrewInterviewInput!) { updateCrewInterview(id: $id, input: $input) { id candidateId candidateName position interviewerName date time type status feedback rating } }`;\nconst REMOVE_INTERVIEW_MUTATION = `mutation RemoveCrewInterview($id: ID!) { removeCrewInterview(id: $id) }`;\n\n// Vendor\nconst CREATE_VENDOR_MUTATION = `mutation CreateCrewVendor($input: CreateCrewVendorInput!) { createCrewVendor(input: $input) { id name category contactPerson email phone status contractValue rating } }`;\nconst UPDATE_VENDOR_MUTATION = `mutation UpdateCrewVendor($id: ID!, $input: UpdateCrewVendorInput!) { updateCrewVendor(id: $id, input: $input) { id name category contactPerson email phone status contractValue rating } }`;\nconst REMOVE_VENDOR_MUTATION = `mutation RemoveCrewVendor($id: ID!) { removeCrewVendor(id: $id) }`;\n\n// Client\nconst CREATE_CLIENT_MUTATION = `mutation CreateCrewClient($input: CreateCrewClientInput!) { createCrewClient(input: $input) { id name industry contactPerson email phone status revenue projects since } }`;\nconst UPDATE_CLIENT_MUTATION = `mutation UpdateCrewClient($id: ID!, $input: UpdateCrewClientInput!) { updateCrewClient(id: $id, input: $input) { id name industry contactPerson email phone status revenue projects since } }`;\nconst REMOVE_CLIENT_MUTATION = `mutation RemoveCrewClient($id: ID!) { removeCrewClient(id: $id) }`;\n\n// Team\nconst CREATE_TEAM_MUTATION = `mutation CreateCrewTeam($input: CreateCrewTeamInput!) { createCrewTeam(input: $input) { id name description leadId leadName members memberCount department status projectCount createdAt } }`;\nconst UPDATE_TEAM_MUTATION = `mutation UpdateCrewTeam($id: ID!, $input: UpdateCrewTeamInput!) { updateCrewTeam(id: $id, input: $input) { id name description leadId leadName members memberCount department status projectCount createdAt } }`;\nconst REMOVE_TEAM_MUTATION = `mutation RemoveCrewTeam($id: ID!) { removeCrewTeam(id: $id) }`;\n\n// Project\nconst CREATE_PROJECT_MUTATION = `mutation CreateCrewProject($input: CreateCrewProjectInput!) { createCrewProject(input: $input) { id name description status priority startDate endDate progress budget spent teamId teamName managerId managerName tasks completedTasks } }`;\nconst UPDATE_PROJECT_MUTATION = `mutation UpdateCrewProject($id: ID!, $input: UpdateCrewProjectInput!) { updateCrewProject(id: $id, input: $input) { id name description status priority startDate endDate progress budget spent teamId teamName managerId managerName tasks completedTasks } }`;\nconst REMOVE_PROJECT_MUTATION = `mutation RemoveCrewProject($id: ID!) { removeCrewProject(id: $id) }`;\n\n// Department\nconst CREATE_DEPARTMENT_MUTATION = `mutation CreateCrewDepartment($input: CreateCrewDepartmentInput!) { createCrewDepartment(input: $input) { id name headId headName employeeCount budget location status } }`;\nconst UPDATE_DEPARTMENT_MUTATION = `mutation UpdateCrewDepartment($id: ID!, $input: UpdateCrewDepartmentInput!) { updateCrewDepartment(id: $id, input: $input) { id name headId headName employeeCount budget location status } }`;\nconst REMOVE_DEPARTMENT_MUTATION = `mutation RemoveCrewDepartment($id: ID!) { removeCrewDepartment(id: $id) }`;\n\n// PayrollRecord\nconst CREATE_PAYROLL_MUTATION = `mutation CreateCrewPayrollRecord($input: CreateCrewPayrollRecordInput!) { createCrewPayrollRecord(input: $input) { id employeeId employeeName department period grossPay deductions netPay status payDate paymentMethod } }`;\nconst UPDATE_PAYROLL_MUTATION = `mutation UpdateCrewPayrollRecord($id: ID!, $input: UpdateCrewPayrollRecordInput!) { updateCrewPayrollRecord(id: $id, input: $input) { id employeeId employeeName department period grossPay deductions netPay status payDate paymentMethod } }`;\nconst REMOVE_PAYROLL_MUTATION = `mutation RemoveCrewPayrollRecord($id: ID!) { removeCrewPayrollRecord(id: $id) }`;\n\n// BOFF-4193 — native payroll calculation engine (PayrollRun/Payslip). Fetched\n// lazily (see `fetchPayrollRuns`/`fetchPayslipsForRun` below), not on\n// bootstrap — same `crewfoundry-payroll` RBAC gate as CrewPayrollRecord\n// above, so a caller without payroll access simply never triggers the fetch\n// rather than failing the dashboard load.\nconst PAYROLL_RUN_FIELDS = `id country currency period payDate status employeeCount totalGrossPay totalNetPay calculatedAt approvedAt approvedBy notes`;\nconst CREW_PAYROLL_RUNS_QUERY = `query CrewPayrollRuns { crewPayrollRuns { ${PAYROLL_RUN_FIELDS} } }`;\nconst CREATE_PAYROLL_RUN_MUTATION = `mutation CreateCrewPayrollRun($input: CreateCrewPayrollRunInput!) { createCrewPayrollRun(input: $input) { ${PAYROLL_RUN_FIELDS} } }`;\nconst CALCULATE_PAYROLL_RUN_MUTATION = `mutation CalculateCrewPayrollRun($id: ID!) { calculateCrewPayrollRun(id: $id) { ${PAYROLL_RUN_FIELDS} } }`;\nconst APPROVE_PAYROLL_RUN_MUTATION = `mutation ApproveCrewPayrollRun($id: ID!) { approveCrewPayrollRun(id: $id) { ${PAYROLL_RUN_FIELDS} } }`;\nconst REMOVE_PAYROLL_RUN_MUTATION = `mutation RemoveCrewPayrollRun($id: ID!) { removeCrewPayrollRun(id: $id) }`;\nconst PAYSLIP_FIELDS = `id payrollRunId employeeId employeeName country currency period annualGrossSalary grossPay taxableIncome incomeTax employeeSocialContrib employerSocialContrib otherDeductions netPay incomeTaxBrackets { label rate bandFrom bandTo taxableInBand taxOnBand } employeeContributions { label rate base amount } employerContributions { label rate base amount } otherDeductionLines { label rate base amount } assumptions`;\nconst CREW_PAYSLIPS_QUERY = `query CrewPayslips($filter: CrewPayslipFilterInput) { crewPayslips(filter: $filter) { ${PAYSLIP_FIELDS} } }`;\n\n// TimeEntry\nconst CREATE_TIME_ENTRY_MUTATION = `mutation CreateCrewTimeEntry($input: CreateCrewTimeEntryInput!) { createCrewTimeEntry(input: $input) { id employeeId employeeName date clockIn clockOut totalHours overtime project status billable } }`;\nconst UPDATE_TIME_ENTRY_MUTATION = `mutation UpdateCrewTimeEntry($id: ID!, $input: UpdateCrewTimeEntryInput!) { updateCrewTimeEntry(id: $id, input: $input) { id employeeId employeeName date clockIn clockOut totalHours overtime project status billable } }`;\nconst REMOVE_TIME_ENTRY_MUTATION = `mutation RemoveCrewTimeEntry($id: ID!) { removeCrewTimeEntry(id: $id) }`;\n\n// BOFF-4195 — AI-assisted candidate/worker matching. `candidate` is the full\n// CrewCandidate record so the UI can show name/skills/experience alongside\n// the score without a second round-trip.\nconst JOB_POSTING_MATCHES_QUERY = `query CrewJobPostingMatches($jobPostingId: ID!, $filter: CrewCandidateMatchFilterInput, $limit: Int) { crewJobPostingMatches(jobPostingId: $jobPostingId, filter: $filter, limit: $limit) { candidateId jobPostingId score skillScore experienceScore matchedSkills missingSkills rationale candidate { id firstName lastName email phone position status appliedDate resumeUrl rating source experience skills currentCompany notes hiredDate } } }`;\n\n// Skills supply/demand. Not part of `crewfoundryBootstrap` (it isn't a bootstrap\n// field), so it is fetched lazily by the one surface that renders it — the\n// resourcing page — rather than on every dashboard load. Same `crewfoundry:read`\n// RBAC resource as the bootstrap query, so any caller that can load the module\n// can read it. `department` is deliberately omitted: the resolver applies it to\n// the employee side only (supply) and not to job postings (demand), which would\n// make the shortage column read as a workspace-wide demand against one\n// department's supply.\nconst SKILLS_GAP_QUERY = `query CrewSkillsGap { crewSkillsGap { skill employeesWithSkill openJobsRequiringSkill shortage } }`;\n\n// BOFF-3987 — employee documents\nconst EMPLOYEE_DOCUMENTS_QUERY = `query EmployeeDocuments($employeeId: ID!) { employeeDocuments(employeeId: $employeeId) { id employeeId fileId title category uploadedBy uploadedAt } }`;\nconst ATTACH_EMPLOYEE_DOCUMENT_MUTATION = `mutation AttachEmployeeDocument($input: AttachCrewEmployeeDocumentInput!) { attachEmployeeDocument(input: $input) { id employeeId fileId title category uploadedBy uploadedAt } }`;\nconst DETACH_EMPLOYEE_DOCUMENT_MUTATION = `mutation DetachEmployeeDocument($id: ID!) { detachEmployeeDocument(id: $id) }`;\n// Files-svc presigned upload dance (same 3-step protocol fe-libs' SaveToFilesButton\n// uses — see ~/products/dev/platform/context/files/), replicated here with the\n// store's own graphqlFetch helper instead of Apollo since this MFE has no Apollo\n// client wired in.\nconst ENSURE_FOLDER_BY_PATH_MUTATION = `mutation EnsureFolderByPath($path: String!) { ensureFolderByPath(path: $path) { id } }`;\nconst INITIATE_UPLOAD_MUTATION = `mutation InitiateUpload($input: InitiateUploadInput!) { initiateUpload(input: $input) { session { id } uploadUrl } }`;\nconst COMPLETE_UPLOAD_MUTATION = `mutation CompleteUpload($input: CompleteUploadInput!) { completeUpload(input: $input) { file { id } } }`;\n\n// BOFF-3983 — onboarding\nconst ONBOARDINGS_QUERY = `query CrewOnboardings { crewOnboardings { id candidateId employeeId fullName position department startDate status currentStage createdAt totalTaskCount completedTaskCount progressPercent tasks { id onboardingId stage title assigneeActorId assigneeName dueDate completedAt sortOrder } } }`;\nconst CREATE_ONBOARDING_MUTATION = `mutation CreateCrewOnboarding($input: CreateCrewOnboardingInput!) { createCrewOnboarding(input: $input) { id candidateId employeeId fullName position department startDate status currentStage createdAt totalTaskCount completedTaskCount progressPercent tasks { id onboardingId stage title assigneeActorId assigneeName dueDate completedAt sortOrder } } }`;\nconst UPDATE_ONBOARDING_MUTATION = `mutation UpdateCrewOnboarding($id: ID!, $input: UpdateCrewOnboardingInput!) { updateCrewOnboarding(id: $id, input: $input) { id candidateId employeeId fullName position department startDate status currentStage createdAt totalTaskCount completedTaskCount progressPercent tasks { id onboardingId stage title assigneeActorId assigneeName dueDate completedAt sortOrder } } }`;\nconst REMOVE_ONBOARDING_MUTATION = `mutation RemoveCrewOnboarding($id: ID!) { removeCrewOnboarding(id: $id) }`;\nconst CREATE_ONBOARDING_TASK_MUTATION = `mutation CreateCrewOnboardingTask($input: CreateCrewOnboardingTaskInput!) { createCrewOnboardingTask(input: $input) { id onboardingId stage title assigneeActorId assigneeName dueDate completedAt sortOrder } }`;\nconst UPDATE_ONBOARDING_TASK_MUTATION = `mutation UpdateCrewOnboardingTask($id: ID!, $input: UpdateCrewOnboardingTaskInput!) { updateCrewOnboardingTask(id: $id, input: $input) { id onboardingId stage title assigneeActorId assigneeName dueDate completedAt sortOrder } }`;\nconst REMOVE_ONBOARDING_TASK_MUTATION = `mutation RemoveCrewOnboardingTask($id: ID!) { removeCrewOnboardingTask(id: $id) }`;\nconst TOGGLE_ONBOARDING_TASK_MUTATION = `mutation ToggleCrewOnboardingTask($id: ID!, $completed: Boolean!) { toggleCrewOnboardingTask(id: $id, completed: $completed) { id onboardingId stage title assigneeActorId assigneeName dueDate completedAt sortOrder } }`;\n\nconst normalizeStatus = (value: string) => value.toUpperCase().replaceAll('-', '_');\n\nconst sharedOwnerError = (capability: string) =>\n new Error(`${capability} isn't available in CrewFoundry.`);\n\n// The backend returns enum values in UPPER_SNAKE case (e.g. \"ACTIVE\", \"FULL_TIME\", \"HIGH\"),\n// but the frontend model + every comparison/filter/badge uses lower_snake ('active', 'full_time').\n// Normalization is per-entity, NOT per-key: fields like `category`/`level` are genuine enums on\n// some entities (Announcement.category, Course.level) yet free text on others (Course.category =\n// \"Programming\", Vendor.category, LearningPath.level) — a global key list would corrupt those.\n// Each list below mirrors exactly the string-literal-union fields declared on that entity's interface.\nconst ENUM_KEYS = {\n employee: ['status', 'type'],\n department: ['status'],\n jobPosting: ['type', 'experienceLevel', 'status'],\n candidate: ['status'],\n interview: ['type', 'status'],\n course: ['level', 'status'],\n marketplaceService: ['priceType', 'status'],\n announcement: ['category', 'priority'],\n team: ['status'],\n project: ['status', 'priority'],\n payrollRecord: ['status'],\n leaveRequest: ['type', 'status'],\n timeEntry: ['status'],\n notification: ['type'],\n paymentMethod: ['type', 'status'],\n invoice: ['status'],\n vendor: ['status'],\n client: ['status'],\n onboarding: ['status', 'currentStage'],\n onboardingTask: ['stage'],\n} as const;\n\n// Lowercase only the given enum keys on an entity read back from the backend; leave every other\n// (free-text) field untouched. Idempotent — safe on already-lowercase values (some subgraphs\n// return lowercase already). This is the read-path inverse of `normalizeStatus`.\nconst lowerEnumKeys = <T>(entity: T, keys: readonly string[]): T => {\n if (!entity || typeof entity !== 'object') return entity;\n const out = { ...(entity as Record<string, unknown>) };\n for (const key of keys) {\n const value = out[key];\n if (typeof value === 'string') out[key] = value.toLowerCase();\n }\n return out as T;\n};\n\n// lowerEnumKeys only lowercases top-level string fields; CrewOnboarding nests\n// a `tasks` array whose own `stage` field also needs the same treatment, so\n// this composes lowerEnumKeys over both levels rather than duplicating it.\nconst lowerOnboarding = (onboarding: CrewOnboarding): CrewOnboarding => {\n const lowered = lowerEnumKeys(onboarding, ENUM_KEYS.onboarding);\n return {\n ...lowered,\n tasks: (onboarding.tasks ?? []).map((task) => lowerEnumKeys(task, ENUM_KEYS.onboardingTask)),\n };\n};\n\nconst toDateOnly = (value?: string | null) => (value ? value.slice(0, 10) : 'TBD');\n\nconst asRecord = (value: unknown): Record<string, unknown> | null => {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return null;\n }\n return value as Record<string, unknown>;\n};\n\nconst asNumber = (value: unknown): number | null => (typeof value === 'number' ? value : null);\n\nconst asString = (value: unknown): string | null => (typeof value === 'string' ? value : null);\n\nconst mapProjectStatus = (value: string): Project['status'] => {\n const status = value.toLowerCase();\n if (status === 'active') return 'active';\n if (status === 'completed') return 'completed';\n if (status === 'cancelled') return 'cancelled';\n if (status === 'archived') return 'on_hold';\n return 'planning';\n};\n\nconst mapGroupStatus = (value: string): Team['status'] => {\n const status = value.toLowerCase();\n if (status === 'active') return 'active';\n if (status === 'inactive' || status === 'archived' || status === 'deleted') return 'disbanded';\n return 'forming';\n};\n\ntype OverlapWorkspaceResponse = {\n projects: {\n items: Array<{\n id: string;\n name: string;\n key: string;\n creatorId: string;\n status: string;\n config?: unknown;\n archived: boolean;\n createdAt: string;\n updatedAt: string;\n }>;\n };\n groups: {\n edges: Array<{\n node: {\n id: string;\n name: string;\n description?: string | null;\n groupType?: string | null;\n status: string;\n memberCount: number;\n ownerId: string;\n createdAt: string;\n };\n }>;\n };\n conversations: {\n edges: Array<{\n node: {\n id: string;\n title?: string | null;\n description?: string | null;\n state: string;\n unreadCount: number;\n memberCount: number;\n updatedAt: string;\n };\n }>;\n };\n};\n\ntype WorkspaceBootstrapWithOverlapResponse = BootstrapPayload & OverlapWorkspaceResponse;\n\ntype OverlapBillingResponse = {\n getBillingAccountsByOrg: Array<{\n id?: string | null;\n name?: string | null;\n isDefault?: boolean | null;\n paymentMethods?: Array<{\n id: string;\n type: string;\n brand?: string | null;\n last4?: string | null;\n isDefault: boolean;\n expiryMonth?: number | null;\n expiryYear?: number | null;\n } | null> | null;\n }>;\n};\n\ntype BootstrapPayload = {\n crewfoundryBootstrap: {\n employees: Employee[];\n departments: Department[];\n jobPostings: JobPosting[];\n candidates: Candidate[];\n interviews: Interview[];\n announcements: Announcement[];\n teams: Team[];\n projects: Project[];\n leaveRequests: LeaveRequest[];\n timeEntries: TimeEntry[];\n vendors: Vendor[];\n clients: Client[];\n };\n};\n\nconst mapOverlapProjects = (\n projects: OverlapWorkspaceResponse['projects']['items'],\n teams: Team[]\n) =>\n projects.map((project, index): Project => {\n const config = asRecord(project.config);\n const budget = asNumber(config?.budget) ?? 25000 + index * 2500;\n const spent =\n asNumber(config?.spent) ?? Math.round(budget * (project.status === 'COMPLETED' ? 1 : 0.62));\n const progress = asNumber(config?.progress) ?? (project.status === 'COMPLETED' ? 100 : 58);\n const tasks = asNumber(config?.tasks) ?? 20 + (index % 9);\n const completedTasks = asNumber(config?.completedTasks) ?? Math.round((progress / 100) * tasks);\n const team = teams[index % Math.max(teams.length, 1)];\n\n return {\n id: project.id,\n name: project.name,\n description: asString(config?.description) || `${project.key} delivery plan`,\n status: mapProjectStatus(project.status),\n priority: (asString(config?.priority)?.toLowerCase() as Project['priority']) || 'medium',\n startDate: toDateOnly(project.createdAt),\n endDate: toDateOnly(project.updatedAt),\n progress,\n budget,\n spent,\n teamId: team?.id || 'unassigned',\n teamName: team?.name || 'Unassigned Team',\n managerId: project.creatorId,\n managerName: `Lead ${project.creatorId.slice(0, 8)}`,\n tasks,\n completedTasks,\n };\n });\n\nconst mapOverlapTeams = (groups: OverlapWorkspaceResponse['groups']['edges']): Team[] =>\n groups.map(({ node }) => ({\n id: node.id,\n name: node.name,\n description: node.description || `${node.name} team workspace`,\n leadId: node.ownerId,\n leadName: `Lead ${node.ownerId.slice(0, 8)}`,\n members: [],\n memberCount: node.memberCount,\n department: node.groupType?.toLowerCase().replaceAll('_', ' ') || 'operations',\n status: mapGroupStatus(node.status),\n projectCount: 0,\n createdAt: toDateOnly(node.createdAt),\n }));\n\nconst mapConversationNotifications = (\n conversations: OverlapWorkspaceResponse['conversations']['edges']\n): Notification[] =>\n conversations\n .filter(({ node }) => node.unreadCount > 0)\n .map(({ node }) => ({\n id: `conversation-${node.id}`,\n title: node.title || 'Conversation update',\n message: `${node.unreadCount} unread in ${node.memberCount}-member channel`,\n type: node.state.toLowerCase() === 'active' ? 'info' : 'warning',\n category: 'communications',\n read: false,\n createdAt: node.updatedAt,\n actionUrl: `/crewfoundry/communications/messages-page`,\n }));\n\nconst mapBillingMethods = (data: OverlapBillingResponse): ExternalPaymentMethod[] => {\n const account =\n data.getBillingAccountsByOrg.find((item) => item?.isDefault && item?.id) ||\n data.getBillingAccountsByOrg.find((item) => item?.id);\n\n if (!account?.id) {\n return [];\n }\n\n return (account.paymentMethods || [])\n .filter((item): item is NonNullable<typeof item> => Boolean(item))\n .map((method) => {\n const month = method.expiryMonth ? String(method.expiryMonth).padStart(2, '0') : undefined;\n const year = method.expiryYear ? String(method.expiryYear) : undefined;\n const isBank = method.type === 'BANK_TRANSFER' || method.type === 'NETBANKING';\n\n return {\n id: method.id,\n type: isBank ? 'bank' : 'card',\n name: `${account.name || 'Billing'} ${isBank ? 'Bank Account' : 'Card'}`,\n bank: method.brand || account.name || 'Billing Account',\n last4: method.last4 || '0000',\n status: method.isDefault ? 'primary' : 'active',\n expiry: month && year ? `${month}/${year}` : undefined,\n };\n });\n};\n\nconst parseGraphql = <T>(result: GraphqlResult<T>, fallbackMessage: string): T => {\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message || fallbackMessage);\n }\n if (!result.data) {\n throw new Error(fallbackMessage);\n }\n return result.data;\n};\n\n// Fields that live on the client-side entity objects but are NEVER accepted by\n// any Crew* Create/Update GraphQL input — either a client-generated temp `id`,\n// a server-set timestamp, or a server-derived rollup counter. Sending them makes\n// the gateway reject the whole operation with a GraphQL validation error (which\n// the public gateway masks to a generic INTERNAL_SERVER_ERROR), so no row is\n// ever written. Stripping them centrally keeps every create/update mutation valid\n// without each caller having to hand-pick input fields. (Counters like rating /\n// applicants / viewCount that a few inputs DO accept are safe to drop too — they\n// default server-side, and the UI never lets a user set a derived rollup.)\nconst MUTATION_INPUT_OMIT_KEYS = new Set<string>([\n 'id',\n 'createdAt',\n 'createdOn',\n 'updatedAt',\n 'deletedAt',\n 'employeeCount',\n 'memberCount',\n 'projectCount',\n 'enrolled',\n 'completed',\n 'completionRate',\n 'reviews',\n 'rating',\n 'tasks',\n 'completedTasks',\n 'spent',\n 'progress',\n 'applicants',\n 'viewCount',\n 'revenue',\n 'projects',\n]);\n\nconst sanitizeMutationInput = (\n variables: Record<string, unknown> | undefined\n): Record<string, unknown> | undefined => {\n if (!variables) {\n return variables;\n }\n const input = variables.input;\n if (!input || typeof input !== 'object' || Array.isArray(input)) {\n return variables;\n }\n const cleaned: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(input as Record<string, unknown>)) {\n if (!MUTATION_INPUT_OMIT_KEYS.has(key)) {\n cleaned[key] = value;\n }\n }\n return { ...variables, input: cleaned };\n};\n\nconst runGraphql = async <T>(\n query: string,\n variables: Record<string, unknown> | undefined,\n params: InitParams,\n fallbackMessage: string,\n gateway: Gateway = 'workspace'\n) => {\n const result = await graphqlFetch<T>({\n gateway,\n query,\n variables: sanitizeMutationInput(variables),\n authToken: params.authToken,\n // `apiGatewayUrl` is the workspace gateway base (dev override only). The\n // global gateway (e.g. billing overlap) resolves its own URL from fe-libs\n // config, so never force the workspace base onto a global-gateway call.\n baseUrl: gateway === 'workspace' ? params.apiGatewayUrl : undefined,\n extraHeaders: {\n ...(gateway === 'workspace' && params.workspaceId\n ? { 'x-workspace-id': params.workspaceId }\n : {}),\n },\n });\n return parseGraphql(result as GraphqlResult<T>, fallbackMessage);\n};\n\n// Overlap queries are best-effort: they enrich the crew bootstrap with real\n// wspace/global platform data but must never block the page if a field is\n// unavailable in a given environment. Swallow to null, but surface the reason\n// in dev so schema-arg drift (the historical failure mode) is visible.\n// Vite injects `import.meta.env.DEV`, but this package's tsconfig does not pull\n// in `vite/client` ambient types — narrow through a typed shape instead of `any`.\nconst isDevEnv = (): boolean => Boolean((import.meta as { env?: { DEV?: boolean } }).env?.DEV);\n\nconst runOverlap = async <T>(\n query: string,\n variables: Record<string, unknown> | undefined,\n params: InitParams,\n fallbackMessage: string,\n gateway: Gateway = 'workspace'\n): Promise<T | null> => {\n try {\n return await runGraphql<T>(query, variables, params, fallbackMessage, gateway);\n } catch (error) {\n if (isDevEnv()) {\n console.warn(`[crewfoundry] overlap query skipped: ${fallbackMessage}`, error);\n }\n return null;\n }\n};\n\n// ── Store Interface ─────────────────────────────────────────────\ninterface CrewFoundryState {\n // Overview\n metrics: OverviewMetric[];\n workItems: WorkItem[];\n selectedView: string;\n\n // Domain data\n employees: Employee[];\n departments: Department[];\n jobPostings: JobPosting[];\n candidates: Candidate[];\n interviews: Interview[];\n courses: LearningCourse[];\n learningPaths: LearningPath[];\n marketplaceServices: MarketplaceService[];\n announcements: Announcement[];\n teams: Team[];\n projects: Project[];\n payrollRecords: PayrollRecord[];\n leaveRequests: LeaveRequest[];\n timeEntries: TimeEntry[];\n notifications: Notification[];\n paymentMethods: ExternalPaymentMethod[];\n invoices: Invoice[];\n vendors: Vendor[];\n clients: Client[];\n employeeDocuments: CrewEmployeeDocument[];\n onboardings: CrewOnboarding[];\n onboardingsLoaded: boolean;\n // BOFF-4195 — AI-assisted matching. Keyed by jobPostingId so switching\n // between postings' match views doesn't require a refetch each time.\n jobPostingMatches: Record<string, CrewCandidateMatch[]>;\n jobPostingMatchesLoading: boolean;\n\n // BOFF-4193 — native payroll calculation engine. Lazy-loaded (see\n // `fetchPayrollRuns` below), same gating rationale as jobPostingMatches.\n payrollRuns: CrewPayrollRun[];\n payrollRunsLoaded: boolean;\n payrollRunsLoading: boolean;\n // Keyed by payrollRunId so switching between runs' payslip views doesn't\n // require a refetch each time.\n payslipsByRun: Record<string, CrewPayslip[]>;\n payslipsLoading: boolean;\n\n // Workspace skills supply/demand. Lazy-loaded by the resourcing page (see\n // `fetchSkillsGap`), so `skillsGapLoaded` distinguishes \"not fetched yet\"\n // from \"fetched and genuinely empty\" — the two render differently.\n skillsGap: CrewSkillsGapRow[];\n skillsGapLoaded: boolean;\n\n // Actions\n initialized: boolean;\n loading: boolean;\n error: string | null;\n initParams: InitParams;\n initializeData: (params: InitParams) => Promise<void>;\n setSelectedView: (view: string) => void;\n setWorkItems: (items: WorkItem[]) => void;\n addEmployee: (employee: Employee) => Promise<void>;\n updateEmployee: (id: string, updates: Partial<Employee>) => Promise<void>;\n removeEmployee: (id: string) => Promise<void>;\n addJobPosting: (job: JobPosting) => Promise<void>;\n updateJobPosting: (id: string, updates: Partial<JobPosting>) => Promise<void>;\n addCandidate: (candidate: Candidate) => Promise<void>;\n updateCandidate: (id: string, updates: Partial<Candidate>) => Promise<void>;\n removeJobPosting: (id: string) => Promise<void>;\n removeCandidate: (id: string) => Promise<void>;\n addAnnouncement: (announcement: Announcement) => Promise<void>;\n updateAnnouncement: (id: string, updates: Partial<Announcement>) => Promise<void>;\n removeAnnouncement: (id: string) => Promise<void>;\n addLeaveRequest: (request: LeaveRequest) => Promise<void>;\n updateLeaveRequest: (id: string, updates: Partial<LeaveRequest>) => Promise<void>;\n addInterview: (interview: Interview) => Promise<void>;\n updateInterview: (id: string, updates: Partial<Interview>) => Promise<void>;\n removeInterview: (id: string) => Promise<void>;\n addInvoice: (invoice: Invoice) => Promise<void>;\n updateInvoice: (id: string, updates: Partial<Invoice>) => Promise<void>;\n removeInvoice: (id: string) => Promise<void>;\n addVendor: (vendor: Vendor) => Promise<void>;\n updateVendor: (id: string, updates: Partial<Vendor>) => Promise<void>;\n removeVendor: (id: string) => Promise<void>;\n addClient: (client: Client) => Promise<void>;\n updateClient: (id: string, updates: Partial<Client>) => Promise<void>;\n removeClient: (id: string) => Promise<void>;\n addTeam: (team: Team) => Promise<void>;\n updateTeam: (id: string, updates: Partial<Team>) => Promise<void>;\n removeTeam: (id: string) => Promise<void>;\n addProject: (project: Project) => Promise<void>;\n updateProject: (id: string, updates: Partial<Project>) => Promise<void>;\n removeProject: (id: string) => Promise<void>;\n addMarketplaceService: (service: MarketplaceService) => Promise<void>;\n updateMarketplaceService: (id: string, updates: Partial<MarketplaceService>) => Promise<void>;\n removeMarketplaceService: (id: string) => Promise<void>;\n addDepartment: (department: Department) => Promise<void>;\n updateDepartment: (id: string, updates: Partial<Department>) => Promise<void>;\n removeDepartment: (id: string) => Promise<void>;\n addCourse: (course: LearningCourse) => Promise<void>;\n updateCourse: (id: string, updates: Partial<LearningCourse>) => Promise<void>;\n removeCourse: (id: string) => Promise<void>;\n enrollCourse: (id: string) => Promise<void>;\n addLearningPath: (path: LearningPath) => Promise<void>;\n updateLearningPath: (id: string, updates: Partial<LearningPath>) => Promise<void>;\n removeLearningPath: (id: string) => Promise<void>;\n addPayrollRecord: (record: PayrollRecord) => Promise<void>;\n updatePayrollRecord: (id: string, updates: Partial<PayrollRecord>) => Promise<void>;\n removePayrollRecord: (id: string) => Promise<void>;\n addTimeEntry: (entry: TimeEntry) => Promise<void>;\n updateTimeEntry: (id: string, updates: Partial<TimeEntry>) => Promise<void>;\n removeTimeEntry: (id: string) => Promise<void>;\n markNotificationRead: (id: string) => Promise<void>;\n markAllNotificationsRead: () => Promise<void>;\n\n // BOFF-4195 — AI-assisted candidate/worker matching\n fetchJobPostingMatches: (jobPostingId: string) => Promise<void>;\n\n // BOFF-4193 — native payroll calculation engine\n fetchPayrollRuns: () => Promise<void>;\n createPayrollRun: (input: {\n country: 'US' | 'IN' | 'GB';\n period: string;\n payDate?: string;\n notes?: string;\n employees: CreateCrewPayrollRunEmployeeInput[];\n }) => Promise<void>;\n calculatePayrollRun: (id: string) => Promise<void>;\n approvePayrollRun: (id: string) => Promise<void>;\n removePayrollRun: (id: string) => Promise<void>;\n fetchPayslipsForRun: (payrollRunId: string) => Promise<void>;\n\n // BOFF-3987 — employee documents\n fetchEmployeeDocuments: (employeeId: string) => Promise<void>;\n uploadEmployeeDocument: (params: {\n employeeId: string;\n file: File;\n title: string;\n category?: string;\n }) => Promise<void>;\n detachEmployeeDocument: (id: string) => Promise<void>;\n\n // BOFF-3983 — onboarding\n fetchOnboardings: () => Promise<void>;\n createOnboarding: (input: {\n candidateId?: string;\n employeeId?: string;\n fullName: string;\n position: string;\n department?: string;\n startDate?: string;\n }) => Promise<void>;\n updateOnboarding: (id: string, updates: Partial<CrewOnboarding>) => Promise<void>;\n removeOnboarding: (id: string) => Promise<void>;\n createOnboardingTask: (input: {\n onboardingId: string;\n stage: OnboardingStage;\n title: string;\n assigneeActorId?: string;\n assigneeName?: string;\n dueDate?: string;\n sortOrder?: number;\n }) => Promise<void>;\n updateOnboardingTask: (id: string, updates: Partial<CrewOnboardingTask>) => Promise<void>;\n removeOnboardingTask: (id: string) => Promise<void>;\n toggleOnboardingTask: (id: string, completed: boolean) => Promise<void>;\n\n fetchSkillsGap: () => Promise<void>;\n}\n\nexport const useCrewFoundryStore = create<CrewFoundryState>((set) => ({\n // Overview\n metrics: [\n { label: 'Total Employees', value: '247', trend: '+5.2%' },\n { label: 'Open Positions', value: '24', trend: '+3 this week' },\n { label: 'Hiring Pipeline', value: '347', trend: '+52 applications' },\n { label: 'Employee Satisfaction', value: '4.6/5', trend: '+0.2' },\n ],\n workItems: [\n {\n id: 'w1',\n title: 'Q1 Performance Review Cycle',\n status: 'active',\n owner: 'HR',\n updatedAt: '2h ago',\n },\n {\n id: 'w2',\n title: 'New Health Benefits Enrollment',\n status: 'active',\n owner: 'Benefits',\n updatedAt: '5h ago',\n },\n {\n id: 'w3',\n title: 'Engineering Headcount Planning',\n status: 'at_risk',\n owner: 'Recruiting',\n updatedAt: '1d ago',\n },\n {\n id: 'w4',\n title: 'Employee Onboarding Automation',\n status: 'completed',\n owner: 'HR Ops',\n updatedAt: '2d ago',\n },\n ],\n selectedView: 'overview',\n initialized: false,\n loading: false,\n error: null,\n initParams: {},\n\n // Domain data\n employees: [],\n departments: [],\n jobPostings: [],\n candidates: [],\n interviews: [],\n courses: [],\n learningPaths: [],\n marketplaceServices: [],\n announcements: [],\n teams: [],\n projects: [],\n payrollRecords: [],\n leaveRequests: [],\n timeEntries: [],\n notifications: [],\n paymentMethods: [],\n invoices: [],\n vendors: [],\n clients: [],\n employeeDocuments: [],\n onboardings: [],\n onboardingsLoaded: false,\n jobPostingMatches: {},\n jobPostingMatchesLoading: false,\n payrollRuns: [],\n payrollRunsLoaded: false,\n payrollRunsLoading: false,\n payslipsByRun: {},\n payslipsLoading: false,\n skillsGap: [],\n skillsGapLoaded: false,\n\n // Actions\n initializeData: async (params) => {\n if (!params.workspaceId) {\n return;\n }\n set({ loading: true, error: null, initParams: params });\n try {\n const [workspaceWithOverlapData, billingOverlap, payrollData] = await Promise.all([\n runOverlap<WorkspaceBootstrapWithOverlapResponse>(\n WORKSPACE_BOOTSTRAP_WITH_OVERLAP_QUERY,\n {\n workspaceId: params.workspaceId,\n projectPagination: { limit: 50, offset: 0 },\n groupFirst: 30,\n conversationFirst: 30,\n },\n params,\n 'Unable to load workspace bootstrap with overlap data'\n ),\n // getBillingAccountsByOrg lives on the GLOBAL supergraph, not wspace —\n // route it to the global gateway (org context comes from the x-org-id\n // header the gateway auto-attaches for global-gateway calls).\n runOverlap<OverlapBillingResponse>(\n OVERLAP_BILLING_QUERY,\n undefined,\n params,\n 'Unable to load overlap billing data',\n 'global'\n ),\n // Payroll (BOFF-4887): fetched via runOverlap (best-effort), not\n // runGraphql, because crewPayrollRecords is gated by the dedicated\n // crewfoundry-payroll RBAC resource — a caller without that grant\n // (e.g. Workforce Coordinator) gets a FORBIDDEN response here, which\n // must degrade to an empty payroll list, not fail the whole\n // dashboard load.\n runOverlap<CrewPayrollRecordsResponse>(\n CREW_PAYROLL_RECORDS_QUERY,\n undefined,\n params,\n 'Unable to load payroll records'\n ),\n ]);\n\n const bootstrap = workspaceWithOverlapData\n ? workspaceWithOverlapData.crewfoundryBootstrap\n : (\n await runGraphql<BootstrapPayload>(\n BOOTSTRAP_QUERY,\n undefined,\n params,\n 'Unable to load CrewFoundry data'\n )\n ).crewfoundryBootstrap;\n\n const overlapTeams = workspaceWithOverlapData\n ? mapOverlapTeams(workspaceWithOverlapData.groups.edges)\n : [];\n const overlapProjects = workspaceWithOverlapData\n ? mapOverlapProjects(workspaceWithOverlapData.projects.items, overlapTeams)\n : [];\n const overlapNotifications = workspaceWithOverlapData\n ? mapConversationNotifications(workspaceWithOverlapData.conversations.edges)\n : [];\n\n const teams = overlapTeams.length > 0 ? overlapTeams : bootstrap.teams;\n const projects = overlapProjects.length > 0 ? overlapProjects : bootstrap.projects;\n const notifications = overlapNotifications;\n const paymentMethods = billingOverlap ? mapBillingMethods(billingOverlap) : [];\n\n // Normalize enum casing (UPPER_SNAKE from the backend -> lower_snake) once, per entity, so\n // every downstream count/filter/badge and the metrics below all operate on the same casing.\n const nEmployees = bootstrap.employees.map((e) => lowerEnumKeys(e, ENUM_KEYS.employee));\n const nDepartments = bootstrap.departments.map((d) => lowerEnumKeys(d, ENUM_KEYS.department));\n const nJobPostings = bootstrap.jobPostings.map((j) => lowerEnumKeys(j, ENUM_KEYS.jobPosting));\n const nCandidates = bootstrap.candidates.map((c) => lowerEnumKeys(c, ENUM_KEYS.candidate));\n const nInterviews = bootstrap.interviews.map((i) => lowerEnumKeys(i, ENUM_KEYS.interview));\n const nCourses: LearningCourse[] = [];\n const nMarketplaceServices: MarketplaceService[] = [];\n const nAnnouncements = bootstrap.announcements.map((a) =>\n lowerEnumKeys(a, ENUM_KEYS.announcement)\n );\n const nTeams = teams.map((t) => lowerEnumKeys(t, ENUM_KEYS.team));\n const nProjects = projects.map((p) => lowerEnumKeys(p, ENUM_KEYS.project));\n // Payroll (BOFF-4887): sourced from its own dedicated, crewfoundry-payroll\n // gated query, not the bootstrap response — see CREW_PAYROLL_RECORDS_QUERY\n // above. `payrollData` is null when the query fails or the caller lacks\n // payroll access; either way that degrades to an empty list.\n const nPayrollRecords = (payrollData?.crewPayrollRecords ?? []).map((r) =>\n lowerEnumKeys(r, ENUM_KEYS.payrollRecord)\n );\n const nLeaveRequests = bootstrap.leaveRequests.map((r) =>\n lowerEnumKeys(r, ENUM_KEYS.leaveRequest)\n );\n const nTimeEntries = bootstrap.timeEntries.map((t) => lowerEnumKeys(t, ENUM_KEYS.timeEntry));\n const nNotifications = notifications.map((n) => lowerEnumKeys(n, ENUM_KEYS.notification));\n const nPaymentMethods = paymentMethods.map((m) => lowerEnumKeys(m, ENUM_KEYS.paymentMethod));\n const nInvoices: Invoice[] = [];\n const nVendors = bootstrap.vendors.map((v) => lowerEnumKeys(v, ENUM_KEYS.vendor));\n const nClients = bootstrap.clients.map((c) => lowerEnumKeys(c, ENUM_KEYS.client));\n\n set({\n employees: nEmployees,\n departments: nDepartments,\n jobPostings: nJobPostings,\n candidates: nCandidates,\n interviews: nInterviews,\n courses: nCourses,\n learningPaths: [],\n marketplaceServices: nMarketplaceServices,\n announcements: nAnnouncements,\n teams: nTeams,\n projects: nProjects,\n payrollRecords: nPayrollRecords,\n leaveRequests: nLeaveRequests,\n timeEntries: nTimeEntries,\n notifications: nNotifications,\n paymentMethods: nPaymentMethods,\n invoices: nInvoices,\n vendors: nVendors,\n clients: nClients,\n metrics: [\n { label: 'Total Employees', value: String(nEmployees.length), trend: 'Live' },\n {\n label: 'Open Positions',\n value: String(nJobPostings.filter((j) => j.status === 'active').length),\n trend: 'Live',\n },\n { label: 'Hiring Pipeline', value: String(nCandidates.length), trend: 'Live' },\n {\n label: 'Cross-Team Projects',\n value: String(nProjects.filter((project) => project.status === 'active').length),\n trend: 'Live',\n },\n ],\n initialized: true,\n loading: false,\n });\n } catch (error) {\n set({\n error: error instanceof Error ? error.message : 'Unable to load CrewFoundry data',\n loading: false,\n });\n }\n },\n setSelectedView: (view) => set({ selectedView: view }),\n setWorkItems: (items) => set({ workItems: items }),\n addEmployee: async (employee) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewEmployee: Employee }>(\n CREATE_EMPLOYEE_MUTATION,\n {\n input: {\n ...employee,\n status: normalizeStatus(employee.status),\n type: normalizeStatus(employee.type),\n },\n },\n params,\n 'Unable to create employee'\n );\n set((state) => ({\n employees: [lowerEnumKeys(data.createCrewEmployee, ENUM_KEYS.employee), ...state.employees],\n }));\n },\n updateEmployee: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const payload = {\n ...updates,\n ...(updates.status ? { status: normalizeStatus(updates.status) } : {}),\n ...(updates.type ? { type: normalizeStatus(updates.type) } : {}),\n };\n const data = await runGraphql<{ updateCrewEmployee: Employee }>(\n UPDATE_EMPLOYEE_MUTATION,\n { id, input: payload },\n params,\n 'Unable to update employee'\n );\n set((state) => ({\n employees: state.employees.map((e) =>\n e.id === id ? lowerEnumKeys(data.updateCrewEmployee, ENUM_KEYS.employee) : e\n ),\n }));\n },\n removeEmployee: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewEmployee: boolean }>(\n REMOVE_EMPLOYEE_MUTATION,\n { id },\n params,\n 'Unable to remove employee'\n );\n set((state) => ({ employees: state.employees.filter((e) => e.id !== id) }));\n },\n addJobPosting: async (job) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewJobPosting: JobPosting }>(\n CREATE_JOB_MUTATION,\n {\n input: {\n ...job,\n status: normalizeStatus(job.status),\n },\n },\n params,\n 'Unable to create job posting'\n );\n set((state) => ({\n jobPostings: [\n lowerEnumKeys(data.createCrewJobPosting, ENUM_KEYS.jobPosting),\n ...state.jobPostings,\n ],\n }));\n },\n updateJobPosting: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewJobPosting: JobPosting }>(\n UPDATE_JOB_MUTATION,\n {\n id,\n input: {\n ...updates,\n ...(updates.status ? { status: normalizeStatus(updates.status) } : {}),\n },\n },\n params,\n 'Unable to update job posting'\n );\n set((state) => ({\n jobPostings: state.jobPostings.map((j) =>\n j.id === id ? lowerEnumKeys(data.updateCrewJobPosting, ENUM_KEYS.jobPosting) : j\n ),\n }));\n },\n addCandidate: async (candidate) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewCandidate: Candidate }>(\n CREATE_CANDIDATE_MUTATION,\n { input: { ...candidate, status: normalizeStatus(candidate.status) } },\n params,\n 'Unable to create candidate'\n );\n set((state) => ({\n candidates: [\n lowerEnumKeys(data.createCrewCandidate, ENUM_KEYS.candidate),\n ...state.candidates,\n ],\n }));\n },\n updateCandidate: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewCandidate: Candidate }>(\n UPDATE_CANDIDATE_MUTATION,\n {\n id,\n input: {\n ...updates,\n ...(updates.status ? { status: normalizeStatus(updates.status) } : {}),\n },\n },\n params,\n 'Unable to update candidate'\n );\n set((state) => ({\n candidates: state.candidates.map((c) =>\n c.id === id ? lowerEnumKeys(data.updateCrewCandidate, ENUM_KEYS.candidate) : c\n ),\n }));\n },\n addAnnouncement: async (announcement) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewAnnouncement: Announcement }>(\n CREATE_ANNOUNCEMENT_MUTATION,\n { input: announcement },\n params,\n 'Unable to create announcement'\n );\n set((state) => ({\n announcements: [\n lowerEnumKeys(data.createCrewAnnouncement, ENUM_KEYS.announcement),\n ...state.announcements,\n ],\n }));\n },\n addLeaveRequest: async (request) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewLeaveRequest: LeaveRequest }>(\n CREATE_LEAVE_MUTATION,\n {\n input: {\n ...request,\n type: normalizeStatus(request.type),\n status: normalizeStatus(request.status),\n },\n },\n params,\n 'Unable to create leave request'\n );\n set((state) => ({\n leaveRequests: [\n lowerEnumKeys(data.createCrewLeaveRequest, ENUM_KEYS.leaveRequest),\n ...state.leaveRequests,\n ],\n }));\n },\n updateLeaveRequest: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewLeaveRequest: LeaveRequest }>(\n UPDATE_LEAVE_MUTATION,\n {\n id,\n input: {\n ...updates,\n ...(updates.type ? { type: normalizeStatus(updates.type) } : {}),\n ...(updates.status ? { status: normalizeStatus(updates.status) } : {}),\n },\n },\n params,\n 'Unable to update leave request'\n );\n set((state) => ({\n leaveRequests: state.leaveRequests.map((l) =>\n l.id === id ? lowerEnumKeys(data.updateCrewLeaveRequest, ENUM_KEYS.leaveRequest) : l\n ),\n }));\n },\n markNotificationRead: async (id) => {\n set((state) => ({\n notifications: state.notifications.map((n) => (n.id === id ? { ...n, read: true } : n)),\n }));\n },\n markAllNotificationsRead: async () => {\n set((state) => ({ notifications: state.notifications.map((n) => ({ ...n, read: true })) }));\n },\n\n // ── Extra removes for existing entities ───────────────────────\n removeJobPosting: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewJobPosting: boolean }>(\n REMOVE_JOB_MUTATION,\n { id },\n params,\n 'Unable to remove job posting'\n );\n set((state) => ({ jobPostings: state.jobPostings.filter((j) => j.id !== id) }));\n },\n removeCandidate: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewCandidate: boolean }>(\n REMOVE_CANDIDATE_MUTATION,\n { id },\n params,\n 'Unable to remove candidate'\n );\n set((state) => ({ candidates: state.candidates.filter((c) => c.id !== id) }));\n },\n updateAnnouncement: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewAnnouncement: Announcement }>(\n UPDATE_ANNOUNCEMENT_MUTATION,\n { id, input: updates },\n params,\n 'Unable to update announcement'\n );\n set((state) => ({\n announcements: state.announcements.map((a) =>\n a.id === id ? lowerEnumKeys(data.updateCrewAnnouncement, ENUM_KEYS.announcement) : a\n ),\n }));\n },\n removeAnnouncement: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewAnnouncement: boolean }>(\n REMOVE_ANNOUNCEMENT_MUTATION,\n { id },\n params,\n 'Unable to remove announcement'\n );\n set((state) => ({ announcements: state.announcements.filter((a) => a.id !== id) }));\n },\n\n // ── Interview (enum: type, status) ────────────────────────────\n addInterview: async (interview) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewInterview: Interview }>(\n CREATE_INTERVIEW_MUTATION,\n {\n input: {\n ...interview,\n type: normalizeStatus(interview.type),\n status: normalizeStatus(interview.status),\n },\n },\n params,\n 'Unable to schedule interview'\n );\n set((state) => ({\n interviews: [\n lowerEnumKeys(data.createCrewInterview, ENUM_KEYS.interview),\n ...state.interviews,\n ],\n }));\n },\n updateInterview: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewInterview: Interview }>(\n UPDATE_INTERVIEW_MUTATION,\n {\n id,\n input: {\n ...updates,\n ...(updates.type ? { type: normalizeStatus(updates.type) } : {}),\n ...(updates.status ? { status: normalizeStatus(updates.status) } : {}),\n },\n },\n params,\n 'Unable to update interview'\n );\n set((state) => ({\n interviews: state.interviews.map((i) =>\n i.id === id ? lowerEnumKeys(data.updateCrewInterview, ENUM_KEYS.interview) : i\n ),\n }));\n },\n removeInterview: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewInterview: boolean }>(\n REMOVE_INTERVIEW_MUTATION,\n { id },\n params,\n 'Unable to remove interview'\n );\n set((state) => ({ interviews: state.interviews.filter((i) => i.id !== id) }));\n },\n\n // ── Invoice data is owned by global-billing-svc ───────────────\n addInvoice: async () => {\n throw sharedOwnerError('Invoices');\n },\n updateInvoice: async () => {\n throw sharedOwnerError('Invoices');\n },\n removeInvoice: async () => {\n throw sharedOwnerError('Invoices');\n },\n\n // ── Vendor (enum: status) ─────────────────────────────────────\n addVendor: async (vendor) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewVendor: Vendor }>(\n CREATE_VENDOR_MUTATION,\n { input: { ...vendor, status: normalizeStatus(vendor.status) } },\n params,\n 'Unable to create vendor'\n );\n set((state) => ({\n vendors: [lowerEnumKeys(data.createCrewVendor, ENUM_KEYS.vendor), ...state.vendors],\n }));\n },\n updateVendor: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewVendor: Vendor }>(\n UPDATE_VENDOR_MUTATION,\n {\n id,\n input: {\n ...updates,\n ...(updates.status ? { status: normalizeStatus(updates.status) } : {}),\n },\n },\n params,\n 'Unable to update vendor'\n );\n set((state) => ({\n vendors: state.vendors.map((v) =>\n v.id === id ? lowerEnumKeys(data.updateCrewVendor, ENUM_KEYS.vendor) : v\n ),\n }));\n },\n removeVendor: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewVendor: boolean }>(\n REMOVE_VENDOR_MUTATION,\n { id },\n params,\n 'Unable to remove vendor'\n );\n set((state) => ({ vendors: state.vendors.filter((v) => v.id !== id) }));\n },\n\n // ── Client (enum: status; `since`) ────────────────────────────\n addClient: async (client) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewClient: Client }>(\n CREATE_CLIENT_MUTATION,\n { input: { ...client, status: normalizeStatus(client.status) } },\n params,\n 'Unable to create client'\n );\n set((state) => ({\n clients: [lowerEnumKeys(data.createCrewClient, ENUM_KEYS.client), ...state.clients],\n }));\n },\n updateClient: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewClient: Client }>(\n UPDATE_CLIENT_MUTATION,\n {\n id,\n input: {\n ...updates,\n ...(updates.status ? { status: normalizeStatus(updates.status) } : {}),\n },\n },\n params,\n 'Unable to update client'\n );\n set((state) => ({\n clients: state.clients.map((c) =>\n c.id === id ? lowerEnumKeys(data.updateCrewClient, ENUM_KEYS.client) : c\n ),\n }));\n },\n removeClient: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewClient: boolean }>(\n REMOVE_CLIENT_MUTATION,\n { id },\n params,\n 'Unable to remove client'\n );\n set((state) => ({ clients: state.clients.filter((c) => c.id !== id) }));\n },\n\n // ── Team (plain status; not normalized) ───────────────────────\n addTeam: async (team) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewTeam: Team }>(\n CREATE_TEAM_MUTATION,\n { input: team },\n params,\n 'Unable to create team'\n );\n set((state) => ({\n teams: [lowerEnumKeys(data.createCrewTeam, ENUM_KEYS.team), ...state.teams],\n }));\n },\n updateTeam: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewTeam: Team }>(\n UPDATE_TEAM_MUTATION,\n { id, input: updates },\n params,\n 'Unable to update team'\n );\n set((state) => ({\n teams: state.teams.map((t) =>\n t.id === id ? lowerEnumKeys(data.updateCrewTeam, ENUM_KEYS.team) : t\n ),\n }));\n },\n removeTeam: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewTeam: boolean }>(\n REMOVE_TEAM_MUTATION,\n { id },\n params,\n 'Unable to remove team'\n );\n set((state) => ({ teams: state.teams.filter((t) => t.id !== id) }));\n },\n\n // ── Project (enum: status, priority) ──────────────────────────\n addProject: async (project) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewProject: Project }>(\n CREATE_PROJECT_MUTATION,\n {\n input: {\n ...project,\n status: normalizeStatus(project.status),\n priority: normalizeStatus(project.priority),\n },\n },\n params,\n 'Unable to create project'\n );\n set((state) => ({\n projects: [lowerEnumKeys(data.createCrewProject, ENUM_KEYS.project), ...state.projects],\n }));\n },\n updateProject: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewProject: Project }>(\n UPDATE_PROJECT_MUTATION,\n {\n id,\n input: {\n ...updates,\n ...(updates.status ? { status: normalizeStatus(updates.status) } : {}),\n ...(updates.priority ? { priority: normalizeStatus(updates.priority) } : {}),\n },\n },\n params,\n 'Unable to update project'\n );\n set((state) => ({\n projects: state.projects.map((p) =>\n p.id === id ? lowerEnumKeys(data.updateCrewProject, ENUM_KEYS.project) : p\n ),\n }));\n },\n removeProject: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewProject: boolean }>(\n REMOVE_PROJECT_MUTATION,\n { id },\n params,\n 'Unable to remove project'\n );\n set((state) => ({ projects: state.projects.filter((p) => p.id !== id) }));\n },\n\n // ── Marketplace data is owned by global-store-svc ─────────────\n addMarketplaceService: async () => {\n throw sharedOwnerError('Marketplace services');\n },\n updateMarketplaceService: async () => {\n throw sharedOwnerError('Marketplace services');\n },\n removeMarketplaceService: async () => {\n throw sharedOwnerError('Marketplace services');\n },\n\n // ── Department (plain status; not normalized) ─────────────────\n addDepartment: async (department) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewDepartment: Department }>(\n CREATE_DEPARTMENT_MUTATION,\n { input: department },\n params,\n 'Unable to create department'\n );\n set((state) => ({\n departments: [\n lowerEnumKeys(data.createCrewDepartment, ENUM_KEYS.department),\n ...state.departments,\n ],\n }));\n },\n updateDepartment: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewDepartment: Department }>(\n UPDATE_DEPARTMENT_MUTATION,\n { id, input: updates },\n params,\n 'Unable to update department'\n );\n set((state) => ({\n departments: state.departments.map((d) =>\n d.id === id ? lowerEnumKeys(data.updateCrewDepartment, ENUM_KEYS.department) : d\n ),\n }));\n },\n removeDepartment: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewDepartment: boolean }>(\n REMOVE_DEPARTMENT_MUTATION,\n { id },\n params,\n 'Unable to remove department'\n );\n set((state) => ({ departments: state.departments.filter((d) => d.id !== id) }));\n },\n\n // ── Learning data is owned by the shared learning service ─────\n addCourse: async () => {\n throw sharedOwnerError('Courses');\n },\n updateCourse: async () => {\n throw sharedOwnerError('Courses');\n },\n removeCourse: async () => {\n throw sharedOwnerError('Courses');\n },\n enrollCourse: async () => {\n throw sharedOwnerError('Course enrollment');\n },\n addLearningPath: async () => {\n throw sharedOwnerError('Learning paths');\n },\n updateLearningPath: async () => {\n throw sharedOwnerError('Learning paths');\n },\n removeLearningPath: async () => {\n throw sharedOwnerError('Learning paths');\n },\n\n // ── PayrollRecord (enum: status) ──────────────────────────────\n addPayrollRecord: async (record) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewPayrollRecord: PayrollRecord }>(\n CREATE_PAYROLL_MUTATION,\n { input: { ...record, status: normalizeStatus(record.status) } },\n params,\n 'Unable to create payroll record'\n );\n set((state) => ({\n payrollRecords: [\n lowerEnumKeys(data.createCrewPayrollRecord, ENUM_KEYS.payrollRecord),\n ...state.payrollRecords,\n ],\n }));\n },\n updatePayrollRecord: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewPayrollRecord: PayrollRecord }>(\n UPDATE_PAYROLL_MUTATION,\n {\n id,\n input: {\n ...updates,\n ...(updates.status ? { status: normalizeStatus(updates.status) } : {}),\n },\n },\n params,\n 'Unable to update payroll record'\n );\n set((state) => ({\n payrollRecords: state.payrollRecords.map((p) =>\n p.id === id ? lowerEnumKeys(data.updateCrewPayrollRecord, ENUM_KEYS.payrollRecord) : p\n ),\n }));\n },\n removePayrollRecord: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewPayrollRecord: boolean }>(\n REMOVE_PAYROLL_MUTATION,\n { id },\n params,\n 'Unable to remove payroll record'\n );\n set((state) => ({ payrollRecords: state.payrollRecords.filter((p) => p.id !== id) }));\n },\n\n // ── TimeEntry (plain status; not normalized) ──────────────────\n addTimeEntry: async (entry) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewTimeEntry: TimeEntry }>(\n CREATE_TIME_ENTRY_MUTATION,\n { input: entry },\n params,\n 'Unable to create time entry'\n );\n set((state) => ({\n timeEntries: [\n lowerEnumKeys(data.createCrewTimeEntry, ENUM_KEYS.timeEntry),\n ...state.timeEntries,\n ],\n }));\n },\n updateTimeEntry: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewTimeEntry: TimeEntry }>(\n UPDATE_TIME_ENTRY_MUTATION,\n { id, input: updates },\n params,\n 'Unable to update time entry'\n );\n set((state) => ({\n timeEntries: state.timeEntries.map((t) =>\n t.id === id ? lowerEnumKeys(data.updateCrewTimeEntry, ENUM_KEYS.timeEntry) : t\n ),\n }));\n },\n removeTimeEntry: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewTimeEntry: boolean }>(\n REMOVE_TIME_ENTRY_MUTATION,\n { id },\n params,\n 'Unable to remove time entry'\n );\n set((state) => ({ timeEntries: state.timeEntries.filter((t) => t.id !== id) }));\n },\n\n // ── BOFF-4195: AI-assisted candidate/worker matching ────────────\n fetchJobPostingMatches: async (jobPostingId) => {\n const params = useCrewFoundryStore.getState().initParams;\n set({ jobPostingMatchesLoading: true });\n try {\n const data = await runGraphql<{ crewJobPostingMatches: CrewCandidateMatch[] }>(\n JOB_POSTING_MATCHES_QUERY,\n { jobPostingId },\n params,\n 'Unable to load candidate matches'\n );\n const normalized = data.crewJobPostingMatches.map((match) => ({\n ...match,\n candidate: lowerEnumKeys(match.candidate, ENUM_KEYS.candidate),\n }));\n set((state) => ({\n jobPostingMatches: { ...state.jobPostingMatches, [jobPostingId]: normalized },\n jobPostingMatchesLoading: false,\n }));\n } catch (err) {\n set({ jobPostingMatchesLoading: false });\n throw err;\n }\n },\n\n // ── BOFF-4193: native payroll calculation engine ────────────────\n fetchPayrollRuns: async () => {\n const params = useCrewFoundryStore.getState().initParams;\n set({ payrollRunsLoading: true });\n try {\n const data = await runGraphql<{ crewPayrollRuns: CrewPayrollRun[] }>(\n CREW_PAYROLL_RUNS_QUERY,\n undefined,\n params,\n 'Unable to load payroll runs'\n );\n set({\n payrollRuns: data.crewPayrollRuns,\n payrollRunsLoaded: true,\n payrollRunsLoading: false,\n });\n } catch (err) {\n set({ payrollRunsLoading: false });\n throw err;\n }\n },\n createPayrollRun: async (input) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewPayrollRun: CrewPayrollRun }>(\n CREATE_PAYROLL_RUN_MUTATION,\n { input },\n params,\n 'Unable to create payroll run'\n );\n set((state) => ({ payrollRuns: [data.createCrewPayrollRun, ...state.payrollRuns] }));\n },\n calculatePayrollRun: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ calculateCrewPayrollRun: CrewPayrollRun }>(\n CALCULATE_PAYROLL_RUN_MUTATION,\n { id },\n params,\n 'Unable to calculate payroll run'\n );\n set((state) => ({\n payrollRuns: state.payrollRuns.map((r) => (r.id === id ? data.calculateCrewPayrollRun : r)),\n // Recalculating replaces the run's payslips server-side — drop any\n // previously cached payslip view for this run so the next open\n // refetches the fresh set instead of showing stale figures.\n payslipsByRun: Object.fromEntries(\n Object.entries(state.payslipsByRun).filter(([runId]) => runId !== id)\n ),\n }));\n },\n approvePayrollRun: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ approveCrewPayrollRun: CrewPayrollRun }>(\n APPROVE_PAYROLL_RUN_MUTATION,\n { id },\n params,\n 'Unable to approve payroll run'\n );\n set((state) => ({\n payrollRuns: state.payrollRuns.map((r) => (r.id === id ? data.approveCrewPayrollRun : r)),\n }));\n },\n removePayrollRun: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewPayrollRun: boolean }>(\n REMOVE_PAYROLL_RUN_MUTATION,\n { id },\n params,\n 'Unable to remove payroll run'\n );\n set((state) => ({\n payrollRuns: state.payrollRuns.filter((r) => r.id !== id),\n payslipsByRun: Object.fromEntries(\n Object.entries(state.payslipsByRun).filter(([runId]) => runId !== id)\n ),\n }));\n },\n fetchPayslipsForRun: async (payrollRunId) => {\n const params = useCrewFoundryStore.getState().initParams;\n set({ payslipsLoading: true });\n try {\n const data = await runGraphql<{ crewPayslips: CrewPayslip[] }>(\n CREW_PAYSLIPS_QUERY,\n { filter: { payrollRunId } },\n params,\n 'Unable to load payslips'\n );\n set((state) => ({\n payslipsByRun: { ...state.payslipsByRun, [payrollRunId]: data.crewPayslips },\n payslipsLoading: false,\n }));\n } catch (err) {\n set({ payslipsLoading: false });\n throw err;\n }\n },\n\n // ── BOFF-3987: employee documents ──────────────────────────────\n fetchEmployeeDocuments: async (employeeId) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ employeeDocuments: CrewEmployeeDocument[] }>(\n EMPLOYEE_DOCUMENTS_QUERY,\n { employeeId },\n params,\n 'Unable to load employee documents'\n );\n set((state) => ({\n // Replace only this employee's documents; leave any other employee's\n // already-fetched documents in the flat cache untouched.\n employeeDocuments: [\n ...state.employeeDocuments.filter((d) => d.employeeId !== employeeId),\n ...data.employeeDocuments,\n ],\n }));\n },\n uploadEmployeeDocument: async ({ employeeId, file, title, category }) => {\n const params = useCrewFoundryStore.getState().initParams;\n\n const folder = await runGraphql<{ ensureFolderByPath: { id: string } }>(\n ENSURE_FOLDER_BY_PATH_MUTATION,\n { path: '/CrewFoundry/Employee Documents' },\n params,\n 'Unable to resolve the employee documents folder'\n );\n\n const extension = file.name.includes('.')\n ? file.name.substring(file.name.lastIndexOf('.') + 1)\n : '';\n const initiated = await runGraphql<{\n initiateUpload: { session: { id: string }; uploadUrl: string | null };\n }>(\n INITIATE_UPLOAD_MUTATION,\n {\n input: {\n fileName: file.name,\n fileSize: String(file.size),\n mimeType: file.type || 'application/octet-stream',\n extension,\n folderId: folder.ensureFolderByPath.id,\n },\n },\n params,\n 'Unable to start the document upload'\n );\n\n if (initiated.initiateUpload.uploadUrl) {\n const putResponse = await fetch(initiated.initiateUpload.uploadUrl, {\n method: 'PUT',\n headers: { 'Content-Type': file.type || 'application/octet-stream' },\n body: file,\n });\n if (!putResponse.ok) {\n throw new Error(`Failed to upload file bytes (status ${putResponse.status})`);\n }\n }\n\n const completed = await runGraphql<{ completeUpload: { file: { id: string } } }>(\n COMPLETE_UPLOAD_MUTATION,\n {\n input: {\n sessionId: initiated.initiateUpload.session.id,\n fileSize: String(file.size),\n folderId: folder.ensureFolderByPath.id,\n },\n },\n params,\n 'Unable to finish the document upload'\n );\n\n const attached = await runGraphql<{ attachEmployeeDocument: CrewEmployeeDocument }>(\n ATTACH_EMPLOYEE_DOCUMENT_MUTATION,\n {\n input: {\n employeeId,\n fileId: completed.completeUpload.file.id,\n title,\n category: category || undefined,\n },\n },\n params,\n 'Unable to attach the document to this employee'\n );\n\n set((state) => ({\n employeeDocuments: [attached.attachEmployeeDocument, ...state.employeeDocuments],\n }));\n },\n detachEmployeeDocument: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ detachEmployeeDocument: boolean }>(\n DETACH_EMPLOYEE_DOCUMENT_MUTATION,\n { id },\n params,\n 'Unable to remove this document'\n );\n set((state) => ({\n employeeDocuments: state.employeeDocuments.filter((d) => d.id !== id),\n }));\n },\n\n // ── BOFF-3983: onboarding ───────────────────────────────────────\n fetchOnboardings: async () => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ crewOnboardings: CrewOnboarding[] }>(\n ONBOARDINGS_QUERY,\n {},\n params,\n 'Unable to load onboarding'\n );\n set({\n onboardings: data.crewOnboardings.map(lowerOnboarding),\n onboardingsLoaded: true,\n });\n },\n createOnboarding: async (input) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewOnboarding: CrewOnboarding }>(\n CREATE_ONBOARDING_MUTATION,\n { input },\n params,\n 'Unable to start onboarding'\n );\n set((state) => ({\n onboardings: [lowerOnboarding(data.createCrewOnboarding), ...state.onboardings],\n }));\n },\n updateOnboarding: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewOnboarding: CrewOnboarding }>(\n UPDATE_ONBOARDING_MUTATION,\n { id, input: updates },\n params,\n 'Unable to update onboarding'\n );\n set((state) => ({\n onboardings: state.onboardings.map((o) =>\n o.id === id ? lowerOnboarding(data.updateCrewOnboarding) : o\n ),\n }));\n },\n removeOnboarding: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewOnboarding: boolean }>(\n REMOVE_ONBOARDING_MUTATION,\n { id },\n params,\n 'Unable to remove onboarding'\n );\n set((state) => ({ onboardings: state.onboardings.filter((o) => o.id !== id) }));\n },\n createOnboardingTask: async (input) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewOnboardingTask: CrewOnboardingTask }>(\n CREATE_ONBOARDING_TASK_MUTATION,\n { input },\n params,\n 'Unable to create onboarding task'\n );\n const task = lowerEnumKeys(data.createCrewOnboardingTask, ENUM_KEYS.onboardingTask);\n set((state) => ({\n onboardings: state.onboardings.map((o) =>\n o.id === task.onboardingId\n ? {\n ...o,\n tasks: [...o.tasks, task],\n totalTaskCount: o.totalTaskCount + 1,\n }\n : o\n ),\n }));\n },\n updateOnboardingTask: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewOnboardingTask: CrewOnboardingTask }>(\n UPDATE_ONBOARDING_TASK_MUTATION,\n { id, input: updates },\n params,\n 'Unable to update onboarding task'\n );\n const task = lowerEnumKeys(data.updateCrewOnboardingTask, ENUM_KEYS.onboardingTask);\n set((state) => ({\n onboardings: state.onboardings.map((o) =>\n o.id === task.onboardingId\n ? { ...o, tasks: o.tasks.map((t) => (t.id === id ? task : t)) }\n : o\n ),\n }));\n },\n removeOnboardingTask: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewOnboardingTask: boolean }>(\n REMOVE_ONBOARDING_TASK_MUTATION,\n { id },\n params,\n 'Unable to remove onboarding task'\n );\n set((state) => ({\n onboardings: state.onboardings.map((o) => {\n if (!o.tasks.some((t) => t.id === id)) return o;\n return {\n ...o,\n tasks: o.tasks.filter((t) => t.id !== id),\n totalTaskCount: Math.max(0, o.totalTaskCount - 1),\n };\n }),\n }));\n },\n toggleOnboardingTask: async (id, completed) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ toggleCrewOnboardingTask: CrewOnboardingTask }>(\n TOGGLE_ONBOARDING_TASK_MUTATION,\n { id, completed },\n params,\n 'Unable to update onboarding task'\n );\n const task = lowerEnumKeys(data.toggleCrewOnboardingTask, ENUM_KEYS.onboardingTask);\n set((state) => ({\n onboardings: state.onboardings.map((o) => {\n if (o.id !== task.onboardingId) return o;\n const tasks = o.tasks.map((t) => (t.id === id ? task : t));\n const completedTaskCount = tasks.filter((t) => t.completedAt).length;\n return {\n ...o,\n tasks,\n completedTaskCount,\n progressPercent:\n tasks.length === 0 ? 0 : Math.round((completedTaskCount / tasks.length) * 1000) / 10,\n };\n }),\n }));\n },\n\n // ── Skills supply/demand ────────────────────────────────────────\n fetchSkillsGap: async () => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ crewSkillsGap: CrewSkillsGapRow[] }>(\n SKILLS_GAP_QUERY,\n undefined,\n params,\n 'Unable to load skills coverage'\n );\n // No enum normalization: every field is a plain string/number, and `skill`\n // is a free-text tag whose casing must be preserved as recorded.\n set({ skillsGap: data.crewSkillsGap, skillsGapLoaded: true });\n },\n}));\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"crewfoundryStore.js","names":[],"sources":["../../../src/crewfoundry/store/crewfoundryStore.ts"],"sourcesContent":["import { create } from 'zustand';\nimport type { OverviewMetric, WorkItem } from '../types';\n\n// ── Domain Types ────────────────────────────────────────────────\nexport interface Employee {\n id: string;\n firstName: string;\n lastName: string;\n email: string;\n department: string;\n position: string;\n status: 'active' | 'inactive' | 'on_leave' | 'terminated';\n type: 'full_time' | 'part_time' | 'contract' | 'intern';\n salary: number;\n startDate: string;\n avatar?: string;\n location: string;\n managerId?: string;\n phone: string;\n performanceScore: number;\n /** Real per-employee skill tags (backend default `[]`). */\n skills: string[];\n /** Exit date for terminated employees; ISO string or null while employed. */\n terminationDate?: string | null;\n}\n\nexport interface Department {\n id: string;\n name: string;\n headId: string;\n headName: string;\n employeeCount: number;\n budget: number;\n location: string;\n status: 'active' | 'restructuring';\n}\n\nexport interface JobPosting {\n id: string;\n title: string;\n department: string;\n location: string;\n type: 'full_time' | 'part_time' | 'contract' | 'remote';\n experienceLevel: 'entry' | 'mid' | 'senior' | 'lead' | 'executive';\n salaryMin: number;\n salaryMax: number;\n status: 'draft' | 'active' | 'paused' | 'closed' | 'filled';\n applicants: number;\n postedDate: string;\n deadline: string;\n description: string;\n requirements: string[];\n benefits: string[];\n}\n\nexport interface Candidate {\n id: string;\n firstName: string;\n lastName: string;\n email: string;\n phone: string;\n position: string;\n status: 'new' | 'screening' | 'interview' | 'assessment' | 'offer' | 'hired' | 'rejected';\n appliedDate: string;\n resumeUrl?: string;\n rating: number;\n source: string;\n experience: number;\n skills: string[];\n currentCompany: string;\n notes: string;\n /** Date the candidate was hired; ISO string or null until hired. */\n hiredDate?: string | null;\n}\n\nexport interface Interview {\n id: string;\n candidateId: string;\n candidateName: string;\n position: string;\n interviewerName: string;\n date: string;\n time: string;\n type: 'phone' | 'video' | 'onsite' | 'panel' | 'technical';\n status: 'scheduled' | 'completed' | 'cancelled' | 'no_show';\n feedback?: string;\n rating?: number;\n}\n\nexport interface LearningCourse {\n id: string;\n title: string;\n category: string;\n instructor: string;\n duration: string;\n level: 'beginner' | 'intermediate' | 'advanced';\n enrolled: number;\n completed: number;\n rating: number;\n status: 'active' | 'draft' | 'archived';\n thumbnail?: string;\n description: string;\n modules: number;\n skills: string[];\n}\n\nexport interface LearningPath {\n id: string;\n title: string;\n description: string;\n courses: string[];\n duration: string;\n level: string;\n enrolled: number;\n completionRate: number;\n}\n\nexport interface MarketplaceService {\n id: string;\n title: string;\n provider: string;\n category: string;\n price: number;\n priceType: 'hourly' | 'fixed' | 'monthly';\n rating: number;\n reviews: number;\n description: string;\n deliveryTime: string;\n status: 'active' | 'paused' | 'pending';\n skills: string[];\n}\n\nexport interface Announcement {\n id: string;\n title: string;\n content: string;\n author: string;\n authorRole: string;\n category: 'general' | 'hr' | 'it' | 'emergency' | 'event';\n priority: 'low' | 'medium' | 'high' | 'urgent';\n isPinned: boolean;\n createdAt: string;\n viewCount: number;\n tags: string[];\n}\n\nexport interface Team {\n id: string;\n name: string;\n description: string;\n leadId: string;\n leadName: string;\n members: string[];\n memberCount: number;\n department: string;\n status: 'active' | 'forming' | 'disbanded';\n projectCount: number;\n createdAt: string;\n}\n\nexport interface Project {\n id: string;\n name: string;\n description: string;\n status: 'planning' | 'active' | 'on_hold' | 'completed' | 'cancelled';\n priority: 'low' | 'medium' | 'high' | 'critical';\n startDate: string;\n endDate: string;\n progress: number;\n budget: number;\n spent: number;\n teamId: string;\n teamName: string;\n managerId: string;\n managerName: string;\n tasks: number;\n completedTasks: number;\n}\n\nexport interface PayrollRecord {\n id: string;\n employeeId: string;\n employeeName: string;\n department: string;\n period: string;\n grossPay: number;\n deductions: number;\n netPay: number;\n status: 'draft' | 'processing' | 'approved' | 'paid' | 'failed';\n payDate: string;\n paymentMethod: string;\n}\n\nexport interface LeaveRequest {\n id: string;\n employeeId: string;\n employeeName: string;\n department: string;\n type: 'annual' | 'sick' | 'personal' | 'maternity' | 'paternity' | 'unpaid';\n startDate: string;\n endDate: string;\n days: number;\n status: 'pending' | 'approved' | 'rejected' | 'cancelled';\n reason: string;\n approverName?: string;\n}\n\nexport interface TimeEntry {\n id: string;\n employeeId: string;\n employeeName: string;\n date: string;\n clockIn: string;\n clockOut: string;\n totalHours: number;\n overtime: number;\n project: string;\n status: 'pending' | 'approved' | 'rejected';\n /** Whether the logged time is billable to a client (backend default `true`). */\n billable: boolean;\n}\n\nexport interface Notification {\n id: string;\n title: string;\n message: string;\n type: 'info' | 'success' | 'warning' | 'error';\n category: string;\n read: boolean;\n createdAt: string;\n actionUrl?: string;\n}\n\nexport interface Invoice {\n id: string;\n number: string;\n clientName: string;\n amount: number;\n status: 'draft' | 'sent' | 'paid' | 'overdue' | 'cancelled';\n issueDate: string;\n dueDate: string;\n items: { description: string; amount: number }[];\n}\n\nexport interface Vendor {\n id: string;\n name: string;\n category: string;\n contactPerson: string;\n email: string;\n phone: string;\n status: 'active' | 'inactive' | 'pending';\n contractValue: number;\n rating: number;\n}\n\nexport interface Client {\n id: string;\n name: string;\n industry: string;\n contactPerson: string;\n email: string;\n phone: string;\n status: 'active' | 'prospect' | 'churned';\n revenue: number;\n projects: number;\n since: string;\n}\n\n// BOFF-3987 — employee profile Documents tab. The binary + canonical file\n// metadata live in wspace-files-svc; this is just the (employee, file)\n// association crewfoundry owns.\nexport interface CrewEmployeeDocument {\n id: string;\n employeeId: string;\n fileId: string;\n title: string;\n category?: string | null;\n uploadedBy: string;\n uploadedAt: string;\n}\n\n// BOFF-3983 — requisition-to-hire onboarding tracker (mock cf-30).\nexport type OnboardingStage = 'offer' | 'paperwork' | 'it_setup' | 'training' | 'day_one';\nexport type OnboardingStatus = 'not_started' | 'in_progress' | 'completed' | 'cancelled';\n\nexport interface CrewOnboardingTask {\n id: string;\n onboardingId: string;\n stage: OnboardingStage;\n title: string;\n assigneeActorId?: string | null;\n assigneeName?: string | null;\n dueDate?: string | null;\n completedAt?: string | null;\n sortOrder: number;\n}\n\nexport interface CrewOnboarding {\n id: string;\n candidateId?: string | null;\n employeeId?: string | null;\n fullName: string;\n position: string;\n department?: string | null;\n startDate?: string | null;\n status: OnboardingStatus;\n currentStage: OnboardingStage;\n createdAt: string;\n tasks: CrewOnboardingTask[];\n totalTaskCount: number;\n completedTaskCount: number;\n progressPercent: number;\n}\n\n// BOFF-4195 — AI-assisted candidate/worker matching engine. Deterministic,\n// rule-based (skill overlap 70% + experience fit 30%) — the backend never\n// fabricates an AI summary; `rationale` is a plain-language breakdown of the\n// two sub-scores so the ranking is never a black box.\nexport interface CrewCandidateMatch {\n candidateId: string;\n jobPostingId: string;\n candidate: Candidate;\n score: number;\n skillScore: number;\n experienceScore: number;\n matchedSkills: string[];\n missingSkills: string[];\n rationale: string;\n}\n\n// Workspace skills supply/demand, computed server-side by\n// `Query.crewSkillsGap` (wspace-crewfoundry-svc analytics service):\n// employeesWithSkill — ACTIVE employees carrying that skill tag\n// openJobsRequiringSkill — non-CLOSED job postings listing it as a requirement\n// shortage — max(0, demand − supply)\n// Rows arrive already sorted by shortage desc, then demand desc. Skills are\n// free-text tags matched case-insensitively; `skill` is the first-seen casing.\nexport interface CrewSkillsGapRow {\n skill: string;\n employeesWithSkill: number;\n openJobsRequiringSkill: number;\n shortage: number;\n}\n\n// BOFF-4193 — native gross-to-net payroll calculation engine (US/IN/GB\n// reference countries). NOT a licensed EOR/statutory-filing platform — see\n// wspace-crewfoundry-svc/src/modules/workforce/services/payroll/README.md\n// for the full scope note. `country`/`status` are kept in the exact\n// UPPER_SNAKE casing the schema's enums use (unlike most entities in this\n// store, which get lower-cased on read) since they're small closed sets\n// displayed as badges, not compared against lower_snake literals elsewhere.\nexport interface CrewPayrollRun {\n id: string;\n country: 'US' | 'IN' | 'GB';\n currency: string;\n period: string;\n payDate?: string | null;\n status: 'DRAFT' | 'CALCULATED' | 'APPROVED' | 'CLOSED';\n employeeCount: number;\n totalGrossPay: number;\n totalNetPay: number;\n calculatedAt?: string | null;\n approvedAt?: string | null;\n approvedBy?: string | null;\n notes?: string | null;\n}\n\nexport interface CrewPayslipTaxBracket {\n label: string;\n rate: number;\n bandFrom: number;\n bandTo?: number | null;\n taxableInBand: number;\n taxOnBand: number;\n}\n\nexport interface CrewPayslipContributionLine {\n label: string;\n rate?: number | null;\n base: number;\n amount: number;\n}\n\nexport interface CrewPayslip {\n id: string;\n payrollRunId: string;\n employeeId: string;\n employeeName: string;\n country: 'US' | 'IN' | 'GB';\n currency: string;\n period: string;\n annualGrossSalary: number;\n grossPay: number;\n taxableIncome: number;\n incomeTax: number;\n employeeSocialContrib: number;\n employerSocialContrib: number;\n otherDeductions: number;\n netPay: number;\n incomeTaxBrackets: CrewPayslipTaxBracket[];\n employeeContributions: CrewPayslipContributionLine[];\n employerContributions: CrewPayslipContributionLine[];\n otherDeductionLines: CrewPayslipContributionLine[];\n assumptions: string[];\n}\n\nexport interface CreateCrewPayrollRunEmployeeInput {\n employeeId: string;\n employeeName: string;\n annualGrossSalary: number;\n}\n\nexport interface ExternalPaymentMethod {\n id: string;\n type: 'bank' | 'card';\n name: string;\n bank: string;\n last4: string;\n status: 'primary' | 'active';\n expiry?: string;\n}\n\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport type { Gateway } from '@burdenoff/fe-libs/shared/config';\n\ntype InitParams = {\n workspaceId?: string;\n authToken?: string;\n apiGatewayUrl?: string;\n actorId?: string;\n};\n\ntype GraphqlResult<T> = {\n data: T | null;\n errors?: Array<{ message: string }>;\n};\n\nconst BOOTSTRAP_QUERY = `\n query CrewfoundryBootstrap {\n crewfoundryBootstrap {\n employees { id firstName lastName email department position status type salary startDate avatar location managerId phone performanceScore skills terminationDate }\n departments { id name headId headName employeeCount budget location status }\n jobPostings { id title department location type experienceLevel salaryMin salaryMax status applicants postedDate deadline description requirements benefits }\n candidates { id firstName lastName email phone position status appliedDate resumeUrl rating source experience skills currentCompany notes hiredDate }\n interviews { id candidateId candidateName position interviewerName date time type status feedback rating }\n announcements { id title content author authorRole category priority isPinned createdAt viewCount tags }\n teams { id name description leadId leadName members memberCount department status projectCount createdAt }\n projects { id name description status priority startDate endDate progress budget spent teamId teamName managerId managerName tasks completedTasks }\n leaveRequests { id employeeId employeeName department type startDate endDate days status reason approverName }\n timeEntries { id employeeId employeeName date clockIn clockOut totalHours overtime project status billable }\n vendors { id name category contactPerson email phone status contractValue rating }\n clients { id name industry contactPerson email phone status revenue projects since }\n }\n }\n`;\n\n// Payroll (BOFF-4887) is deliberately NOT part of crewfoundryBootstrap — it is\n// gated by the dedicated `crewfoundry-payroll` RBAC resource rather than the\n// general `crewfoundry:read` the bootstrap query uses, so a Workforce\n// Coordinator (or any role with only general workforce access) must not be\n// able to read gross/net pay and deductions just by loading the dashboard.\n// Fetched separately via `runOverlap` below (best-effort — a FORBIDDEN\n// response from a role without payroll access degrades to an empty list\n// rather than failing the whole bootstrap load).\nconst CREW_PAYROLL_RECORDS_QUERY = `\n query CrewPayrollRecords {\n crewPayrollRecords { id employeeId employeeName department period grossPay deductions netPay status payDate paymentMethod }\n }\n`;\n\ntype CrewPayrollRecordsResponse = {\n crewPayrollRecords: PayrollRecord[];\n};\n\nconst WORKSPACE_BOOTSTRAP_WITH_OVERLAP_QUERY = `\n query CrewfoundryWorkspaceBootstrapWithOverlap(\n $workspaceId: ID!\n $projectPagination: PaginationInput\n $groupFirst: Int\n $conversationFirst: Int\n ) {\n crewfoundryBootstrap {\n employees { id firstName lastName email department position status type salary startDate avatar location managerId phone performanceScore skills terminationDate }\n departments { id name headId headName employeeCount budget location status }\n jobPostings { id title department location type experienceLevel salaryMin salaryMax status applicants postedDate deadline description requirements benefits }\n candidates { id firstName lastName email phone position status appliedDate resumeUrl rating source experience skills currentCompany notes hiredDate }\n interviews { id candidateId candidateName position interviewerName date time type status feedback rating }\n announcements { id title content author authorRole category priority isPinned createdAt viewCount tags }\n teams { id name description leadId leadName members memberCount department status projectCount createdAt }\n projects { id name description status priority startDate endDate progress budget spent teamId teamName managerId managerName tasks completedTasks }\n leaveRequests { id employeeId employeeName department type startDate endDate days status reason approverName }\n timeEntries { id employeeId employeeName date clockIn clockOut totalHours overtime project status billable }\n vendors { id name category contactPerson email phone status contractValue rating }\n clients { id name industry contactPerson email phone status revenue projects since }\n }\n projects(filter: { workspaceId: $workspaceId }, pagination: $projectPagination) {\n items {\n id\n name\n key\n creatorId\n status\n config\n archived\n createdAt\n updatedAt\n }\n }\n groups(first: $groupFirst) {\n edges {\n node {\n id\n name\n description\n groupType\n status\n memberCount\n ownerId\n createdAt\n }\n }\n }\n conversations(first: $conversationFirst) {\n edges {\n node {\n id\n title\n description\n state\n unreadCount\n memberCount\n updatedAt\n }\n }\n }\n }\n`;\n\nconst OVERLAP_BILLING_QUERY = `\n query CrewfoundryOverlapBilling {\n getBillingAccountsByOrg {\n id\n name\n isDefault\n paymentMethods {\n id\n type\n brand\n last4\n isDefault\n expiryMonth\n expiryYear\n }\n }\n }\n`;\n\nconst CREATE_EMPLOYEE_MUTATION = `mutation CreateCrewEmployee($input: CreateCrewEmployeeInput!) { createCrewEmployee(input: $input) { id firstName lastName email department position status type salary startDate avatar location managerId phone performanceScore skills terminationDate } }`;\nconst UPDATE_EMPLOYEE_MUTATION = `mutation UpdateCrewEmployee($id: ID!, $input: UpdateCrewEmployeeInput!) { updateCrewEmployee(id: $id, input: $input) { id firstName lastName email department position status type salary startDate avatar location managerId phone performanceScore skills terminationDate } }`;\nconst REMOVE_EMPLOYEE_MUTATION = `mutation RemoveCrewEmployee($id: ID!) { removeCrewEmployee(id: $id) }`;\nconst CREATE_JOB_MUTATION = `mutation CreateCrewJobPosting($input: CreateCrewJobPostingInput!) { createCrewJobPosting(input: $input) { id title department location type experienceLevel salaryMin salaryMax status applicants postedDate deadline description requirements benefits } }`;\nconst UPDATE_JOB_MUTATION = `mutation UpdateCrewJobPosting($id: ID!, $input: UpdateCrewJobPostingInput!) { updateCrewJobPosting(id: $id, input: $input) { id title department location type experienceLevel salaryMin salaryMax status applicants postedDate deadline description requirements benefits } }`;\nconst CREATE_CANDIDATE_MUTATION = `mutation CreateCrewCandidate($input: CreateCrewCandidateInput!) { createCrewCandidate(input: $input) { id firstName lastName email phone position status appliedDate resumeUrl rating source experience skills currentCompany notes hiredDate } }`;\nconst UPDATE_CANDIDATE_MUTATION = `mutation UpdateCrewCandidate($id: ID!, $input: UpdateCrewCandidateInput!) { updateCrewCandidate(id: $id, input: $input) { id firstName lastName email phone position status appliedDate resumeUrl rating source experience skills currentCompany notes hiredDate } }`;\nconst CREATE_ANNOUNCEMENT_MUTATION = `mutation CreateCrewAnnouncement($input: CreateCrewAnnouncementInput!) { createCrewAnnouncement(input: $input) { id title content author authorRole category priority isPinned createdAt viewCount tags } }`;\nconst CREATE_LEAVE_MUTATION = `mutation CreateCrewLeaveRequest($input: CreateCrewLeaveRequestInput!) { createCrewLeaveRequest(input: $input) { id employeeId employeeName department type startDate endDate days status reason approverName } }`;\nconst UPDATE_LEAVE_MUTATION = `mutation UpdateCrewLeaveRequest($id: ID!, $input: UpdateCrewLeaveRequestInput!) { updateCrewLeaveRequest(id: $id, input: $input) { id employeeId employeeName department type startDate endDate days status reason approverName } }`;\n// Extra removes / updates for existing entities (delete + edit buttons).\nconst REMOVE_JOB_MUTATION = `mutation RemoveCrewJobPosting($id: ID!) { removeCrewJobPosting(id: $id) }`;\nconst REMOVE_CANDIDATE_MUTATION = `mutation RemoveCrewCandidate($id: ID!) { removeCrewCandidate(id: $id) }`;\nconst UPDATE_ANNOUNCEMENT_MUTATION = `mutation UpdateCrewAnnouncement($id: ID!, $input: UpdateCrewAnnouncementInput!) { updateCrewAnnouncement(id: $id, input: $input) { id title content author authorRole category priority isPinned createdAt viewCount tags } }`;\nconst REMOVE_ANNOUNCEMENT_MUTATION = `mutation RemoveCrewAnnouncement($id: ID!) { removeCrewAnnouncement(id: $id) }`;\n\n// Interview\nconst CREATE_INTERVIEW_MUTATION = `mutation CreateCrewInterview($input: CreateCrewInterviewInput!) { createCrewInterview(input: $input) { id candidateId candidateName position interviewerName date time type status feedback rating } }`;\nconst UPDATE_INTERVIEW_MUTATION = `mutation UpdateCrewInterview($id: ID!, $input: UpdateCrewInterviewInput!) { updateCrewInterview(id: $id, input: $input) { id candidateId candidateName position interviewerName date time type status feedback rating } }`;\nconst REMOVE_INTERVIEW_MUTATION = `mutation RemoveCrewInterview($id: ID!) { removeCrewInterview(id: $id) }`;\n\n// Vendor\nconst CREATE_VENDOR_MUTATION = `mutation CreateCrewVendor($input: CreateCrewVendorInput!) { createCrewVendor(input: $input) { id name category contactPerson email phone status contractValue rating } }`;\nconst UPDATE_VENDOR_MUTATION = `mutation UpdateCrewVendor($id: ID!, $input: UpdateCrewVendorInput!) { updateCrewVendor(id: $id, input: $input) { id name category contactPerson email phone status contractValue rating } }`;\nconst REMOVE_VENDOR_MUTATION = `mutation RemoveCrewVendor($id: ID!) { removeCrewVendor(id: $id) }`;\n\n// Client\nconst CREATE_CLIENT_MUTATION = `mutation CreateCrewClient($input: CreateCrewClientInput!) { createCrewClient(input: $input) { id name industry contactPerson email phone status revenue projects since } }`;\nconst UPDATE_CLIENT_MUTATION = `mutation UpdateCrewClient($id: ID!, $input: UpdateCrewClientInput!) { updateCrewClient(id: $id, input: $input) { id name industry contactPerson email phone status revenue projects since } }`;\nconst REMOVE_CLIENT_MUTATION = `mutation RemoveCrewClient($id: ID!) { removeCrewClient(id: $id) }`;\n\n// Team\nconst CREATE_TEAM_MUTATION = `mutation CreateCrewTeam($input: CreateCrewTeamInput!) { createCrewTeam(input: $input) { id name description leadId leadName members memberCount department status projectCount createdAt } }`;\nconst UPDATE_TEAM_MUTATION = `mutation UpdateCrewTeam($id: ID!, $input: UpdateCrewTeamInput!) { updateCrewTeam(id: $id, input: $input) { id name description leadId leadName members memberCount department status projectCount createdAt } }`;\nconst REMOVE_TEAM_MUTATION = `mutation RemoveCrewTeam($id: ID!) { removeCrewTeam(id: $id) }`;\n\n// Project\nconst CREATE_PROJECT_MUTATION = `mutation CreateCrewProject($input: CreateCrewProjectInput!) { createCrewProject(input: $input) { id name description status priority startDate endDate progress budget spent teamId teamName managerId managerName tasks completedTasks } }`;\nconst UPDATE_PROJECT_MUTATION = `mutation UpdateCrewProject($id: ID!, $input: UpdateCrewProjectInput!) { updateCrewProject(id: $id, input: $input) { id name description status priority startDate endDate progress budget spent teamId teamName managerId managerName tasks completedTasks } }`;\nconst REMOVE_PROJECT_MUTATION = `mutation RemoveCrewProject($id: ID!) { removeCrewProject(id: $id) }`;\n\n// Department\nconst CREATE_DEPARTMENT_MUTATION = `mutation CreateCrewDepartment($input: CreateCrewDepartmentInput!) { createCrewDepartment(input: $input) { id name headId headName employeeCount budget location status } }`;\nconst UPDATE_DEPARTMENT_MUTATION = `mutation UpdateCrewDepartment($id: ID!, $input: UpdateCrewDepartmentInput!) { updateCrewDepartment(id: $id, input: $input) { id name headId headName employeeCount budget location status } }`;\nconst REMOVE_DEPARTMENT_MUTATION = `mutation RemoveCrewDepartment($id: ID!) { removeCrewDepartment(id: $id) }`;\n\n// PayrollRecord\nconst CREATE_PAYROLL_MUTATION = `mutation CreateCrewPayrollRecord($input: CreateCrewPayrollRecordInput!) { createCrewPayrollRecord(input: $input) { id employeeId employeeName department period grossPay deductions netPay status payDate paymentMethod } }`;\nconst UPDATE_PAYROLL_MUTATION = `mutation UpdateCrewPayrollRecord($id: ID!, $input: UpdateCrewPayrollRecordInput!) { updateCrewPayrollRecord(id: $id, input: $input) { id employeeId employeeName department period grossPay deductions netPay status payDate paymentMethod } }`;\nconst REMOVE_PAYROLL_MUTATION = `mutation RemoveCrewPayrollRecord($id: ID!) { removeCrewPayrollRecord(id: $id) }`;\n\n// BOFF-4193 — native payroll calculation engine (PayrollRun/Payslip). Fetched\n// lazily (see `fetchPayrollRuns`/`fetchPayslipsForRun` below), not on\n// bootstrap — same `crewfoundry-payroll` RBAC gate as CrewPayrollRecord\n// above, so a caller without payroll access simply never triggers the fetch\n// rather than failing the dashboard load.\nconst PAYROLL_RUN_FIELDS = `id country currency period payDate status employeeCount totalGrossPay totalNetPay calculatedAt approvedAt approvedBy notes`;\nconst CREW_PAYROLL_RUNS_QUERY = `query CrewPayrollRuns { crewPayrollRuns { ${PAYROLL_RUN_FIELDS} } }`;\nconst CREATE_PAYROLL_RUN_MUTATION = `mutation CreateCrewPayrollRun($input: CreateCrewPayrollRunInput!) { createCrewPayrollRun(input: $input) { ${PAYROLL_RUN_FIELDS} } }`;\nconst CALCULATE_PAYROLL_RUN_MUTATION = `mutation CalculateCrewPayrollRun($id: ID!) { calculateCrewPayrollRun(id: $id) { ${PAYROLL_RUN_FIELDS} } }`;\nconst APPROVE_PAYROLL_RUN_MUTATION = `mutation ApproveCrewPayrollRun($id: ID!) { approveCrewPayrollRun(id: $id) { ${PAYROLL_RUN_FIELDS} } }`;\nconst REMOVE_PAYROLL_RUN_MUTATION = `mutation RemoveCrewPayrollRun($id: ID!) { removeCrewPayrollRun(id: $id) }`;\nconst PAYSLIP_FIELDS = `id payrollRunId employeeId employeeName country currency period annualGrossSalary grossPay taxableIncome incomeTax employeeSocialContrib employerSocialContrib otherDeductions netPay incomeTaxBrackets { label rate bandFrom bandTo taxableInBand taxOnBand } employeeContributions { label rate base amount } employerContributions { label rate base amount } otherDeductionLines { label rate base amount } assumptions`;\nconst CREW_PAYSLIPS_QUERY = `query CrewPayslips($filter: CrewPayslipFilterInput) { crewPayslips(filter: $filter) { ${PAYSLIP_FIELDS} } }`;\n\n// TimeEntry\nconst CREATE_TIME_ENTRY_MUTATION = `mutation CreateCrewTimeEntry($input: CreateCrewTimeEntryInput!) { createCrewTimeEntry(input: $input) { id employeeId employeeName date clockIn clockOut totalHours overtime project status billable } }`;\nconst UPDATE_TIME_ENTRY_MUTATION = `mutation UpdateCrewTimeEntry($id: ID!, $input: UpdateCrewTimeEntryInput!) { updateCrewTimeEntry(id: $id, input: $input) { id employeeId employeeName date clockIn clockOut totalHours overtime project status billable } }`;\nconst REMOVE_TIME_ENTRY_MUTATION = `mutation RemoveCrewTimeEntry($id: ID!) { removeCrewTimeEntry(id: $id) }`;\n\n// BOFF-4195 — AI-assisted candidate/worker matching. `candidate` is the full\n// CrewCandidate record so the UI can show name/skills/experience alongside\n// the score without a second round-trip.\nconst JOB_POSTING_MATCHES_QUERY = `query CrewJobPostingMatches($jobPostingId: ID!, $filter: CrewCandidateMatchFilterInput, $limit: Int) { crewJobPostingMatches(jobPostingId: $jobPostingId, filter: $filter, limit: $limit) { candidateId jobPostingId score skillScore experienceScore matchedSkills missingSkills rationale candidate { id firstName lastName email phone position status appliedDate resumeUrl rating source experience skills currentCompany notes hiredDate } } }`;\n\n// Skills supply/demand. Not part of `crewfoundryBootstrap` (it isn't a bootstrap\n// field), so it is fetched lazily by the one surface that renders it — the\n// resourcing page — rather than on every dashboard load. Same `crewfoundry:read`\n// RBAC resource as the bootstrap query, so any caller that can load the module\n// can read it. `department` is deliberately omitted: the resolver applies it to\n// the employee side only (supply) and not to job postings (demand), which would\n// make the shortage column read as a workspace-wide demand against one\n// department's supply.\nconst SKILLS_GAP_QUERY = `query CrewSkillsGap { crewSkillsGap { skill employeesWithSkill openJobsRequiringSkill shortage } }`;\n\n// BOFF-3987 — employee documents\nconst EMPLOYEE_DOCUMENTS_QUERY = `query EmployeeDocuments($employeeId: ID!) { employeeDocuments(employeeId: $employeeId) { id employeeId fileId title category uploadedBy uploadedAt } }`;\nconst ATTACH_EMPLOYEE_DOCUMENT_MUTATION = `mutation AttachEmployeeDocument($input: AttachCrewEmployeeDocumentInput!) { attachEmployeeDocument(input: $input) { id employeeId fileId title category uploadedBy uploadedAt } }`;\nconst DETACH_EMPLOYEE_DOCUMENT_MUTATION = `mutation DetachEmployeeDocument($id: ID!) { detachEmployeeDocument(id: $id) }`;\n// Files-svc presigned upload dance (same 3-step protocol fe-libs' SaveToFilesButton\n// uses — see ~/products/dev/platform/context/data/files/), replicated here with the\n// store's own graphqlFetch helper instead of Apollo since this MFE has no Apollo\n// client wired in.\nconst ENSURE_FOLDER_BY_PATH_MUTATION = `mutation EnsureFolderByPath($path: String!) { ensureFolderByPath(path: $path) { id } }`;\nconst INITIATE_UPLOAD_MUTATION = `mutation InitiateUpload($input: InitiateUploadInput!) { initiateUpload(input: $input) { session { id } uploadUrl } }`;\nconst COMPLETE_UPLOAD_MUTATION = `mutation CompleteUpload($input: CompleteUploadInput!) { completeUpload(input: $input) { file { id } } }`;\n\n// BOFF-3983 — onboarding\nconst ONBOARDINGS_QUERY = `query CrewOnboardings { crewOnboardings { id candidateId employeeId fullName position department startDate status currentStage createdAt totalTaskCount completedTaskCount progressPercent tasks { id onboardingId stage title assigneeActorId assigneeName dueDate completedAt sortOrder } } }`;\nconst CREATE_ONBOARDING_MUTATION = `mutation CreateCrewOnboarding($input: CreateCrewOnboardingInput!) { createCrewOnboarding(input: $input) { id candidateId employeeId fullName position department startDate status currentStage createdAt totalTaskCount completedTaskCount progressPercent tasks { id onboardingId stage title assigneeActorId assigneeName dueDate completedAt sortOrder } } }`;\nconst UPDATE_ONBOARDING_MUTATION = `mutation UpdateCrewOnboarding($id: ID!, $input: UpdateCrewOnboardingInput!) { updateCrewOnboarding(id: $id, input: $input) { id candidateId employeeId fullName position department startDate status currentStage createdAt totalTaskCount completedTaskCount progressPercent tasks { id onboardingId stage title assigneeActorId assigneeName dueDate completedAt sortOrder } } }`;\nconst REMOVE_ONBOARDING_MUTATION = `mutation RemoveCrewOnboarding($id: ID!) { removeCrewOnboarding(id: $id) }`;\nconst CREATE_ONBOARDING_TASK_MUTATION = `mutation CreateCrewOnboardingTask($input: CreateCrewOnboardingTaskInput!) { createCrewOnboardingTask(input: $input) { id onboardingId stage title assigneeActorId assigneeName dueDate completedAt sortOrder } }`;\nconst UPDATE_ONBOARDING_TASK_MUTATION = `mutation UpdateCrewOnboardingTask($id: ID!, $input: UpdateCrewOnboardingTaskInput!) { updateCrewOnboardingTask(id: $id, input: $input) { id onboardingId stage title assigneeActorId assigneeName dueDate completedAt sortOrder } }`;\nconst REMOVE_ONBOARDING_TASK_MUTATION = `mutation RemoveCrewOnboardingTask($id: ID!) { removeCrewOnboardingTask(id: $id) }`;\nconst TOGGLE_ONBOARDING_TASK_MUTATION = `mutation ToggleCrewOnboardingTask($id: ID!, $completed: Boolean!) { toggleCrewOnboardingTask(id: $id, completed: $completed) { id onboardingId stage title assigneeActorId assigneeName dueDate completedAt sortOrder } }`;\n\nconst normalizeStatus = (value: string) => value.toUpperCase().replaceAll('-', '_');\n\nconst sharedOwnerError = (capability: string) =>\n new Error(`${capability} isn't available in CrewFoundry.`);\n\n// The backend returns enum values in UPPER_SNAKE case (e.g. \"ACTIVE\", \"FULL_TIME\", \"HIGH\"),\n// but the frontend model + every comparison/filter/badge uses lower_snake ('active', 'full_time').\n// Normalization is per-entity, NOT per-key: fields like `category`/`level` are genuine enums on\n// some entities (Announcement.category, Course.level) yet free text on others (Course.category =\n// \"Programming\", Vendor.category, LearningPath.level) — a global key list would corrupt those.\n// Each list below mirrors exactly the string-literal-union fields declared on that entity's interface.\nconst ENUM_KEYS = {\n employee: ['status', 'type'],\n department: ['status'],\n jobPosting: ['type', 'experienceLevel', 'status'],\n candidate: ['status'],\n interview: ['type', 'status'],\n course: ['level', 'status'],\n marketplaceService: ['priceType', 'status'],\n announcement: ['category', 'priority'],\n team: ['status'],\n project: ['status', 'priority'],\n payrollRecord: ['status'],\n leaveRequest: ['type', 'status'],\n timeEntry: ['status'],\n notification: ['type'],\n paymentMethod: ['type', 'status'],\n invoice: ['status'],\n vendor: ['status'],\n client: ['status'],\n onboarding: ['status', 'currentStage'],\n onboardingTask: ['stage'],\n} as const;\n\n// Lowercase only the given enum keys on an entity read back from the backend; leave every other\n// (free-text) field untouched. Idempotent — safe on already-lowercase values (some subgraphs\n// return lowercase already). This is the read-path inverse of `normalizeStatus`.\nconst lowerEnumKeys = <T>(entity: T, keys: readonly string[]): T => {\n if (!entity || typeof entity !== 'object') return entity;\n const out = { ...(entity as Record<string, unknown>) };\n for (const key of keys) {\n const value = out[key];\n if (typeof value === 'string') out[key] = value.toLowerCase();\n }\n return out as T;\n};\n\n// lowerEnumKeys only lowercases top-level string fields; CrewOnboarding nests\n// a `tasks` array whose own `stage` field also needs the same treatment, so\n// this composes lowerEnumKeys over both levels rather than duplicating it.\nconst lowerOnboarding = (onboarding: CrewOnboarding): CrewOnboarding => {\n const lowered = lowerEnumKeys(onboarding, ENUM_KEYS.onboarding);\n return {\n ...lowered,\n tasks: (onboarding.tasks ?? []).map((task) => lowerEnumKeys(task, ENUM_KEYS.onboardingTask)),\n };\n};\n\nconst toDateOnly = (value?: string | null) => (value ? value.slice(0, 10) : 'TBD');\n\nconst asRecord = (value: unknown): Record<string, unknown> | null => {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return null;\n }\n return value as Record<string, unknown>;\n};\n\nconst asNumber = (value: unknown): number | null => (typeof value === 'number' ? value : null);\n\nconst asString = (value: unknown): string | null => (typeof value === 'string' ? value : null);\n\nconst mapProjectStatus = (value: string): Project['status'] => {\n const status = value.toLowerCase();\n if (status === 'active') return 'active';\n if (status === 'completed') return 'completed';\n if (status === 'cancelled') return 'cancelled';\n if (status === 'archived') return 'on_hold';\n return 'planning';\n};\n\nconst mapGroupStatus = (value: string): Team['status'] => {\n const status = value.toLowerCase();\n if (status === 'active') return 'active';\n if (status === 'inactive' || status === 'archived' || status === 'deleted') return 'disbanded';\n return 'forming';\n};\n\ntype OverlapWorkspaceResponse = {\n projects: {\n items: Array<{\n id: string;\n name: string;\n key: string;\n creatorId: string;\n status: string;\n config?: unknown;\n archived: boolean;\n createdAt: string;\n updatedAt: string;\n }>;\n };\n groups: {\n edges: Array<{\n node: {\n id: string;\n name: string;\n description?: string | null;\n groupType?: string | null;\n status: string;\n memberCount: number;\n ownerId: string;\n createdAt: string;\n };\n }>;\n };\n conversations: {\n edges: Array<{\n node: {\n id: string;\n title?: string | null;\n description?: string | null;\n state: string;\n unreadCount: number;\n memberCount: number;\n updatedAt: string;\n };\n }>;\n };\n};\n\ntype WorkspaceBootstrapWithOverlapResponse = BootstrapPayload & OverlapWorkspaceResponse;\n\ntype OverlapBillingResponse = {\n getBillingAccountsByOrg: Array<{\n id?: string | null;\n name?: string | null;\n isDefault?: boolean | null;\n paymentMethods?: Array<{\n id: string;\n type: string;\n brand?: string | null;\n last4?: string | null;\n isDefault: boolean;\n expiryMonth?: number | null;\n expiryYear?: number | null;\n } | null> | null;\n }>;\n};\n\ntype BootstrapPayload = {\n crewfoundryBootstrap: {\n employees: Employee[];\n departments: Department[];\n jobPostings: JobPosting[];\n candidates: Candidate[];\n interviews: Interview[];\n announcements: Announcement[];\n teams: Team[];\n projects: Project[];\n leaveRequests: LeaveRequest[];\n timeEntries: TimeEntry[];\n vendors: Vendor[];\n clients: Client[];\n };\n};\n\nconst mapOverlapProjects = (\n projects: OverlapWorkspaceResponse['projects']['items'],\n teams: Team[]\n) =>\n projects.map((project, index): Project => {\n const config = asRecord(project.config);\n const budget = asNumber(config?.budget) ?? 25000 + index * 2500;\n const spent =\n asNumber(config?.spent) ?? Math.round(budget * (project.status === 'COMPLETED' ? 1 : 0.62));\n const progress = asNumber(config?.progress) ?? (project.status === 'COMPLETED' ? 100 : 58);\n const tasks = asNumber(config?.tasks) ?? 20 + (index % 9);\n const completedTasks = asNumber(config?.completedTasks) ?? Math.round((progress / 100) * tasks);\n const team = teams[index % Math.max(teams.length, 1)];\n\n return {\n id: project.id,\n name: project.name,\n description: asString(config?.description) || `${project.key} delivery plan`,\n status: mapProjectStatus(project.status),\n priority: (asString(config?.priority)?.toLowerCase() as Project['priority']) || 'medium',\n startDate: toDateOnly(project.createdAt),\n endDate: toDateOnly(project.updatedAt),\n progress,\n budget,\n spent,\n teamId: team?.id || 'unassigned',\n teamName: team?.name || 'Unassigned Team',\n managerId: project.creatorId,\n managerName: `Lead ${project.creatorId.slice(0, 8)}`,\n tasks,\n completedTasks,\n };\n });\n\nconst mapOverlapTeams = (groups: OverlapWorkspaceResponse['groups']['edges']): Team[] =>\n groups.map(({ node }) => ({\n id: node.id,\n name: node.name,\n description: node.description || `${node.name} team workspace`,\n leadId: node.ownerId,\n leadName: `Lead ${node.ownerId.slice(0, 8)}`,\n members: [],\n memberCount: node.memberCount,\n department: node.groupType?.toLowerCase().replaceAll('_', ' ') || 'operations',\n status: mapGroupStatus(node.status),\n projectCount: 0,\n createdAt: toDateOnly(node.createdAt),\n }));\n\nconst mapConversationNotifications = (\n conversations: OverlapWorkspaceResponse['conversations']['edges']\n): Notification[] =>\n conversations\n .filter(({ node }) => node.unreadCount > 0)\n .map(({ node }) => ({\n id: `conversation-${node.id}`,\n title: node.title || 'Conversation update',\n message: `${node.unreadCount} unread in ${node.memberCount}-member channel`,\n type: node.state.toLowerCase() === 'active' ? 'info' : 'warning',\n category: 'communications',\n read: false,\n createdAt: node.updatedAt,\n actionUrl: `/crewfoundry/communications/messages-page`,\n }));\n\nconst mapBillingMethods = (data: OverlapBillingResponse): ExternalPaymentMethod[] => {\n const account =\n data.getBillingAccountsByOrg.find((item) => item?.isDefault && item?.id) ||\n data.getBillingAccountsByOrg.find((item) => item?.id);\n\n if (!account?.id) {\n return [];\n }\n\n return (account.paymentMethods || [])\n .filter((item): item is NonNullable<typeof item> => Boolean(item))\n .map((method) => {\n const month = method.expiryMonth ? String(method.expiryMonth).padStart(2, '0') : undefined;\n const year = method.expiryYear ? String(method.expiryYear) : undefined;\n const isBank = method.type === 'BANK_TRANSFER' || method.type === 'NETBANKING';\n\n return {\n id: method.id,\n type: isBank ? 'bank' : 'card',\n name: `${account.name || 'Billing'} ${isBank ? 'Bank Account' : 'Card'}`,\n bank: method.brand || account.name || 'Billing Account',\n last4: method.last4 || '0000',\n status: method.isDefault ? 'primary' : 'active',\n expiry: month && year ? `${month}/${year}` : undefined,\n };\n });\n};\n\nconst parseGraphql = <T>(result: GraphqlResult<T>, fallbackMessage: string): T => {\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message || fallbackMessage);\n }\n if (!result.data) {\n throw new Error(fallbackMessage);\n }\n return result.data;\n};\n\n// Fields that live on the client-side entity objects but are NEVER accepted by\n// any Crew* Create/Update GraphQL input — either a client-generated temp `id`,\n// a server-set timestamp, or a server-derived rollup counter. Sending them makes\n// the gateway reject the whole operation with a GraphQL validation error (which\n// the public gateway masks to a generic INTERNAL_SERVER_ERROR), so no row is\n// ever written. Stripping them centrally keeps every create/update mutation valid\n// without each caller having to hand-pick input fields. (Counters like rating /\n// applicants / viewCount that a few inputs DO accept are safe to drop too — they\n// default server-side, and the UI never lets a user set a derived rollup.)\nconst MUTATION_INPUT_OMIT_KEYS = new Set<string>([\n 'id',\n 'createdAt',\n 'createdOn',\n 'updatedAt',\n 'deletedAt',\n 'employeeCount',\n 'memberCount',\n 'projectCount',\n 'enrolled',\n 'completed',\n 'completionRate',\n 'reviews',\n 'rating',\n 'tasks',\n 'completedTasks',\n 'spent',\n 'progress',\n 'applicants',\n 'viewCount',\n 'revenue',\n 'projects',\n]);\n\nconst sanitizeMutationInput = (\n variables: Record<string, unknown> | undefined\n): Record<string, unknown> | undefined => {\n if (!variables) {\n return variables;\n }\n const input = variables.input;\n if (!input || typeof input !== 'object' || Array.isArray(input)) {\n return variables;\n }\n const cleaned: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(input as Record<string, unknown>)) {\n if (!MUTATION_INPUT_OMIT_KEYS.has(key)) {\n cleaned[key] = value;\n }\n }\n return { ...variables, input: cleaned };\n};\n\nconst runGraphql = async <T>(\n query: string,\n variables: Record<string, unknown> | undefined,\n params: InitParams,\n fallbackMessage: string,\n gateway: Gateway = 'workspace'\n) => {\n const result = await graphqlFetch<T>({\n gateway,\n query,\n variables: sanitizeMutationInput(variables),\n authToken: params.authToken,\n // `apiGatewayUrl` is the workspace gateway base (dev override only). The\n // global gateway (e.g. billing overlap) resolves its own URL from fe-libs\n // config, so never force the workspace base onto a global-gateway call.\n baseUrl: gateway === 'workspace' ? params.apiGatewayUrl : undefined,\n extraHeaders: {\n ...(gateway === 'workspace' && params.workspaceId\n ? { 'x-workspace-id': params.workspaceId }\n : {}),\n },\n });\n return parseGraphql(result as GraphqlResult<T>, fallbackMessage);\n};\n\n// Overlap queries are best-effort: they enrich the crew bootstrap with real\n// wspace/global platform data but must never block the page if a field is\n// unavailable in a given environment. Swallow to null, but surface the reason\n// in dev so schema-arg drift (the historical failure mode) is visible.\n// Vite injects `import.meta.env.DEV`, but this package's tsconfig does not pull\n// in `vite/client` ambient types — narrow through a typed shape instead of `any`.\nconst isDevEnv = (): boolean => Boolean((import.meta as { env?: { DEV?: boolean } }).env?.DEV);\n\nconst runOverlap = async <T>(\n query: string,\n variables: Record<string, unknown> | undefined,\n params: InitParams,\n fallbackMessage: string,\n gateway: Gateway = 'workspace'\n): Promise<T | null> => {\n try {\n return await runGraphql<T>(query, variables, params, fallbackMessage, gateway);\n } catch (error) {\n if (isDevEnv()) {\n console.warn(`[crewfoundry] overlap query skipped: ${fallbackMessage}`, error);\n }\n return null;\n }\n};\n\n// ── Store Interface ─────────────────────────────────────────────\ninterface CrewFoundryState {\n // Overview\n metrics: OverviewMetric[];\n workItems: WorkItem[];\n selectedView: string;\n\n // Domain data\n employees: Employee[];\n departments: Department[];\n jobPostings: JobPosting[];\n candidates: Candidate[];\n interviews: Interview[];\n courses: LearningCourse[];\n learningPaths: LearningPath[];\n marketplaceServices: MarketplaceService[];\n announcements: Announcement[];\n teams: Team[];\n projects: Project[];\n payrollRecords: PayrollRecord[];\n leaveRequests: LeaveRequest[];\n timeEntries: TimeEntry[];\n notifications: Notification[];\n paymentMethods: ExternalPaymentMethod[];\n invoices: Invoice[];\n vendors: Vendor[];\n clients: Client[];\n employeeDocuments: CrewEmployeeDocument[];\n onboardings: CrewOnboarding[];\n onboardingsLoaded: boolean;\n // BOFF-4195 — AI-assisted matching. Keyed by jobPostingId so switching\n // between postings' match views doesn't require a refetch each time.\n jobPostingMatches: Record<string, CrewCandidateMatch[]>;\n jobPostingMatchesLoading: boolean;\n\n // BOFF-4193 — native payroll calculation engine. Lazy-loaded (see\n // `fetchPayrollRuns` below), same gating rationale as jobPostingMatches.\n payrollRuns: CrewPayrollRun[];\n payrollRunsLoaded: boolean;\n payrollRunsLoading: boolean;\n // Keyed by payrollRunId so switching between runs' payslip views doesn't\n // require a refetch each time.\n payslipsByRun: Record<string, CrewPayslip[]>;\n payslipsLoading: boolean;\n\n // Workspace skills supply/demand. Lazy-loaded by the resourcing page (see\n // `fetchSkillsGap`), so `skillsGapLoaded` distinguishes \"not fetched yet\"\n // from \"fetched and genuinely empty\" — the two render differently.\n skillsGap: CrewSkillsGapRow[];\n skillsGapLoaded: boolean;\n\n // Actions\n initialized: boolean;\n loading: boolean;\n error: string | null;\n initParams: InitParams;\n initializeData: (params: InitParams) => Promise<void>;\n setSelectedView: (view: string) => void;\n setWorkItems: (items: WorkItem[]) => void;\n addEmployee: (employee: Employee) => Promise<void>;\n updateEmployee: (id: string, updates: Partial<Employee>) => Promise<void>;\n removeEmployee: (id: string) => Promise<void>;\n addJobPosting: (job: JobPosting) => Promise<void>;\n updateJobPosting: (id: string, updates: Partial<JobPosting>) => Promise<void>;\n addCandidate: (candidate: Candidate) => Promise<void>;\n updateCandidate: (id: string, updates: Partial<Candidate>) => Promise<void>;\n removeJobPosting: (id: string) => Promise<void>;\n removeCandidate: (id: string) => Promise<void>;\n addAnnouncement: (announcement: Announcement) => Promise<void>;\n updateAnnouncement: (id: string, updates: Partial<Announcement>) => Promise<void>;\n removeAnnouncement: (id: string) => Promise<void>;\n addLeaveRequest: (request: LeaveRequest) => Promise<void>;\n updateLeaveRequest: (id: string, updates: Partial<LeaveRequest>) => Promise<void>;\n addInterview: (interview: Interview) => Promise<void>;\n updateInterview: (id: string, updates: Partial<Interview>) => Promise<void>;\n removeInterview: (id: string) => Promise<void>;\n addInvoice: (invoice: Invoice) => Promise<void>;\n updateInvoice: (id: string, updates: Partial<Invoice>) => Promise<void>;\n removeInvoice: (id: string) => Promise<void>;\n addVendor: (vendor: Vendor) => Promise<void>;\n updateVendor: (id: string, updates: Partial<Vendor>) => Promise<void>;\n removeVendor: (id: string) => Promise<void>;\n addClient: (client: Client) => Promise<void>;\n updateClient: (id: string, updates: Partial<Client>) => Promise<void>;\n removeClient: (id: string) => Promise<void>;\n addTeam: (team: Team) => Promise<void>;\n updateTeam: (id: string, updates: Partial<Team>) => Promise<void>;\n removeTeam: (id: string) => Promise<void>;\n addProject: (project: Project) => Promise<void>;\n updateProject: (id: string, updates: Partial<Project>) => Promise<void>;\n removeProject: (id: string) => Promise<void>;\n addMarketplaceService: (service: MarketplaceService) => Promise<void>;\n updateMarketplaceService: (id: string, updates: Partial<MarketplaceService>) => Promise<void>;\n removeMarketplaceService: (id: string) => Promise<void>;\n addDepartment: (department: Department) => Promise<void>;\n updateDepartment: (id: string, updates: Partial<Department>) => Promise<void>;\n removeDepartment: (id: string) => Promise<void>;\n addCourse: (course: LearningCourse) => Promise<void>;\n updateCourse: (id: string, updates: Partial<LearningCourse>) => Promise<void>;\n removeCourse: (id: string) => Promise<void>;\n enrollCourse: (id: string) => Promise<void>;\n addLearningPath: (path: LearningPath) => Promise<void>;\n updateLearningPath: (id: string, updates: Partial<LearningPath>) => Promise<void>;\n removeLearningPath: (id: string) => Promise<void>;\n addPayrollRecord: (record: PayrollRecord) => Promise<void>;\n updatePayrollRecord: (id: string, updates: Partial<PayrollRecord>) => Promise<void>;\n removePayrollRecord: (id: string) => Promise<void>;\n addTimeEntry: (entry: TimeEntry) => Promise<void>;\n updateTimeEntry: (id: string, updates: Partial<TimeEntry>) => Promise<void>;\n removeTimeEntry: (id: string) => Promise<void>;\n markNotificationRead: (id: string) => Promise<void>;\n markAllNotificationsRead: () => Promise<void>;\n\n // BOFF-4195 — AI-assisted candidate/worker matching\n fetchJobPostingMatches: (jobPostingId: string) => Promise<void>;\n\n // BOFF-4193 — native payroll calculation engine\n fetchPayrollRuns: () => Promise<void>;\n createPayrollRun: (input: {\n country: 'US' | 'IN' | 'GB';\n period: string;\n payDate?: string;\n notes?: string;\n employees: CreateCrewPayrollRunEmployeeInput[];\n }) => Promise<void>;\n calculatePayrollRun: (id: string) => Promise<void>;\n approvePayrollRun: (id: string) => Promise<void>;\n removePayrollRun: (id: string) => Promise<void>;\n fetchPayslipsForRun: (payrollRunId: string) => Promise<void>;\n\n // BOFF-3987 — employee documents\n fetchEmployeeDocuments: (employeeId: string) => Promise<void>;\n uploadEmployeeDocument: (params: {\n employeeId: string;\n file: File;\n title: string;\n category?: string;\n }) => Promise<void>;\n detachEmployeeDocument: (id: string) => Promise<void>;\n\n // BOFF-3983 — onboarding\n fetchOnboardings: () => Promise<void>;\n createOnboarding: (input: {\n candidateId?: string;\n employeeId?: string;\n fullName: string;\n position: string;\n department?: string;\n startDate?: string;\n }) => Promise<void>;\n updateOnboarding: (id: string, updates: Partial<CrewOnboarding>) => Promise<void>;\n removeOnboarding: (id: string) => Promise<void>;\n createOnboardingTask: (input: {\n onboardingId: string;\n stage: OnboardingStage;\n title: string;\n assigneeActorId?: string;\n assigneeName?: string;\n dueDate?: string;\n sortOrder?: number;\n }) => Promise<void>;\n updateOnboardingTask: (id: string, updates: Partial<CrewOnboardingTask>) => Promise<void>;\n removeOnboardingTask: (id: string) => Promise<void>;\n toggleOnboardingTask: (id: string, completed: boolean) => Promise<void>;\n\n fetchSkillsGap: () => Promise<void>;\n}\n\nexport const useCrewFoundryStore = create<CrewFoundryState>((set) => ({\n // Overview\n metrics: [\n { label: 'Total Employees', value: '247', trend: '+5.2%' },\n { label: 'Open Positions', value: '24', trend: '+3 this week' },\n { label: 'Hiring Pipeline', value: '347', trend: '+52 applications' },\n { label: 'Employee Satisfaction', value: '4.6/5', trend: '+0.2' },\n ],\n workItems: [\n {\n id: 'w1',\n title: 'Q1 Performance Review Cycle',\n status: 'active',\n owner: 'HR',\n updatedAt: '2h ago',\n },\n {\n id: 'w2',\n title: 'New Health Benefits Enrollment',\n status: 'active',\n owner: 'Benefits',\n updatedAt: '5h ago',\n },\n {\n id: 'w3',\n title: 'Engineering Headcount Planning',\n status: 'at_risk',\n owner: 'Recruiting',\n updatedAt: '1d ago',\n },\n {\n id: 'w4',\n title: 'Employee Onboarding Automation',\n status: 'completed',\n owner: 'HR Ops',\n updatedAt: '2d ago',\n },\n ],\n selectedView: 'overview',\n initialized: false,\n loading: false,\n error: null,\n initParams: {},\n\n // Domain data\n employees: [],\n departments: [],\n jobPostings: [],\n candidates: [],\n interviews: [],\n courses: [],\n learningPaths: [],\n marketplaceServices: [],\n announcements: [],\n teams: [],\n projects: [],\n payrollRecords: [],\n leaveRequests: [],\n timeEntries: [],\n notifications: [],\n paymentMethods: [],\n invoices: [],\n vendors: [],\n clients: [],\n employeeDocuments: [],\n onboardings: [],\n onboardingsLoaded: false,\n jobPostingMatches: {},\n jobPostingMatchesLoading: false,\n payrollRuns: [],\n payrollRunsLoaded: false,\n payrollRunsLoading: false,\n payslipsByRun: {},\n payslipsLoading: false,\n skillsGap: [],\n skillsGapLoaded: false,\n\n // Actions\n initializeData: async (params) => {\n if (!params.workspaceId) {\n return;\n }\n set({ loading: true, error: null, initParams: params });\n try {\n const [workspaceWithOverlapData, billingOverlap, payrollData] = await Promise.all([\n runOverlap<WorkspaceBootstrapWithOverlapResponse>(\n WORKSPACE_BOOTSTRAP_WITH_OVERLAP_QUERY,\n {\n workspaceId: params.workspaceId,\n projectPagination: { limit: 50, offset: 0 },\n groupFirst: 30,\n conversationFirst: 30,\n },\n params,\n 'Unable to load workspace bootstrap with overlap data'\n ),\n // getBillingAccountsByOrg lives on the GLOBAL supergraph, not wspace —\n // route it to the global gateway (org context comes from the x-org-id\n // header the gateway auto-attaches for global-gateway calls).\n runOverlap<OverlapBillingResponse>(\n OVERLAP_BILLING_QUERY,\n undefined,\n params,\n 'Unable to load overlap billing data',\n 'global'\n ),\n // Payroll (BOFF-4887): fetched via runOverlap (best-effort), not\n // runGraphql, because crewPayrollRecords is gated by the dedicated\n // crewfoundry-payroll RBAC resource — a caller without that grant\n // (e.g. Workforce Coordinator) gets a FORBIDDEN response here, which\n // must degrade to an empty payroll list, not fail the whole\n // dashboard load.\n runOverlap<CrewPayrollRecordsResponse>(\n CREW_PAYROLL_RECORDS_QUERY,\n undefined,\n params,\n 'Unable to load payroll records'\n ),\n ]);\n\n const bootstrap = workspaceWithOverlapData\n ? workspaceWithOverlapData.crewfoundryBootstrap\n : (\n await runGraphql<BootstrapPayload>(\n BOOTSTRAP_QUERY,\n undefined,\n params,\n 'Unable to load CrewFoundry data'\n )\n ).crewfoundryBootstrap;\n\n const overlapTeams = workspaceWithOverlapData\n ? mapOverlapTeams(workspaceWithOverlapData.groups.edges)\n : [];\n const overlapProjects = workspaceWithOverlapData\n ? mapOverlapProjects(workspaceWithOverlapData.projects.items, overlapTeams)\n : [];\n const overlapNotifications = workspaceWithOverlapData\n ? mapConversationNotifications(workspaceWithOverlapData.conversations.edges)\n : [];\n\n const teams = overlapTeams.length > 0 ? overlapTeams : bootstrap.teams;\n const projects = overlapProjects.length > 0 ? overlapProjects : bootstrap.projects;\n const notifications = overlapNotifications;\n const paymentMethods = billingOverlap ? mapBillingMethods(billingOverlap) : [];\n\n // Normalize enum casing (UPPER_SNAKE from the backend -> lower_snake) once, per entity, so\n // every downstream count/filter/badge and the metrics below all operate on the same casing.\n const nEmployees = bootstrap.employees.map((e) => lowerEnumKeys(e, ENUM_KEYS.employee));\n const nDepartments = bootstrap.departments.map((d) => lowerEnumKeys(d, ENUM_KEYS.department));\n const nJobPostings = bootstrap.jobPostings.map((j) => lowerEnumKeys(j, ENUM_KEYS.jobPosting));\n const nCandidates = bootstrap.candidates.map((c) => lowerEnumKeys(c, ENUM_KEYS.candidate));\n const nInterviews = bootstrap.interviews.map((i) => lowerEnumKeys(i, ENUM_KEYS.interview));\n const nCourses: LearningCourse[] = [];\n const nMarketplaceServices: MarketplaceService[] = [];\n const nAnnouncements = bootstrap.announcements.map((a) =>\n lowerEnumKeys(a, ENUM_KEYS.announcement)\n );\n const nTeams = teams.map((t) => lowerEnumKeys(t, ENUM_KEYS.team));\n const nProjects = projects.map((p) => lowerEnumKeys(p, ENUM_KEYS.project));\n // Payroll (BOFF-4887): sourced from its own dedicated, crewfoundry-payroll\n // gated query, not the bootstrap response — see CREW_PAYROLL_RECORDS_QUERY\n // above. `payrollData` is null when the query fails or the caller lacks\n // payroll access; either way that degrades to an empty list.\n const nPayrollRecords = (payrollData?.crewPayrollRecords ?? []).map((r) =>\n lowerEnumKeys(r, ENUM_KEYS.payrollRecord)\n );\n const nLeaveRequests = bootstrap.leaveRequests.map((r) =>\n lowerEnumKeys(r, ENUM_KEYS.leaveRequest)\n );\n const nTimeEntries = bootstrap.timeEntries.map((t) => lowerEnumKeys(t, ENUM_KEYS.timeEntry));\n const nNotifications = notifications.map((n) => lowerEnumKeys(n, ENUM_KEYS.notification));\n const nPaymentMethods = paymentMethods.map((m) => lowerEnumKeys(m, ENUM_KEYS.paymentMethod));\n const nInvoices: Invoice[] = [];\n const nVendors = bootstrap.vendors.map((v) => lowerEnumKeys(v, ENUM_KEYS.vendor));\n const nClients = bootstrap.clients.map((c) => lowerEnumKeys(c, ENUM_KEYS.client));\n\n set({\n employees: nEmployees,\n departments: nDepartments,\n jobPostings: nJobPostings,\n candidates: nCandidates,\n interviews: nInterviews,\n courses: nCourses,\n learningPaths: [],\n marketplaceServices: nMarketplaceServices,\n announcements: nAnnouncements,\n teams: nTeams,\n projects: nProjects,\n payrollRecords: nPayrollRecords,\n leaveRequests: nLeaveRequests,\n timeEntries: nTimeEntries,\n notifications: nNotifications,\n paymentMethods: nPaymentMethods,\n invoices: nInvoices,\n vendors: nVendors,\n clients: nClients,\n metrics: [\n { label: 'Total Employees', value: String(nEmployees.length), trend: 'Live' },\n {\n label: 'Open Positions',\n value: String(nJobPostings.filter((j) => j.status === 'active').length),\n trend: 'Live',\n },\n { label: 'Hiring Pipeline', value: String(nCandidates.length), trend: 'Live' },\n {\n label: 'Cross-Team Projects',\n value: String(nProjects.filter((project) => project.status === 'active').length),\n trend: 'Live',\n },\n ],\n initialized: true,\n loading: false,\n });\n } catch (error) {\n set({\n error: error instanceof Error ? error.message : 'Unable to load CrewFoundry data',\n loading: false,\n });\n }\n },\n setSelectedView: (view) => set({ selectedView: view }),\n setWorkItems: (items) => set({ workItems: items }),\n addEmployee: async (employee) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewEmployee: Employee }>(\n CREATE_EMPLOYEE_MUTATION,\n {\n input: {\n ...employee,\n status: normalizeStatus(employee.status),\n type: normalizeStatus(employee.type),\n },\n },\n params,\n 'Unable to create employee'\n );\n set((state) => ({\n employees: [lowerEnumKeys(data.createCrewEmployee, ENUM_KEYS.employee), ...state.employees],\n }));\n },\n updateEmployee: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const payload = {\n ...updates,\n ...(updates.status ? { status: normalizeStatus(updates.status) } : {}),\n ...(updates.type ? { type: normalizeStatus(updates.type) } : {}),\n };\n const data = await runGraphql<{ updateCrewEmployee: Employee }>(\n UPDATE_EMPLOYEE_MUTATION,\n { id, input: payload },\n params,\n 'Unable to update employee'\n );\n set((state) => ({\n employees: state.employees.map((e) =>\n e.id === id ? lowerEnumKeys(data.updateCrewEmployee, ENUM_KEYS.employee) : e\n ),\n }));\n },\n removeEmployee: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewEmployee: boolean }>(\n REMOVE_EMPLOYEE_MUTATION,\n { id },\n params,\n 'Unable to remove employee'\n );\n set((state) => ({ employees: state.employees.filter((e) => e.id !== id) }));\n },\n addJobPosting: async (job) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewJobPosting: JobPosting }>(\n CREATE_JOB_MUTATION,\n {\n input: {\n ...job,\n status: normalizeStatus(job.status),\n },\n },\n params,\n 'Unable to create job posting'\n );\n set((state) => ({\n jobPostings: [\n lowerEnumKeys(data.createCrewJobPosting, ENUM_KEYS.jobPosting),\n ...state.jobPostings,\n ],\n }));\n },\n updateJobPosting: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewJobPosting: JobPosting }>(\n UPDATE_JOB_MUTATION,\n {\n id,\n input: {\n ...updates,\n ...(updates.status ? { status: normalizeStatus(updates.status) } : {}),\n },\n },\n params,\n 'Unable to update job posting'\n );\n set((state) => ({\n jobPostings: state.jobPostings.map((j) =>\n j.id === id ? lowerEnumKeys(data.updateCrewJobPosting, ENUM_KEYS.jobPosting) : j\n ),\n }));\n },\n addCandidate: async (candidate) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewCandidate: Candidate }>(\n CREATE_CANDIDATE_MUTATION,\n { input: { ...candidate, status: normalizeStatus(candidate.status) } },\n params,\n 'Unable to create candidate'\n );\n set((state) => ({\n candidates: [\n lowerEnumKeys(data.createCrewCandidate, ENUM_KEYS.candidate),\n ...state.candidates,\n ],\n }));\n },\n updateCandidate: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewCandidate: Candidate }>(\n UPDATE_CANDIDATE_MUTATION,\n {\n id,\n input: {\n ...updates,\n ...(updates.status ? { status: normalizeStatus(updates.status) } : {}),\n },\n },\n params,\n 'Unable to update candidate'\n );\n set((state) => ({\n candidates: state.candidates.map((c) =>\n c.id === id ? lowerEnumKeys(data.updateCrewCandidate, ENUM_KEYS.candidate) : c\n ),\n }));\n },\n addAnnouncement: async (announcement) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewAnnouncement: Announcement }>(\n CREATE_ANNOUNCEMENT_MUTATION,\n { input: announcement },\n params,\n 'Unable to create announcement'\n );\n set((state) => ({\n announcements: [\n lowerEnumKeys(data.createCrewAnnouncement, ENUM_KEYS.announcement),\n ...state.announcements,\n ],\n }));\n },\n addLeaveRequest: async (request) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewLeaveRequest: LeaveRequest }>(\n CREATE_LEAVE_MUTATION,\n {\n input: {\n ...request,\n type: normalizeStatus(request.type),\n status: normalizeStatus(request.status),\n },\n },\n params,\n 'Unable to create leave request'\n );\n set((state) => ({\n leaveRequests: [\n lowerEnumKeys(data.createCrewLeaveRequest, ENUM_KEYS.leaveRequest),\n ...state.leaveRequests,\n ],\n }));\n },\n updateLeaveRequest: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewLeaveRequest: LeaveRequest }>(\n UPDATE_LEAVE_MUTATION,\n {\n id,\n input: {\n ...updates,\n ...(updates.type ? { type: normalizeStatus(updates.type) } : {}),\n ...(updates.status ? { status: normalizeStatus(updates.status) } : {}),\n },\n },\n params,\n 'Unable to update leave request'\n );\n set((state) => ({\n leaveRequests: state.leaveRequests.map((l) =>\n l.id === id ? lowerEnumKeys(data.updateCrewLeaveRequest, ENUM_KEYS.leaveRequest) : l\n ),\n }));\n },\n markNotificationRead: async (id) => {\n set((state) => ({\n notifications: state.notifications.map((n) => (n.id === id ? { ...n, read: true } : n)),\n }));\n },\n markAllNotificationsRead: async () => {\n set((state) => ({ notifications: state.notifications.map((n) => ({ ...n, read: true })) }));\n },\n\n // ── Extra removes for existing entities ───────────────────────\n removeJobPosting: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewJobPosting: boolean }>(\n REMOVE_JOB_MUTATION,\n { id },\n params,\n 'Unable to remove job posting'\n );\n set((state) => ({ jobPostings: state.jobPostings.filter((j) => j.id !== id) }));\n },\n removeCandidate: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewCandidate: boolean }>(\n REMOVE_CANDIDATE_MUTATION,\n { id },\n params,\n 'Unable to remove candidate'\n );\n set((state) => ({ candidates: state.candidates.filter((c) => c.id !== id) }));\n },\n updateAnnouncement: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewAnnouncement: Announcement }>(\n UPDATE_ANNOUNCEMENT_MUTATION,\n { id, input: updates },\n params,\n 'Unable to update announcement'\n );\n set((state) => ({\n announcements: state.announcements.map((a) =>\n a.id === id ? lowerEnumKeys(data.updateCrewAnnouncement, ENUM_KEYS.announcement) : a\n ),\n }));\n },\n removeAnnouncement: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewAnnouncement: boolean }>(\n REMOVE_ANNOUNCEMENT_MUTATION,\n { id },\n params,\n 'Unable to remove announcement'\n );\n set((state) => ({ announcements: state.announcements.filter((a) => a.id !== id) }));\n },\n\n // ── Interview (enum: type, status) ────────────────────────────\n addInterview: async (interview) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewInterview: Interview }>(\n CREATE_INTERVIEW_MUTATION,\n {\n input: {\n ...interview,\n type: normalizeStatus(interview.type),\n status: normalizeStatus(interview.status),\n },\n },\n params,\n 'Unable to schedule interview'\n );\n set((state) => ({\n interviews: [\n lowerEnumKeys(data.createCrewInterview, ENUM_KEYS.interview),\n ...state.interviews,\n ],\n }));\n },\n updateInterview: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewInterview: Interview }>(\n UPDATE_INTERVIEW_MUTATION,\n {\n id,\n input: {\n ...updates,\n ...(updates.type ? { type: normalizeStatus(updates.type) } : {}),\n ...(updates.status ? { status: normalizeStatus(updates.status) } : {}),\n },\n },\n params,\n 'Unable to update interview'\n );\n set((state) => ({\n interviews: state.interviews.map((i) =>\n i.id === id ? lowerEnumKeys(data.updateCrewInterview, ENUM_KEYS.interview) : i\n ),\n }));\n },\n removeInterview: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewInterview: boolean }>(\n REMOVE_INTERVIEW_MUTATION,\n { id },\n params,\n 'Unable to remove interview'\n );\n set((state) => ({ interviews: state.interviews.filter((i) => i.id !== id) }));\n },\n\n // ── Invoice data is owned by global-billing-svc ───────────────\n addInvoice: async () => {\n throw sharedOwnerError('Invoices');\n },\n updateInvoice: async () => {\n throw sharedOwnerError('Invoices');\n },\n removeInvoice: async () => {\n throw sharedOwnerError('Invoices');\n },\n\n // ── Vendor (enum: status) ─────────────────────────────────────\n addVendor: async (vendor) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewVendor: Vendor }>(\n CREATE_VENDOR_MUTATION,\n { input: { ...vendor, status: normalizeStatus(vendor.status) } },\n params,\n 'Unable to create vendor'\n );\n set((state) => ({\n vendors: [lowerEnumKeys(data.createCrewVendor, ENUM_KEYS.vendor), ...state.vendors],\n }));\n },\n updateVendor: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewVendor: Vendor }>(\n UPDATE_VENDOR_MUTATION,\n {\n id,\n input: {\n ...updates,\n ...(updates.status ? { status: normalizeStatus(updates.status) } : {}),\n },\n },\n params,\n 'Unable to update vendor'\n );\n set((state) => ({\n vendors: state.vendors.map((v) =>\n v.id === id ? lowerEnumKeys(data.updateCrewVendor, ENUM_KEYS.vendor) : v\n ),\n }));\n },\n removeVendor: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewVendor: boolean }>(\n REMOVE_VENDOR_MUTATION,\n { id },\n params,\n 'Unable to remove vendor'\n );\n set((state) => ({ vendors: state.vendors.filter((v) => v.id !== id) }));\n },\n\n // ── Client (enum: status; `since`) ────────────────────────────\n addClient: async (client) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewClient: Client }>(\n CREATE_CLIENT_MUTATION,\n { input: { ...client, status: normalizeStatus(client.status) } },\n params,\n 'Unable to create client'\n );\n set((state) => ({\n clients: [lowerEnumKeys(data.createCrewClient, ENUM_KEYS.client), ...state.clients],\n }));\n },\n updateClient: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewClient: Client }>(\n UPDATE_CLIENT_MUTATION,\n {\n id,\n input: {\n ...updates,\n ...(updates.status ? { status: normalizeStatus(updates.status) } : {}),\n },\n },\n params,\n 'Unable to update client'\n );\n set((state) => ({\n clients: state.clients.map((c) =>\n c.id === id ? lowerEnumKeys(data.updateCrewClient, ENUM_KEYS.client) : c\n ),\n }));\n },\n removeClient: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewClient: boolean }>(\n REMOVE_CLIENT_MUTATION,\n { id },\n params,\n 'Unable to remove client'\n );\n set((state) => ({ clients: state.clients.filter((c) => c.id !== id) }));\n },\n\n // ── Team (plain status; not normalized) ───────────────────────\n addTeam: async (team) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewTeam: Team }>(\n CREATE_TEAM_MUTATION,\n { input: team },\n params,\n 'Unable to create team'\n );\n set((state) => ({\n teams: [lowerEnumKeys(data.createCrewTeam, ENUM_KEYS.team), ...state.teams],\n }));\n },\n updateTeam: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewTeam: Team }>(\n UPDATE_TEAM_MUTATION,\n { id, input: updates },\n params,\n 'Unable to update team'\n );\n set((state) => ({\n teams: state.teams.map((t) =>\n t.id === id ? lowerEnumKeys(data.updateCrewTeam, ENUM_KEYS.team) : t\n ),\n }));\n },\n removeTeam: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewTeam: boolean }>(\n REMOVE_TEAM_MUTATION,\n { id },\n params,\n 'Unable to remove team'\n );\n set((state) => ({ teams: state.teams.filter((t) => t.id !== id) }));\n },\n\n // ── Project (enum: status, priority) ──────────────────────────\n addProject: async (project) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewProject: Project }>(\n CREATE_PROJECT_MUTATION,\n {\n input: {\n ...project,\n status: normalizeStatus(project.status),\n priority: normalizeStatus(project.priority),\n },\n },\n params,\n 'Unable to create project'\n );\n set((state) => ({\n projects: [lowerEnumKeys(data.createCrewProject, ENUM_KEYS.project), ...state.projects],\n }));\n },\n updateProject: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewProject: Project }>(\n UPDATE_PROJECT_MUTATION,\n {\n id,\n input: {\n ...updates,\n ...(updates.status ? { status: normalizeStatus(updates.status) } : {}),\n ...(updates.priority ? { priority: normalizeStatus(updates.priority) } : {}),\n },\n },\n params,\n 'Unable to update project'\n );\n set((state) => ({\n projects: state.projects.map((p) =>\n p.id === id ? lowerEnumKeys(data.updateCrewProject, ENUM_KEYS.project) : p\n ),\n }));\n },\n removeProject: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewProject: boolean }>(\n REMOVE_PROJECT_MUTATION,\n { id },\n params,\n 'Unable to remove project'\n );\n set((state) => ({ projects: state.projects.filter((p) => p.id !== id) }));\n },\n\n // ── Marketplace data is owned by global-store-svc ─────────────\n addMarketplaceService: async () => {\n throw sharedOwnerError('Marketplace services');\n },\n updateMarketplaceService: async () => {\n throw sharedOwnerError('Marketplace services');\n },\n removeMarketplaceService: async () => {\n throw sharedOwnerError('Marketplace services');\n },\n\n // ── Department (plain status; not normalized) ─────────────────\n addDepartment: async (department) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewDepartment: Department }>(\n CREATE_DEPARTMENT_MUTATION,\n { input: department },\n params,\n 'Unable to create department'\n );\n set((state) => ({\n departments: [\n lowerEnumKeys(data.createCrewDepartment, ENUM_KEYS.department),\n ...state.departments,\n ],\n }));\n },\n updateDepartment: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewDepartment: Department }>(\n UPDATE_DEPARTMENT_MUTATION,\n { id, input: updates },\n params,\n 'Unable to update department'\n );\n set((state) => ({\n departments: state.departments.map((d) =>\n d.id === id ? lowerEnumKeys(data.updateCrewDepartment, ENUM_KEYS.department) : d\n ),\n }));\n },\n removeDepartment: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewDepartment: boolean }>(\n REMOVE_DEPARTMENT_MUTATION,\n { id },\n params,\n 'Unable to remove department'\n );\n set((state) => ({ departments: state.departments.filter((d) => d.id !== id) }));\n },\n\n // ── Learning data is owned by the shared learning service ─────\n addCourse: async () => {\n throw sharedOwnerError('Courses');\n },\n updateCourse: async () => {\n throw sharedOwnerError('Courses');\n },\n removeCourse: async () => {\n throw sharedOwnerError('Courses');\n },\n enrollCourse: async () => {\n throw sharedOwnerError('Course enrollment');\n },\n addLearningPath: async () => {\n throw sharedOwnerError('Learning paths');\n },\n updateLearningPath: async () => {\n throw sharedOwnerError('Learning paths');\n },\n removeLearningPath: async () => {\n throw sharedOwnerError('Learning paths');\n },\n\n // ── PayrollRecord (enum: status) ──────────────────────────────\n addPayrollRecord: async (record) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewPayrollRecord: PayrollRecord }>(\n CREATE_PAYROLL_MUTATION,\n { input: { ...record, status: normalizeStatus(record.status) } },\n params,\n 'Unable to create payroll record'\n );\n set((state) => ({\n payrollRecords: [\n lowerEnumKeys(data.createCrewPayrollRecord, ENUM_KEYS.payrollRecord),\n ...state.payrollRecords,\n ],\n }));\n },\n updatePayrollRecord: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewPayrollRecord: PayrollRecord }>(\n UPDATE_PAYROLL_MUTATION,\n {\n id,\n input: {\n ...updates,\n ...(updates.status ? { status: normalizeStatus(updates.status) } : {}),\n },\n },\n params,\n 'Unable to update payroll record'\n );\n set((state) => ({\n payrollRecords: state.payrollRecords.map((p) =>\n p.id === id ? lowerEnumKeys(data.updateCrewPayrollRecord, ENUM_KEYS.payrollRecord) : p\n ),\n }));\n },\n removePayrollRecord: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewPayrollRecord: boolean }>(\n REMOVE_PAYROLL_MUTATION,\n { id },\n params,\n 'Unable to remove payroll record'\n );\n set((state) => ({ payrollRecords: state.payrollRecords.filter((p) => p.id !== id) }));\n },\n\n // ── TimeEntry (plain status; not normalized) ──────────────────\n addTimeEntry: async (entry) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewTimeEntry: TimeEntry }>(\n CREATE_TIME_ENTRY_MUTATION,\n { input: entry },\n params,\n 'Unable to create time entry'\n );\n set((state) => ({\n timeEntries: [\n lowerEnumKeys(data.createCrewTimeEntry, ENUM_KEYS.timeEntry),\n ...state.timeEntries,\n ],\n }));\n },\n updateTimeEntry: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewTimeEntry: TimeEntry }>(\n UPDATE_TIME_ENTRY_MUTATION,\n { id, input: updates },\n params,\n 'Unable to update time entry'\n );\n set((state) => ({\n timeEntries: state.timeEntries.map((t) =>\n t.id === id ? lowerEnumKeys(data.updateCrewTimeEntry, ENUM_KEYS.timeEntry) : t\n ),\n }));\n },\n removeTimeEntry: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewTimeEntry: boolean }>(\n REMOVE_TIME_ENTRY_MUTATION,\n { id },\n params,\n 'Unable to remove time entry'\n );\n set((state) => ({ timeEntries: state.timeEntries.filter((t) => t.id !== id) }));\n },\n\n // ── BOFF-4195: AI-assisted candidate/worker matching ────────────\n fetchJobPostingMatches: async (jobPostingId) => {\n const params = useCrewFoundryStore.getState().initParams;\n set({ jobPostingMatchesLoading: true });\n try {\n const data = await runGraphql<{ crewJobPostingMatches: CrewCandidateMatch[] }>(\n JOB_POSTING_MATCHES_QUERY,\n { jobPostingId },\n params,\n 'Unable to load candidate matches'\n );\n const normalized = data.crewJobPostingMatches.map((match) => ({\n ...match,\n candidate: lowerEnumKeys(match.candidate, ENUM_KEYS.candidate),\n }));\n set((state) => ({\n jobPostingMatches: { ...state.jobPostingMatches, [jobPostingId]: normalized },\n jobPostingMatchesLoading: false,\n }));\n } catch (err) {\n set({ jobPostingMatchesLoading: false });\n throw err;\n }\n },\n\n // ── BOFF-4193: native payroll calculation engine ────────────────\n fetchPayrollRuns: async () => {\n const params = useCrewFoundryStore.getState().initParams;\n set({ payrollRunsLoading: true });\n try {\n const data = await runGraphql<{ crewPayrollRuns: CrewPayrollRun[] }>(\n CREW_PAYROLL_RUNS_QUERY,\n undefined,\n params,\n 'Unable to load payroll runs'\n );\n set({\n payrollRuns: data.crewPayrollRuns,\n payrollRunsLoaded: true,\n payrollRunsLoading: false,\n });\n } catch (err) {\n set({ payrollRunsLoading: false });\n throw err;\n }\n },\n createPayrollRun: async (input) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewPayrollRun: CrewPayrollRun }>(\n CREATE_PAYROLL_RUN_MUTATION,\n { input },\n params,\n 'Unable to create payroll run'\n );\n set((state) => ({ payrollRuns: [data.createCrewPayrollRun, ...state.payrollRuns] }));\n },\n calculatePayrollRun: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ calculateCrewPayrollRun: CrewPayrollRun }>(\n CALCULATE_PAYROLL_RUN_MUTATION,\n { id },\n params,\n 'Unable to calculate payroll run'\n );\n set((state) => ({\n payrollRuns: state.payrollRuns.map((r) => (r.id === id ? data.calculateCrewPayrollRun : r)),\n // Recalculating replaces the run's payslips server-side — drop any\n // previously cached payslip view for this run so the next open\n // refetches the fresh set instead of showing stale figures.\n payslipsByRun: Object.fromEntries(\n Object.entries(state.payslipsByRun).filter(([runId]) => runId !== id)\n ),\n }));\n },\n approvePayrollRun: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ approveCrewPayrollRun: CrewPayrollRun }>(\n APPROVE_PAYROLL_RUN_MUTATION,\n { id },\n params,\n 'Unable to approve payroll run'\n );\n set((state) => ({\n payrollRuns: state.payrollRuns.map((r) => (r.id === id ? data.approveCrewPayrollRun : r)),\n }));\n },\n removePayrollRun: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewPayrollRun: boolean }>(\n REMOVE_PAYROLL_RUN_MUTATION,\n { id },\n params,\n 'Unable to remove payroll run'\n );\n set((state) => ({\n payrollRuns: state.payrollRuns.filter((r) => r.id !== id),\n payslipsByRun: Object.fromEntries(\n Object.entries(state.payslipsByRun).filter(([runId]) => runId !== id)\n ),\n }));\n },\n fetchPayslipsForRun: async (payrollRunId) => {\n const params = useCrewFoundryStore.getState().initParams;\n set({ payslipsLoading: true });\n try {\n const data = await runGraphql<{ crewPayslips: CrewPayslip[] }>(\n CREW_PAYSLIPS_QUERY,\n { filter: { payrollRunId } },\n params,\n 'Unable to load payslips'\n );\n set((state) => ({\n payslipsByRun: { ...state.payslipsByRun, [payrollRunId]: data.crewPayslips },\n payslipsLoading: false,\n }));\n } catch (err) {\n set({ payslipsLoading: false });\n throw err;\n }\n },\n\n // ── BOFF-3987: employee documents ──────────────────────────────\n fetchEmployeeDocuments: async (employeeId) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ employeeDocuments: CrewEmployeeDocument[] }>(\n EMPLOYEE_DOCUMENTS_QUERY,\n { employeeId },\n params,\n 'Unable to load employee documents'\n );\n set((state) => ({\n // Replace only this employee's documents; leave any other employee's\n // already-fetched documents in the flat cache untouched.\n employeeDocuments: [\n ...state.employeeDocuments.filter((d) => d.employeeId !== employeeId),\n ...data.employeeDocuments,\n ],\n }));\n },\n uploadEmployeeDocument: async ({ employeeId, file, title, category }) => {\n const params = useCrewFoundryStore.getState().initParams;\n\n const folder = await runGraphql<{ ensureFolderByPath: { id: string } }>(\n ENSURE_FOLDER_BY_PATH_MUTATION,\n { path: '/CrewFoundry/Employee Documents' },\n params,\n 'Unable to resolve the employee documents folder'\n );\n\n const extension = file.name.includes('.')\n ? file.name.substring(file.name.lastIndexOf('.') + 1)\n : '';\n const initiated = await runGraphql<{\n initiateUpload: { session: { id: string }; uploadUrl: string | null };\n }>(\n INITIATE_UPLOAD_MUTATION,\n {\n input: {\n fileName: file.name,\n fileSize: String(file.size),\n mimeType: file.type || 'application/octet-stream',\n extension,\n folderId: folder.ensureFolderByPath.id,\n },\n },\n params,\n 'Unable to start the document upload'\n );\n\n if (initiated.initiateUpload.uploadUrl) {\n const putResponse = await fetch(initiated.initiateUpload.uploadUrl, {\n method: 'PUT',\n headers: { 'Content-Type': file.type || 'application/octet-stream' },\n body: file,\n });\n if (!putResponse.ok) {\n throw new Error(`Failed to upload file bytes (status ${putResponse.status})`);\n }\n }\n\n const completed = await runGraphql<{ completeUpload: { file: { id: string } } }>(\n COMPLETE_UPLOAD_MUTATION,\n {\n input: {\n sessionId: initiated.initiateUpload.session.id,\n fileSize: String(file.size),\n folderId: folder.ensureFolderByPath.id,\n },\n },\n params,\n 'Unable to finish the document upload'\n );\n\n const attached = await runGraphql<{ attachEmployeeDocument: CrewEmployeeDocument }>(\n ATTACH_EMPLOYEE_DOCUMENT_MUTATION,\n {\n input: {\n employeeId,\n fileId: completed.completeUpload.file.id,\n title,\n category: category || undefined,\n },\n },\n params,\n 'Unable to attach the document to this employee'\n );\n\n set((state) => ({\n employeeDocuments: [attached.attachEmployeeDocument, ...state.employeeDocuments],\n }));\n },\n detachEmployeeDocument: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ detachEmployeeDocument: boolean }>(\n DETACH_EMPLOYEE_DOCUMENT_MUTATION,\n { id },\n params,\n 'Unable to remove this document'\n );\n set((state) => ({\n employeeDocuments: state.employeeDocuments.filter((d) => d.id !== id),\n }));\n },\n\n // ── BOFF-3983: onboarding ───────────────────────────────────────\n fetchOnboardings: async () => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ crewOnboardings: CrewOnboarding[] }>(\n ONBOARDINGS_QUERY,\n {},\n params,\n 'Unable to load onboarding'\n );\n set({\n onboardings: data.crewOnboardings.map(lowerOnboarding),\n onboardingsLoaded: true,\n });\n },\n createOnboarding: async (input) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewOnboarding: CrewOnboarding }>(\n CREATE_ONBOARDING_MUTATION,\n { input },\n params,\n 'Unable to start onboarding'\n );\n set((state) => ({\n onboardings: [lowerOnboarding(data.createCrewOnboarding), ...state.onboardings],\n }));\n },\n updateOnboarding: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewOnboarding: CrewOnboarding }>(\n UPDATE_ONBOARDING_MUTATION,\n { id, input: updates },\n params,\n 'Unable to update onboarding'\n );\n set((state) => ({\n onboardings: state.onboardings.map((o) =>\n o.id === id ? lowerOnboarding(data.updateCrewOnboarding) : o\n ),\n }));\n },\n removeOnboarding: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewOnboarding: boolean }>(\n REMOVE_ONBOARDING_MUTATION,\n { id },\n params,\n 'Unable to remove onboarding'\n );\n set((state) => ({ onboardings: state.onboardings.filter((o) => o.id !== id) }));\n },\n createOnboardingTask: async (input) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ createCrewOnboardingTask: CrewOnboardingTask }>(\n CREATE_ONBOARDING_TASK_MUTATION,\n { input },\n params,\n 'Unable to create onboarding task'\n );\n const task = lowerEnumKeys(data.createCrewOnboardingTask, ENUM_KEYS.onboardingTask);\n set((state) => ({\n onboardings: state.onboardings.map((o) =>\n o.id === task.onboardingId\n ? {\n ...o,\n tasks: [...o.tasks, task],\n totalTaskCount: o.totalTaskCount + 1,\n }\n : o\n ),\n }));\n },\n updateOnboardingTask: async (id, updates) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ updateCrewOnboardingTask: CrewOnboardingTask }>(\n UPDATE_ONBOARDING_TASK_MUTATION,\n { id, input: updates },\n params,\n 'Unable to update onboarding task'\n );\n const task = lowerEnumKeys(data.updateCrewOnboardingTask, ENUM_KEYS.onboardingTask);\n set((state) => ({\n onboardings: state.onboardings.map((o) =>\n o.id === task.onboardingId\n ? { ...o, tasks: o.tasks.map((t) => (t.id === id ? task : t)) }\n : o\n ),\n }));\n },\n removeOnboardingTask: async (id) => {\n const params = useCrewFoundryStore.getState().initParams;\n await runGraphql<{ removeCrewOnboardingTask: boolean }>(\n REMOVE_ONBOARDING_TASK_MUTATION,\n { id },\n params,\n 'Unable to remove onboarding task'\n );\n set((state) => ({\n onboardings: state.onboardings.map((o) => {\n if (!o.tasks.some((t) => t.id === id)) return o;\n return {\n ...o,\n tasks: o.tasks.filter((t) => t.id !== id),\n totalTaskCount: Math.max(0, o.totalTaskCount - 1),\n };\n }),\n }));\n },\n toggleOnboardingTask: async (id, completed) => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ toggleCrewOnboardingTask: CrewOnboardingTask }>(\n TOGGLE_ONBOARDING_TASK_MUTATION,\n { id, completed },\n params,\n 'Unable to update onboarding task'\n );\n const task = lowerEnumKeys(data.toggleCrewOnboardingTask, ENUM_KEYS.onboardingTask);\n set((state) => ({\n onboardings: state.onboardings.map((o) => {\n if (o.id !== task.onboardingId) return o;\n const tasks = o.tasks.map((t) => (t.id === id ? task : t));\n const completedTaskCount = tasks.filter((t) => t.completedAt).length;\n return {\n ...o,\n tasks,\n completedTaskCount,\n progressPercent:\n tasks.length === 0 ? 0 : Math.round((completedTaskCount / tasks.length) * 1000) / 10,\n };\n }),\n }));\n },\n\n // ── Skills supply/demand ────────────────────────────────────────\n fetchSkillsGap: async () => {\n const params = useCrewFoundryStore.getState().initParams;\n const data = await runGraphql<{ crewSkillsGap: CrewSkillsGapRow[] }>(\n SKILLS_GAP_QUERY,\n undefined,\n params,\n 'Unable to load skills coverage'\n );\n // No enum normalization: every field is a plain string/number, and `skill`\n // is a free-text tag whose casing must be preserved as recorded.\n set({ skillsGap: data.crewSkillsGap, skillsGapLoaded: true });\n },\n}));\n"],"mappings":"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"}
|