@burdenoff/microfe-proserviceworld 2026.703.1 → 2026.706.2

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 (79) hide show
  1. package/dist/index.js +2 -3
  2. package/dist/node_modules/clsx/dist/clsx.js +18 -0
  3. package/dist/node_modules/clsx/dist/clsx.js.map +1 -0
  4. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +1716 -0
  5. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
  6. package/dist/proserviceworld/ProServiceWorldRoot.js +92 -136
  7. package/dist/proserviceworld/ProServiceWorldRoot.js.map +1 -1
  8. package/dist/proserviceworld/ProServiceWorldRoutes.js +18 -26
  9. package/dist/proserviceworld/ProServiceWorldRoutes.js.map +1 -1
  10. package/dist/proserviceworld/components/DiscussionThread.js +76 -0
  11. package/dist/proserviceworld/components/DiscussionThread.js.map +1 -0
  12. package/dist/proserviceworld/components/ui.js +273 -0
  13. package/dist/proserviceworld/components/ui.js.map +1 -0
  14. package/dist/proserviceworld/hooks/useEntityComments.js +56 -0
  15. package/dist/proserviceworld/hooks/useEntityComments.js.map +1 -0
  16. package/dist/proserviceworld/hooks/useFiles.js +45 -0
  17. package/dist/proserviceworld/hooks/useFiles.js.map +1 -0
  18. package/dist/proserviceworld/hooks/useProServiceWorldApi.js +416 -0
  19. package/dist/proserviceworld/hooks/useProServiceWorldApi.js.map +1 -0
  20. package/dist/proserviceworld/index.js +0 -1
  21. package/dist/proserviceworld/pages/AnalyticsPage.js +163 -475
  22. package/dist/proserviceworld/pages/AnalyticsPage.js.map +1 -1
  23. package/dist/proserviceworld/pages/ClientDetailPage.js +243 -0
  24. package/dist/proserviceworld/pages/ClientDetailPage.js.map +1 -0
  25. package/dist/proserviceworld/pages/ClientsPage.js +295 -0
  26. package/dist/proserviceworld/pages/ClientsPage.js.map +1 -0
  27. package/dist/proserviceworld/pages/CompliancePage.js +307 -0
  28. package/dist/proserviceworld/pages/CompliancePage.js.map +1 -0
  29. package/dist/proserviceworld/pages/ConflictsPage.js +234 -0
  30. package/dist/proserviceworld/pages/ConflictsPage.js.map +1 -0
  31. package/dist/proserviceworld/pages/DashboardPage.js +179 -632
  32. package/dist/proserviceworld/pages/DashboardPage.js.map +1 -1
  33. package/dist/proserviceworld/pages/DocumentsPage.js +235 -539
  34. package/dist/proserviceworld/pages/DocumentsPage.js.map +1 -1
  35. package/dist/proserviceworld/pages/EngagementDetailPage.js +449 -0
  36. package/dist/proserviceworld/pages/EngagementDetailPage.js.map +1 -0
  37. package/dist/proserviceworld/pages/EngagementsPage.js +256 -0
  38. package/dist/proserviceworld/pages/EngagementsPage.js.map +1 -0
  39. package/dist/proserviceworld/pages/InvoicesPage.js +303 -0
  40. package/dist/proserviceworld/pages/InvoicesPage.js.map +1 -0
  41. package/dist/proserviceworld/pages/MattersPage.js +275 -0
  42. package/dist/proserviceworld/pages/MattersPage.js.map +1 -0
  43. package/dist/proserviceworld/pages/PracticeAreasPage.js +176 -0
  44. package/dist/proserviceworld/pages/PracticeAreasPage.js.map +1 -0
  45. package/dist/proserviceworld/pages/TimePage.js +310 -0
  46. package/dist/proserviceworld/pages/TimePage.js.map +1 -0
  47. package/dist/proserviceworld/utils/cn.js +10 -0
  48. package/dist/proserviceworld/utils/cn.js.map +1 -0
  49. package/dist/proserviceworld/utils/nav.js +11 -0
  50. package/dist/proserviceworld/utils/nav.js.map +1 -0
  51. package/package.json +1 -1
  52. package/dist/proserviceworld/pages/AdminReportsPage.js +0 -579
  53. package/dist/proserviceworld/pages/AdminReportsPage.js.map +0 -1
  54. package/dist/proserviceworld/pages/AdminUsersPage.js +0 -468
  55. package/dist/proserviceworld/pages/AdminUsersPage.js.map +0 -1
  56. package/dist/proserviceworld/pages/BookingsPage.js +0 -446
  57. package/dist/proserviceworld/pages/BookingsPage.js.map +0 -1
  58. package/dist/proserviceworld/pages/MessagesPage.js +0 -238
  59. package/dist/proserviceworld/pages/MessagesPage.js.map +0 -1
  60. package/dist/proserviceworld/pages/PaymentsPage.js +0 -537
  61. package/dist/proserviceworld/pages/PaymentsPage.js.map +0 -1
  62. package/dist/proserviceworld/pages/ProjectsPage.js +0 -627
  63. package/dist/proserviceworld/pages/ProjectsPage.js.map +0 -1
  64. package/dist/proserviceworld/pages/ProviderDetailPage.js +0 -538
  65. package/dist/proserviceworld/pages/ProviderDetailPage.js.map +0 -1
  66. package/dist/proserviceworld/pages/ProvidersPage.js +0 -347
  67. package/dist/proserviceworld/pages/ProvidersPage.js.map +0 -1
  68. package/dist/proserviceworld/pages/ReviewsPage.js +0 -356
  69. package/dist/proserviceworld/pages/ReviewsPage.js.map +0 -1
  70. package/dist/proserviceworld/pages/ServiceDetailsPage.js +0 -631
  71. package/dist/proserviceworld/pages/ServiceDetailsPage.js.map +0 -1
  72. package/dist/proserviceworld/pages/ServicesPage.js +0 -344
  73. package/dist/proserviceworld/pages/ServicesPage.js.map +0 -1
  74. package/dist/proserviceworld/pages/VerificationPage.js +0 -370
  75. package/dist/proserviceworld/pages/VerificationPage.js.map +0 -1
  76. package/dist/proserviceworld/store/proserviceworldStore.js +0 -2490
  77. package/dist/proserviceworld/store/proserviceworldStore.js.map +0 -1
  78. package/dist/proserviceworld/utils/i18n.js +0 -15
  79. package/dist/proserviceworld/utils/i18n.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"DocumentsPage.js","names":[],"sources":["../../../src/proserviceworld/pages/DocumentsPage.tsx"],"sourcesContent":["import { useState, useMemo } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Input,\n GlassCard,\n AuroraBackground,\n GradientText,\n CTAOverflowMenu,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Search,\n FolderOpen,\n FileText,\n File,\n Image,\n Upload,\n Grid3x3,\n List,\n Star,\n Download,\n Share2,\n MoreHorizontal,\n ArrowUpDown,\n ChevronRight,\n FolderPlus,\n Users,\n Clock,\n HardDrive,\n} from 'lucide-react';\nimport { useProServiceWorldStore } from '../store/proserviceworldStore';\n\ntype ViewMode = 'grid' | 'list';\ntype SortBy = 'name' | 'date' | 'size';\ntype FileFilter = 'all' | 'pdf' | 'docx' | 'txt' | 'image' | 'starred';\n\nconst fileTypeIcons: Record<string, typeof FileText> = {\n pdf: FileText,\n docx: File,\n doc: File,\n txt: FileText,\n image: Image,\n video: File,\n audio: File,\n other: File,\n};\n\nconst fileTypeColors: Record<string, string> = {\n pdf: 'text-status-error-text',\n docx: 'text-accent-blue',\n doc: 'text-accent-blue',\n txt: 'text-text-secondary',\n image: 'text-status-success-text',\n video: 'text-accent-purple',\n audio: 'text-status-warning-text',\n other: 'text-text-secondary',\n};\n\nconst folderColors: Record<string, string> = {\n blue: 'text-accent-blue',\n green: 'text-status-success-text',\n purple: 'text-accent-purple',\n yellow: 'text-status-warning-text',\n red: 'text-status-error-text',\n};\n\nfunction formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nexport function DocumentsPage() {\n const { documents, folders } = useProServiceWorldStore();\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [searchQuery, setSearchQuery] = useState('');\n const [fileFilter, setFileFilter] = useState<FileFilter>('all');\n const [sortBy, setSortBy] = useState<SortBy>('date');\n const [sortAsc, setSortAsc] = useState(false);\n const [selectedFolderId, setSelectedFolderId] = useState<string | null>(null);\n const [showUploadModal, setShowUploadModal] = useState(false);\n const [showCreateFolderModal, setShowCreateFolderModal] = useState(false);\n\n const filteredDocs = useMemo(() => {\n let result = [...documents];\n\n // Folder filter\n if (selectedFolderId) {\n result = result.filter((d) => d.folderId === selectedFolderId);\n }\n\n // Type filter\n if (fileFilter === 'starred') {\n result = result.filter((d) => d.isStarred);\n } else if (fileFilter !== 'all') {\n result = result.filter((d) => d.type === fileFilter);\n }\n\n // Search\n if (searchQuery.trim()) {\n const q = searchQuery.toLowerCase();\n result = result.filter(\n (d) => d.name.toLowerCase().includes(q) || d.tags.some((t) => t.toLowerCase().includes(q))\n );\n }\n\n // Sort\n result.sort((a, b) => {\n const cmp =\n sortBy === 'name'\n ? a.name.localeCompare(b.name)\n : sortBy === 'date'\n ? new Date(b.modifiedAt).getTime() - new Date(a.modifiedAt).getTime()\n : b.size - a.size;\n return sortAsc ? -cmp : cmp;\n });\n\n return result;\n }, [documents, selectedFolderId, fileFilter, searchQuery, sortBy, sortAsc]);\n\n const totalSize = documents.reduce((s, d) => s + d.size, 0);\n const starredCount = documents.filter((d) => d.isStarred).length;\n const currentFolder = selectedFolderId ? folders.find((f) => f.id === selectedFolderId) : null;\n\n const handleSort = (field: SortBy) => {\n if (sortBy === field) setSortAsc(!sortAsc);\n else {\n setSortBy(field);\n setSortAsc(false);\n }\n };\n\n return (\n <div className=\"space-y-6 p-6 bg-bg-canvas min-h-screen\">\n {/* Hero header — the ONE aurora backdrop for this page. */}\n <div className=\"relative overflow-hidden rounded-card -mx-2 px-2 pt-1\">\n <AuroraBackground intensity=\"subtle\" />\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold tracking-tight\">\n <GradientText>Documents</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n Manage files, folders, and shared documents.\n </p>\n </div>\n <CTAOverflowMenu\n primary={{\n label: 'Upload',\n onSelect: () => setShowUploadModal(true),\n icon: <Upload className=\"h-4 w-4\" />,\n }}\n actions={[\n {\n label: 'New Folder',\n onSelect: () => setShowCreateFolderModal(true),\n icon: <FolderPlus className=\"h-4 w-4\" />,\n },\n ]}\n />\n </div>\n </div>\n\n <PagePurpose icon={<FolderOpen className=\"h-4 w-4\" />}>\n A shared home for the files behind your work — contracts, deliverables, briefs, and more.\n Organise documents into folders, star the ones you reach for often, search across\n everything, and share files securely with clients and collaborators.\n </PagePurpose>\n\n {/* Stats */}\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-lg bg-accent-blue-subtle p-2\">\n <FileText className=\"h-5 w-5 text-accent-blue\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold tabular-nums\">{documents.length}</p>\n <p className=\"text-xs text-text-secondary\">Total Files</p>\n </div>\n </div>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-lg bg-accent-purple-subtle p-2\">\n <FolderOpen className=\"h-5 w-5 text-accent-purple\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold tabular-nums\">{folders.length}</p>\n <p className=\"text-xs text-text-secondary\">Folders</p>\n </div>\n </div>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-lg bg-status-warning-bg-subtle p-2\">\n <Star className=\"h-5 w-5 text-status-warning-text\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold tabular-nums\">{starredCount}</p>\n <p className=\"text-xs text-text-secondary\">Starred</p>\n </div>\n </div>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-lg bg-status-success-bg-subtle p-2\">\n <HardDrive className=\"h-5 w-5 text-status-success-text\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold tabular-nums\">{formatFileSize(totalSize)}</p>\n <p className=\"text-xs text-text-secondary\">Total Size</p>\n </div>\n </div>\n </CardContent>\n </GlassCard>\n </div>\n\n {/* Breadcrumb */}\n <div className=\"flex items-center gap-2 text-sm\">\n <button\n onClick={() => setSelectedFolderId(null)}\n className={`font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus rounded ${!selectedFolderId ? 'text-accent-blue' : 'text-text-secondary hover:text-text-primary'}`}\n >\n All Files\n </button>\n {currentFolder && (\n <>\n <ChevronRight className=\"h-4 w-4 text-text-secondary\" />\n <span className=\"font-medium text-accent-blue\">{currentFolder.name}</span>\n </>\n )}\n </div>\n\n {/* Search, Filters, View Toggle */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center\">\n <div className=\"relative flex-1\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search documents...\"\n aria-label=\"Search documents\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"pl-10\"\n />\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <div className=\"flex flex-wrap gap-1\">\n {(['all', 'pdf', 'docx', 'txt', 'starred'] as FileFilter[]).map((filter) => (\n <button\n key={filter}\n onClick={() => setFileFilter(filter)}\n aria-pressed={fileFilter === filter}\n className={`rounded px-2 py-1 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${\n fileFilter === filter\n ? 'bg-accent-blue text-action-primary-text'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n }`}\n >\n {filter === 'all' ? 'All' : filter === 'starred' ? 'Starred' : filter.toUpperCase()}\n </button>\n ))}\n </div>\n <div className=\"flex rounded border border-border-seam\">\n <button\n onClick={() => setViewMode('grid')}\n aria-label=\"Grid view\"\n aria-pressed={viewMode === 'grid'}\n className={`p-1.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${viewMode === 'grid' ? 'bg-accent-blue text-action-primary-text' : 'text-text-secondary'}`}\n >\n <Grid3x3 className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => setViewMode('list')}\n aria-label=\"List view\"\n aria-pressed={viewMode === 'list'}\n className={`p-1.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${viewMode === 'list' ? 'bg-accent-blue text-action-primary-text' : 'text-text-secondary'}`}\n >\n <List className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n </div>\n\n {/* Sort */}\n <div className=\"flex gap-2\">\n {[\n { field: 'name' as SortBy, label: 'Name' },\n { field: 'date' as SortBy, label: 'Date' },\n { field: 'size' as SortBy, label: 'Size' },\n ].map(({ field, label }) => (\n <button\n key={field}\n onClick={() => handleSort(field)}\n aria-pressed={sortBy === field}\n className={`flex items-center gap-1 rounded border px-3 py-1 text-xs focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${\n sortBy === field\n ? 'border-accent-blue bg-accent-blue-subtle text-accent-blue'\n : 'border-border-seam text-text-secondary hover:text-text-primary'\n }`}\n >\n <ArrowUpDown className=\"h-3 w-3\" />\n {label}\n </button>\n ))}\n </div>\n\n {/* Folders section (only on root) */}\n {!selectedFolderId && (\n <div>\n <h3 className=\"mb-3 text-sm font-medium text-text-secondary\">Folders</h3>\n <div className=\"grid gap-3 sm:grid-cols-2 lg:grid-cols-4\">\n {folders.map((folder) => (\n <button\n key={folder.id}\n onClick={() => setSelectedFolderId(folder.id)}\n className=\"flex items-center gap-3 rounded-lg border border-border-seam p-3 text-left transition-shadow hover:shadow-pop focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <FolderOpen\n className={`h-8 w-8 ${folderColors[folder.color] ?? 'text-text-secondary'}`}\n />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate font-medium text-sm\">{folder.name}</p>\n <p className=\"text-xs text-text-secondary\">\n <span className=\"tabular-nums\">{folder.documentsCount}</span> files\n {folder.isPublic && ' - Public'}\n </p>\n </div>\n </button>\n ))}\n </div>\n </div>\n )}\n\n {/* Files */}\n <div>\n <h3 className=\"mb-3 text-sm font-medium text-text-secondary\">\n Files {filteredDocs.length > 0 && `(${filteredDocs.length})`}\n </h3>\n\n {filteredDocs.length === 0 ? (\n <Card className=\"border-border-seam\">\n <CardContent className=\"py-6\">\n <IllustratedEmptyState\n illustration={\n searchQuery || fileFilter !== 'all' ? 'empty-search' : 'empty-files'\n }\n title=\"No files found\"\n description=\"Try adjusting your filters or upload a new document.\"\n action={\n <Button size=\"sm\" onClick={() => setShowUploadModal(true)}>\n <Upload className=\"mr-2 h-4 w-4\" /> Upload\n </Button>\n }\n />\n </CardContent>\n </Card>\n ) : viewMode === 'grid' ? (\n <div className=\"grid gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n {filteredDocs.map((doc) => {\n const TypeIcon = fileTypeIcons[doc.type] ?? File;\n return (\n <GlassCard\n key={doc.id}\n treatment=\"plain\"\n className=\"border-border-seam transition-shadow hover:shadow-pop\"\n >\n <CardContent className=\"pt-4\">\n <div className=\"flex items-start justify-between\">\n <TypeIcon\n className={`h-10 w-10 ${fileTypeColors[doc.type] ?? 'text-text-secondary'}`}\n />\n <div className=\"flex items-center gap-1\">\n {doc.isStarred && (\n <Star className=\"h-4 w-4 fill-status-warning-text text-status-warning-text\" />\n )}\n {doc.sharedWith > 0 && <Share2 className=\"h-4 w-4 text-text-secondary\" />}\n </div>\n </div>\n <h4 className=\"mt-3 truncate font-medium text-sm\">{doc.name}</h4>\n <div className=\"mt-1 flex items-center gap-2 text-xs text-text-secondary\">\n <span className=\"tabular-nums\">{formatFileSize(doc.size)}</span>\n <span className=\"tabular-nums\">v{doc.version}</span>\n </div>\n <div className=\"mt-2 flex items-center gap-1 text-xs text-text-secondary\">\n <Clock className=\"h-3 w-3\" /> {doc.modifiedAt}\n </div>\n <div className=\"mt-3 flex gap-1\">\n <Button variant=\"outline\" size=\"sm\" className=\"flex-1 text-xs\">\n <Download className=\"mr-1 h-3 w-3\" /> Download\n </Button>\n <Button variant=\"outline\" size=\"sm\" aria-label=\"More actions\">\n <MoreHorizontal className=\"h-3 w-3\" />\n </Button>\n </div>\n </CardContent>\n </GlassCard>\n );\n })}\n </div>\n ) : (\n <Card className=\"border-border-seam\">\n <CardContent className=\"pt-4\">\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam text-left text-text-secondary\">\n <th className=\"p-3\">Name</th>\n <th className=\"p-3\">Type</th>\n <th className=\"p-3\">Size</th>\n <th className=\"p-3\">Modified</th>\n <th className=\"p-3\">Uploaded By</th>\n <th className=\"p-3 text-right\">Actions</th>\n </tr>\n </thead>\n <tbody>\n {filteredDocs.map((doc) => {\n const TypeIcon = fileTypeIcons[doc.type] ?? File;\n return (\n <tr key={doc.id} className=\"border-b border-border-seam hover:bg-bg-sunken\">\n <td className=\"p-3\">\n <div className=\"flex items-center gap-2\">\n <TypeIcon\n className={`h-5 w-5 ${fileTypeColors[doc.type] ?? 'text-text-secondary'}`}\n />\n <span className=\"font-medium\">{doc.name}</span>\n {doc.isStarred && (\n <Star className=\"h-3 w-3 fill-status-warning-text text-status-warning-text\" />\n )}\n {doc.sharedWith > 0 && (\n <span className=\"flex items-center gap-0.5 text-xs text-text-secondary\">\n <Users className=\"h-3 w-3\" />{' '}\n <span className=\"tabular-nums\">{doc.sharedWith}</span>\n </span>\n )}\n </div>\n </td>\n <td className=\"p-3 uppercase text-xs\">{doc.type}</td>\n <td className=\"p-3 tabular-nums\">{formatFileSize(doc.size)}</td>\n <td className=\"p-3 text-text-secondary\">{doc.modifiedAt}</td>\n <td className=\"p-3 text-text-secondary\">{doc.uploadedBy}</td>\n <td className=\"p-3\">\n <div className=\"flex justify-end gap-1\">\n <Button variant=\"outline\" size=\"sm\" aria-label=\"Download\">\n <Download className=\"h-3 w-3\" />\n </Button>\n <Button variant=\"outline\" size=\"sm\" aria-label=\"Share\">\n <Share2 className=\"h-3 w-3\" />\n </Button>\n </div>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n </CardContent>\n </Card>\n )}\n </div>\n\n {/* Upload Modal */}\n {showUploadModal && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay\">\n <GlassCard treatment=\"glass\" className=\"mx-4 w-full max-w-md border-border-seam\">\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle>Upload Files</CardTitle>\n <Button variant=\"outline\" size=\"sm\" onClick={() => setShowUploadModal(false)}>\n Close\n </Button>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"flex flex-col items-center justify-center rounded-lg border-2 border-dashed border-border-seam p-8\">\n <Upload className=\"h-10 w-10 text-text-secondary\" />\n <p className=\"mt-2 text-sm font-medium\">Drag & drop files here</p>\n <p className=\"text-xs text-text-secondary\">or click to browse</p>\n </div>\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"upload-folder\">\n Destination Folder\n </label>\n <select\n id=\"upload-folder\"\n className=\"w-full rounded border border-border-seam bg-bg-surface px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n >\n <option value=\"\">Root</option>\n {folders.map((f) => (\n <option key={f.id} value={f.id}>\n {f.name}\n </option>\n ))}\n </select>\n </div>\n <div className=\"flex gap-2\">\n <Button\n variant=\"outline\"\n className=\"flex-1\"\n onClick={() => setShowUploadModal(false)}\n >\n Cancel\n </Button>\n <Button className=\"flex-1\">Upload</Button>\n </div>\n </CardContent>\n </GlassCard>\n </div>\n )}\n\n {/* Create Folder Modal */}\n {showCreateFolderModal && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay\">\n <GlassCard treatment=\"glass\" className=\"mx-4 w-full max-w-md border-border-seam\">\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle>Create Folder</CardTitle>\n <Button variant=\"outline\" size=\"sm\" onClick={() => setShowCreateFolderModal(false)}>\n Close\n </Button>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"folder-name\">\n Folder Name\n </label>\n <Input id=\"folder-name\" placeholder=\"Enter folder name...\" />\n </div>\n <div className=\"flex gap-2\">\n <Button\n variant=\"outline\"\n className=\"flex-1\"\n onClick={() => setShowCreateFolderModal(false)}\n >\n Cancel\n </Button>\n <Button className=\"flex-1\">Create</Button>\n </div>\n </CardContent>\n </GlassCard>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"DocumentsPage.js","names":[],"sources":["../../../src/proserviceworld/pages/DocumentsPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Plus, FileText, Download, Archive, Lock, Upload } from 'lucide-react';\nimport {\n useDocuments,\n useCreateDocument,\n useArchiveDocument,\n type PswDocumentClassification,\n} from '../hooks/useProServiceWorldApi';\nimport { uploadDocumentFile, getDocumentDownloadUrl } from '../hooks/useFiles';\nimport {\n PageHeader,\n SectionCard,\n LoadingBlock,\n ErrorState,\n Empty,\n StatusPill,\n Modal,\n Field,\n TextInput,\n TextArea,\n NativeSelect,\n Banner,\n TableWrap,\n Th,\n Td,\n enumOptions,\n fmtDate,\n humanize,\n} from '../components/ui';\n\nconst CLASSIFICATIONS = ['PUBLIC', 'INTERNAL', 'CONFIDENTIAL', 'PRIVILEGED'] as const;\n\nexport const DocumentsPage: FC = () => {\n const [classFilter, setClassFilter] = useState<PswDocumentClassification | ''>('');\n const [creating, setCreating] = useState(false);\n const [banner, setBanner] = useState<{ tone: 'success' | 'danger'; text: string } | null>(null);\n const { data, isLoading, error, refetch } = useDocuments({\n classification: classFilter || undefined,\n });\n const archive = useArchiveDocument();\n\n const download = async (fileId: string) => {\n try {\n const url = await getDocumentDownloadUrl(fileId);\n window.open(url, '_blank', 'noopener');\n } catch (e) {\n setBanner({\n tone: 'danger',\n text: e instanceof Error ? e.message : 'Could not open document.',\n });\n }\n };\n\n return (\n <div className=\"space-y-6\">\n <PageHeader\n title=\"Documents\"\n subtitle=\"Engagement letters, opinions, filings and evidence — metadata, classification and legal hold here; the file itself lives in the shared Files service.\"\n icon={<FileText className=\"h-6 w-6\" />}\n actions={\n <Button onClick={() => setCreating(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n Add document\n </Button>\n }\n />\n\n {banner && (\n <Banner tone={banner.tone} onClose={() => setBanner(null)}>\n {banner.text}\n </Banner>\n )}\n\n <div className=\"flex justify-end\">\n <NativeSelect\n value={classFilter}\n onChange={(e) => setClassFilter(e.target.value as PswDocumentClassification | '')}\n className=\"sm:w-52\"\n >\n <option value=\"\">All classifications</option>\n {enumOptions(CLASSIFICATIONS)}\n </NativeSelect>\n </div>\n\n {isLoading ? (\n <LoadingBlock />\n ) : error ? (\n <ErrorState error={error} onRetry={() => void refetch()} />\n ) : !data || data.items.length === 0 ? (\n <Empty\n title=\"No documents\"\n description=\"Add a document — upload the file to the shared Files service and record it here.\"\n icon={<FileText className=\"h-8 w-8\" />}\n action={\n <Button onClick={() => setCreating(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n Add document\n </Button>\n }\n />\n ) : (\n <SectionCard title={`${data.totalCount} document${data.totalCount === 1 ? '' : 's'}`}>\n <TableWrap>\n <thead>\n <tr>\n <Th>Name</Th>\n <Th>Classification</Th>\n <Th>Status</Th>\n <Th>Added</Th>\n <Th className=\"text-right\">Actions</Th>\n </tr>\n </thead>\n <tbody>\n {data.items.map((d) => (\n <tr key={d.id} className=\"hover:bg-bg-sunken/50\">\n <Td>\n <span className=\"flex items-center gap-1.5 font-medium text-text-primary\">\n {d.legalHold && (\n <Lock\n className=\"h-3.5 w-3.5 text-status-warning-text\"\n aria-label=\"Legal hold\"\n />\n )}\n {d.name} <span className=\"text-xs text-text-tertiary\">v{d.version}</span>\n </span>\n </Td>\n <Td>{humanize(d.classification)}</Td>\n <Td>\n <StatusPill status={d.status} />\n </Td>\n <Td>{fmtDate(d.createdAt)}</Td>\n <Td className=\"text-right\">\n <div className=\"flex justify-end gap-1\">\n {d.file?.id && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => void download(d.file!.id)}\n aria-label=\"Download\"\n >\n <Download className=\"h-4 w-4\" />\n </Button>\n )}\n {d.status !== 'ARCHIVED' && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label=\"Archive\"\n disabled={archive.isPending}\n onClick={() =>\n archive.mutate(d.id, {\n onSuccess: () =>\n setBanner({ tone: 'success', text: `${d.name} archived.` }),\n onError: (e) =>\n setBanner({\n tone: 'danger',\n text: e instanceof Error ? e.message : 'Failed.',\n }),\n })\n }\n >\n <Archive className=\"h-4 w-4\" />\n </Button>\n )}\n </div>\n </Td>\n </tr>\n ))}\n </tbody>\n </TableWrap>\n </SectionCard>\n )}\n\n {creating && (\n <DocumentModal\n onClose={() => setCreating(false)}\n onDone={() => {\n void refetch();\n setBanner({ tone: 'success', text: 'Document added.' });\n }}\n />\n )}\n </div>\n );\n};\n\nconst DocumentModal: FC<{ onClose: () => void; onDone: () => void }> = ({ onClose, onDone }) => {\n const create = useCreateDocument();\n const [form, setForm] = useState({\n name: '',\n classification: 'INTERNAL',\n description: '',\n legalHold: false,\n });\n const [file, setFile] = useState<File | null>(null);\n const [uploading, setUploading] = useState(false);\n const [err, setErr] = useState<string | null>(null);\n\n const submit = async () => {\n setErr(null);\n if (!form.name.trim()) return setErr('Document name is required.');\n let fileId: string | null = null;\n if (file) {\n try {\n setUploading(true);\n const uploaded = await uploadDocumentFile(file);\n fileId = uploaded.fileId;\n } catch (e) {\n setUploading(false);\n return setErr(e instanceof Error ? e.message : 'Upload failed.');\n }\n setUploading(false);\n }\n create.mutate(\n {\n name: form.name.trim(),\n classification: form.classification,\n description: form.description.trim() || null,\n legalHold: form.legalHold,\n fileId,\n },\n {\n onSuccess: () => {\n onDone();\n onClose();\n },\n onError: (e) => setErr(e instanceof Error ? e.message : 'Failed.'),\n }\n );\n };\n\n const busy = uploading || create.isPending;\n return (\n <Modal\n open\n onClose={onClose}\n title=\"Add document\"\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={busy}>\n Cancel\n </Button>\n <Button onClick={() => void submit()} disabled={busy}>\n {uploading ? 'Uploading…' : create.isPending ? 'Saving…' : 'Add document'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <Field label=\"Name\" required>\n <TextInput\n value={form.name}\n onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))}\n placeholder=\"Engagement letter — Acme FY26\"\n />\n </Field>\n <Field label=\"Classification\">\n <NativeSelect\n value={form.classification}\n onChange={(e) => setForm((f) => ({ ...f, classification: e.target.value }))}\n >\n {enumOptions(CLASSIFICATIONS)}\n </NativeSelect>\n </Field>\n <Field label=\"File\" hint=\"Optional — stored in the shared Files service.\">\n <label className=\"flex cursor-pointer items-center gap-2 rounded-lg border border-dashed border-border-seam bg-bg-canvas px-3 py-3 text-sm text-text-secondary hover:border-accent\">\n <Upload className=\"h-4 w-4 text-text-tertiary\" />\n {file ? file.name : 'Choose a file…'}\n <input\n type=\"file\"\n className=\"hidden\"\n onChange={(e) => setFile(e.target.files?.[0] ?? null)}\n />\n </label>\n </Field>\n <Field label=\"Description\">\n <TextArea\n value={form.description}\n onChange={(e) => setForm((f) => ({ ...f, description: e.target.value }))}\n />\n </Field>\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <input\n type=\"checkbox\"\n checked={form.legalHold}\n onChange={(e) => setForm((f) => ({ ...f, legalHold: e.target.checked }))}\n />\n Place under legal hold (cannot be disposed)\n </label>\n </div>\n </Modal>\n );\n};\n"],"mappings":"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"}
