@burdenoff/microfe-crewfoundry 2026.1029.2 → 2026.1030.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/crewfoundry/lib/useFocusedRecordParam.d.ts +51 -0
  2. package/dist/crewfoundry/lib/useFocusedRecordParam.d.ts.map +1 -0
  3. package/dist/crewfoundry/lib/useFocusedRecordParam.js +38 -0
  4. package/dist/crewfoundry/lib/useFocusedRecordParam.js.map +1 -0
  5. package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.d.ts.map +1 -1
  6. package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js +119 -112
  7. package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js.map +1 -1
  8. package/dist/crewfoundry/pages/CompanyClientManagementPagePage.d.ts.map +1 -1
  9. package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js +78 -71
  10. package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js.map +1 -1
  11. package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.d.ts.map +1 -1
  12. package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js +88 -81
  13. package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js.map +1 -1
  14. package/dist/crewfoundry/pages/HiringCandidatesPagePage.d.ts.map +1 -1
  15. package/dist/crewfoundry/pages/HiringCandidatesPagePage.js +133 -126
  16. package/dist/crewfoundry/pages/HiringCandidatesPagePage.js.map +1 -1
  17. package/dist/crewfoundry/pages/HiringInterviewsPagePage.d.ts.map +1 -1
  18. package/dist/crewfoundry/pages/HiringInterviewsPagePage.js +150 -143
  19. package/dist/crewfoundry/pages/HiringInterviewsPagePage.js.map +1 -1
  20. package/dist/crewfoundry/pages/HrDepartmentsPagePage.d.ts.map +1 -1
  21. package/dist/crewfoundry/pages/HrDepartmentsPagePage.js +110 -103
  22. package/dist/crewfoundry/pages/HrDepartmentsPagePage.js.map +1 -1
  23. package/dist/crewfoundry/pages/HrLeaveManagementPagePage.d.ts.map +1 -1
  24. package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js +173 -162
  25. package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js.map +1 -1
  26. package/dist/crewfoundry/pages/HrPayrollPagePage.d.ts.map +1 -1
  27. package/dist/crewfoundry/pages/HrPayrollPagePage.js +169 -158
  28. package/dist/crewfoundry/pages/HrPayrollPagePage.js.map +1 -1
  29. package/dist/crewfoundry/pages/HrTimeTrackingPagePage.d.ts.map +1 -1
  30. package/dist/crewfoundry/pages/HrTimeTrackingPagePage.js +179 -163
  31. package/dist/crewfoundry/pages/HrTimeTrackingPagePage.js.map +1 -1
  32. package/dist/utils/pageContext.d.ts +43 -4
  33. package/dist/utils/pageContext.d.ts.map +1 -1
  34. package/dist/utils/pageContext.js +10 -3
  35. package/dist/utils/pageContext.js.map +1 -1
  36. package/package.json +3 -2
