@burdenoff/microfe-proserviceworld 2026.624.1 → 2026.625.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/proserviceworld/pages/AdminReportsPage.js +365 -332
- package/dist/proserviceworld/pages/AdminReportsPage.js.map +1 -1
- package/dist/proserviceworld/pages/AdminUsersPage.js +257 -237
- package/dist/proserviceworld/pages/AdminUsersPage.js.map +1 -1
- package/dist/proserviceworld/pages/AnalyticsPage.js +343 -315
- package/dist/proserviceworld/pages/AnalyticsPage.js.map +1 -1
- package/dist/proserviceworld/pages/BookingsPage.js +191 -174
- package/dist/proserviceworld/pages/BookingsPage.js.map +1 -1
- package/dist/proserviceworld/pages/DashboardPage.js +383 -346
- package/dist/proserviceworld/pages/DashboardPage.js.map +1 -1
- package/dist/proserviceworld/pages/DocumentsPage.js +383 -318
- package/dist/proserviceworld/pages/DocumentsPage.js.map +1 -1
- package/dist/proserviceworld/pages/MessagesPage.js +70 -75
- package/dist/proserviceworld/pages/MessagesPage.js.map +1 -1
- package/dist/proserviceworld/pages/PaymentsPage.js +367 -340
- package/dist/proserviceworld/pages/PaymentsPage.js.map +1 -1
- package/dist/proserviceworld/pages/ProjectsPage.js +428 -381
- package/dist/proserviceworld/pages/ProjectsPage.js.map +1 -1
- package/dist/proserviceworld/pages/ProviderDetailPage.js +390 -346
- package/dist/proserviceworld/pages/ProviderDetailPage.js.map +1 -1
- package/dist/proserviceworld/pages/ProvidersPage.js +245 -244
- package/dist/proserviceworld/pages/ProvidersPage.js.map +1 -1
- package/dist/proserviceworld/pages/ReviewsPage.js +14 -14
- package/dist/proserviceworld/pages/ReviewsPage.js.map +1 -1
- package/dist/proserviceworld/pages/ServiceDetailsPage.js +338 -305
- package/dist/proserviceworld/pages/ServiceDetailsPage.js.map +1 -1
- package/dist/proserviceworld/pages/ServicesPage.js +156 -153
- package/dist/proserviceworld/pages/ServicesPage.js.map +1 -1
- package/dist/proserviceworld/pages/VerificationPage.js +260 -251
- package/dist/proserviceworld/pages/VerificationPage.js.map +1 -1
- package/package.json +2 -2
|
@@ -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} from '@burdenoff/fe-libs/ui';\nimport {\n Search,\n FolderOpen,\n FileText,\n File,\n Image,\n Upload,\n Plus,\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-red-500',\n docx: 'text-blue-500',\n doc: 'text-blue-500',\n txt: 'text-gray-500',\n image: 'text-green-500',\n video: 'text-purple-500',\n audio: 'text-orange-500',\n other: 'text-gray-400',\n};\n\nconst folderColors: Record<string, string> = {\n blue: 'text-blue-500',\n green: 'text-green-500',\n purple: 'text-purple-500',\n yellow: 'text-yellow-500',\n red: 'text-red-500',\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\">\n {/* Header */}\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\">Documents</h1>\n <p className=\"text-sm text-text-secondary\">\n Manage files, folders, and shared documents.\n </p>\n </div>\n <div className=\"flex gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={() => setShowCreateFolderModal(true)}>\n <FolderPlus className=\"mr-2 h-4 w-4\" /> New Folder\n </Button>\n <Button size=\"sm\" onClick={() => setShowUploadModal(true)}>\n <Upload className=\"mr-2 h-4 w-4\" /> Upload\n </Button>\n </div>\n </div>\n\n {/* Stats */}\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-lg bg-blue-100 p-2 dark:bg-blue-950\">\n <FileText className=\"h-5 w-5 text-blue-600\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold\">{documents.length}</p>\n <p className=\"text-xs text-text-secondary\">Total Files</p>\n </div>\n </div>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-lg bg-purple-100 p-2 dark:bg-purple-950\">\n <FolderOpen className=\"h-5 w-5 text-purple-600\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold\">{folders.length}</p>\n <p className=\"text-xs text-text-secondary\">Folders</p>\n </div>\n </div>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-lg bg-yellow-100 p-2 dark:bg-yellow-950\">\n <Star className=\"h-5 w-5 text-yellow-600\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold\">{starredCount}</p>\n <p className=\"text-xs text-text-secondary\">Starred</p>\n </div>\n </div>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-lg bg-green-100 p-2 dark:bg-green-950\">\n <HardDrive className=\"h-5 w-5 text-green-600\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold\">{formatFileSize(totalSize)}</p>\n <p className=\"text-xs text-text-secondary\">Total Size</p>\n </div>\n </div>\n </CardContent>\n </Card>\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 ${!selectedFolderId ? 'text-primary' : '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-primary\">{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 value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"pl-10\"\n />\n </div>\n <div className=\"flex 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 className={`rounded px-2 py-1 text-xs font-medium transition-colors ${\n fileFilter === filter\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-secondary hover:bg-bg-sunken/80'\n }`}\n >\n {filter === 'all' ? 'All' : filter === 'starred' ? 'Starred' : filter.toUpperCase()}\n </button>\n ))}\n </div>\n <div className=\"flex rounded border\">\n <button\n onClick={() => setViewMode('grid')}\n className={`p-1.5 ${viewMode === 'grid' ? 'bg-action-primary-bg text-action-primary-text' : 'text-text-secondary'}`}\n >\n <Grid3x3 className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => setViewMode('list')}\n className={`p-1.5 ${viewMode === 'list' ? 'bg-action-primary-bg 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 className={`flex items-center gap-1 rounded border px-3 py-1 text-xs ${\n sortBy === field\n ? 'border-primary bg-action-primary-bg/5 text-primary'\n : 'text-text-secondary hover:bg-bg-sunken/50'\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 p-3 text-left transition-colors hover:bg-bg-sunken/50\"\n >\n <FolderOpen\n className={`h-8 w-8 ${folderColors[folder.color] ?? 'text-gray-500'}`}\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 {folder.documentsCount} 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>\n <CardContent className=\"py-12 text-center text-text-secondary\">\n No files found. Try adjusting your filters.\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 <Card key={doc.id} className=\"hover:bg-bg-sunken/30 transition-colors\">\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-gray-400'}`}\n />\n <div className=\"flex items-center gap-1\">\n {doc.isStarred && (\n <Star className=\"h-4 w-4 fill-yellow-400 text-yellow-400\" />\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>{formatFileSize(doc.size)}</span>\n <span>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\">\n <MoreHorizontal className=\"h-3 w-3\" />\n </Button>\n </div>\n </CardContent>\n </Card>\n );\n })}\n </div>\n ) : (\n <Card>\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 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 hover:bg-bg-sunken/50\">\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-gray-400'}`}\n />\n <span className=\"font-medium\">{doc.name}</span>\n {doc.isStarred && (\n <Star className=\"h-3 w-3 fill-yellow-400 text-yellow-400\" />\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\" /> {doc.sharedWith}\n </span>\n )}\n </div>\n </td>\n <td className=\"p-3 uppercase text-xs\">{doc.type}</td>\n <td className=\"p-3\">{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\">\n <Download className=\"h-3 w-3\" />\n </Button>\n <Button variant=\"outline\" size=\"sm\">\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-black/50\">\n <Card className=\"mx-4 w-full max-w-md\">\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 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\">Destination Folder</label>\n <select className=\"w-full rounded border bg-bg-surface px-3 py-2 text-sm\">\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 </Card>\n </div>\n )}\n\n {/* Create Folder Modal */}\n {showCreateFolderModal && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\">\n <Card className=\"mx-4 w-full max-w-md\">\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\">Folder Name</label>\n <Input 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 </Card>\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 { 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} 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 {/* 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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"}
|
|
@@ -4,72 +4,76 @@ import { useMemo as n, useState as r } from "react";
|
|
|
4
4
|
import { Check as i, CheckCheck as a, Circle as o, MessageSquare as s, MoreHorizontal as c, Paperclip as l, Phone as u, Search as d, Send as f, Smile as p, Video as m } from "lucide-react";
|
|
5
5
|
import { useI18n as h } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
6
6
|
import { Fragment as g, jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
7
|
-
import {
|
|
7
|
+
import { AuroraBackground as y, Badge as b, Button as x, GradientText as S, IllustratedEmptyState as C, Input as w } from "@burdenoff/fe-libs/ui";
|
|
8
8
|
//#region src/proserviceworld/pages/MessagesPage.tsx
|
|
9
|
-
function
|
|
10
|
-
let { t:
|
|
11
|
-
if (!
|
|
12
|
-
let e =
|
|
13
|
-
return
|
|
14
|
-
}, [
|
|
9
|
+
function T() {
|
|
10
|
+
let { t: T } = h(), E = (e, n, r) => t(T, e, n, r), { conversations: D } = e(), [O, k] = r(""), [A, j] = r(D[0]?.id ?? ""), [M, N] = r(""), P = n(() => {
|
|
11
|
+
if (!O.trim()) return D;
|
|
12
|
+
let e = O.toLowerCase();
|
|
13
|
+
return D.filter((t) => t.participantName.toLowerCase().includes(e) || t.lastMessage.toLowerCase().includes(e));
|
|
14
|
+
}, [D, O]), F = D.find((e) => e.id === A), I = D.reduce((e, t) => e + t.unreadCount, 0), L = I > 0 ? E("proserviceworld.messages.subtitle.unread", "{{count}} unread messages", { count: I }) : E("proserviceworld.messages.subtitle.caughtUp", "All caught up"), R = (e) => {
|
|
15
15
|
let t = new Date(e), n = Math.floor(((/* @__PURE__ */ new Date()).getTime() - t.getTime()) / (1e3 * 60 * 60 * 24));
|
|
16
16
|
return n === 0 ? t.toLocaleTimeString([], {
|
|
17
17
|
hour: "2-digit",
|
|
18
18
|
minute: "2-digit"
|
|
19
|
-
}) : n === 1 ?
|
|
19
|
+
}) : n === 1 ? E("proserviceworld.messages.time.yesterday", "Yesterday") : n < 7 ? t.toLocaleDateString([], { weekday: "short" }) : t.toLocaleDateString([], {
|
|
20
20
|
month: "short",
|
|
21
21
|
day: "numeric"
|
|
22
22
|
});
|
|
23
23
|
};
|
|
24
24
|
return /* @__PURE__ */ v("div", {
|
|
25
|
-
className: "p-6",
|
|
25
|
+
className: "p-6 bg-bg-canvas min-h-screen",
|
|
26
26
|
children: [/* @__PURE__ */ v("div", {
|
|
27
|
-
className: "mb-6 flex items-center justify-between",
|
|
28
|
-
children: [
|
|
29
|
-
|
|
30
|
-
children:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
27
|
+
className: "relative overflow-hidden rounded-card -mx-2 px-2 pt-1 mb-6 flex items-center justify-between gap-4",
|
|
28
|
+
children: [
|
|
29
|
+
/* @__PURE__ */ _(y, { intensity: "subtle" }),
|
|
30
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("h1", {
|
|
31
|
+
className: "text-2xl font-semibold tracking-tight",
|
|
32
|
+
children: /* @__PURE__ */ _(S, { children: E("proserviceworld.messages.title", "Messages") })
|
|
33
|
+
}), /* @__PURE__ */ _("p", {
|
|
34
|
+
className: "text-sm text-text-secondary",
|
|
35
|
+
children: L
|
|
36
|
+
})] }),
|
|
37
|
+
/* @__PURE__ */ v(x, {
|
|
38
|
+
size: "sm",
|
|
39
|
+
children: [/* @__PURE__ */ _(s, { className: "mr-2 h-4 w-4" }), E("proserviceworld.messages.actions.newMessage", "New Message")]
|
|
40
|
+
})
|
|
41
|
+
]
|
|
38
42
|
}), /* @__PURE__ */ v("div", {
|
|
39
|
-
className: "grid gap-0 overflow-hidden rounded-
|
|
43
|
+
className: "grid gap-0 overflow-hidden rounded-card border border-border-seam bg-bg-surface shadow-pop lg:grid-cols-3",
|
|
40
44
|
style: {
|
|
41
45
|
height: "calc(100vh - 220px)",
|
|
42
46
|
minHeight: "500px"
|
|
43
47
|
},
|
|
44
48
|
children: [/* @__PURE__ */ v("div", {
|
|
45
|
-
className: "flex flex-col border-r lg:col-span-1",
|
|
49
|
+
className: "flex flex-col border-r border-border-seam lg:col-span-1",
|
|
46
50
|
children: [/* @__PURE__ */ _("div", {
|
|
47
|
-
className: "border-b p-3",
|
|
51
|
+
className: "border-b border-border-seam p-3",
|
|
48
52
|
children: /* @__PURE__ */ v("div", {
|
|
49
53
|
className: "relative",
|
|
50
|
-
children: [/* @__PURE__ */ _(d, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ _(
|
|
51
|
-
placeholder:
|
|
52
|
-
value:
|
|
53
|
-
onChange: (e) =>
|
|
54
|
+
children: [/* @__PURE__ */ _(d, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ _(w, {
|
|
55
|
+
placeholder: E("proserviceworld.messages.search.placeholder", "Search conversations..."),
|
|
56
|
+
value: O,
|
|
57
|
+
onChange: (e) => k(e.target.value),
|
|
54
58
|
className: "pl-10"
|
|
55
59
|
})]
|
|
56
60
|
})
|
|
57
61
|
}), /* @__PURE__ */ _("div", {
|
|
58
62
|
className: "flex-1 overflow-y-auto",
|
|
59
|
-
children:
|
|
63
|
+
children: P.length === 0 ? /* @__PURE__ */ _("div", {
|
|
60
64
|
className: "p-6 text-center text-sm text-text-secondary",
|
|
61
|
-
children:
|
|
62
|
-
}) :
|
|
63
|
-
onClick: () =>
|
|
64
|
-
className: `w-full border-b p-3 text-left transition-colors ${
|
|
65
|
+
children: E("proserviceworld.messages.empty.list", "No conversations found.")
|
|
66
|
+
}) : P.map((e) => /* @__PURE__ */ _("button", {
|
|
67
|
+
onClick: () => j(e.id),
|
|
68
|
+
className: `w-full border-b border-border-seam p-3 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-border-focus ${A === e.id ? "bg-accent-soft" : "hover:bg-bg-sunken/50"}`,
|
|
65
69
|
children: /* @__PURE__ */ v("div", {
|
|
66
70
|
className: "flex items-start gap-3",
|
|
67
71
|
children: [/* @__PURE__ */ v("div", {
|
|
68
72
|
className: "relative shrink-0",
|
|
69
73
|
children: [/* @__PURE__ */ _("div", {
|
|
70
|
-
className: "flex h-10 w-10 items-center justify-center rounded-full bg-
|
|
74
|
+
className: "flex h-10 w-10 items-center justify-center rounded-full bg-accent-soft font-semibold text-text-primary",
|
|
71
75
|
children: e.participantName.charAt(0)
|
|
72
|
-
}), e.online && /* @__PURE__ */ _(o, { className: "absolute -bottom-0.5 -right-0.5 h-3.5 w-3.5 fill-
|
|
76
|
+
}), e.online && /* @__PURE__ */ _(o, { className: "absolute -bottom-0.5 -right-0.5 h-3.5 w-3.5 fill-status-success-text text-status-success-text stroke-bg-surface motion-safe:animate-vc-glow-pulse" })]
|
|
73
77
|
}), /* @__PURE__ */ v("div", {
|
|
74
78
|
className: "min-w-0 flex-1",
|
|
75
79
|
children: [
|
|
@@ -80,7 +84,7 @@ function S() {
|
|
|
80
84
|
children: e.participantName
|
|
81
85
|
}), /* @__PURE__ */ _("span", {
|
|
82
86
|
className: "shrink-0 text-xs text-text-secondary",
|
|
83
|
-
children:
|
|
87
|
+
children: R(e.lastMessageTime)
|
|
84
88
|
})]
|
|
85
89
|
}),
|
|
86
90
|
/* @__PURE__ */ _("p", {
|
|
@@ -92,7 +96,7 @@ function S() {
|
|
|
92
96
|
children: [/* @__PURE__ */ _("p", {
|
|
93
97
|
className: `truncate text-xs ${e.unreadCount > 0 ? "font-medium text-text-primary" : "text-text-secondary"}`,
|
|
94
98
|
children: e.lastMessage
|
|
95
|
-
}), e.unreadCount > 0 && /* @__PURE__ */ _(
|
|
99
|
+
}), e.unreadCount > 0 && /* @__PURE__ */ _(b, {
|
|
96
100
|
className: "ml-2 h-5 w-5 shrink-0 rounded-full p-0 text-xs flex items-center justify-center",
|
|
97
101
|
children: e.unreadCount
|
|
98
102
|
})]
|
|
@@ -104,43 +108,43 @@ function S() {
|
|
|
104
108
|
})]
|
|
105
109
|
}), /* @__PURE__ */ _("div", {
|
|
106
110
|
className: "flex flex-col lg:col-span-2",
|
|
107
|
-
children:
|
|
111
|
+
children: F ? /* @__PURE__ */ v(g, { children: [
|
|
108
112
|
/* @__PURE__ */ v("div", {
|
|
109
|
-
className: "flex items-center justify-between border-b p-3",
|
|
113
|
+
className: "flex items-center justify-between border-b border-border-seam p-3",
|
|
110
114
|
children: [/* @__PURE__ */ v("div", {
|
|
111
115
|
className: "flex items-center gap-3",
|
|
112
116
|
children: [/* @__PURE__ */ v("div", {
|
|
113
117
|
className: "relative",
|
|
114
118
|
children: [/* @__PURE__ */ _("div", {
|
|
115
|
-
className: "flex h-10 w-10 items-center justify-center rounded-full bg-
|
|
116
|
-
children:
|
|
117
|
-
}),
|
|
119
|
+
className: "flex h-10 w-10 items-center justify-center rounded-full bg-accent-soft font-semibold text-text-primary",
|
|
120
|
+
children: F.participantName.charAt(0)
|
|
121
|
+
}), F.online && /* @__PURE__ */ _(o, { className: "absolute -bottom-0.5 -right-0.5 h-3.5 w-3.5 fill-status-success-text text-status-success-text stroke-bg-surface motion-safe:animate-vc-glow-pulse" })]
|
|
118
122
|
}), /* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("h3", {
|
|
119
123
|
className: "font-medium",
|
|
120
|
-
children:
|
|
124
|
+
children: F.participantName
|
|
121
125
|
}), /* @__PURE__ */ v("p", {
|
|
122
126
|
className: "text-xs text-text-secondary",
|
|
123
127
|
children: [
|
|
124
|
-
|
|
128
|
+
F.online ? E("proserviceworld.messages.presence.online", "Online") : E("proserviceworld.messages.presence.offline", "Offline"),
|
|
125
129
|
" ",
|
|
126
130
|
"- ",
|
|
127
|
-
|
|
131
|
+
F.participantRole
|
|
128
132
|
]
|
|
129
133
|
})] })]
|
|
130
134
|
}), /* @__PURE__ */ v("div", {
|
|
131
135
|
className: "flex gap-1",
|
|
132
136
|
children: [
|
|
133
|
-
/* @__PURE__ */ _(
|
|
137
|
+
/* @__PURE__ */ _(x, {
|
|
134
138
|
variant: "outline",
|
|
135
139
|
size: "sm",
|
|
136
140
|
children: /* @__PURE__ */ _(u, { className: "h-4 w-4" })
|
|
137
141
|
}),
|
|
138
|
-
/* @__PURE__ */ _(
|
|
142
|
+
/* @__PURE__ */ _(x, {
|
|
139
143
|
variant: "outline",
|
|
140
144
|
size: "sm",
|
|
141
145
|
children: /* @__PURE__ */ _(m, { className: "h-4 w-4" })
|
|
142
146
|
}),
|
|
143
|
-
/* @__PURE__ */ _(
|
|
147
|
+
/* @__PURE__ */ _(x, {
|
|
144
148
|
variant: "outline",
|
|
145
149
|
size: "sm",
|
|
146
150
|
children: /* @__PURE__ */ _(c, { className: "h-4 w-4" })
|
|
@@ -150,12 +154,12 @@ function S() {
|
|
|
150
154
|
}),
|
|
151
155
|
/* @__PURE__ */ _("div", {
|
|
152
156
|
className: "flex-1 overflow-y-auto p-4 space-y-4",
|
|
153
|
-
children:
|
|
157
|
+
children: F.messages.map((e) => /* @__PURE__ */ _("div", {
|
|
154
158
|
className: `flex ${e.isOwn ? "justify-end" : "justify-start"}`,
|
|
155
159
|
children: /* @__PURE__ */ v("div", {
|
|
156
160
|
className: `flex max-w-[75%] items-end gap-2 ${e.isOwn ? "flex-row-reverse" : ""}`,
|
|
157
161
|
children: [!e.isOwn && /* @__PURE__ */ _("div", {
|
|
158
|
-
className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-
|
|
162
|
+
className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-accent-soft text-xs font-semibold text-text-primary",
|
|
159
163
|
children: e.senderName.charAt(0)
|
|
160
164
|
}), /* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("div", {
|
|
161
165
|
className: `rounded-2xl px-4 py-2 ${e.isOwn ? "bg-action-primary-bg text-action-primary-text rounded-br-md" : "bg-bg-sunken rounded-bl-md"}`,
|
|
@@ -165,58 +169,49 @@ function S() {
|
|
|
165
169
|
})
|
|
166
170
|
}), /* @__PURE__ */ v("div", {
|
|
167
171
|
className: `mt-1 flex items-center gap-1 text-xs text-text-secondary ${e.isOwn ? "justify-end" : ""}`,
|
|
168
|
-
children: [/* @__PURE__ */ _("span", { children:
|
|
172
|
+
children: [/* @__PURE__ */ _("span", { children: R(e.timestamp) }), e.isOwn && (e.read ? /* @__PURE__ */ _(a, { className: "h-3 w-3 text-accent-blue" }) : /* @__PURE__ */ _(i, { className: "h-3 w-3" }))]
|
|
169
173
|
})] })]
|
|
170
174
|
})
|
|
171
175
|
}, e.id))
|
|
172
176
|
}),
|
|
173
177
|
/* @__PURE__ */ _("div", {
|
|
174
|
-
className: "border-t p-3",
|
|
178
|
+
className: "border-t border-border-seam p-3",
|
|
175
179
|
children: /* @__PURE__ */ v("div", {
|
|
176
180
|
className: "flex items-center gap-2",
|
|
177
181
|
children: [
|
|
178
|
-
/* @__PURE__ */ _(
|
|
182
|
+
/* @__PURE__ */ _(x, {
|
|
179
183
|
variant: "outline",
|
|
180
184
|
size: "sm",
|
|
181
185
|
children: /* @__PURE__ */ _(l, { className: "h-4 w-4" })
|
|
182
186
|
}),
|
|
183
|
-
/* @__PURE__ */ _(
|
|
184
|
-
placeholder:
|
|
185
|
-
value:
|
|
186
|
-
onChange: (e) =>
|
|
187
|
+
/* @__PURE__ */ _(w, {
|
|
188
|
+
placeholder: E("proserviceworld.messages.input.placeholder", "Type a message..."),
|
|
189
|
+
value: M,
|
|
190
|
+
onChange: (e) => N(e.target.value),
|
|
187
191
|
onKeyDown: (e) => {
|
|
188
|
-
e.key === "Enter" &&
|
|
192
|
+
e.key === "Enter" && M.trim() && N("");
|
|
189
193
|
},
|
|
190
194
|
className: "flex-1"
|
|
191
195
|
}),
|
|
192
|
-
/* @__PURE__ */ _(
|
|
196
|
+
/* @__PURE__ */ _(x, {
|
|
193
197
|
variant: "outline",
|
|
194
198
|
size: "sm",
|
|
195
199
|
children: /* @__PURE__ */ _(p, { className: "h-4 w-4" })
|
|
196
200
|
}),
|
|
197
|
-
/* @__PURE__ */ _(
|
|
201
|
+
/* @__PURE__ */ _(x, {
|
|
198
202
|
size: "sm",
|
|
199
|
-
disabled: !
|
|
203
|
+
disabled: !M.trim(),
|
|
200
204
|
children: /* @__PURE__ */ _(f, { className: "h-4 w-4" })
|
|
201
205
|
})
|
|
202
206
|
]
|
|
203
207
|
})
|
|
204
208
|
})
|
|
205
209
|
] }) : /* @__PURE__ */ _("div", {
|
|
206
|
-
className: "flex flex-1 items-center justify-center
|
|
207
|
-
children: /* @__PURE__ */
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
/* @__PURE__ */ _("p", {
|
|
212
|
-
className: "mt-2 font-medium",
|
|
213
|
-
children: C("proserviceworld.messages.emptyConversation.title", "Select a conversation")
|
|
214
|
-
}),
|
|
215
|
-
/* @__PURE__ */ _("p", {
|
|
216
|
-
className: "text-sm",
|
|
217
|
-
children: C("proserviceworld.messages.emptyConversation.description", "Choose a conversation to start messaging.")
|
|
218
|
-
})
|
|
219
|
-
]
|
|
210
|
+
className: "flex flex-1 items-center justify-center",
|
|
211
|
+
children: /* @__PURE__ */ _(C, {
|
|
212
|
+
illustration: "empty-notifications",
|
|
213
|
+
title: E("proserviceworld.messages.emptyConversation.title", "Select a conversation"),
|
|
214
|
+
description: E("proserviceworld.messages.emptyConversation.description", "Choose a conversation to start messaging.")
|
|
220
215
|
})
|
|
221
216
|
})
|
|
222
217
|
})]
|
|
@@ -224,6 +219,6 @@ function S() {
|
|
|
224
219
|
});
|
|
225
220
|
}
|
|
226
221
|
//#endregion
|
|
227
|
-
export {
|
|
222
|
+
export { T as MessagesPage };
|
|
228
223
|
|
|
229
224
|
//# sourceMappingURL=MessagesPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MessagesPage.js","names":[],"sources":["../../../src/proserviceworld/pages/MessagesPage.tsx"],"sourcesContent":["import { useState, useMemo } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { Badge, Button, Card, CardContent, Input } from '@burdenoff/fe-libs/ui';\nimport {\n Search,\n Send,\n Circle,\n CheckCheck,\n Check,\n MessageSquare,\n Phone,\n Video,\n MoreHorizontal,\n Paperclip,\n Smile,\n} from 'lucide-react';\nimport { useProServiceWorldStore } from '../store/proserviceworldStore';\nimport { tWithFallback } from '../utils/i18n';\n\nexport function MessagesPage() {\n const { t } = useI18n();\n const tr = (key: string, fallback: string, params?: Record<string, string | number>): string =>\n tWithFallback(t, key, fallback, params);\n const { conversations } = useProServiceWorldStore();\n const [searchQuery, setSearchQuery] = useState('');\n const [selectedConversationId, setSelectedConversationId] = useState<string>(\n conversations[0]?.id ?? ''\n );\n const [messageInput, setMessageInput] = useState('');\n\n const filteredConversations = useMemo(() => {\n if (!searchQuery.trim()) return conversations;\n const q = searchQuery.toLowerCase();\n return conversations.filter(\n (c) => c.participantName.toLowerCase().includes(q) || c.lastMessage.toLowerCase().includes(q)\n );\n }, [conversations, searchQuery]);\n\n const selectedConversation = conversations.find((c) => c.id === selectedConversationId);\n const totalUnread = conversations.reduce((sum, c) => sum + c.unreadCount, 0);\n const unreadSummary =\n totalUnread > 0\n ? tr('proserviceworld.messages.subtitle.unread', '{{count}} unread messages', {\n count: totalUnread,\n })\n : tr('proserviceworld.messages.subtitle.caughtUp', 'All caught up');\n\n const formatTime = (timestamp: string) => {\n const date = new Date(timestamp);\n const now = new Date();\n const diffDays = Math.floor((now.getTime() - date.getTime()) / (1000 * 60 * 60 * 24));\n if (diffDays === 0) {\n return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });\n }\n if (diffDays === 1) return tr('proserviceworld.messages.time.yesterday', 'Yesterday');\n if (diffDays < 7) return date.toLocaleDateString([], { weekday: 'short' });\n return date.toLocaleDateString([], { month: 'short', day: 'numeric' });\n };\n\n return (\n <div className=\"p-6\">\n <div className=\"mb-6 flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n {tr('proserviceworld.messages.title', 'Messages')}\n </h1>\n <p className=\"text-sm text-text-secondary\">{unreadSummary}</p>\n </div>\n <Button size=\"sm\">\n <MessageSquare className=\"mr-2 h-4 w-4\" />\n {tr('proserviceworld.messages.actions.newMessage', 'New Message')}\n </Button>\n </div>\n\n <div\n className=\"grid gap-0 overflow-hidden rounded-lg border lg:grid-cols-3\"\n style={{ height: 'calc(100vh - 220px)', minHeight: '500px' }}\n >\n {/* Conversation sidebar */}\n <div className=\"flex flex-col border-r lg:col-span-1\">\n {/* Search */}\n <div className=\"border-b p-3\">\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder={tr(\n 'proserviceworld.messages.search.placeholder',\n 'Search conversations...'\n )}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"pl-10\"\n />\n </div>\n </div>\n\n {/* Conversation list */}\n <div className=\"flex-1 overflow-y-auto\">\n {filteredConversations.length === 0 ? (\n <div className=\"p-6 text-center text-sm text-text-secondary\">\n {tr('proserviceworld.messages.empty.list', 'No conversations found.')}\n </div>\n ) : (\n filteredConversations.map((conversation) => (\n <button\n key={conversation.id}\n onClick={() => setSelectedConversationId(conversation.id)}\n className={`w-full border-b p-3 text-left transition-colors ${\n selectedConversationId === conversation.id\n ? 'bg-action-primary-bg/5'\n : 'hover:bg-bg-sunken/50'\n }`}\n >\n <div className=\"flex items-start gap-3\">\n {/* Avatar */}\n <div className=\"relative shrink-0\">\n <div className=\"flex h-10 w-10 items-center justify-center rounded-full bg-action-primary-bg/10 font-semibold text-primary\">\n {conversation.participantName.charAt(0)}\n </div>\n {conversation.online && (\n <Circle className=\"absolute -bottom-0.5 -right-0.5 h-3.5 w-3.5 fill-green-500 text-green-500 stroke-background\" />\n )}\n </div>\n\n {/* Content */}\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center justify-between\">\n <h3\n className={`truncate text-sm ${conversation.unreadCount > 0 ? 'font-bold' : 'font-medium'}`}\n >\n {conversation.participantName}\n </h3>\n <span className=\"shrink-0 text-xs text-text-secondary\">\n {formatTime(conversation.lastMessageTime)}\n </span>\n </div>\n <p className=\"text-xs text-text-secondary\">\n {conversation.participantRole}\n </p>\n <div className=\"mt-1 flex items-center justify-between\">\n <p\n className={`truncate text-xs ${conversation.unreadCount > 0 ? 'font-medium text-text-primary' : 'text-text-secondary'}`}\n >\n {conversation.lastMessage}\n </p>\n {conversation.unreadCount > 0 && (\n <Badge className=\"ml-2 h-5 w-5 shrink-0 rounded-full p-0 text-xs flex items-center justify-center\">\n {conversation.unreadCount}\n </Badge>\n )}\n </div>\n </div>\n </div>\n </button>\n ))\n )}\n </div>\n </div>\n\n {/* Chat area */}\n <div className=\"flex flex-col lg:col-span-2\">\n {selectedConversation ? (\n <>\n {/* Chat header */}\n <div className=\"flex items-center justify-between border-b p-3\">\n <div className=\"flex items-center gap-3\">\n <div className=\"relative\">\n <div className=\"flex h-10 w-10 items-center justify-center rounded-full bg-action-primary-bg/10 font-semibold text-primary\">\n {selectedConversation.participantName.charAt(0)}\n </div>\n {selectedConversation.online && (\n <Circle className=\"absolute -bottom-0.5 -right-0.5 h-3.5 w-3.5 fill-green-500 text-green-500 stroke-background\" />\n )}\n </div>\n <div>\n <h3 className=\"font-medium\">{selectedConversation.participantName}</h3>\n <p className=\"text-xs text-text-secondary\">\n {selectedConversation.online\n ? tr('proserviceworld.messages.presence.online', 'Online')\n : tr('proserviceworld.messages.presence.offline', 'Offline')}{' '}\n - {selectedConversation.participantRole}\n </p>\n </div>\n </div>\n <div className=\"flex gap-1\">\n <Button variant=\"outline\" size=\"sm\">\n <Phone className=\"h-4 w-4\" />\n </Button>\n <Button variant=\"outline\" size=\"sm\">\n <Video className=\"h-4 w-4\" />\n </Button>\n <Button variant=\"outline\" size=\"sm\">\n <MoreHorizontal className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n\n {/* Messages area */}\n <div className=\"flex-1 overflow-y-auto p-4 space-y-4\">\n {selectedConversation.messages.map((message) => (\n <div\n key={message.id}\n className={`flex ${message.isOwn ? 'justify-end' : 'justify-start'}`}\n >\n <div\n className={`flex max-w-[75%] items-end gap-2 ${message.isOwn ? 'flex-row-reverse' : ''}`}\n >\n {!message.isOwn && (\n <div className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-action-primary-bg/10 text-xs font-semibold text-primary\">\n {message.senderName.charAt(0)}\n </div>\n )}\n <div>\n <div\n className={`rounded-2xl px-4 py-2 ${\n message.isOwn\n ? 'bg-action-primary-bg text-action-primary-text rounded-br-md'\n : 'bg-bg-sunken rounded-bl-md'\n }`}\n >\n <p className=\"text-sm\">{message.content}</p>\n </div>\n <div\n className={`mt-1 flex items-center gap-1 text-xs text-text-secondary ${message.isOwn ? 'justify-end' : ''}`}\n >\n <span>{formatTime(message.timestamp)}</span>\n {message.isOwn &&\n (message.read ? (\n <CheckCheck className=\"h-3 w-3 text-blue-500\" />\n ) : (\n <Check className=\"h-3 w-3\" />\n ))}\n </div>\n </div>\n </div>\n </div>\n ))}\n </div>\n\n {/* Message input */}\n <div className=\"border-t p-3\">\n <div className=\"flex items-center gap-2\">\n <Button variant=\"outline\" size=\"sm\">\n <Paperclip className=\"h-4 w-4\" />\n </Button>\n <Input\n placeholder={tr(\n 'proserviceworld.messages.input.placeholder',\n 'Type a message...'\n )}\n value={messageInput}\n onChange={(e) => setMessageInput(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' && messageInput.trim()) {\n setMessageInput('');\n }\n }}\n className=\"flex-1\"\n />\n <Button variant=\"outline\" size=\"sm\">\n <Smile className=\"h-4 w-4\" />\n </Button>\n <Button size=\"sm\" disabled={!messageInput.trim()}>\n <Send className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n </>\n ) : (\n <div className=\"flex flex-1 items-center justify-center text-text-secondary\">\n <div className=\"text-center\">\n <MessageSquare className=\"mx-auto h-12 w-12 text-text-secondary/50\" />\n <p className=\"mt-2 font-medium\">\n {tr('proserviceworld.messages.emptyConversation.title', 'Select a conversation')}\n </p>\n <p className=\"text-sm\">\n {tr(\n 'proserviceworld.messages.emptyConversation.description',\n 'Choose a conversation to start messaging.'\n )}\n </p>\n </div>\n </div>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MessagesPage.js","names":[],"sources":["../../../src/proserviceworld/pages/MessagesPage.tsx"],"sourcesContent":["import { useState, useMemo } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell';\nimport {\n Badge,\n Button,\n Input,\n AuroraBackground,\n GradientText,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Search,\n Send,\n Circle,\n CheckCheck,\n Check,\n MessageSquare,\n Phone,\n Video,\n MoreHorizontal,\n Paperclip,\n Smile,\n} from 'lucide-react';\nimport { useProServiceWorldStore } from '../store/proserviceworldStore';\nimport { tWithFallback } from '../utils/i18n';\n\nexport function MessagesPage() {\n const { t } = useI18n();\n const tr = (key: string, fallback: string, params?: Record<string, string | number>): string =>\n tWithFallback(t, key, fallback, params);\n const { conversations } = useProServiceWorldStore();\n const [searchQuery, setSearchQuery] = useState('');\n const [selectedConversationId, setSelectedConversationId] = useState<string>(\n conversations[0]?.id ?? ''\n );\n const [messageInput, setMessageInput] = useState('');\n\n const filteredConversations = useMemo(() => {\n if (!searchQuery.trim()) return conversations;\n const q = searchQuery.toLowerCase();\n return conversations.filter(\n (c) => c.participantName.toLowerCase().includes(q) || c.lastMessage.toLowerCase().includes(q)\n );\n }, [conversations, searchQuery]);\n\n const selectedConversation = conversations.find((c) => c.id === selectedConversationId);\n const totalUnread = conversations.reduce((sum, c) => sum + c.unreadCount, 0);\n const unreadSummary =\n totalUnread > 0\n ? tr('proserviceworld.messages.subtitle.unread', '{{count}} unread messages', {\n count: totalUnread,\n })\n : tr('proserviceworld.messages.subtitle.caughtUp', 'All caught up');\n\n const formatTime = (timestamp: string) => {\n const date = new Date(timestamp);\n const now = new Date();\n const diffDays = Math.floor((now.getTime() - date.getTime()) / (1000 * 60 * 60 * 24));\n if (diffDays === 0) {\n return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });\n }\n if (diffDays === 1) return tr('proserviceworld.messages.time.yesterday', 'Yesterday');\n if (diffDays < 7) return date.toLocaleDateString([], { weekday: 'short' });\n return date.toLocaleDateString([], { month: 'short', day: 'numeric' });\n };\n\n return (\n <div className=\"p-6 bg-bg-canvas min-h-screen\">\n <div className=\"relative overflow-hidden rounded-card -mx-2 px-2 pt-1 mb-6 flex items-center justify-between gap-4\">\n <AuroraBackground intensity=\"subtle\" />\n <div>\n <h1 className=\"text-2xl font-semibold tracking-tight\">\n <GradientText>{tr('proserviceworld.messages.title', 'Messages')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">{unreadSummary}</p>\n </div>\n <Button size=\"sm\">\n <MessageSquare className=\"mr-2 h-4 w-4\" />\n {tr('proserviceworld.messages.actions.newMessage', 'New Message')}\n </Button>\n </div>\n\n <div\n className=\"grid gap-0 overflow-hidden rounded-card border border-border-seam bg-bg-surface shadow-pop lg:grid-cols-3\"\n style={{ height: 'calc(100vh - 220px)', minHeight: '500px' }}\n >\n {/* Conversation sidebar */}\n <div className=\"flex flex-col border-r border-border-seam lg:col-span-1\">\n {/* Search */}\n <div className=\"border-b border-border-seam p-3\">\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder={tr(\n 'proserviceworld.messages.search.placeholder',\n 'Search conversations...'\n )}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"pl-10\"\n />\n </div>\n </div>\n\n {/* Conversation list */}\n <div className=\"flex-1 overflow-y-auto\">\n {filteredConversations.length === 0 ? (\n <div className=\"p-6 text-center text-sm text-text-secondary\">\n {tr('proserviceworld.messages.empty.list', 'No conversations found.')}\n </div>\n ) : (\n filteredConversations.map((conversation) => (\n <button\n key={conversation.id}\n onClick={() => setSelectedConversationId(conversation.id)}\n className={`w-full border-b border-border-seam p-3 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-border-focus ${\n selectedConversationId === conversation.id\n ? 'bg-accent-soft'\n : 'hover:bg-bg-sunken/50'\n }`}\n >\n <div className=\"flex items-start gap-3\">\n {/* Avatar */}\n <div className=\"relative shrink-0\">\n <div className=\"flex h-10 w-10 items-center justify-center rounded-full bg-accent-soft font-semibold text-text-primary\">\n {conversation.participantName.charAt(0)}\n </div>\n {conversation.online && (\n <Circle className=\"absolute -bottom-0.5 -right-0.5 h-3.5 w-3.5 fill-status-success-text text-status-success-text stroke-bg-surface motion-safe:animate-vc-glow-pulse\" />\n )}\n </div>\n\n {/* Content */}\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center justify-between\">\n <h3\n className={`truncate text-sm ${conversation.unreadCount > 0 ? 'font-bold' : 'font-medium'}`}\n >\n {conversation.participantName}\n </h3>\n <span className=\"shrink-0 text-xs text-text-secondary\">\n {formatTime(conversation.lastMessageTime)}\n </span>\n </div>\n <p className=\"text-xs text-text-secondary\">\n {conversation.participantRole}\n </p>\n <div className=\"mt-1 flex items-center justify-between\">\n <p\n className={`truncate text-xs ${conversation.unreadCount > 0 ? 'font-medium text-text-primary' : 'text-text-secondary'}`}\n >\n {conversation.lastMessage}\n </p>\n {conversation.unreadCount > 0 && (\n <Badge className=\"ml-2 h-5 w-5 shrink-0 rounded-full p-0 text-xs flex items-center justify-center\">\n {conversation.unreadCount}\n </Badge>\n )}\n </div>\n </div>\n </div>\n </button>\n ))\n )}\n </div>\n </div>\n\n {/* Chat area */}\n <div className=\"flex flex-col lg:col-span-2\">\n {selectedConversation ? (\n <>\n {/* Chat header */}\n <div className=\"flex items-center justify-between border-b border-border-seam p-3\">\n <div className=\"flex items-center gap-3\">\n <div className=\"relative\">\n <div className=\"flex h-10 w-10 items-center justify-center rounded-full bg-accent-soft font-semibold text-text-primary\">\n {selectedConversation.participantName.charAt(0)}\n </div>\n {selectedConversation.online && (\n <Circle className=\"absolute -bottom-0.5 -right-0.5 h-3.5 w-3.5 fill-status-success-text text-status-success-text stroke-bg-surface motion-safe:animate-vc-glow-pulse\" />\n )}\n </div>\n <div>\n <h3 className=\"font-medium\">{selectedConversation.participantName}</h3>\n <p className=\"text-xs text-text-secondary\">\n {selectedConversation.online\n ? tr('proserviceworld.messages.presence.online', 'Online')\n : tr('proserviceworld.messages.presence.offline', 'Offline')}{' '}\n - {selectedConversation.participantRole}\n </p>\n </div>\n </div>\n <div className=\"flex gap-1\">\n <Button variant=\"outline\" size=\"sm\">\n <Phone className=\"h-4 w-4\" />\n </Button>\n <Button variant=\"outline\" size=\"sm\">\n <Video className=\"h-4 w-4\" />\n </Button>\n <Button variant=\"outline\" size=\"sm\">\n <MoreHorizontal className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n\n {/* Messages area */}\n <div className=\"flex-1 overflow-y-auto p-4 space-y-4\">\n {selectedConversation.messages.map((message) => (\n <div\n key={message.id}\n className={`flex ${message.isOwn ? 'justify-end' : 'justify-start'}`}\n >\n <div\n className={`flex max-w-[75%] items-end gap-2 ${message.isOwn ? 'flex-row-reverse' : ''}`}\n >\n {!message.isOwn && (\n <div className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-accent-soft text-xs font-semibold text-text-primary\">\n {message.senderName.charAt(0)}\n </div>\n )}\n <div>\n <div\n className={`rounded-2xl px-4 py-2 ${\n message.isOwn\n ? 'bg-action-primary-bg text-action-primary-text rounded-br-md'\n : 'bg-bg-sunken rounded-bl-md'\n }`}\n >\n <p className=\"text-sm\">{message.content}</p>\n </div>\n <div\n className={`mt-1 flex items-center gap-1 text-xs text-text-secondary ${message.isOwn ? 'justify-end' : ''}`}\n >\n <span>{formatTime(message.timestamp)}</span>\n {message.isOwn &&\n (message.read ? (\n <CheckCheck className=\"h-3 w-3 text-accent-blue\" />\n ) : (\n <Check className=\"h-3 w-3\" />\n ))}\n </div>\n </div>\n </div>\n </div>\n ))}\n </div>\n\n {/* Message input */}\n <div className=\"border-t border-border-seam p-3\">\n <div className=\"flex items-center gap-2\">\n <Button variant=\"outline\" size=\"sm\">\n <Paperclip className=\"h-4 w-4\" />\n </Button>\n <Input\n placeholder={tr(\n 'proserviceworld.messages.input.placeholder',\n 'Type a message...'\n )}\n value={messageInput}\n onChange={(e) => setMessageInput(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' && messageInput.trim()) {\n setMessageInput('');\n }\n }}\n className=\"flex-1\"\n />\n <Button variant=\"outline\" size=\"sm\">\n <Smile className=\"h-4 w-4\" />\n </Button>\n <Button size=\"sm\" disabled={!messageInput.trim()}>\n <Send className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n </>\n ) : (\n <div className=\"flex flex-1 items-center justify-center\">\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title={tr('proserviceworld.messages.emptyConversation.title', 'Select a conversation')}\n description={tr(\n 'proserviceworld.messages.emptyConversation.description',\n 'Choose a conversation to start messaging.'\n )}\n />\n </div>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|