@@ -0,0 +1,449 @@
1
+ import { useCompleteMilestone as e, useCreateDeliverable as t, useCreateMilestone as n, useEngagement as r, useTimeEntries as i, useTransitionDeliverable as a, useTransitionEngagement as o } from "../hooks/useProServiceWorldApi.js";
2
+ import { useNav as s } from "../utils/nav.js";
3
+ import { Banner as c, Empty as l, ErrorState as u, Field as d, LoadingBlock as f, MetricCard as p, Modal as m, NativeSelect as h, PageHeader as g, SectionCard as _, StatusPill as v, TextArea as y, TextInput as b, fmtDate as x, fmtMinutesAsHours as S, fmtMoney as C, humanize as w } from "../components/ui.js";
4
+ import { DiscussionThread as T } from "../components/DiscussionThread.js";
5
+ import { useState as E } from "react";
6
+ import { ArrowLeft as D, CheckCircle2 as O, Clock as k, FileText as A, Flag as j, Plus as M } from "lucide-react";
7
+ import { useParams as N } from "react-router-dom";
8
+ import { Fragment as P, jsx as F, jsxs as I } from "react/jsx-runtime";
9
+ import { Button as L } from "@burdenoff/fe-libs/ui";
10
+ //#region src/proserviceworld/pages/EngagementDetailPage.tsx
11
+ var R = {
12
+ PROPOSAL: ["ACCEPTED", "CANCELLED"],
13
+ ACCEPTED: ["IN_PROGRESS", "CANCELLED"],
14
+ IN_PROGRESS: ["REVIEW", "CANCELLED"],
15
+ REVIEW: [
16
+ "DELIVERED",
17
+ "IN_PROGRESS",
18
+ "CANCELLED"
19
+ ],
20
+ DELIVERED: ["INVOICED", "CANCELLED"],
21
+ INVOICED: ["CLOSED"],
22
+ CLOSED: [],
23
+ CANCELLED: []
24
+ }, z = [
25
+ "DRAFT",
26
+ "INTERNAL_REVIEW",
27
+ "CLIENT_REVIEW",
28
+ "ACCEPTED",
29
+ "REJECTED"
30
+ ], B = () => {
31
+ let { id: e = "" } = N(), t = s(), { data: n, isLoading: a, error: d, refetch: m } = r(e), h = i({ engagementId: e }), y = o(), [b, O] = E(!1), [P, z] = E(!1), [B, G] = E(null);
32
+ if (a) return /* @__PURE__ */ F(f, {});
33
+ if (d) return /* @__PURE__ */ F(u, {
34
+ error: d,
35
+ onRetry: () => void m()
36
+ });
37
+ if (!n) return /* @__PURE__ */ F(l, { title: "Engagement not found" });
38
+ let K = R[n.status] ?? [], q = (h.data?.items ?? []).reduce((e, t) => e + t.minutes, 0);
39
+ return /* @__PURE__ */ I("div", {
40
+ className: "space-y-6",
41
+ children: [
42
+ /* @__PURE__ */ I("button", {
43
+ type: "button",
44
+ onClick: () => t("engagements"),
45
+ className: "flex items-center gap-1.5 text-sm text-text-secondary hover:text-accent",
46
+ children: [/* @__PURE__ */ F(D, { className: "h-4 w-4" }), " Back to engagements"]
47
+ }),
48
+ /* @__PURE__ */ F(g, {
49
+ title: n.name,
50
+ subtitle: n.client ? `${n.client.name} · ${w(n.feeArrangement)}` : w(n.feeArrangement),
51
+ actions: /* @__PURE__ */ F(v, { status: n.status })
52
+ }),
53
+ B && /* @__PURE__ */ F(c, {
54
+ tone: B.tone,
55
+ onClose: () => G(null),
56
+ children: B.text
57
+ }),
58
+ /* @__PURE__ */ I("div", {
59
+ className: "grid grid-cols-2 gap-3 lg:grid-cols-4",
60
+ children: [
61
+ /* @__PURE__ */ F(p, {
62
+ label: "Fee",
63
+ value: C(n.feeAmount, n.currency),
64
+ hint: w(n.feeArrangement)
65
+ }),
66
+ /* @__PURE__ */ F(p, {
67
+ label: "Deliverables",
68
+ value: n.deliverables.length
69
+ }),
70
+ /* @__PURE__ */ F(p, {
71
+ label: "Milestones",
72
+ value: n.milestones.length
73
+ }),
74
+ /* @__PURE__ */ F(p, {
75
+ label: "Time logged",
76
+ value: S(q),
77
+ hint: `${h.data?.totalCount ?? 0} entries`
78
+ })
79
+ ]
80
+ }),
81
+ K.length > 0 && /* @__PURE__ */ F(_, {
82
+ title: "Advance lifecycle",
83
+ description: `Current status: ${w(n.status)}`,
84
+ children: /* @__PURE__ */ F("div", {
85
+ className: "flex flex-wrap gap-2",
86
+ children: K.map((e) => /* @__PURE__ */ F(L, {
87
+ size: "sm",
88
+ variant: e === "CANCELLED" ? "outline" : "default",
89
+ disabled: y.isPending,
90
+ onClick: () => y.mutate({
91
+ id: n.id,
92
+ status: e
93
+ }, {
94
+ onSuccess: () => G({
95
+ tone: "success",
96
+ text: `Moved to ${w(e)}.`
97
+ }),
98
+ onError: (e) => G({
99
+ tone: "danger",
100
+ text: e instanceof Error ? e.message : "Transition failed."
101
+ })
102
+ }),
103
+ children: w(e)
104
+ }, e))
105
+ })
106
+ }),
107
+ /* @__PURE__ */ I("div", {
108
+ className: "grid grid-cols-1 gap-4 lg:grid-cols-2",
109
+ children: [/* @__PURE__ */ F(_, {
110
+ title: "Deliverables",
111
+ action: /* @__PURE__ */ I(L, {
112
+ size: "sm",
113
+ variant: "outline",
114
+ onClick: () => O(!0),
115
+ children: [/* @__PURE__ */ F(M, { className: "mr-1.5 h-4 w-4" }), "Add"]
116
+ }),
117
+ children: n.deliverables.length === 0 ? /* @__PURE__ */ F(l, {
118
+ title: "No deliverables",
119
+ description: "Track the tangible outputs of this engagement.",
120
+ icon: /* @__PURE__ */ F(A, { className: "h-7 w-7" })
121
+ }) : /* @__PURE__ */ F("ul", {
122
+ className: "space-y-2",
123
+ children: n.deliverables.map((e) => /* @__PURE__ */ F("li", {
124
+ className: "rounded-lg border border-border-seam p-3",
125
+ children: /* @__PURE__ */ I("div", {
126
+ className: "flex items-start justify-between gap-2",
127
+ children: [/* @__PURE__ */ I("div", { children: [/* @__PURE__ */ I("div", {
128
+ className: "text-sm font-medium text-text-primary",
129
+ children: [
130
+ e.name,
131
+ " ",
132
+ /* @__PURE__ */ I("span", {
133
+ className: "text-xs text-text-tertiary",
134
+ children: ["v", e.version]
135
+ })
136
+ ]
137
+ }), e.dueDate && /* @__PURE__ */ I("div", {
138
+ className: "text-xs text-text-tertiary",
139
+ children: ["Due ", x(e.dueDate)]
140
+ })] }), /* @__PURE__ */ F(V, {
141
+ id: e.id,
142
+ engagementId: n.id,
143
+ status: e.status,
144
+ onDone: () => void m()
145
+ })]
146
+ })
147
+ }, e.id))
148
+ })
149
+ }), /* @__PURE__ */ F(_, {
150
+ title: "Milestones",
151
+ action: /* @__PURE__ */ I(L, {
152
+ size: "sm",
153
+ variant: "outline",
154
+ onClick: () => z(!0),
155
+ children: [/* @__PURE__ */ F(M, { className: "mr-1.5 h-4 w-4" }), "Add"]
156
+ }),
157
+ children: n.milestones.length === 0 ? /* @__PURE__ */ F(l, {
158
+ title: "No milestones",
159
+ description: "Add checkpoints — some can trigger billing.",
160
+ icon: /* @__PURE__ */ F(j, { className: "h-7 w-7" })
161
+ }) : /* @__PURE__ */ F("ul", {
162
+ className: "space-y-2",
163
+ children: n.milestones.map((e) => /* @__PURE__ */ I("li", {
164
+ className: "flex items-center justify-between rounded-lg border border-border-seam p-3",
165
+ children: [/* @__PURE__ */ I("div", { children: [/* @__PURE__ */ F("div", {
166
+ className: "text-sm font-medium text-text-primary",
167
+ children: e.name
168
+ }), /* @__PURE__ */ I("div", {
169
+ className: "text-xs text-text-tertiary",
170
+ children: [
171
+ e.triggersInvoice ? `Bills ${C(e.triggerAmount, n.currency)} · ` : "",
172
+ "Due ",
173
+ x(e.dueDate)
174
+ ]
175
+ })] }), /* @__PURE__ */ I("div", {
176
+ className: "flex items-center gap-2",
177
+ children: [/* @__PURE__ */ F(v, { status: e.status }), e.status !== "COMPLETED" && e.status !== "CANCELLED" && /* @__PURE__ */ F(H, {
178
+ id: e.id,
179
+ engagementId: n.id,
180
+ onDone: () => void m()
181
+ })]
182
+ })]
183
+ }, e.id))
184
+ })
185
+ })]
186
+ }),
187
+ /* @__PURE__ */ F(_, {
188
+ title: "Time logged",
189
+ action: /* @__PURE__ */ I(L, {
190
+ size: "sm",
191
+ variant: "outline",
192
+ onClick: () => t("time"),
193
+ children: [/* @__PURE__ */ F(k, { className: "mr-1.5 h-4 w-4" }), "Log time"]
194
+ }),
195
+ children: h.isLoading ? /* @__PURE__ */ F(f, { label: "Loading…" }) : !h.data || h.data.items.length === 0 ? /* @__PURE__ */ F(l, {
196
+ title: "No time logged",
197
+ description: "Time logged against this engagement will appear here."
198
+ }) : /* @__PURE__ */ F("ul", {
199
+ className: "divide-y divide-border-seam",
200
+ children: h.data.items.slice(0, 8).map((e) => /* @__PURE__ */ I("li", {
201
+ className: "flex items-center justify-between py-2 text-sm",
202
+ children: [/* @__PURE__ */ I("span", {
203
+ className: "text-text-secondary",
204
+ children: [
205
+ x(e.date),
206
+ " · ",
207
+ e.narrative || "Time entry"
208
+ ]
209
+ }), /* @__PURE__ */ I("span", {
210
+ className: "flex items-center gap-2",
211
+ children: [/* @__PURE__ */ F("span", {
212
+ className: "text-text-primary",
213
+ children: S(e.minutes)
214
+ }), /* @__PURE__ */ F(v, { status: e.status })]
215
+ })]
216
+ }, e.id))
217
+ })
218
+ }),
219
+ /* @__PURE__ */ F(T, {
220
+ entityType: "proserviceworld-engagement",
221
+ entityId: n.id
222
+ }),
223
+ b && /* @__PURE__ */ F(U, {
224
+ engagementId: n.id,
225
+ onClose: () => O(!1),
226
+ onDone: () => {
227
+ m(), G({
228
+ tone: "success",
229
+ text: "Deliverable added."
230
+ });
231
+ }
232
+ }),
233
+ P && /* @__PURE__ */ F(W, {
234
+ engagementId: n.id,
235
+ onClose: () => z(!1),
236
+ onDone: () => {
237
+ m(), G({
238
+ tone: "success",
239
+ text: "Milestone added."
240
+ });
241
+ }
242
+ })
243
+ ]
244
+ });
245
+ }, V = ({ id: e, engagementId: t, status: n, onDone: r }) => {
246
+ let i = a();
247
+ return /* @__PURE__ */ F(h, {
248
+ value: n,
249
+ disabled: i.isPending,
250
+ onChange: (n) => i.mutate({
251
+ id: e,
252
+ status: n.target.value,
253
+ engagementId: t
254
+ }, { onSuccess: r }),
255
+ className: "w-40",
256
+ children: z.map((e) => /* @__PURE__ */ F("option", {
257
+ value: e,
258
+ children: w(e)
259
+ }, e))
260
+ });
261
+ }, H = ({ id: t, engagementId: n, onDone: r }) => {
262
+ let i = e();
263
+ return /* @__PURE__ */ F(L, {
264
+ variant: "ghost",
265
+ size: "sm",
266
+ "aria-label": "Complete milestone",
267
+ disabled: i.isPending,
268
+ onClick: () => i.mutate({
269
+ id: t,
270
+ engagementId: n
271
+ }, { onSuccess: r }),
272
+ children: /* @__PURE__ */ F(O, { className: "h-4 w-4" })
273
+ });
274
+ }, U = ({ engagementId: e, onClose: n, onDone: r }) => {
275
+ let i = t(), [a, o] = E({
276
+ name: "",
277
+ description: "",
278
+ dueDate: "",
279
+ acceptanceCriteria: ""
280
+ }), [s, l] = E(null), u = (e) => (t) => o((n) => ({
281
+ ...n,
282
+ [e]: t.target.value
283
+ }));
284
+ return /* @__PURE__ */ F(m, {
285
+ open: !0,
286
+ onClose: n,
287
+ title: "Add deliverable",
288
+ footer: /* @__PURE__ */ I(P, { children: [/* @__PURE__ */ F(L, {
289
+ variant: "outline",
290
+ onClick: n,
291
+ disabled: i.isPending,
292
+ children: "Cancel"
293
+ }), /* @__PURE__ */ F(L, {
294
+ onClick: () => {
295
+ if (l(null), !a.name.trim()) return l("Name is required.");
296
+ i.mutate({
297
+ engagementId: e,
298
+ name: a.name.trim(),
299
+ description: a.description.trim() || null,
300
+ acceptanceCriteria: a.acceptanceCriteria.trim() || null,
301
+ dueDate: a.dueDate ? new Date(a.dueDate).toISOString() : null
302
+ }, {
303
+ onSuccess: () => {
304
+ r(), n();
305
+ },
306
+ onError: (e) => l(e instanceof Error ? e.message : "Failed.")
307
+ });
308
+ },
309
+ disabled: i.isPending,
310
+ children: i.isPending ? "Adding…" : "Add"
311
+ })] }),
312
+ children: /* @__PURE__ */ I("div", {
313
+ className: "space-y-3",
314
+ children: [
315
+ s && /* @__PURE__ */ F(c, {
316
+ tone: "danger",
317
+ children: s
318
+ }),
319
+ /* @__PURE__ */ F(d, {
320
+ label: "Name",
321
+ required: !0,
322
+ children: /* @__PURE__ */ F(b, {
323
+ value: a.name,
324
+ onChange: u("name"),
325
+ placeholder: "Draft audit report"
326
+ })
327
+ }),
328
+ /* @__PURE__ */ F(d, {
329
+ label: "Due date",
330
+ children: /* @__PURE__ */ F(b, {
331
+ type: "date",
332
+ value: a.dueDate,
333
+ onChange: u("dueDate")
334
+ })
335
+ }),
336
+ /* @__PURE__ */ F(d, {
337
+ label: "Acceptance criteria",
338
+ children: /* @__PURE__ */ F(y, {
339
+ value: a.acceptanceCriteria,
340
+ onChange: u("acceptanceCriteria")
341
+ })
342
+ }),
343
+ /* @__PURE__ */ F(d, {
344
+ label: "Description",
345
+ children: /* @__PURE__ */ F(y, {
346
+ value: a.description,
347
+ onChange: u("description")
348
+ })
349
+ })
350
+ ]
351
+ })
352
+ });
353
+ }, W = ({ engagementId: e, onClose: t, onDone: r }) => {
354
+ let i = n(), [a, o] = E({
355
+ name: "",
356
+ dueDate: "",
357
+ triggerAmount: "",
358
+ triggersInvoice: !1
359
+ }), [s, l] = E(null);
360
+ return /* @__PURE__ */ F(m, {
361
+ open: !0,
362
+ onClose: t,
363
+ title: "Add milestone",
364
+ footer: /* @__PURE__ */ I(P, { children: [/* @__PURE__ */ F(L, {
365
+ variant: "outline",
366
+ onClick: t,
367
+ disabled: i.isPending,
368
+ children: "Cancel"
369
+ }), /* @__PURE__ */ F(L, {
370
+ onClick: () => {
371
+ if (l(null), !a.name.trim()) return l("Name is required.");
372
+ i.mutate({
373
+ engagementId: e,
374
+ name: a.name.trim(),
375
+ dueDate: a.dueDate ? new Date(a.dueDate).toISOString() : null,
376
+ triggerAmount: a.triggerAmount ? Number(a.triggerAmount) : 0,
377
+ triggersInvoice: a.triggersInvoice
378
+ }, {
379
+ onSuccess: () => {
380
+ r(), t();
381
+ },
382
+ onError: (e) => l(e instanceof Error ? e.message : "Failed.")
383
+ });
384
+ },
385
+ disabled: i.isPending,
386
+ children: i.isPending ? "Adding…" : "Add"
387
+ })] }),
388
+ children: /* @__PURE__ */ I("div", {
389
+ className: "space-y-3",
390
+ children: [
391
+ s && /* @__PURE__ */ F(c, {
392
+ tone: "danger",
393
+ children: s
394
+ }),
395
+ /* @__PURE__ */ F(d, {
396
+ label: "Name",
397
+ required: !0,
398
+ children: /* @__PURE__ */ F(b, {
399
+ value: a.name,
400
+ onChange: (e) => o((t) => ({
401
+ ...t,
402
+ name: e.target.value
403
+ })),
404
+ placeholder: "Phase 1 sign-off"
405
+ })
406
+ }),
407
+ /* @__PURE__ */ F(d, {
408
+ label: "Due date",
409
+ children: /* @__PURE__ */ F(b, {
410
+ type: "date",
411
+ value: a.dueDate,
412
+ onChange: (e) => o((t) => ({
413
+ ...t,
414
+ dueDate: e.target.value
415
+ }))
416
+ })
417
+ }),
418
+ /* @__PURE__ */ F(d, {
419
+ label: "Trigger amount",
420
+ hint: "Amount billed when this milestone completes (if it triggers an invoice).",
421
+ children: /* @__PURE__ */ F(b, {
422
+ type: "number",
423
+ min: "0",
424
+ value: a.triggerAmount,
425
+ onChange: (e) => o((t) => ({
426
+ ...t,
427
+ triggerAmount: e.target.value
428
+ }))
429
+ })
430
+ }),
431
+ /* @__PURE__ */ I("label", {
432
+ className: "flex items-center gap-2 text-sm text-text-secondary",
433
+ children: [/* @__PURE__ */ F("input", {
434
+ type: "checkbox",
435
+ checked: a.triggersInvoice,
436
+ onChange: (e) => o((t) => ({
437
+ ...t,
438
+ triggersInvoice: e.target.checked
439
+ }))
440
+ }), "Completing this milestone triggers an invoice"]
441
+ })
442
+ ]
443
+ })
444
+ });
445
+ };
446
+ //#endregion
447
+ export { B as EngagementDetailPage };
448
+
449
+ //# sourceMappingURL=EngagementDetailPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EngagementDetailPage.js","names":[],"sources":["../../../src/proserviceworld/pages/EngagementDetailPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { ArrowLeft, Plus, CheckCircle2, FileText, Flag, Clock } from 'lucide-react';\nimport {\n useEngagement,\n useTransitionEngagement,\n useCreateDeliverable,\n useTransitionDeliverable,\n useCreateMilestone,\n useCompleteMilestone,\n useTimeEntries,\n type PswEngagementStatus,\n type PswDeliverableStatus,\n} from '../hooks/useProServiceWorldApi';\nimport { useNav } from '../utils/nav';\nimport { DiscussionThread } from '../components/DiscussionThread';\nimport {\n PageHeader,\n SectionCard,\n LoadingBlock,\n ErrorState,\n Empty,\n StatusPill,\n Modal,\n Field,\n TextInput,\n TextArea,\n NativeSelect,\n Banner,\n MetricCard,\n fmtDate,\n fmtMoney,\n fmtMinutesAsHours,\n humanize,\n} from '../components/ui';\n\n// Mirrors the svc ENGAGEMENT_TRANSITIONS state machine so we only offer valid moves.\nconst NEXT: Record<PswEngagementStatus, PswEngagementStatus[]> = {\n PROPOSAL: ['ACCEPTED', 'CANCELLED'],\n ACCEPTED: ['IN_PROGRESS', 'CANCELLED'],\n IN_PROGRESS: ['REVIEW', 'CANCELLED'],\n REVIEW: ['DELIVERED', 'IN_PROGRESS', 'CANCELLED'],\n DELIVERED: ['INVOICED', 'CANCELLED'],\n INVOICED: ['CLOSED'],\n CLOSED: [],\n CANCELLED: [],\n};\nconst DELIVERABLE_STATUSES: PswDeliverableStatus[] = [\n 'DRAFT',\n 'INTERNAL_REVIEW',\n 'CLIENT_REVIEW',\n 'ACCEPTED',\n 'REJECTED',\n];\n\nexport const EngagementDetailPage: FC = () => {\n const { id = '' } = useParams();\n const nav = useNav();\n const { data: e, isLoading, error, refetch } = useEngagement(id);\n const time = useTimeEntries({ engagementId: id });\n const transition = useTransitionEngagement();\n const [addDeliverable, setAddDeliverable] = useState(false);\n const [addMilestone, setAddMilestone] = useState(false);\n const [banner, setBanner] = useState<{ tone: 'success' | 'danger'; text: string } | null>(null);\n\n if (isLoading) return <LoadingBlock />;\n if (error) return <ErrorState error={error} onRetry={() => void refetch()} />;\n if (!e) return <Empty title=\"Engagement not found\" />;\n\n const nextStates = NEXT[e.status] ?? [];\n const loggedMinutes = (time.data?.items ?? []).reduce((sum, t) => sum + t.minutes, 0);\n\n return (\n <div className=\"space-y-6\">\n <button\n type=\"button\"\n onClick={() => nav('engagements')}\n className=\"flex items-center gap-1.5 text-sm text-text-secondary hover:text-accent\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back to engagements\n </button>\n\n <PageHeader\n title={e.name}\n subtitle={\n e.client ? `${e.client.name} · ${humanize(e.feeArrangement)}` : humanize(e.feeArrangement)\n }\n actions={<StatusPill status={e.status} />}\n />\n\n {banner && (\n <Banner tone={banner.tone} onClose={() => setBanner(null)}>\n {banner.text}\n </Banner>\n )}\n\n <div className=\"grid grid-cols-2 gap-3 lg:grid-cols-4\">\n <MetricCard\n label=\"Fee\"\n value={fmtMoney(e.feeAmount, e.currency)}\n hint={humanize(e.feeArrangement)}\n />\n <MetricCard label=\"Deliverables\" value={e.deliverables.length} />\n <MetricCard label=\"Milestones\" value={e.milestones.length} />\n <MetricCard\n label=\"Time logged\"\n value={fmtMinutesAsHours(loggedMinutes)}\n hint={`${time.data?.totalCount ?? 0} entries`}\n />\n </div>\n\n {nextStates.length > 0 && (\n <SectionCard\n title=\"Advance lifecycle\"\n description={`Current status: ${humanize(e.status)}`}\n >\n <div className=\"flex flex-wrap gap-2\">\n {nextStates.map((s) => (\n <Button\n key={s}\n size=\"sm\"\n variant={s === 'CANCELLED' ? 'outline' : 'default'}\n disabled={transition.isPending}\n onClick={() =>\n transition.mutate(\n { id: e.id, status: s },\n {\n onSuccess: () =>\n setBanner({ tone: 'success', text: `Moved to ${humanize(s)}.` }),\n onError: (err) =>\n setBanner({\n tone: 'danger',\n text: err instanceof Error ? err.message : 'Transition failed.',\n }),\n }\n )\n }\n >\n {humanize(s)}\n </Button>\n ))}\n </div>\n </SectionCard>\n )}\n\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n <SectionCard\n title=\"Deliverables\"\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => setAddDeliverable(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n Add\n </Button>\n }\n >\n {e.deliverables.length === 0 ? (\n <Empty\n title=\"No deliverables\"\n description=\"Track the tangible outputs of this engagement.\"\n icon={<FileText className=\"h-7 w-7\" />}\n />\n ) : (\n <ul className=\"space-y-2\">\n {e.deliverables.map((d) => (\n <li key={d.id} className=\"rounded-lg border border-border-seam p-3\">\n <div className=\"flex items-start justify-between gap-2\">\n <div>\n <div className=\"text-sm font-medium text-text-primary\">\n {d.name} <span className=\"text-xs text-text-tertiary\">v{d.version}</span>\n </div>\n {d.dueDate && (\n <div className=\"text-xs text-text-tertiary\">Due {fmtDate(d.dueDate)}</div>\n )}\n </div>\n <DeliverableStatusSelect\n id={d.id}\n engagementId={e.id}\n status={d.status}\n onDone={() => void refetch()}\n />\n </div>\n </li>\n ))}\n </ul>\n )}\n </SectionCard>\n\n <SectionCard\n title=\"Milestones\"\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => setAddMilestone(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n Add\n </Button>\n }\n >\n {e.milestones.length === 0 ? (\n <Empty\n title=\"No milestones\"\n description=\"Add checkpoints — some can trigger billing.\"\n icon={<Flag className=\"h-7 w-7\" />}\n />\n ) : (\n <ul className=\"space-y-2\">\n {e.milestones.map((m) => (\n <li\n key={m.id}\n className=\"flex items-center justify-between rounded-lg border border-border-seam p-3\"\n >\n <div>\n <div className=\"text-sm font-medium text-text-primary\">{m.name}</div>\n <div className=\"text-xs text-text-tertiary\">\n {m.triggersInvoice ? `Bills ${fmtMoney(m.triggerAmount, e.currency)} · ` : ''}\n Due {fmtDate(m.dueDate)}\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <StatusPill status={m.status} />\n {m.status !== 'COMPLETED' && m.status !== 'CANCELLED' && (\n <CompleteMilestoneButton\n id={m.id}\n engagementId={e.id}\n onDone={() => void refetch()}\n />\n )}\n </div>\n </li>\n ))}\n </ul>\n )}\n </SectionCard>\n </div>\n\n <SectionCard\n title=\"Time logged\"\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => nav('time')}>\n <Clock className=\"mr-1.5 h-4 w-4\" />\n Log time\n </Button>\n }\n >\n {time.isLoading ? (\n <LoadingBlock label=\"Loading…\" />\n ) : !time.data || time.data.items.length === 0 ? (\n <Empty\n title=\"No time logged\"\n description=\"Time logged against this engagement will appear here.\"\n />\n ) : (\n <ul className=\"divide-y divide-border-seam\">\n {time.data.items.slice(0, 8).map((t) => (\n <li key={t.id} className=\"flex items-center justify-between py-2 text-sm\">\n <span className=\"text-text-secondary\">\n {fmtDate(t.date)} · {t.narrative || 'Time entry'}\n </span>\n <span className=\"flex items-center gap-2\">\n <span className=\"text-text-primary\">{fmtMinutesAsHours(t.minutes)}</span>\n <StatusPill status={t.status} />\n </span>\n </li>\n ))}\n </ul>\n )}\n </SectionCard>\n\n <DiscussionThread entityType=\"proserviceworld-engagement\" entityId={e.id} />\n\n {addDeliverable && (\n <DeliverableModal\n engagementId={e.id}\n onClose={() => setAddDeliverable(false)}\n onDone={() => {\n void refetch();\n setBanner({ tone: 'success', text: 'Deliverable added.' });\n }}\n />\n )}\n {addMilestone && (\n <MilestoneModal\n engagementId={e.id}\n onClose={() => setAddMilestone(false)}\n onDone={() => {\n void refetch();\n setBanner({ tone: 'success', text: 'Milestone added.' });\n }}\n />\n )}\n </div>\n );\n};\n\nconst DeliverableStatusSelect: FC<{\n id: string;\n engagementId: string;\n status: PswDeliverableStatus;\n onDone: () => void;\n}> = ({ id, engagementId, status, onDone }) => {\n const transition = useTransitionDeliverable();\n return (\n <NativeSelect\n value={status}\n disabled={transition.isPending}\n onChange={(ev) =>\n transition.mutate(\n { id, status: ev.target.value as PswDeliverableStatus, engagementId },\n { onSuccess: onDone }\n )\n }\n className=\"w-40\"\n >\n {DELIVERABLE_STATUSES.map((s) => (\n <option key={s} value={s}>\n {humanize(s)}\n </option>\n ))}\n </NativeSelect>\n );\n};\n\nconst CompleteMilestoneButton: FC<{ id: string; engagementId: string; onDone: () => void }> = ({\n id,\n engagementId,\n onDone,\n}) => {\n const complete = useCompleteMilestone();\n return (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label=\"Complete milestone\"\n disabled={complete.isPending}\n onClick={() => complete.mutate({ id, engagementId }, { onSuccess: onDone })}\n >\n <CheckCircle2 className=\"h-4 w-4\" />\n </Button>\n );\n};\n\nconst DeliverableModal: FC<{ engagementId: string; onClose: () => void; onDone: () => void }> = ({\n engagementId,\n onClose,\n onDone,\n}) => {\n const create = useCreateDeliverable();\n const [form, setForm] = useState({\n name: '',\n description: '',\n dueDate: '',\n acceptanceCriteria: '',\n });\n const [err, setErr] = useState<string | null>(null);\n const set = (k: keyof typeof form) => (e: { target: { value: string } }) =>\n setForm((f) => ({ ...f, [k]: e.target.value }));\n const submit = () => {\n setErr(null);\n if (!form.name.trim()) return setErr('Name is required.');\n create.mutate(\n {\n engagementId,\n name: form.name.trim(),\n description: form.description.trim() || null,\n acceptanceCriteria: form.acceptanceCriteria.trim() || null,\n dueDate: form.dueDate ? new Date(form.dueDate).toISOString() : null,\n },\n {\n onSuccess: () => {\n onDone();\n onClose();\n },\n onError: (e) => setErr(e instanceof Error ? e.message : 'Failed.'),\n }\n );\n };\n return (\n <Modal\n open\n onClose={onClose}\n title=\"Add deliverable\"\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={create.isPending}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={create.isPending}>\n {create.isPending ? 'Adding…' : 'Add'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <Field label=\"Name\" required>\n <TextInput value={form.name} onChange={set('name')} placeholder=\"Draft audit report\" />\n </Field>\n <Field label=\"Due date\">\n <TextInput type=\"date\" value={form.dueDate} onChange={set('dueDate')} />\n </Field>\n <Field label=\"Acceptance criteria\">\n <TextArea value={form.acceptanceCriteria} onChange={set('acceptanceCriteria')} />\n </Field>\n <Field label=\"Description\">\n <TextArea value={form.description} onChange={set('description')} />\n </Field>\n </div>\n </Modal>\n );\n};\n\nconst MilestoneModal: FC<{ engagementId: string; onClose: () => void; onDone: () => void }> = ({\n engagementId,\n onClose,\n onDone,\n}) => {\n const create = useCreateMilestone();\n const [form, setForm] = useState({\n name: '',\n dueDate: '',\n triggerAmount: '',\n triggersInvoice: false,\n });\n const [err, setErr] = useState<string | null>(null);\n const submit = () => {\n setErr(null);\n if (!form.name.trim()) return setErr('Name is required.');\n create.mutate(\n {\n engagementId,\n name: form.name.trim(),\n dueDate: form.dueDate ? new Date(form.dueDate).toISOString() : null,\n triggerAmount: form.triggerAmount ? Number(form.triggerAmount) : 0,\n triggersInvoice: form.triggersInvoice,\n },\n {\n onSuccess: () => {\n onDone();\n onClose();\n },\n onError: (e) => setErr(e instanceof Error ? e.message : 'Failed.'),\n }\n );\n };\n return (\n <Modal\n open\n onClose={onClose}\n title=\"Add milestone\"\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={create.isPending}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={create.isPending}>\n {create.isPending ? 'Adding…' : 'Add'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <Field label=\"Name\" required>\n <TextInput\n value={form.name}\n onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))}\n placeholder=\"Phase 1 sign-off\"\n />\n </Field>\n <Field label=\"Due date\">\n <TextInput\n type=\"date\"\n value={form.dueDate}\n onChange={(e) => setForm((f) => ({ ...f, dueDate: e.target.value }))}\n />\n </Field>\n <Field\n label=\"Trigger amount\"\n hint=\"Amount billed when this milestone completes (if it triggers an invoice).\"\n >\n <TextInput\n type=\"number\"\n min=\"0\"\n value={form.triggerAmount}\n onChange={(e) => setForm((f) => ({ ...f, triggerAmount: e.target.value }))}\n />\n </Field>\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <input\n type=\"checkbox\"\n checked={form.triggersInvoice}\n onChange={(e) => setForm((f) => ({ ...f, triggersInvoice: e.target.checked }))}\n />\n Completing this milestone triggers an invoice\n </label>\n </div>\n </Modal>\n );\n};\n"],"mappings":"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"}