@@ -1 +1 @@
1
- {"version":3,"file":"CommunicationsAnnouncementsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/CommunicationsAnnouncementsPagePage.tsx"],"sourcesContent":["import {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n Input,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { formatRelativeTime } from '@burdenoff/fe-libs/shared/utils';\nimport { Eye, MessageSquare, Pencil, Pin, PinOff, Plus, Search, Send, Trash2 } from 'lucide-react';\nimport { useMemo, useState } from 'react';\nimport { useNavigate } from 'react-router';\nimport {\n CONVERSATIONS_ROUTE,\n useCrewFoundryStore,\n type Announcement,\n} from '../store/crewfoundryStore';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { AvatarStack } from '../components/viz';\n\nconst CATEGORY_OPTIONS = [\n { label: 'General', value: 'general' },\n { label: 'HR', value: 'hr' },\n { label: 'IT', value: 'it' },\n { label: 'Emergency', value: 'emergency' },\n { label: 'Event', value: 'event' },\n];\n\nconst PRIORITY_OPTIONS = [\n { label: 'Low', value: 'low' },\n { label: 'Medium', value: 'medium' },\n { label: 'High', value: 'high' },\n { label: 'Urgent', value: 'urgent' },\n];\n\n// Category → tokenized pill styling (semantic tokens only — themes cleanly).\nconst CATEGORY_STYLE: Record<Announcement['category'], string> = {\n general: 'bg-bg-sunken text-text-secondary',\n hr: 'bg-status-success-bg text-status-success-text',\n it: 'bg-bg-sunken text-text-secondary',\n emergency: 'bg-status-error-bg text-status-error-text',\n event: 'bg-status-info-bg text-status-info-text',\n};\n\nconst PRIORITY_STYLE: Record<Announcement['priority'], string> = {\n low: 'bg-bg-sunken text-text-secondary',\n medium: 'bg-bg-sunken text-text-secondary',\n high: 'bg-status-warning-bg text-status-warning-text',\n urgent: 'bg-status-error-bg text-status-error-text',\n};\n\nconst CREATE_FIELDS: FieldConfig[] = [\n { name: 'title', label: 'Title', type: 'text', required: true, colSpan: 2 },\n { name: 'content', label: 'Content', type: 'textarea', required: true, colSpan: 2 },\n { name: 'author', label: 'Author', type: 'text', required: true },\n { name: 'authorRole', label: 'Author Role', type: 'text', required: true },\n {\n name: 'category',\n label: 'Category',\n type: 'select',\n required: true,\n options: CATEGORY_OPTIONS,\n placeholder: 'Select a category',\n },\n {\n name: 'priority',\n label: 'Priority',\n type: 'select',\n required: true,\n options: PRIORITY_OPTIONS,\n placeholder: 'Select a priority',\n },\n { name: 'tags', label: 'Tags', type: 'tags', placeholder: 'Add a tag and press Enter' },\n { name: 'isPinned', label: 'Pin to top', type: 'switch' },\n];\n\n// Author/role are immutable after creation (they are not part of the update\n// input), so the edit form omits them.\nconst EDIT_FIELDS: FieldConfig[] = CREATE_FIELDS.filter(\n (field) => field.name !== 'author' && field.name !== 'authorRole'\n);\n\nconst relativeTime = (value: string): string => {\n if (!value) {\n return '';\n }\n try {\n return formatRelativeTime(value);\n } catch {\n return value;\n }\n};\n\nexport function CommunicationsAnnouncementsPagePage() {\n const { announcements, notifications, addAnnouncement, updateAnnouncement, removeAnnouncement } =\n useCrewFoundryStore();\n const { showToast } = useGlobalUi();\n const navigate = useNavigate();\n const form = useFormModal<Announcement>();\n const [deleteTarget, setDeleteTarget] = useState<Announcement | null>(null);\n const [deleting, setDeleting] = useState(false);\n const [search, setSearch] = useState('');\n\n const editing = form.context;\n\n // Real conversations surfaced by the workspace overlap query land in the store\n // as `communications`-category notifications (id `conversation-*`). Render the\n // rail from that real data — no fabricated threads.\n const conversations = useMemo(\n () => notifications.filter((n) => n.category === 'communications'),\n [notifications]\n );\n\n const query = search.trim().toLowerCase();\n const filtered = useMemo(\n () =>\n query.length === 0\n ? announcements\n : announcements.filter((a) =>\n `${a.title} ${a.content} ${a.tags.join(' ')}`.toLowerCase().includes(query)\n ),\n [announcements, query]\n );\n\n const pinned = filtered.filter((a) => a.isPinned);\n const regular = filtered.filter((a) => !a.isPinned);\n\n const handleSubmit = async (values: FormValues) => {\n const tags = Array.isArray(values.tags) ? values.tags : [];\n if (editing) {\n await updateAnnouncement(editing.id, {\n title: String(values.title),\n content: String(values.content),\n category: String(values.category) as Announcement['category'],\n priority: String(values.priority) as Announcement['priority'],\n isPinned: Boolean(values.isPinned),\n tags,\n });\n showToast('Announcement updated', 'success');\n return;\n }\n await addAnnouncement({\n id: crypto.randomUUID(),\n title: String(values.title),\n content: String(values.content),\n author: String(values.author),\n authorRole: String(values.authorRole),\n category: String(values.category) as Announcement['category'],\n priority: String(values.priority) as Announcement['priority'],\n isPinned: Boolean(values.isPinned),\n createdAt: new Date().toISOString().slice(0, 10),\n viewCount: 0,\n tags,\n });\n showToast('Announcement published', 'success');\n };\n\n const togglePin = async (announcement: Announcement) => {\n try {\n await updateAnnouncement(announcement.id, { isPinned: !announcement.isPinned });\n showToast(announcement.isPinned ? 'Unpinned' : 'Pinned to top', 'success');\n } catch (error) {\n showToast(error instanceof Error ? error.message : 'Unable to update announcement', 'error');\n }\n };\n\n const confirmDelete = async () => {\n if (!deleteTarget) {\n return;\n }\n setDeleting(true);\n try {\n await removeAnnouncement(deleteTarget.id);\n showToast('Announcement deleted', 'success');\n setDeleteTarget(null);\n } catch (error) {\n showToast(error instanceof Error ? error.message : 'Unable to delete announcement', 'error');\n } finally {\n setDeleting(false);\n }\n };\n\n // Messaging is owned by the shell's shared Conversations app\n // (wspace-conversations-svc); send there instead of a local composer that\n // could not deliver anything.\n const openMessages = () => navigate(CONVERSATIONS_ROUTE);\n\n const initialValues: FormValues | undefined = editing\n ? {\n title: editing.title,\n content: editing.content,\n category: editing.category,\n priority: editing.priority,\n tags: editing.tags,\n isPinned: editing.isPinned,\n }\n : undefined;\n\n const renderCard = (ann: Announcement, isPinnedCard: boolean) => (\n <GlassCard\n key={ann.id}\n treatment={isPinnedCard ? 'glass' : 'plain'}\n glow={isPinnedCard}\n className=\"transition-shadow hover:shadow-elevation-2\"\n >\n <CardHeader className=\"pb-3\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <Badge className={`border-transparent text-[10px] ${PRIORITY_STYLE[ann.priority]}`}>\n {formatEnumLabel(ann.priority)}\n </Badge>\n <Badge className={`border-transparent text-[10px] ${CATEGORY_STYLE[ann.category]}`}>\n {formatEnumLabel(ann.category)}\n </Badge>\n <span className=\"flex items-center gap-1.5 text-xs text-text-secondary\">\n <AvatarStack people={[{ name: ann.author }]} size={20} />\n {ann.author}\n <span className=\"text-text-muted\">· {relativeTime(ann.createdAt)}</span>\n </span>\n </div>\n <div className=\"flex items-center gap-0.5\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className={`h-7 w-7 ${ann.isPinned ? 'text-status-info-text' : ''}`}\n aria-label={ann.isPinned ? `Unpin ${ann.title}` : `Pin ${ann.title}`}\n aria-pressed={ann.isPinned}\n onClick={() => void togglePin(ann)}\n >\n {ann.isPinned ? <PinOff className=\"h-3.5 w-3.5\" /> : <Pin className=\"h-3.5 w-3.5\" />}\n </Button>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-7 w-7\"\n aria-label={`Edit announcement ${ann.title}`}\n onClick={() => form.openModal(ann)}\n >\n <Pencil className=\"h-3.5 w-3.5\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-7 w-7 text-status-error-text\"\n aria-label={`Delete announcement ${ann.title}`}\n onClick={() => setDeleteTarget(ann)}\n >\n <Trash2 className=\"h-3.5 w-3.5\" />\n </Button>\n </div>\n </div>\n <CardTitle className=\"text-base\">{ann.title}</CardTitle>\n </CardHeader>\n <CardContent className=\"pt-0\">\n <p className=\"text-sm text-text-secondary\">{ann.content}</p>\n {ann.tags.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {ann.tags.map((t) => (\n <Badge key={t} variant=\"outline\" className=\"text-[9px]\">\n {t}\n </Badge>\n ))}\n </div>\n )}\n <div className=\"mt-3 flex items-center gap-1 text-xs text-text-muted\">\n <Eye className=\"h-3 w-3\" />\n {ann.viewCount} read\n </div>\n </CardContent>\n </GlassCard>\n );\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Announcements</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {announcements.length} company announcements\n </p>\n <PagePurpose className=\"mt-1\">\n Broadcast company-wide news and updates so everyone stays informed — post announcements\n here when you have something the whole team needs to see.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => form.openModal()}>\n <Plus className=\"h-4 w-4 mr-1\" />\n New Announcement\n </Button>\n </div>\n\n <div className=\"relative max-w-md\">\n <Search className=\"absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search announcements...\"\n className=\"pl-9\"\n value={search}\n onChange={(event) => setSearch(event.target.value)}\n />\n </div>\n\n <div className=\"grid gap-6 lg:grid-cols-3\">\n {/* Feed */}\n <div className=\"space-y-4 lg:col-span-2\">\n {filtered.length === 0 && (\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title={query ? 'No announcements match' : 'No announcements yet'}\n description={\n query\n ? 'Try a different search term.'\n : 'Post an announcement to share news with the whole team.'\n }\n />\n )}\n {pinned.length > 0 && (\n <div className=\"space-y-3\">\n <h2 className=\"flex items-center gap-1 text-sm font-medium\">\n <Pin className=\"h-4 w-4\" />\n Pinned\n </h2>\n {pinned.map((ann) => renderCard(ann, true))}\n </div>\n )}\n {regular.length > 0 && (\n <div className=\"space-y-3\">{regular.map((ann) => renderCard(ann, false))}</div>\n )}\n </div>\n\n {/* Conversations rail */}\n <div className=\"lg:col-span-1\">\n <Card className=\"lg:sticky lg:top-6\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-3\">\n <CardTitle className=\"text-base\">Conversations</CardTitle>\n <Button variant=\"ghost\" size=\"sm\" onClick={openMessages}>\n <MessageSquare className=\"mr-1 h-3.5 w-3.5\" />\n Open\n </Button>\n </CardHeader>\n <CardContent className=\"space-y-1\">\n {conversations.length === 0 ? (\n <p className=\"py-6 text-center text-xs text-text-muted\">\n No active conversations yet. Team channels will appear here as they get activity.\n </p>\n ) : (\n conversations.map((conv) => (\n <button\n key={conv.id}\n type=\"button\"\n onClick={openMessages}\n className=\"flex w-full items-start gap-3 rounded-[var(--radius-card)] p-2 text-left transition-colors hover:bg-bg-sunken\"\n >\n <AvatarStack people={[{ name: conv.title }]} size={32} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <p className=\"truncate text-sm font-medium\">{conv.title}</p>\n <span className=\"shrink-0 text-[10px] text-text-muted\">\n {relativeTime(conv.createdAt)}\n </span>\n </div>\n <p className=\"truncate text-xs text-text-secondary\">{conv.message}</p>\n </div>\n {!conv.read && (\n <span\n className=\"mt-1.5 h-2 w-2 shrink-0 rounded-full bg-status-success-text\"\n aria-label=\"Unread\"\n />\n )}\n </button>\n ))\n )}\n <Button variant=\"outline\" size=\"sm\" className=\"mt-3 w-full\" onClick={openMessages}>\n <Send className=\"mr-1 h-3.5 w-3.5\" />\n Write a message in Conversations\n </Button>\n </CardContent>\n </Card>\n </div>\n </div>\n\n <FormModal\n open={form.open}\n onOpenChange={form.onOpenChange}\n title={editing ? 'Edit Announcement' : 'New Announcement'}\n description={\n editing\n ? 'Update this announcement for the whole team.'\n : 'Share news and updates across the company.'\n }\n fields={editing ? EDIT_FIELDS : CREATE_FIELDS}\n initialValues={initialValues}\n submitLabel={editing ? 'Save changes' : 'Publish'}\n onSubmit={handleSubmit}\n />\n\n <AlertDialog\n open={deleteTarget !== null}\n onOpenChange={(open) => {\n if (!open) {\n setDeleteTarget(null);\n }\n }}\n >\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Delete announcement?</AlertDialogTitle>\n <AlertDialogDescription>\n {deleteTarget\n ? `\"${deleteTarget.title}\" will be permanently removed. This cannot be undone.`\n : ''}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel disabled={deleting}>Cancel</AlertDialogCancel>\n <AlertDialogAction\n onClick={(event) => {\n event.preventDefault();\n void confirmDelete();\n }}\n disabled={deleting}\n >\n {deleting ? 'Deleting…' : 'Delete'}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"CommunicationsAnnouncementsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/CommunicationsAnnouncementsPagePage.tsx"],"sourcesContent":["import {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n Input,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { formatRelativeTime } from '@burdenoff/fe-libs/shared/utils';\nimport { Eye, MessageSquare, Pencil, Pin, PinOff, Plus, Search, Send, Trash2 } from 'lucide-react';\nimport { useMemo, useState } from 'react';\nimport { useNavigate } from 'react-router';\nimport {\n CONVERSATIONS_ROUTE,\n useCrewFoundryStore,\n type Announcement,\n} from '../store/crewfoundryStore';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { useFocusedRecordParam } from '../lib/useFocusedRecordParam';\nimport { AvatarStack } from '../components/viz';\n\nconst CATEGORY_OPTIONS = [\n { label: 'General', value: 'general' },\n { label: 'HR', value: 'hr' },\n { label: 'IT', value: 'it' },\n { label: 'Emergency', value: 'emergency' },\n { label: 'Event', value: 'event' },\n];\n\nconst PRIORITY_OPTIONS = [\n { label: 'Low', value: 'low' },\n { label: 'Medium', value: 'medium' },\n { label: 'High', value: 'high' },\n { label: 'Urgent', value: 'urgent' },\n];\n\n// Category → tokenized pill styling (semantic tokens only — themes cleanly).\nconst CATEGORY_STYLE: Record<Announcement['category'], string> = {\n general: 'bg-bg-sunken text-text-secondary',\n hr: 'bg-status-success-bg text-status-success-text',\n it: 'bg-bg-sunken text-text-secondary',\n emergency: 'bg-status-error-bg text-status-error-text',\n event: 'bg-status-info-bg text-status-info-text',\n};\n\nconst PRIORITY_STYLE: Record<Announcement['priority'], string> = {\n low: 'bg-bg-sunken text-text-secondary',\n medium: 'bg-bg-sunken text-text-secondary',\n high: 'bg-status-warning-bg text-status-warning-text',\n urgent: 'bg-status-error-bg text-status-error-text',\n};\n\nconst CREATE_FIELDS: FieldConfig[] = [\n { name: 'title', label: 'Title', type: 'text', required: true, colSpan: 2 },\n { name: 'content', label: 'Content', type: 'textarea', required: true, colSpan: 2 },\n { name: 'author', label: 'Author', type: 'text', required: true },\n { name: 'authorRole', label: 'Author Role', type: 'text', required: true },\n {\n name: 'category',\n label: 'Category',\n type: 'select',\n required: true,\n options: CATEGORY_OPTIONS,\n placeholder: 'Select a category',\n },\n {\n name: 'priority',\n label: 'Priority',\n type: 'select',\n required: true,\n options: PRIORITY_OPTIONS,\n placeholder: 'Select a priority',\n },\n { name: 'tags', label: 'Tags', type: 'tags', placeholder: 'Add a tag and press Enter' },\n { name: 'isPinned', label: 'Pin to top', type: 'switch' },\n];\n\n// Author/role are immutable after creation (they are not part of the update\n// input), so the edit form omits them.\nconst EDIT_FIELDS: FieldConfig[] = CREATE_FIELDS.filter(\n (field) => field.name !== 'author' && field.name !== 'authorRole'\n);\n\nconst relativeTime = (value: string): string => {\n if (!value) {\n return '';\n }\n try {\n return formatRelativeTime(value);\n } catch {\n return value;\n }\n};\n\nexport function CommunicationsAnnouncementsPagePage() {\n const { announcements, notifications, addAnnouncement, updateAnnouncement, removeAnnouncement } =\n useCrewFoundryStore();\n const { showToast } = useGlobalUi();\n const navigate = useNavigate();\n const form = useFormModal<Announcement>();\n // The announcement open in the edit dialog is \"this announcement\" for the assistant.\n useFocusedRecordParam({\n records: announcements,\n activeId: form.open ? (form.context?.id ?? null) : null,\n onRestore: form.openModal,\n });\n const [deleteTarget, setDeleteTarget] = useState<Announcement | null>(null);\n const [deleting, setDeleting] = useState(false);\n const [search, setSearch] = useState('');\n\n const editing = form.context;\n\n // Real conversations surfaced by the workspace overlap query land in the store\n // as `communications`-category notifications (id `conversation-*`). Render the\n // rail from that real data — no fabricated threads.\n const conversations = useMemo(\n () => notifications.filter((n) => n.category === 'communications'),\n [notifications]\n );\n\n const query = search.trim().toLowerCase();\n const filtered = useMemo(\n () =>\n query.length === 0\n ? announcements\n : announcements.filter((a) =>\n `${a.title} ${a.content} ${a.tags.join(' ')}`.toLowerCase().includes(query)\n ),\n [announcements, query]\n );\n\n const pinned = filtered.filter((a) => a.isPinned);\n const regular = filtered.filter((a) => !a.isPinned);\n\n const handleSubmit = async (values: FormValues) => {\n const tags = Array.isArray(values.tags) ? values.tags : [];\n if (editing) {\n await updateAnnouncement(editing.id, {\n title: String(values.title),\n content: String(values.content),\n category: String(values.category) as Announcement['category'],\n priority: String(values.priority) as Announcement['priority'],\n isPinned: Boolean(values.isPinned),\n tags,\n });\n showToast('Announcement updated', 'success');\n return;\n }\n await addAnnouncement({\n id: crypto.randomUUID(),\n title: String(values.title),\n content: String(values.content),\n author: String(values.author),\n authorRole: String(values.authorRole),\n category: String(values.category) as Announcement['category'],\n priority: String(values.priority) as Announcement['priority'],\n isPinned: Boolean(values.isPinned),\n createdAt: new Date().toISOString().slice(0, 10),\n viewCount: 0,\n tags,\n });\n showToast('Announcement published', 'success');\n };\n\n const togglePin = async (announcement: Announcement) => {\n try {\n await updateAnnouncement(announcement.id, { isPinned: !announcement.isPinned });\n showToast(announcement.isPinned ? 'Unpinned' : 'Pinned to top', 'success');\n } catch (error) {\n showToast(error instanceof Error ? error.message : 'Unable to update announcement', 'error');\n }\n };\n\n const confirmDelete = async () => {\n if (!deleteTarget) {\n return;\n }\n setDeleting(true);\n try {\n await removeAnnouncement(deleteTarget.id);\n showToast('Announcement deleted', 'success');\n setDeleteTarget(null);\n } catch (error) {\n showToast(error instanceof Error ? error.message : 'Unable to delete announcement', 'error');\n } finally {\n setDeleting(false);\n }\n };\n\n // Messaging is owned by the shell's shared Conversations app\n // (wspace-conversations-svc); send there instead of a local composer that\n // could not deliver anything.\n const openMessages = () => navigate(CONVERSATIONS_ROUTE);\n\n const initialValues: FormValues | undefined = editing\n ? {\n title: editing.title,\n content: editing.content,\n category: editing.category,\n priority: editing.priority,\n tags: editing.tags,\n isPinned: editing.isPinned,\n }\n : undefined;\n\n const renderCard = (ann: Announcement, isPinnedCard: boolean) => (\n <GlassCard\n key={ann.id}\n treatment={isPinnedCard ? 'glass' : 'plain'}\n glow={isPinnedCard}\n className=\"transition-shadow hover:shadow-elevation-2\"\n >\n <CardHeader className=\"pb-3\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <Badge className={`border-transparent text-[10px] ${PRIORITY_STYLE[ann.priority]}`}>\n {formatEnumLabel(ann.priority)}\n </Badge>\n <Badge className={`border-transparent text-[10px] ${CATEGORY_STYLE[ann.category]}`}>\n {formatEnumLabel(ann.category)}\n </Badge>\n <span className=\"flex items-center gap-1.5 text-xs text-text-secondary\">\n <AvatarStack people={[{ name: ann.author }]} size={20} />\n {ann.author}\n <span className=\"text-text-muted\">· {relativeTime(ann.createdAt)}</span>\n </span>\n </div>\n <div className=\"flex items-center gap-0.5\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className={`h-7 w-7 ${ann.isPinned ? 'text-status-info-text' : ''}`}\n aria-label={ann.isPinned ? `Unpin ${ann.title}` : `Pin ${ann.title}`}\n aria-pressed={ann.isPinned}\n onClick={() => void togglePin(ann)}\n >\n {ann.isPinned ? <PinOff className=\"h-3.5 w-3.5\" /> : <Pin className=\"h-3.5 w-3.5\" />}\n </Button>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-7 w-7\"\n aria-label={`Edit announcement ${ann.title}`}\n onClick={() => form.openModal(ann)}\n >\n <Pencil className=\"h-3.5 w-3.5\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-7 w-7 text-status-error-text\"\n aria-label={`Delete announcement ${ann.title}`}\n onClick={() => setDeleteTarget(ann)}\n >\n <Trash2 className=\"h-3.5 w-3.5\" />\n </Button>\n </div>\n </div>\n <CardTitle className=\"text-base\">{ann.title}</CardTitle>\n </CardHeader>\n <CardContent className=\"pt-0\">\n <p className=\"text-sm text-text-secondary\">{ann.content}</p>\n {ann.tags.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {ann.tags.map((t) => (\n <Badge key={t} variant=\"outline\" className=\"text-[9px]\">\n {t}\n </Badge>\n ))}\n </div>\n )}\n <div className=\"mt-3 flex items-center gap-1 text-xs text-text-muted\">\n <Eye className=\"h-3 w-3\" />\n {ann.viewCount} read\n </div>\n </CardContent>\n </GlassCard>\n );\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Announcements</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {announcements.length} company announcements\n </p>\n <PagePurpose className=\"mt-1\">\n Broadcast company-wide news and updates so everyone stays informed — post announcements\n here when you have something the whole team needs to see.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => form.openModal()}>\n <Plus className=\"h-4 w-4 mr-1\" />\n New Announcement\n </Button>\n </div>\n\n <div className=\"relative max-w-md\">\n <Search className=\"absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search announcements...\"\n className=\"pl-9\"\n value={search}\n onChange={(event) => setSearch(event.target.value)}\n />\n </div>\n\n <div className=\"grid gap-6 lg:grid-cols-3\">\n {/* Feed */}\n <div className=\"space-y-4 lg:col-span-2\">\n {filtered.length === 0 && (\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title={query ? 'No announcements match' : 'No announcements yet'}\n description={\n query\n ? 'Try a different search term.'\n : 'Post an announcement to share news with the whole team.'\n }\n />\n )}\n {pinned.length > 0 && (\n <div className=\"space-y-3\">\n <h2 className=\"flex items-center gap-1 text-sm font-medium\">\n <Pin className=\"h-4 w-4\" />\n Pinned\n </h2>\n {pinned.map((ann) => renderCard(ann, true))}\n </div>\n )}\n {regular.length > 0 && (\n <div className=\"space-y-3\">{regular.map((ann) => renderCard(ann, false))}</div>\n )}\n </div>\n\n {/* Conversations rail */}\n <div className=\"lg:col-span-1\">\n <Card className=\"lg:sticky lg:top-6\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-3\">\n <CardTitle className=\"text-base\">Conversations</CardTitle>\n <Button variant=\"ghost\" size=\"sm\" onClick={openMessages}>\n <MessageSquare className=\"mr-1 h-3.5 w-3.5\" />\n Open\n </Button>\n </CardHeader>\n <CardContent className=\"space-y-1\">\n {conversations.length === 0 ? (\n <p className=\"py-6 text-center text-xs text-text-muted\">\n No active conversations yet. Team channels will appear here as they get activity.\n </p>\n ) : (\n conversations.map((conv) => (\n <button\n key={conv.id}\n type=\"button\"\n onClick={openMessages}\n className=\"flex w-full items-start gap-3 rounded-[var(--radius-card)] p-2 text-left transition-colors hover:bg-bg-sunken\"\n >\n <AvatarStack people={[{ name: conv.title }]} size={32} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <p className=\"truncate text-sm font-medium\">{conv.title}</p>\n <span className=\"shrink-0 text-[10px] text-text-muted\">\n {relativeTime(conv.createdAt)}\n </span>\n </div>\n <p className=\"truncate text-xs text-text-secondary\">{conv.message}</p>\n </div>\n {!conv.read && (\n <span\n className=\"mt-1.5 h-2 w-2 shrink-0 rounded-full bg-status-success-text\"\n aria-label=\"Unread\"\n />\n )}\n </button>\n ))\n )}\n <Button variant=\"outline\" size=\"sm\" className=\"mt-3 w-full\" onClick={openMessages}>\n <Send className=\"mr-1 h-3.5 w-3.5\" />\n Write a message in Conversations\n </Button>\n </CardContent>\n </Card>\n </div>\n </div>\n\n <FormModal\n open={form.open}\n onOpenChange={form.onOpenChange}\n title={editing ? 'Edit Announcement' : 'New Announcement'}\n description={\n editing\n ? 'Update this announcement for the whole team.'\n : 'Share news and updates across the company.'\n }\n fields={editing ? EDIT_FIELDS : CREATE_FIELDS}\n initialValues={initialValues}\n submitLabel={editing ? 'Save changes' : 'Publish'}\n onSubmit={handleSubmit}\n />\n\n <AlertDialog\n open={deleteTarget !== null}\n onOpenChange={(open) => {\n if (!open) {\n setDeleteTarget(null);\n }\n }}\n >\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Delete announcement?</AlertDialogTitle>\n <AlertDialogDescription>\n {deleteTarget\n ? `\"${deleteTarget.title}\" will be permanently removed. This cannot be undone.`\n : ''}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel disabled={deleting}>Cancel</AlertDialogCancel>\n <AlertDialogAction\n onClick={(event) => {\n event.preventDefault();\n void confirmDelete();\n }}\n disabled={deleting}\n >\n {deleting ? 'Deleting…' : 'Delete'}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAyCA,IAAM,IAAmB;CACvB;EAAE,OAAO;EAAW,OAAO;EAAW;CACtC;EAAE,OAAO;EAAM,OAAO;EAAM;CAC5B;EAAE,OAAO;EAAM,OAAO;EAAM;CAC5B;EAAE,OAAO;EAAa,OAAO;EAAa;CAC1C;EAAE,OAAO;EAAS,OAAO;EAAS;CACnC,EAEK,IAAmB;CACvB;EAAE,OAAO;EAAO,OAAO;EAAO;CAC9B;EAAE,OAAO;EAAU,OAAO;EAAU;CACpC;EAAE,OAAO;EAAQ,OAAO;EAAQ;CAChC;EAAE,OAAO;EAAU,OAAO;EAAU;CACrC,EAGK,IAA2D;CAC/D,SAAS;CACT,IAAI;CACJ,IAAI;CACJ,WAAW;CACX,OAAO;CACR,EAEK,IAA2D;CAC/D,KAAK;CACL,QAAQ;CACR,MAAM;CACN,QAAQ;CACT,EAEK,IAA+B;CACnC;EAAE,MAAM;EAAS,OAAO;EAAS,MAAM;EAAQ,UAAU;EAAM,SAAS;EAAG;CAC3E;EAAE,MAAM;EAAW,OAAO;EAAW,MAAM;EAAY,UAAU;EAAM,SAAS;EAAG;CACnF;EAAE,MAAM;EAAU,OAAO;EAAU,MAAM;EAAQ,UAAU;EAAM;CACjE;EAAE,MAAM;EAAc,OAAO;EAAe,MAAM;EAAQ,UAAU;EAAM;CAC1E;EACE,MAAM;EACN,OAAO;EACP,MAAM;EACN,UAAU;EACV,SAAS;EACT,aAAa;EACd;CACD;EACE,MAAM;EACN,OAAO;EACP,MAAM;EACN,UAAU;EACV,SAAS;EACT,aAAa;EACd;CACD;EAAE,MAAM;EAAQ,OAAO;EAAQ,MAAM;EAAQ,aAAa;EAA6B;CACvF;EAAE,MAAM;EAAY,OAAO;EAAc,MAAM;EAAU;CAC1D,EAIK,IAA6B,EAAc,QAC9C,MAAU,EAAM,SAAS,YAAY,EAAM,SAAS,aACtD,EAEK,KAAgB,MAA0B;AAC9C,KAAI,CAAC,EACH,QAAO;AAET,KAAI;AACF,SAAO,EAAmB,EAAM;SAC1B;AACN,SAAO;;;AAIX,SAAgB,IAAsC;CACpD,IAAM,EAAE,kBAAe,kBAAe,oBAAiB,uBAAoB,0BACzE,GAAqB,EACjB,EAAE,iBAAc,GAAa,EAC7B,KAAW,GAAa,EACxB,IAAO,GAA4B;AAEzC,IAAsB;EACpB,SAAS;EACT,UAAU,EAAK,OAAQ,EAAK,SAAS,MAAM,OAAQ;EACnD,WAAW,EAAK;EACjB,CAAC;CACF,IAAM,CAAC,GAAc,KAAmB,EAA8B,KAAK,EACrE,CAAC,GAAU,KAAe,EAAS,GAAM,EACzC,CAAC,GAAQ,MAAa,EAAS,GAAG,EAElC,IAAU,EAAK,SAKf,IAAgB,QACd,EAAc,QAAQ,MAAM,EAAE,aAAa,iBAAiB,EAClE,CAAC,EAAc,CAChB,EAEK,IAAQ,EAAO,MAAM,CAAC,aAAa,EACnC,IAAW,QAEb,EAAM,WAAW,IACb,IACA,EAAc,QAAQ,MACpB,GAAG,EAAE,MAAM,GAAG,EAAE,QAAQ,GAAG,EAAE,KAAK,KAAK,IAAI,GAAG,aAAa,CAAC,SAAS,EAAM,CAC5E,EACP,CAAC,GAAe,EAAM,CACvB,EAEK,IAAS,EAAS,QAAQ,MAAM,EAAE,SAAS,EAC3C,IAAU,EAAS,QAAQ,MAAM,CAAC,EAAE,SAAS,EAE7C,KAAe,OAAO,MAAuB;EACjD,IAAM,IAAO,MAAM,QAAQ,EAAO,KAAK,GAAG,EAAO,OAAO,EAAE;AAC1D,MAAI,GAAS;AASX,GARA,MAAM,EAAmB,EAAQ,IAAI;IACnC,OAAO,OAAO,EAAO,MAAM;IAC3B,SAAS,OAAO,EAAO,QAAQ;IAC/B,UAAU,OAAO,EAAO,SAAS;IACjC,UAAU,OAAO,EAAO,SAAS;IACjC,UAAU,EAAQ,EAAO;IACzB;IACD,CAAC,EACF,EAAU,wBAAwB,UAAU;AAC5C;;AAeF,EAbA,MAAM,EAAgB;GACpB,IAAI,OAAO,YAAY;GACvB,OAAO,OAAO,EAAO,MAAM;GAC3B,SAAS,OAAO,EAAO,QAAQ;GAC/B,QAAQ,OAAO,EAAO,OAAO;GAC7B,YAAY,OAAO,EAAO,WAAW;GACrC,UAAU,OAAO,EAAO,SAAS;GACjC,UAAU,OAAO,EAAO,SAAS;GACjC,UAAU,EAAQ,EAAO;GACzB,4BAAW,IAAI,MAAM,EAAC,aAAa,CAAC,MAAM,GAAG,GAAG;GAChD,WAAW;GACX;GACD,CAAC,EACF,EAAU,0BAA0B,UAAU;IAG1C,KAAY,OAAO,MAA+B;AACtD,MAAI;AAEF,GADA,MAAM,EAAmB,EAAa,IAAI,EAAE,UAAU,CAAC,EAAa,UAAU,CAAC,EAC/E,EAAU,EAAa,WAAW,aAAa,iBAAiB,UAAU;WACnE,GAAO;AACd,KAAU,aAAiB,QAAQ,EAAM,UAAU,iCAAiC,QAAQ;;IAI1F,KAAgB,YAAY;AAC3B,SAGL;KAAY,GAAK;AACjB,OAAI;AAGF,IAFA,MAAM,EAAmB,EAAa,GAAG,EACzC,EAAU,wBAAwB,UAAU,EAC5C,EAAgB,KAAK;YACd,GAAO;AACd,MAAU,aAAiB,QAAQ,EAAM,UAAU,iCAAiC,QAAQ;aACpF;AACR,MAAY,GAAM;;;IAOhB,UAAqB,GAAS,EAAoB,EAElD,IAAwC,IAC1C;EACE,OAAO,EAAQ;EACf,SAAS,EAAQ;EACjB,UAAU,EAAQ;EAClB,UAAU,EAAQ;EAClB,MAAM,EAAQ;EACd,UAAU,EAAQ;EACnB,GACD,KAAA,GAEE,KAAc,GAAmB,MACrC,kBAAC,IAAD;EAEE,WAAW,IAAe,UAAU;EACpC,MAAM;EACN,WAAU;YAJZ,CAME,kBAAC,GAAD;GAAY,WAAU;aAAtB,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OAAO,WAAW,kCAAkC,EAAe,EAAI;iBACpE,EAAgB,EAAI,SAAS;OACxB,CAAA;MACR,kBAAC,GAAD;OAAO,WAAW,kCAAkC,EAAe,EAAI;iBACpE,EAAgB,EAAI,SAAS;OACxB,CAAA;MACR,kBAAC,QAAD;OAAM,WAAU;iBAAhB;QACE,kBAAC,GAAD;SAAa,QAAQ,CAAC,EAAE,MAAM,EAAI,QAAQ,CAAC;SAAE,MAAM;SAAM,CAAA;QACxD,EAAI;QACL,kBAAC,QAAD;SAAM,WAAU;mBAAhB,CAAkC,MAAG,EAAa,EAAI,UAAU,CAAQ;;QACnE;;MACH;QACN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OACE,SAAQ;OACR,MAAK;OACL,WAAW,WAAW,EAAI,WAAW,0BAA0B;OAC/D,cAAY,EAAI,WAAW,SAAS,EAAI,UAAU,OAAO,EAAI;OAC7D,gBAAc,EAAI;OAClB,eAAe,KAAK,GAAU,EAAI;iBAEjC,EAAI,WAAW,kBAAC,IAAD,EAAQ,WAAU,eAAgB,CAAA,GAAG,kBAAC,GAAD,EAAK,WAAU,eAAgB,CAAA;OAC7E,CAAA;MACT,kBAAC,GAAD;OACE,SAAQ;OACR,MAAK;OACL,WAAU;OACV,cAAY,qBAAqB,EAAI;OACrC,eAAe,EAAK,UAAU,EAAI;iBAElC,kBAAC,IAAD,EAAQ,WAAU,eAAgB,CAAA;OAC3B,CAAA;MACT,kBAAC,GAAD;OACE,SAAQ;OACR,MAAK;OACL,WAAU;OACV,cAAY,uBAAuB,EAAI;OACvC,eAAe,EAAgB,EAAI;iBAEnC,kBAAC,GAAD,EAAQ,WAAU,eAAgB,CAAA;OAC3B,CAAA;MACL;OACF;OACN,kBAAC,GAAD;IAAW,WAAU;cAAa,EAAI;IAAkB,CAAA,CAC7C;MACb,kBAAC,GAAD;GAAa,WAAU;aAAvB;IACE,kBAAC,KAAD;KAAG,WAAU;eAA+B,EAAI;KAAY,CAAA;IAC3D,EAAI,KAAK,SAAS,KACjB,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAI,KAAK,KAAK,MACb,kBAAC,GAAD;MAAe,SAAQ;MAAU,WAAU;gBACxC;MACK,EAFI,EAEJ,CACR;KACE,CAAA;IAER,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,IAAD,EAAK,WAAU,WAAY,CAAA;MAC1B,EAAI;MAAU;MACX;;IACM;KACJ;IArEL,EAAI,GAqEC;AAGd,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAA,UAAA;KACE,kBAAC,MAAD;MAAI,WAAU;gBACZ,kBAAC,GAAD,EAAA,UAAc,iBAA4B,CAAA;MACvC,CAAA;KACL,kBAAC,KAAD;MAAG,WAAU;gBAAb,CACG,EAAc,QAAO,yBACpB;;KACJ,kBAAC,GAAD;MAAa,WAAU;gBAAO;MAGhB,CAAA;KACV,EAAA,CAAA,EACN,kBAAC,GAAD;KAAQ,MAAK;KAAK,eAAe,EAAK,WAAW;eAAjD,CACE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,mBAE1B;OACL;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,wEAAyE,CAAA,EAC3F,kBAAC,GAAD;KACE,aAAY;KACZ,WAAU;KACV,OAAO;KACP,WAAW,MAAU,GAAU,EAAM,OAAO,MAAM;KAClD,CAAA,CACE;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CAEE,kBAAC,OAAD;KAAK,WAAU;eAAf;MACG,EAAS,WAAW,KACnB,kBAAC,GAAD;OACE,cAAa;OACb,OAAO,IAAQ,2BAA2B;OAC1C,aACE,IACI,iCACA;OAEN,CAAA;MAEH,EAAO,SAAS,KACf,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,MAAD;QAAI,WAAU;kBAAd,CACE,kBAAC,GAAD,EAAK,WAAU,WAAY,CAAA,EAAA,SAExB;WACJ,EAAO,KAAK,MAAQ,EAAW,GAAK,GAAK,CAAC,CACvC;;MAEP,EAAQ,SAAS,KAChB,kBAAC,OAAD;OAAK,WAAU;iBAAa,EAAQ,KAAK,MAAQ,EAAW,GAAK,GAAM,CAAC;OAAO,CAAA;MAE7E;QAGN,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,IAAD;MAAM,WAAU;gBAAhB,CACE,kBAAC,GAAD;OAAY,WAAU;iBAAtB,CACE,kBAAC,GAAD;QAAW,WAAU;kBAAY;QAAyB,CAAA,EAC1D,kBAAC,GAAD;QAAQ,SAAQ;QAAQ,MAAK;QAAK,SAAS;kBAA3C,CACE,kBAAC,IAAD,EAAe,WAAU,oBAAqB,CAAA,EAAA,OAEvC;UACE;UACb,kBAAC,GAAD;OAAa,WAAU;iBAAvB,CACG,EAAc,WAAW,IACxB,kBAAC,KAAD;QAAG,WAAU;kBAA2C;QAEpD,CAAA,GAEJ,EAAc,KAAK,MACjB,kBAAC,UAAD;QAEE,MAAK;QACL,SAAS;QACT,WAAU;kBAJZ;SAME,kBAAC,GAAD;UAAa,QAAQ,CAAC,EAAE,MAAM,EAAK,OAAO,CAAC;UAAE,MAAM;UAAM,CAAA;SACzD,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,KAAD;YAAG,WAAU;sBAAgC,EAAK;YAAU,CAAA,EAC5D,kBAAC,QAAD;YAAM,WAAU;sBACb,EAAa,EAAK,UAAU;YACxB,CAAA,CACH;cACN,kBAAC,KAAD;WAAG,WAAU;qBAAwC,EAAK;WAAY,CAAA,CAClE;;SACL,CAAC,EAAK,QACL,kBAAC,QAAD;UACE,WAAU;UACV,cAAW;UACX,CAAA;SAEG;UArBF,EAAK,GAqBH,CACT,EAEJ,kBAAC,GAAD;QAAQ,SAAQ;QAAU,MAAK;QAAK,WAAU;QAAc,SAAS;kBAArE,CACE,kBAAC,GAAD,EAAM,WAAU,oBAAqB,CAAA,EAAA,mCAE9B;UACG;SACT;;KACH,CAAA,CACF;;GAEN,kBAAC,GAAD;IACE,MAAM,EAAK;IACX,cAAc,EAAK;IACnB,OAAO,IAAU,sBAAsB;IACvC,aACE,IACI,iDACA;IAEN,QAAQ,IAAU,IAAc;IACjB;IACf,aAAa,IAAU,iBAAiB;IACxC,UAAU;IACV,CAAA;GAEF,kBAAC,GAAD;IACE,MAAM,MAAiB;IACvB,eAAe,MAAS;AACtB,KAAK,KACH,EAAgB,KAAK;;cAIzB,kBAAC,IAAD,EAAA,UAAA,CACE,kBAAC,IAAD,EAAA,UAAA,CACE,kBAAC,IAAD,EAAA,UAAkB,wBAAuC,CAAA,EACzD,kBAAC,IAAD,EAAA,UACG,IACG,IAAI,EAAa,MAAM,yDACvB,IACmB,CAAA,CACP,EAAA,CAAA,EACpB,kBAAC,IAAD,EAAA,UAAA,CACE,kBAAC,IAAD;KAAmB,UAAU;eAAU;KAA0B,CAAA,EACjE,kBAAC,GAAD;KACE,UAAU,MAAU;AAEb,MADL,EAAM,gBAAgB,EACjB,IAAe;;KAEtB,UAAU;eAET,IAAW,cAAc;KACR,CAAA,CACF,EAAA,CAAA,CACD,EAAA,CAAA;IACT,CAAA;GACV"}
@@ -1 +1 @@
1
- {"version":3,"file":"CompanyClientManagementPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/CompanyClientManagementPagePage.tsx"],"names":[],"mappings":"AA2GA,wBAAgB,+BAA+B,gCAsQ9C"}
1
+ {"version":3,"file":"CompanyClientManagementPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/CompanyClientManagementPagePage.tsx"],"names":[],"mappings":"AA4GA,wBAAgB,+BAA+B,gCA4Q9C"}
@@ -5,12 +5,13 @@ import { AvatarStack as r } from "../components/viz/AvatarStack.js";
5
5
  import { SegmentedTabs as ee } from "../components/viz/SegmentedTabs.js";
6
6
  import "../components/viz/index.js";
7
7
  import { FormModal as te, useFormModal as i } from "../components/forms/FormModal.js";
8
- import { clientRevenue as a, totalContractValue as o } from "../lib/companyFinance.js";
9
- import { useState as s } from "react";
10
- import { MoreHorizontal as c, Pencil as l, Plus as u, Search as ne, Trash2 as re } from "lucide-react";
11
- import { useNavigate as ie } from "react-router";
12
- import { jsx as d, jsxs as f } from "react/jsx-runtime";
13
- import { AlertDialog as ae, AlertDialogAction as oe, AlertDialogCancel as se, AlertDialogContent as ce, AlertDialogDescription as le, AlertDialogFooter as p, AlertDialogHeader as m, AlertDialogTitle as h, Badge as g, Button as _, Card as v, CardContent as y, CardHeader as b, CardTitle as x, DropdownMenu as S, DropdownMenuContent as C, DropdownMenuItem as w, DropdownMenuSeparator as T, DropdownMenuTrigger as E, GradientText as D, IllustratedEmptyState as O, Input as k, PagePurpose as A, Pagination as j, ResponsiveTable as M, Select as N, SelectContent as P, SelectItem as F, SelectTrigger as I, SelectValue as ue } from "@burdenoff/fe-libs/ui";
8
+ import { useFocusedRecordParam as a } from "../lib/useFocusedRecordParam.js";
9
+ import { clientRevenue as o, totalContractValue as s } from "../lib/companyFinance.js";
10
+ import { useState as c } from "react";
11
+ import { MoreHorizontal as l, Pencil as ne, Plus as re, Search as ie, Trash2 as ae } from "lucide-react";
12
+ import { useNavigate as oe } from "react-router";
13
+ import { jsx as u, jsxs as d } from "react/jsx-runtime";
14
+ import { AlertDialog as se, AlertDialogAction as ce, AlertDialogCancel as le, AlertDialogContent as ue, AlertDialogDescription as f, AlertDialogFooter as p, AlertDialogHeader as m, AlertDialogTitle as h, Badge as g, Button as _, Card as v, CardContent as y, CardHeader as b, CardTitle as x, DropdownMenu as S, DropdownMenuContent as C, DropdownMenuItem as w, DropdownMenuSeparator as T, DropdownMenuTrigger as E, GradientText as D, IllustratedEmptyState as O, Input as k, PagePurpose as A, Pagination as j, ResponsiveTable as M, Select as N, SelectContent as P, SelectItem as F, SelectTrigger as de, SelectValue as I } from "@burdenoff/fe-libs/ui";
14
15
  import { usePagination as L } from "@burdenoff/fe-libs/shared/hooks";
15
16
  //#region src/crewfoundry/pages/CompanyClientManagementPagePage.tsx
16
17
  var R = [
@@ -97,21 +98,27 @@ var R = [
97
98
  since: String(e.since ?? "")
98
99
  }), V = (e) => e === "active" ? "default" : e === "prospect" ? "secondary" : "destructive", H = (e) => e >= 1e6 ? `$${(e / 1e6).toFixed(1)}M` : e >= 1e3 ? `$${Math.round(e / 1e3)}k` : `$${Math.round(e)}`;
99
100
  function U() {
100
- let { clients: U, vendors: W, addClient: de, updateClient: fe, removeClient: pe } = e(), me = ie(), G = i(), [K, he] = s(""), [q, ge] = s("all"), [J, Y] = s(null), { page: _e, pageSize: X, offset: Z, setPage: Q } = L({ initialPageSize: 25 }), ve = U.filter((e) => e.status === "active"), ye = o(U, W), be = a(U), $ = U.filter((e) => {
101
+ let { clients: U, vendors: W, addClient: fe, updateClient: pe, removeClient: me } = e(), he = oe(), G = i();
102
+ a({
103
+ records: U,
104
+ activeId: G.open ? G.context?.id ?? null : null,
105
+ onRestore: G.openModal
106
+ });
107
+ let [K, ge] = c(""), [q, _e] = c("all"), [J, Y] = c(null), { page: ve, pageSize: X, offset: Z, setPage: Q } = L({ initialPageSize: 25 }), ye = U.filter((e) => e.status === "active"), be = s(U, W), xe = o(U), $ = U.filter((e) => {
101
108
  let t = `${e.name} ${e.industry} ${e.contactPerson} ${e.email}`.toLowerCase().includes(K.toLowerCase()), n = q === "all" || e.status === q;
102
109
  return t && n;
103
- }), xe = $.slice(Z, Z + X);
104
- return /* @__PURE__ */ f("div", {
110
+ }), Se = $.slice(Z, Z + X);
111
+ return /* @__PURE__ */ d("div", {
105
112
  className: "space-y-6 p-6",
106
113
  children: [
107
- /* @__PURE__ */ f("div", {
114
+ /* @__PURE__ */ d("div", {
108
115
  className: "flex items-center justify-between",
109
- children: [/* @__PURE__ */ f("div", { children: [
110
- /* @__PURE__ */ d("h1", {
116
+ children: [/* @__PURE__ */ d("div", { children: [
117
+ /* @__PURE__ */ u("h1", {
111
118
  className: "text-2xl font-semibold",
112
- children: /* @__PURE__ */ d(D, { children: "Clients & Vendors" })
119
+ children: /* @__PURE__ */ u(D, { children: "Clients & Vendors" })
113
120
  }),
114
- /* @__PURE__ */ f("p", {
121
+ /* @__PURE__ */ d("p", {
115
122
  className: "text-sm text-text-secondary",
116
123
  children: [
117
124
  U.length,
@@ -120,22 +127,22 @@ function U() {
120
127
  " vendors"
121
128
  ]
122
129
  }),
123
- /* @__PURE__ */ d(A, {
130
+ /* @__PURE__ */ u(A, {
124
131
  className: "mt-1",
125
132
  children: "Keep track of the clients you work with — their contacts, status, and engagements — so account relationships never slip through the cracks."
126
133
  })
127
- ] }), /* @__PURE__ */ f(_, {
134
+ ] }), /* @__PURE__ */ d(_, {
128
135
  size: "sm",
129
136
  onClick: () => G.openModal(),
130
- children: [/* @__PURE__ */ d(u, { className: "h-4 w-4 mr-1" }), "Add Client"]
137
+ children: [/* @__PURE__ */ u(re, { className: "h-4 w-4 mr-1" }), "Add Client"]
131
138
  })]
132
139
  }),
133
- /* @__PURE__ */ d(ee, {
140
+ /* @__PURE__ */ u(ee, {
134
141
  "aria-label": "Clients and vendors",
135
142
  variant: "underline",
136
143
  value: "clients",
137
144
  onChange: (e) => {
138
- e === "vendors" && me("../company/vendor-management-page");
145
+ e === "vendors" && he("../company/vendor-management-page");
139
146
  },
140
147
  tabs: [{
141
148
  id: "clients",
@@ -147,90 +154,90 @@ function U() {
147
154
  count: W.length
148
155
  }]
149
156
  }),
150
- /* @__PURE__ */ f("div", {
157
+ /* @__PURE__ */ d("div", {
151
158
  className: "grid gap-4 sm:grid-cols-2 xl:grid-cols-4",
152
159
  children: [
153
- /* @__PURE__ */ d(n, {
160
+ /* @__PURE__ */ u(n, {
154
161
  label: "Active Clients",
155
- value: ve.length
162
+ value: ye.length
156
163
  }),
157
- /* @__PURE__ */ d(n, {
164
+ /* @__PURE__ */ u(n, {
158
165
  label: "Vendors",
159
166
  value: W.length
160
167
  }),
161
- /* @__PURE__ */ d(n, {
168
+ /* @__PURE__ */ u(n, {
162
169
  label: "Total Contract Value",
163
- value: H(ye)
170
+ value: H(be)
164
171
  }),
165
- /* @__PURE__ */ d(n, {
172
+ /* @__PURE__ */ u(n, {
166
173
  label: "Client Revenue",
167
- value: H(be)
174
+ value: H(xe)
168
175
  })
169
176
  ]
170
177
  }),
171
- /* @__PURE__ */ f(v, { children: [/* @__PURE__ */ d(b, { children: /* @__PURE__ */ d(x, { children: "Client Portfolio" }) }), /* @__PURE__ */ f(y, {
178
+ /* @__PURE__ */ d(v, { children: [/* @__PURE__ */ u(b, { children: /* @__PURE__ */ u(x, { children: "Client Portfolio" }) }), /* @__PURE__ */ d(y, {
172
179
  className: "space-y-4",
173
180
  children: [
174
- /* @__PURE__ */ f("div", {
181
+ /* @__PURE__ */ d("div", {
175
182
  className: "flex flex-col gap-2 sm:flex-row sm:items-center",
176
- children: [/* @__PURE__ */ f("div", {
183
+ children: [/* @__PURE__ */ d("div", {
177
184
  className: "relative flex-1",
178
- children: [/* @__PURE__ */ d(ne, { className: "absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ d(k, {
185
+ children: [/* @__PURE__ */ u(ie, { className: "absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ u(k, {
179
186
  placeholder: "Search clients...",
180
187
  className: "pl-9",
181
188
  value: K,
182
189
  onChange: (e) => {
183
- he(e.target.value), Q(1);
190
+ ge(e.target.value), Q(1);
184
191
  }
185
192
  })]
186
- }), /* @__PURE__ */ f(N, {
193
+ }), /* @__PURE__ */ d(N, {
187
194
  value: q,
188
195
  onValueChange: (e) => {
189
- ge(e), Q(1);
196
+ _e(e), Q(1);
190
197
  },
191
- children: [/* @__PURE__ */ d(I, {
198
+ children: [/* @__PURE__ */ u(de, {
192
199
  className: "w-full sm:w-40",
193
200
  "aria-label": "Filter by status",
194
- children: /* @__PURE__ */ d(ue, {})
195
- }), /* @__PURE__ */ f(P, { children: [
196
- /* @__PURE__ */ d(F, {
201
+ children: /* @__PURE__ */ u(I, {})
202
+ }), /* @__PURE__ */ d(P, { children: [
203
+ /* @__PURE__ */ u(F, {
197
204
  value: "all",
198
205
  children: "All statuses"
199
206
  }),
200
- /* @__PURE__ */ d(F, {
207
+ /* @__PURE__ */ u(F, {
201
208
  value: "active",
202
209
  children: "Active"
203
210
  }),
204
- /* @__PURE__ */ d(F, {
211
+ /* @__PURE__ */ u(F, {
205
212
  value: "prospect",
206
213
  children: "Prospect"
207
214
  }),
208
- /* @__PURE__ */ d(F, {
215
+ /* @__PURE__ */ u(F, {
209
216
  value: "churned",
210
217
  children: "Churned"
211
218
  })
212
219
  ] })]
213
220
  })]
214
221
  }),
215
- /* @__PURE__ */ d(M, {
216
- data: xe,
222
+ /* @__PURE__ */ u(M, {
223
+ data: Se,
217
224
  rowKey: (e) => e.id,
218
225
  columns: [
219
226
  {
220
227
  key: "name",
221
228
  header: "Client",
222
229
  priority: "primary",
223
- cell: (e) => /* @__PURE__ */ f("div", {
230
+ cell: (e) => /* @__PURE__ */ d("div", {
224
231
  className: "flex items-center gap-3",
225
- children: [/* @__PURE__ */ d(r, {
232
+ children: [/* @__PURE__ */ u(r, {
226
233
  people: [{ name: e.name }],
227
234
  size: 36
228
- }), /* @__PURE__ */ f("div", {
235
+ }), /* @__PURE__ */ d("div", {
229
236
  className: "min-w-0",
230
- children: [/* @__PURE__ */ d("p", {
237
+ children: [/* @__PURE__ */ u("p", {
231
238
  className: "truncate font-medium",
232
239
  children: e.name
233
- }), /* @__PURE__ */ d("p", {
240
+ }), /* @__PURE__ */ u("p", {
234
241
  className: "truncate text-xs text-text-secondary",
235
242
  children: e.industry || "—"
236
243
  })]
@@ -241,10 +248,10 @@ function U() {
241
248
  key: "contact",
242
249
  header: "Primary Contact",
243
250
  priority: "secondary",
244
- cell: (e) => /* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("p", {
251
+ cell: (e) => /* @__PURE__ */ d("div", { children: [/* @__PURE__ */ u("p", {
245
252
  className: "text-xs",
246
253
  children: e.contactPerson || "—"
247
- }), /* @__PURE__ */ d("p", {
254
+ }), /* @__PURE__ */ u("p", {
248
255
  className: "text-xs text-text-secondary",
249
256
  children: e.email
250
257
  })] })
@@ -253,7 +260,7 @@ function U() {
253
260
  key: "status",
254
261
  header: "Status",
255
262
  priority: "secondary",
256
- cell: (e) => /* @__PURE__ */ d(g, {
263
+ cell: (e) => /* @__PURE__ */ u(g, {
257
264
  variant: V(e.status),
258
265
  className: "text-[10px]",
259
266
  children: t(e.status)
@@ -265,7 +272,7 @@ function U() {
265
272
  priority: "hidden-on-mobile",
266
273
  headerClassName: "text-right",
267
274
  cellClassName: "text-right",
268
- cell: (e) => /* @__PURE__ */ d("span", {
275
+ cell: (e) => /* @__PURE__ */ u("span", {
269
276
  className: "tabular-nums",
270
277
  children: e.projects
271
278
  })
@@ -276,54 +283,54 @@ function U() {
276
283
  priority: "secondary",
277
284
  headerClassName: "text-right",
278
285
  cellClassName: "text-right",
279
- cell: (e) => /* @__PURE__ */ f("span", {
286
+ cell: (e) => /* @__PURE__ */ d("span", {
280
287
  className: "tabular-nums",
281
288
  children: ["$", e.revenue.toLocaleString()]
282
289
  })
283
290
  }
284
291
  ],
285
- emptyState: /* @__PURE__ */ d(O, {
292
+ emptyState: /* @__PURE__ */ u(O, {
286
293
  illustration: "empty-search",
287
294
  title: "No clients match",
288
295
  description: "Try a different search term or status filter."
289
296
  }),
290
- rowActions: (e) => /* @__PURE__ */ d("div", {
297
+ rowActions: (e) => /* @__PURE__ */ u("div", {
291
298
  className: "flex items-center justify-end",
292
- children: /* @__PURE__ */ f(S, { children: [/* @__PURE__ */ d(E, {
299
+ children: /* @__PURE__ */ d(S, { children: [/* @__PURE__ */ u(E, {
293
300
  asChild: !0,
294
- children: /* @__PURE__ */ d(_, {
301
+ children: /* @__PURE__ */ u(_, {
295
302
  variant: "ghost",
296
303
  size: "icon",
297
304
  className: "h-8 w-8",
298
305
  "aria-label": `More actions for ${e.name}`,
299
- children: /* @__PURE__ */ d(c, { className: "h-4 w-4" })
306
+ children: /* @__PURE__ */ u(l, { className: "h-4 w-4" })
300
307
  })
301
- }), /* @__PURE__ */ f(C, {
308
+ }), /* @__PURE__ */ d(C, {
302
309
  align: "end",
303
310
  children: [
304
- /* @__PURE__ */ f(w, {
311
+ /* @__PURE__ */ d(w, {
305
312
  onSelect: () => G.openModal(e),
306
- children: [/* @__PURE__ */ d(l, { className: "mr-2 h-4 w-4" }), "Edit"]
313
+ children: [/* @__PURE__ */ u(ne, { className: "mr-2 h-4 w-4" }), "Edit"]
307
314
  }),
308
- /* @__PURE__ */ d(T, {}),
309
- /* @__PURE__ */ f(w, {
315
+ /* @__PURE__ */ u(T, {}),
316
+ /* @__PURE__ */ d(w, {
310
317
  className: "text-status-error-text",
311
318
  onSelect: () => Y(e),
312
- children: [/* @__PURE__ */ d(re, { className: "mr-2 h-4 w-4" }), "Delete"]
319
+ children: [/* @__PURE__ */ u(ae, { className: "mr-2 h-4 w-4" }), "Delete"]
313
320
  })
314
321
  ]
315
322
  })] })
316
323
  })
317
324
  }),
318
- $.length > X && /* @__PURE__ */ d(j, {
319
- page: _e,
325
+ $.length > X && /* @__PURE__ */ u(j, {
326
+ page: ve,
320
327
  pageSize: X,
321
328
  totalItems: $.length,
322
329
  onPageChange: Q
323
330
  })
324
331
  ]
325
332
  })] }),
326
- /* @__PURE__ */ d(te, {
333
+ /* @__PURE__ */ u(te, {
327
334
  open: G.open,
328
335
  onOpenChange: G.onOpenChange,
329
336
  title: G.context ? "Edit client" : "Add client",
@@ -332,17 +339,17 @@ function U() {
332
339
  initialValues: G.context ? z(G.context) : void 0,
333
340
  submitLabel: G.context ? "Save changes" : "Add client",
334
341
  onSubmit: async (e) => {
335
- G.context ? await fe(G.context.id, B(e, G.context)) : await de(B(e));
342
+ G.context ? await pe(G.context.id, B(e, G.context)) : await fe(B(e));
336
343
  }
337
344
  }),
338
- /* @__PURE__ */ d(ae, {
345
+ /* @__PURE__ */ u(se, {
339
346
  open: J !== null,
340
347
  onOpenChange: (e) => {
341
348
  e || Y(null);
342
349
  },
343
- children: /* @__PURE__ */ f(ce, { children: [/* @__PURE__ */ f(m, { children: [/* @__PURE__ */ d(h, { children: "Delete client?" }), /* @__PURE__ */ d(le, { children: J ? `This removes “${J.name}” from your client portfolio. This action cannot be undone.` : "" })] }), /* @__PURE__ */ f(p, { children: [/* @__PURE__ */ d(se, { children: "Cancel" }), /* @__PURE__ */ d(oe, {
350
+ children: /* @__PURE__ */ d(ue, { children: [/* @__PURE__ */ d(m, { children: [/* @__PURE__ */ u(h, { children: "Delete client?" }), /* @__PURE__ */ u(f, { children: J ? `This removes “${J.name}” from your client portfolio. This action cannot be undone.` : "" })] }), /* @__PURE__ */ d(p, { children: [/* @__PURE__ */ u(le, { children: "Cancel" }), /* @__PURE__ */ u(ce, {
344
351
  onClick: (e) => {
345
- e.preventDefault(), J && pe(J.id), Y(null);
352
+ e.preventDefault(), J && me(J.id), Y(null);
346
353
  },
347
354
  children: "Delete"
348
355
  })] })] })
@@ -1 +1 @@
1
- {"version":3,"file":"CompanyClientManagementPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/CompanyClientManagementPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n GradientText,\n IllustratedEmptyState,\n Input,\n PagePurpose,\n Pagination,\n ResponsiveTable,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { MoreHorizontal, Pencil, Plus, Search, Trash2 } from 'lucide-react';\nimport { useNavigate } from 'react-router';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\nimport { useCrewFoundryStore, type Client } from '../store/crewfoundryStore';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { AvatarStack, SegmentedTabs, StatCard } from '../components/viz';\nimport { clientRevenue, totalContractValue } from '../lib/companyFinance';\n\nconst CLIENT_FIELDS: FieldConfig[] = [\n { name: 'name', label: 'Client name', type: 'text', required: true },\n { name: 'industry', label: 'Industry', type: 'text' },\n { name: 'contactPerson', label: 'Contact person', type: 'text' },\n { name: 'email', label: 'Contact email', type: 'email' },\n { name: 'phone', label: 'Phone', type: 'text' },\n {\n name: 'status',\n label: 'Status',\n type: 'select',\n options: [\n { label: 'Active', value: 'active' },\n { label: 'Prospect', value: 'prospect' },\n { label: 'Churned', value: 'churned' },\n ],\n },\n { name: 'revenue', label: 'Revenue (USD)', type: 'number' },\n { name: 'projects', label: 'Projects', type: 'number' },\n { name: 'since', label: 'Client since', type: 'date' },\n];\n\nconst toInitialValues = (client: Client): FormValues => ({\n name: client.name,\n industry: client.industry,\n contactPerson: client.contactPerson,\n email: client.email,\n phone: client.phone,\n status: client.status,\n revenue: client.revenue,\n projects: client.projects,\n since: client.since ? client.since.slice(0, 10) : '',\n});\n\nconst valuesToClient = (values: FormValues, base?: Client): Client => ({\n id: base?.id ?? crypto.randomUUID(),\n name: String(values.name ?? ''),\n industry: String(values.industry ?? ''),\n contactPerson: String(values.contactPerson ?? ''),\n email: String(values.email ?? ''),\n phone: String(values.phone ?? ''),\n status: (values.status as Client['status']) || 'prospect',\n revenue: typeof values.revenue === 'number' ? values.revenue : Number(values.revenue) || 0,\n projects: typeof values.projects === 'number' ? values.projects : Number(values.projects) || 0,\n since: String(values.since ?? ''),\n});\n\nconst statusVariant = (status: Client['status']) =>\n status === 'active' ? 'default' : status === 'prospect' ? 'secondary' : 'destructive';\n\nconst compactMoney = (value: number): string => {\n if (value >= 1_000_000) {\n return `$${(value / 1_000_000).toFixed(1)}M`;\n }\n if (value >= 1_000) {\n return `$${Math.round(value / 1_000)}k`;\n }\n return `$${Math.round(value)}`;\n};\n\nexport function CompanyClientManagementPagePage() {\n const { clients, vendors, addClient, updateClient, removeClient } = useCrewFoundryStore();\n const navigate = useNavigate();\n const modal = useFormModal<Client>();\n const [search, setSearch] = useState('');\n const [filterStatus, setFilterStatus] = useState<string>('all');\n const [deleteTarget, setDeleteTarget] = useState<Client | null>(null);\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 25 });\n\n const activeClients = clients.filter((c) => c.status === 'active');\n const contractValue = totalContractValue(clients, vendors);\n const revenue = clientRevenue(clients);\n\n const filtered = clients.filter((c) => {\n const matchSearch = `${c.name} ${c.industry} ${c.contactPerson} ${c.email}`\n .toLowerCase()\n .includes(search.toLowerCase());\n const matchStatus = filterStatus === 'all' || c.status === filterStatus;\n return matchSearch && matchStatus;\n });\n const paged = filtered.slice(offset, offset + pageSize);\n\n const handleSubmit = async (values: FormValues) => {\n if (modal.context) {\n await updateClient(modal.context.id, valuesToClient(values, modal.context));\n } else {\n await addClient(valuesToClient(values));\n }\n };\n\n const columns: ResponsiveTableColumn<Client>[] = [\n {\n key: 'name',\n header: 'Client',\n priority: 'primary',\n cell: (c) => (\n <div className=\"flex items-center gap-3\">\n <AvatarStack people={[{ name: c.name }]} size={36} />\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium\">{c.name}</p>\n <p className=\"truncate text-xs text-text-secondary\">{c.industry || '—'}</p>\n </div>\n </div>\n ),\n },\n {\n key: 'contact',\n header: 'Primary Contact',\n priority: 'secondary',\n cell: (c) => (\n <div>\n <p className=\"text-xs\">{c.contactPerson || '—'}</p>\n <p className=\"text-xs text-text-secondary\">{c.email}</p>\n </div>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n priority: 'secondary',\n cell: (c) => (\n <Badge variant={statusVariant(c.status)} className=\"text-[10px]\">\n {formatEnumLabel(c.status)}\n </Badge>\n ),\n },\n {\n key: 'projects',\n header: 'Projects',\n priority: 'hidden-on-mobile',\n headerClassName: 'text-right',\n cellClassName: 'text-right',\n cell: (c) => <span className=\"tabular-nums\">{c.projects}</span>,\n },\n {\n key: 'revenue',\n header: 'Value',\n priority: 'secondary',\n headerClassName: 'text-right',\n cellClassName: 'text-right',\n cell: (c) => <span className=\"tabular-nums\">${c.revenue.toLocaleString()}</span>,\n },\n ];\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Clients &amp; Vendors</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {clients.length} clients · {vendors.length} vendors\n </p>\n <PagePurpose className=\"mt-1\">\n Keep track of the clients you work with — their contacts, status, and engagements — so\n account relationships never slip through the cracks.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => modal.openModal()}>\n <Plus className=\"h-4 w-4 mr-1\" />\n Add Client\n </Button>\n </div>\n\n <SegmentedTabs\n aria-label=\"Clients and vendors\"\n variant=\"underline\"\n value=\"clients\"\n onChange={(id) => {\n if (id === 'vendors') {\n navigate('../company/vendor-management-page');\n }\n }}\n tabs={[\n { id: 'clients', label: 'Clients', count: clients.length },\n { id: 'vendors', label: 'Vendors', count: vendors.length },\n ]}\n />\n\n <div className=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n <StatCard label=\"Active Clients\" value={activeClients.length} />\n <StatCard label=\"Vendors\" value={vendors.length} />\n <StatCard label=\"Total Contract Value\" value={compactMoney(contractValue)} />\n <StatCard label=\"Client Revenue\" value={compactMoney(revenue)} />\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>Client Portfolio</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center\">\n <div className=\"relative flex-1\">\n <Search className=\"absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search clients...\"\n className=\"pl-9\"\n value={search}\n onChange={(event) => {\n setSearch(event.target.value);\n setPage(1);\n }}\n />\n </div>\n <Select\n value={filterStatus}\n onValueChange={(value) => {\n setFilterStatus(value);\n setPage(1);\n }}\n >\n <SelectTrigger className=\"w-full sm:w-40\" aria-label=\"Filter by status\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">All statuses</SelectItem>\n <SelectItem value=\"active\">Active</SelectItem>\n <SelectItem value=\"prospect\">Prospect</SelectItem>\n <SelectItem value=\"churned\">Churned</SelectItem>\n </SelectContent>\n </Select>\n </div>\n <ResponsiveTable\n data={paged}\n rowKey={(c) => c.id}\n columns={columns}\n emptyState={\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No clients match\"\n description=\"Try a different search term or status filter.\"\n />\n }\n rowActions={(c) => (\n <div className=\"flex items-center justify-end\">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8\"\n aria-label={`More actions for ${c.name}`}\n >\n <MoreHorizontal className=\"h-4 w-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onSelect={() => modal.openModal(c)}>\n <Pencil className=\"mr-2 h-4 w-4\" />\n Edit\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n className=\"text-status-error-text\"\n onSelect={() => setDeleteTarget(c)}\n >\n <Trash2 className=\"mr-2 h-4 w-4\" />\n Delete\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n )}\n />\n {filtered.length > pageSize && (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={filtered.length}\n onPageChange={setPage}\n />\n )}\n </CardContent>\n </Card>\n\n <FormModal\n open={modal.open}\n onOpenChange={modal.onOpenChange}\n title={modal.context ? 'Edit client' : 'Add client'}\n description=\"Track the client relationship, contacts, and engagement value.\"\n fields={CLIENT_FIELDS}\n initialValues={modal.context ? toInitialValues(modal.context) : undefined}\n submitLabel={modal.context ? 'Save changes' : 'Add client'}\n onSubmit={handleSubmit}\n />\n\n <AlertDialog\n open={deleteTarget !== null}\n onOpenChange={(open) => {\n if (!open) {\n setDeleteTarget(null);\n }\n }}\n >\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Delete client?</AlertDialogTitle>\n <AlertDialogDescription>\n {deleteTarget\n ? `This removes “${deleteTarget.name}” from your client portfolio. This action cannot be undone.`\n : ''}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Cancel</AlertDialogCancel>\n <AlertDialogAction\n onClick={(event) => {\n event.preventDefault();\n if (deleteTarget) {\n void removeClient(deleteTarget.id);\n }\n setDeleteTarget(null);\n }}\n >\n Delete\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"CompanyClientManagementPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/CompanyClientManagementPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n GradientText,\n IllustratedEmptyState,\n Input,\n PagePurpose,\n Pagination,\n ResponsiveTable,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { MoreHorizontal, Pencil, Plus, Search, Trash2 } from 'lucide-react';\nimport { useNavigate } from 'react-router';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\nimport { useCrewFoundryStore, type Client } from '../store/crewfoundryStore';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { useFocusedRecordParam } from '../lib/useFocusedRecordParam';\nimport { AvatarStack, SegmentedTabs, StatCard } from '../components/viz';\nimport { clientRevenue, totalContractValue } from '../lib/companyFinance';\n\nconst CLIENT_FIELDS: FieldConfig[] = [\n { name: 'name', label: 'Client name', type: 'text', required: true },\n { name: 'industry', label: 'Industry', type: 'text' },\n { name: 'contactPerson', label: 'Contact person', type: 'text' },\n { name: 'email', label: 'Contact email', type: 'email' },\n { name: 'phone', label: 'Phone', type: 'text' },\n {\n name: 'status',\n label: 'Status',\n type: 'select',\n options: [\n { label: 'Active', value: 'active' },\n { label: 'Prospect', value: 'prospect' },\n { label: 'Churned', value: 'churned' },\n ],\n },\n { name: 'revenue', label: 'Revenue (USD)', type: 'number' },\n { name: 'projects', label: 'Projects', type: 'number' },\n { name: 'since', label: 'Client since', type: 'date' },\n];\n\nconst toInitialValues = (client: Client): FormValues => ({\n name: client.name,\n industry: client.industry,\n contactPerson: client.contactPerson,\n email: client.email,\n phone: client.phone,\n status: client.status,\n revenue: client.revenue,\n projects: client.projects,\n since: client.since ? client.since.slice(0, 10) : '',\n});\n\nconst valuesToClient = (values: FormValues, base?: Client): Client => ({\n id: base?.id ?? crypto.randomUUID(),\n name: String(values.name ?? ''),\n industry: String(values.industry ?? ''),\n contactPerson: String(values.contactPerson ?? ''),\n email: String(values.email ?? ''),\n phone: String(values.phone ?? ''),\n status: (values.status as Client['status']) || 'prospect',\n revenue: typeof values.revenue === 'number' ? values.revenue : Number(values.revenue) || 0,\n projects: typeof values.projects === 'number' ? values.projects : Number(values.projects) || 0,\n since: String(values.since ?? ''),\n});\n\nconst statusVariant = (status: Client['status']) =>\n status === 'active' ? 'default' : status === 'prospect' ? 'secondary' : 'destructive';\n\nconst compactMoney = (value: number): string => {\n if (value >= 1_000_000) {\n return `$${(value / 1_000_000).toFixed(1)}M`;\n }\n if (value >= 1_000) {\n return `$${Math.round(value / 1_000)}k`;\n }\n return `$${Math.round(value)}`;\n};\n\nexport function CompanyClientManagementPagePage() {\n const { clients, vendors, addClient, updateClient, removeClient } = useCrewFoundryStore();\n const navigate = useNavigate();\n const modal = useFormModal<Client>();\n // The client open in the edit dialog is \"this client\" for the assistant.\n useFocusedRecordParam({\n records: clients,\n activeId: modal.open ? (modal.context?.id ?? null) : null,\n onRestore: modal.openModal,\n });\n const [search, setSearch] = useState('');\n const [filterStatus, setFilterStatus] = useState<string>('all');\n const [deleteTarget, setDeleteTarget] = useState<Client | null>(null);\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 25 });\n\n const activeClients = clients.filter((c) => c.status === 'active');\n const contractValue = totalContractValue(clients, vendors);\n const revenue = clientRevenue(clients);\n\n const filtered = clients.filter((c) => {\n const matchSearch = `${c.name} ${c.industry} ${c.contactPerson} ${c.email}`\n .toLowerCase()\n .includes(search.toLowerCase());\n const matchStatus = filterStatus === 'all' || c.status === filterStatus;\n return matchSearch && matchStatus;\n });\n const paged = filtered.slice(offset, offset + pageSize);\n\n const handleSubmit = async (values: FormValues) => {\n if (modal.context) {\n await updateClient(modal.context.id, valuesToClient(values, modal.context));\n } else {\n await addClient(valuesToClient(values));\n }\n };\n\n const columns: ResponsiveTableColumn<Client>[] = [\n {\n key: 'name',\n header: 'Client',\n priority: 'primary',\n cell: (c) => (\n <div className=\"flex items-center gap-3\">\n <AvatarStack people={[{ name: c.name }]} size={36} />\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium\">{c.name}</p>\n <p className=\"truncate text-xs text-text-secondary\">{c.industry || '—'}</p>\n </div>\n </div>\n ),\n },\n {\n key: 'contact',\n header: 'Primary Contact',\n priority: 'secondary',\n cell: (c) => (\n <div>\n <p className=\"text-xs\">{c.contactPerson || '—'}</p>\n <p className=\"text-xs text-text-secondary\">{c.email}</p>\n </div>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n priority: 'secondary',\n cell: (c) => (\n <Badge variant={statusVariant(c.status)} className=\"text-[10px]\">\n {formatEnumLabel(c.status)}\n </Badge>\n ),\n },\n {\n key: 'projects',\n header: 'Projects',\n priority: 'hidden-on-mobile',\n headerClassName: 'text-right',\n cellClassName: 'text-right',\n cell: (c) => <span className=\"tabular-nums\">{c.projects}</span>,\n },\n {\n key: 'revenue',\n header: 'Value',\n priority: 'secondary',\n headerClassName: 'text-right',\n cellClassName: 'text-right',\n cell: (c) => <span className=\"tabular-nums\">${c.revenue.toLocaleString()}</span>,\n },\n ];\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Clients &amp; Vendors</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {clients.length} clients · {vendors.length} vendors\n </p>\n <PagePurpose className=\"mt-1\">\n Keep track of the clients you work with — their contacts, status, and engagements — so\n account relationships never slip through the cracks.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => modal.openModal()}>\n <Plus className=\"h-4 w-4 mr-1\" />\n Add Client\n </Button>\n </div>\n\n <SegmentedTabs\n aria-label=\"Clients and vendors\"\n variant=\"underline\"\n value=\"clients\"\n onChange={(id) => {\n if (id === 'vendors') {\n navigate('../company/vendor-management-page');\n }\n }}\n tabs={[\n { id: 'clients', label: 'Clients', count: clients.length },\n { id: 'vendors', label: 'Vendors', count: vendors.length },\n ]}\n />\n\n <div className=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n <StatCard label=\"Active Clients\" value={activeClients.length} />\n <StatCard label=\"Vendors\" value={vendors.length} />\n <StatCard label=\"Total Contract Value\" value={compactMoney(contractValue)} />\n <StatCard label=\"Client Revenue\" value={compactMoney(revenue)} />\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>Client Portfolio</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center\">\n <div className=\"relative flex-1\">\n <Search className=\"absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search clients...\"\n className=\"pl-9\"\n value={search}\n onChange={(event) => {\n setSearch(event.target.value);\n setPage(1);\n }}\n />\n </div>\n <Select\n value={filterStatus}\n onValueChange={(value) => {\n setFilterStatus(value);\n setPage(1);\n }}\n >\n <SelectTrigger className=\"w-full sm:w-40\" aria-label=\"Filter by status\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">All statuses</SelectItem>\n <SelectItem value=\"active\">Active</SelectItem>\n <SelectItem value=\"prospect\">Prospect</SelectItem>\n <SelectItem value=\"churned\">Churned</SelectItem>\n </SelectContent>\n </Select>\n </div>\n <ResponsiveTable\n data={paged}\n rowKey={(c) => c.id}\n columns={columns}\n emptyState={\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No clients match\"\n description=\"Try a different search term or status filter.\"\n />\n }\n rowActions={(c) => (\n <div className=\"flex items-center justify-end\">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8\"\n aria-label={`More actions for ${c.name}`}\n >\n <MoreHorizontal className=\"h-4 w-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onSelect={() => modal.openModal(c)}>\n <Pencil className=\"mr-2 h-4 w-4\" />\n Edit\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n className=\"text-status-error-text\"\n onSelect={() => setDeleteTarget(c)}\n >\n <Trash2 className=\"mr-2 h-4 w-4\" />\n Delete\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n )}\n />\n {filtered.length > pageSize && (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={filtered.length}\n onPageChange={setPage}\n />\n )}\n </CardContent>\n </Card>\n\n <FormModal\n open={modal.open}\n onOpenChange={modal.onOpenChange}\n title={modal.context ? 'Edit client' : 'Add client'}\n description=\"Track the client relationship, contacts, and engagement value.\"\n fields={CLIENT_FIELDS}\n initialValues={modal.context ? toInitialValues(modal.context) : undefined}\n submitLabel={modal.context ? 'Save changes' : 'Add client'}\n onSubmit={handleSubmit}\n />\n\n <AlertDialog\n open={deleteTarget !== null}\n onOpenChange={(open) => {\n if (!open) {\n setDeleteTarget(null);\n }\n }}\n >\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Delete client?</AlertDialogTitle>\n <AlertDialogDescription>\n {deleteTarget\n ? `This removes “${deleteTarget.name}” from your client portfolio. This action cannot be undone.`\n : ''}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Cancel</AlertDialogCancel>\n <AlertDialogAction\n onClick={(event) => {\n event.preventDefault();\n if (deleteTarget) {\n void removeClient(deleteTarget.id);\n }\n setDeleteTarget(null);\n }}\n >\n Delete\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CompanyVendorManagementPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/CompanyVendorManagementPagePage.tsx"],"names":[],"mappings":"AAsGA,wBAAgB,+BAA+B,gCA+P9C"}
1
+ {"version":3,"file":"CompanyVendorManagementPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/CompanyVendorManagementPagePage.tsx"],"names":[],"mappings":"AAuGA,wBAAgB,+BAA+B,gCAqQ9C"}