@burdenoff/microfe-workspaces 2026.625.2 → 2026.625.4
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/pages/ActivityListPage.js +32 -48
- package/dist/pages/ActivityListPage.js.map +1 -1
- package/dist/pages/MembersListPage.js +196 -241
- package/dist/pages/MembersListPage.js.map +1 -1
- package/dist/pages/PendingInvitesPage.js +69 -78
- package/dist/pages/PendingInvitesPage.js.map +1 -1
- package/dist/pages/ProjectDetailPage.js +37 -52
- package/dist/pages/ProjectDetailPage.js.map +1 -1
- package/dist/pages/ProjectMembersPage.js +74 -87
- package/dist/pages/ProjectMembersPage.js.map +1 -1
- package/dist/pages/ProjectsListPage.js +103 -118
- package/dist/pages/ProjectsListPage.js.map +1 -1
- package/dist/pages/WorkspaceDetailPage.js +74 -76
- package/dist/pages/WorkspaceDetailPage.js.map +1 -1
- package/dist/pages/WorkspacesListPage.js +167 -178
- package/dist/pages/WorkspacesListPage.js.map +1 -1
- package/dist/pages/dashboard/widgets/AgentsWidget.js +21 -22
- package/dist/pages/dashboard/widgets/AgentsWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/ProjectsWidget.js +26 -27
- package/dist/pages/dashboard/widgets/ProjectsWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/RecentActivityWidget.js +19 -23
- package/dist/pages/dashboard/widgets/RecentActivityWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/SessionsWidget.js +30 -32
- package/dist/pages/dashboard/widgets/SessionsWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/UpcomingEventsWidget.js +18 -20
- package/dist/pages/dashboard/widgets/UpcomingEventsWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/VibesWidget.js +18 -19
- package/dist/pages/dashboard/widgets/VibesWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/WelcomeWidget.js +14 -12
- package/dist/pages/dashboard/widgets/WelcomeWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/WorkspacesWidget.js +20 -21
- package/dist/pages/dashboard/widgets/WorkspacesWidget.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectsListPage.js","names":[],"sources":["../../src/pages/ProjectsListPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { FolderKanban, Plus, Grid3x3, List, Search } from 'lucide-react';\nimport { TagBadge } from '@burdenoff/fe-libs/tag';\nimport {\n Button,\n Input,\n Card,\n CardContent,\n GlassCard,\n PageContainer,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Skeleton,\n} from '@burdenoff/fe-libs/ui';\nimport { useDebounce } from '@burdenoff/fe-libs/shared/hooks';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useWorkspacesStore } from '../store/workspacesStore';\nimport { nativeConfirm, nativeImpact } from '../utils/nativeBridge';\nimport { useMyProjects } from '../hooks/useProjects';\nimport {\n useArchiveProject,\n useDeleteProject,\n useUnarchiveProject,\n} from '../hooks/useProjectMutations';\nimport { ProjectCard } from '../components/ProjectCard';\nimport { CreateProjectDialog } from '../components/CreateProjectDialog';\nimport { useWorkspacePermissions } from '../hooks/useWorkspacePermissions';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\nimport { tWithFallback } from '../utils/i18n';\nimport { getProjectDisplayTags } from '../utils/projectTags';\nimport type { ProjectTag, Workspace } from '../types';\n\nexport function ProjectsListPage() {\n const { t } = useI18n();\n\n // The URL path `:workspaceId` (when present) intentionally OVERRIDES the active\n // context — it scopes this page to a specific workspace for deep-links/drill-in.\n // The cross-workspace route (`/workspaces/projects`, no path param) leaves it\n // undefined so the list stays cross-workspace. The stale-path-after-context-switch\n // problem is handled by WorkspaceContextPathSync (it rewrites this segment when the\n // active context changes), so the override never points at a stale workspace.\n // (BOFF-2805 follow-up)\n const { workspaceId } = useParams<{ workspaceId: string }>();\n useWorkspacesPageView('workspaces.projects', { workspaceId, requireParams: true });\n const {\n viewMode,\n setViewMode,\n projectFilters,\n setProjectFilters,\n clearProjectFilters,\n selectedProjectIds,\n toggleProjectSelection,\n clearProjectSelection,\n openCreateProjectDialog,\n isCreateProjectDialogOpen,\n } = useWorkspacesStore();\n\n const [searchValue, setSearchValue] = useState(projectFilters.search || '');\n const [activeTagFilters, setActiveTagFilters] = useState<ProjectTag[]>([]);\n const [matchAllTagFilters, setMatchAllTagFilters] = useState(true);\n const debouncedSearch = useDebounce(searchValue, 500);\n\n // Update filters when debounced search changes\n useEffect(() => {\n setProjectFilters({ search: debouncedSearch });\n }, [debouncedSearch, setProjectFilters]);\n\n useEffect(() => {\n if (activeTagFilters.length === 0) {\n setProjectFilters({\n tagIds: undefined,\n tagHierarchyPaths: undefined,\n matchAllTags: undefined,\n includeDescendants: undefined,\n });\n return;\n }\n\n setProjectFilters({\n tagIds: activeTagFilters.map((tag) => tag.tagId),\n tagHierarchyPaths: activeTagFilters\n .map((tag) => tag.hierarchyPath)\n .filter((tagPath): tagPath is string => typeof tagPath === 'string' && tagPath.length > 0),\n matchAllTags: matchAllTagFilters,\n includeDescendants: true,\n });\n }, [activeTagFilters, matchAllTagFilters, setProjectFilters]);\n\n useEffect(() => {\n return () => clearProjectSelection();\n }, [clearProjectSelection]);\n\n // Membership-scoped query: backend filters by actorId, so no client-side\n // permission gate is needed. Page is shared by `/workspaces/projects` and\n // `/workspaces/:workspaceId/projects`; the latter narrows the list below.\n const PROJECTS_PAGE_SIZE = 500;\n const {\n data: myData,\n isLoading,\n error,\n } = useMyProjects({\n limit: PROJECTS_PAGE_SIZE,\n offset: 0,\n });\n\n const data = useMemo(() => {\n const all = myData?.items ?? [];\n const q = (projectFilters.search ?? '').trim().toLowerCase();\n const tagIds = projectFilters.tagIds ?? [];\n const matchAllTags = projectFilters.matchAllTags ?? true;\n\n const matchesWorkspace = (project: (typeof all)[number]) =>\n !workspaceId || project.workspaceId === workspaceId;\n const matchesSearch = (project: (typeof all)[number]) =>\n !q || project.name.toLowerCase().includes(q) || project.key.toLowerCase().includes(q);\n const matchesStatus = (project: (typeof all)[number]) =>\n !projectFilters.status || project.status === projectFilters.status;\n const matchesArchived = (project: (typeof all)[number]) =>\n typeof projectFilters.archived === 'undefined' ||\n project.archived === projectFilters.archived;\n const matchesTags = (project: (typeof all)[number]) => {\n if (tagIds.length === 0) return true;\n const projectTagIds = new Set(getProjectDisplayTags(project).map((t) => t.tagId));\n return matchAllTags\n ? tagIds.every((id) => projectTagIds.has(id))\n : tagIds.some((id) => projectTagIds.has(id));\n };\n\n const filtered = all.filter(\n (project) =>\n matchesWorkspace(project) &&\n matchesSearch(project) &&\n matchesStatus(project) &&\n matchesArchived(project) &&\n matchesTags(project)\n );\n\n // Surface the real backend totals so the \"Showing X of Y\" footer can flag\n // truncation. For narrowed views (workspace/tag filter), the backend total\n // doesn't reflect the post-filter count, so we report the filtered size as\n // total. But we MUST preserve `hasMore` from upstream — if the upstream\n // query was capped (hasMore=true), matches outside the first page are\n // silently hidden, and the footer needs to keep flagging that.\n const apiTotal = myData?.total ?? filtered.length;\n const apiHasMore = myData?.hasMore ?? false;\n const isNarrowed = !!workspaceId || tagIds.length > 0;\n return {\n items: filtered,\n total: isNarrowed ? filtered.length : apiTotal,\n hasMore: apiHasMore,\n };\n }, [myData?.items, myData?.total, myData?.hasMore, projectFilters, workspaceId]);\n\n const { mutateAsync: archiveProject } = useArchiveProject(workspaceId);\n const { mutateAsync: unarchiveProject } = useUnarchiveProject(workspaceId);\n const { mutateAsync: deleteProject } = useDeleteProject(workspaceId);\n\n const selectedProjects = useMemo(\n () => data?.items.filter((project) => selectedProjectIds.has(project.id)) ?? [],\n [data?.items, selectedProjectIds]\n );\n\n const archivableProjects = useMemo(\n () => selectedProjects.filter((project) => !project.archived),\n [selectedProjects]\n );\n\n const unarchivableProjects = useMemo(\n () => selectedProjects.filter((project) => project.archived),\n [selectedProjects]\n );\n\n const allVisibleSelected =\n (data?.items.length ?? 0) > 0 &&\n (data?.items ?? []).every((project) => selectedProjectIds.has(project.id));\n const partiallySelected =\n (data?.items ?? []).some((project) => selectedProjectIds.has(project.id)) &&\n !allVisibleSelected;\n\n const {\n canCreateProject,\n canDeleteProject,\n canArchiveProject,\n isLoading: permissionsLoading,\n } = useWorkspacePermissions();\n\n if (permissionsLoading) {\n return (\n <div className=\"space-y-4 p-6\">\n <Skeleton className=\"h-10 w-1/3\" />\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-4\">\n <Skeleton className=\"h-32\" />\n <Skeleton className=\"h-32\" />\n <Skeleton className=\"h-32\" />\n </div>\n </div>\n );\n }\n\n // No client-side list gate: this page calls `myProjects` which is\n // membership-scoped — the resolver filters by actorId on the backend, so the\n // data set returned IS the access list. If the user is in zero projects, the\n // page renders the empty state below. Per-action checks (canCreateProject,\n // canDeleteProject, canArchiveProject) remain in place to hide buttons the\n // user isn't authorized for on their workspace.\n\n const handleCreateProject = () => {\n openCreateProjectDialog({} as Workspace);\n };\n\n const handleStatusFilter = (value: string) => {\n if (value === 'all') {\n const nextFilters = { ...projectFilters };\n delete nextFilters.status;\n setProjectFilters(nextFilters);\n } else {\n setProjectFilters({ status: value as 'ACTIVE' | 'INACTIVE' | 'ARCHIVED' });\n }\n };\n\n const handleArchivedFilter = (value: string) => {\n if (value === 'all') {\n const nextFilters = { ...projectFilters };\n delete nextFilters.archived;\n setProjectFilters(nextFilters);\n } else {\n setProjectFilters({ archived: value === 'true' });\n }\n };\n\n const toggleTagFilter = (tag: ProjectTag) => {\n setActiveTagFilters((current) => {\n const exists = current.some((entry) => entry.tagId === tag.tagId);\n if (exists) {\n return current.filter((entry) => entry.tagId !== tag.tagId);\n }\n\n return [...current, tag];\n });\n };\n\n const clearAllProjectFilters = () => {\n setSearchValue('');\n setActiveTagFilters([]);\n setMatchAllTagFilters(true);\n clearProjectFilters();\n };\n\n const toggleAllProjects = (checked: boolean) => {\n (data?.items ?? []).forEach((project) => {\n const isSelected = selectedProjectIds.has(project.id);\n if (checked !== isSelected) {\n toggleProjectSelection(project.id);\n }\n });\n };\n\n const handleBulkArchive = async () => {\n if (archivableProjects.length === 0) return;\n await Promise.allSettled(archivableProjects.map((project) => archiveProject(project.id)));\n clearProjectSelection();\n };\n\n const handleBulkUnarchive = async () => {\n if (unarchivableProjects.length === 0) return;\n await Promise.allSettled(unarchivableProjects.map((project) => unarchiveProject(project.id)));\n clearProjectSelection();\n };\n\n const handleBulkDelete = async () => {\n if (selectedProjects.length === 0) return;\n void nativeImpact('medium');\n const message =\n tWithFallback(t, 'projectsList.bulkDeleteConfirmPrefix', 'Delete ') +\n selectedProjects.length +\n ' ' +\n tWithFallback(t, 'projectsList.bulkDeleteConfirmProject', 'selected project') +\n (selectedProjects.length === 1\n ? ''\n : tWithFallback(t, 'projectsList.bulkDeleteConfirmPluralSuffix', 's')) +\n tWithFallback(t, 'projectsList.bulkDeleteConfirmSuffix', '? This cannot be undone.');\n const native = await nativeConfirm({\n title: 'Delete projects',\n message,\n okButtonTitle: 'Delete',\n cancelButtonTitle: 'Cancel',\n });\n const confirmed = native === null ? window.confirm(message) : native;\n if (!confirmed) return;\n\n await Promise.allSettled(selectedProjects.map((project) => deleteProject(project.id)));\n clearProjectSelection();\n };\n\n return (\n <PageContainer padded={false}>\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n {/* Header */}\n <div className=\"flex flex-col sm:flex-row sm:items-center justify-between gap-4\">\n <div>\n <h1 className=\"text-2xl sm:text-3xl font-bold\">\n {tWithFallback(t, 'projectsList.pageTitle', 'Projects')}\n </h1>\n <p className=\"text-text-secondary\">\n {tWithFallback(t, 'projectsList.pageSubtitle', 'Manage and organize your projects')}\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n {/* View mode toggle */}\n <div className=\"flex items-center gap-1 border rounded-lg p-1\">\n <Button\n variant={viewMode === 'grid' ? 'secondary' : 'ghost'}\n size=\"sm\"\n onClick={() => setViewMode('grid')}\n className=\"size-8 p-0\"\n aria-label={tWithFallback(\n t,\n 'projectsList.gridViewAriaLabel',\n 'Show projects in grid view'\n )}\n >\n <Grid3x3 size={16} />\n </Button>\n <Button\n variant={viewMode === 'list' ? 'secondary' : 'ghost'}\n size=\"sm\"\n onClick={() => setViewMode('list')}\n className=\"size-8 p-0\"\n aria-label={tWithFallback(\n t,\n 'projectsList.listViewAriaLabel',\n 'Show projects in list view'\n )}\n >\n <List size={16} />\n </Button>\n </div>\n {canCreateProject && (\n <Button onClick={handleCreateProject}>\n <Plus className=\"size-4 sm:mr-2\" />\n <span className=\"hidden sm:inline\">\n {tWithFallback(t, 'projectsList.createProject', 'Create Project')}\n </span>\n </Button>\n )}\n </div>\n </div>\n\n {/* Filters & Search */}\n <div className=\"flex flex-col sm:flex-row gap-3\">\n <div className=\"relative flex-1\">\n <Search\n className=\"absolute left-3 top-1/2 transform -translate-y-1/2 text-text-secondary\"\n size={18}\n />\n <Input\n placeholder={tWithFallback(t, 'projectsList.searchPlaceholder', 'Search projects...')}\n value={searchValue}\n onChange={(e) => setSearchValue(e.target.value)}\n className=\"pl-[3.25rem]\"\n />\n </div>\n <Select value={projectFilters.status || 'all'} onValueChange={handleStatusFilter}>\n <SelectTrigger className=\"w-full sm:w-[180px]\">\n <SelectValue\n placeholder={tWithFallback(t, 'projectsList.filterByStatus', 'Filter by status')}\n />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">\n {tWithFallback(t, 'projectsList.statusAll', 'All Statuses')}\n </SelectItem>\n <SelectItem value=\"ACTIVE\">\n {tWithFallback(t, 'projectsList.statusActive', 'Active')}\n </SelectItem>\n <SelectItem value=\"INACTIVE\">\n {tWithFallback(t, 'projectsList.statusInactive', 'Inactive')}\n </SelectItem>\n </SelectContent>\n </Select>\n <Select\n value={\n projectFilters.archived === undefined\n ? 'all'\n : projectFilters.archived\n ? 'true'\n : 'false'\n }\n onValueChange={handleArchivedFilter}\n >\n <SelectTrigger className=\"w-full sm:w-[180px]\">\n <SelectValue\n placeholder={tWithFallback(t, 'projectsList.filterByArchive', 'Filter by archive')}\n />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">\n {tWithFallback(t, 'projectsList.archiveAll', 'All Projects')}\n </SelectItem>\n <SelectItem value=\"false\">\n {tWithFallback(t, 'projectsList.archiveActiveProjects', 'Active Projects')}\n </SelectItem>\n <SelectItem value=\"true\">\n {tWithFallback(t, 'projectsList.archiveArchived', 'Archived')}\n </SelectItem>\n </SelectContent>\n </Select>\n {(projectFilters.search ||\n projectFilters.status ||\n projectFilters.archived !== undefined ||\n activeTagFilters.length > 0) && (\n <Button variant=\"outline\" onClick={clearAllProjectFilters}>\n {tWithFallback(t, 'projectsList.clearFilters', 'Clear Filters')}\n </Button>\n )}\n </div>\n\n {activeTagFilters.length > 0 && (\n <div className=\"flex flex-col gap-3 rounded-lg border border-border-seam bg-bg-surface px-4 py-3\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <p className=\"text-sm font-medium\">\n {tWithFallback(t, 'projectsList.activeTagFilters', 'Active tag filters')}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {tWithFallback(\n t,\n 'projectsList.activeTagFiltersDescription',\n 'Click a project tag to add or remove it from the current filter set.'\n )}\n </p>\n </div>\n <Select\n value={matchAllTagFilters ? 'all' : 'any'}\n onValueChange={(value) => setMatchAllTagFilters(value === 'all')}\n >\n <SelectTrigger className=\"w-full sm:w-[180px]\">\n <SelectValue\n placeholder={tWithFallback(t, 'projectsList.tagMatchMode', 'Tag match mode')}\n />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">\n {tWithFallback(t, 'projectsList.tagMatchAll', 'Match all tags')}\n </SelectItem>\n <SelectItem value=\"any\">\n {tWithFallback(t, 'projectsList.tagMatchAny', 'Match any tag')}\n </SelectItem>\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"flex flex-wrap gap-2\">\n {activeTagFilters.map((tag) => (\n <TagBadge\n key={tag.tagId}\n id={tag.tagId}\n label={tag.label}\n color={tag.color ?? undefined}\n icon={tag.icon ?? undefined}\n description={tag.description ?? undefined}\n removable\n onRemove={() => toggleTagFilter(tag)}\n size=\"sm\"\n />\n ))}\n </div>\n </div>\n )}\n\n {/* Error State */}\n {error && (\n <GlassCard treatment=\"plain\" className=\"border-status-error-border\">\n <CardContent className=\"pt-6\">\n <div className=\"flex flex-col items-center justify-center py-8 text-center\">\n <p className=\"text-status-error-text font-semibold mb-2\">\n {tWithFallback(t, 'projectsList.errorLoading', 'Error loading projects')}\n </p>\n <p className=\"text-sm text-text-secondary\">\n {error instanceof Error\n ? error.message\n : tWithFallback(t, 'projectsList.unknownError', 'An unknown error occurred')}\n </p>\n </div>\n </CardContent>\n </GlassCard>\n )}\n\n {/* Loading State */}\n {isLoading && (\n <div className=\"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4\">\n {Array.from({ length: 6 }).map((_, i) => (\n <Card key={i} className=\"border-border-seam\">\n <CardContent className=\"pt-6\">\n <div className=\"space-y-3\">\n <Skeleton className=\"size-12 rounded-lg\" />\n <Skeleton className=\"h-4 w-3/4\" />\n <Skeleton className=\"h-4 w-1/2\" />\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n )}\n\n {/* Empty State */}\n {!isLoading && !error && data?.items.length === 0 && (\n <GlassCard treatment=\"plain\" className=\"border-border-seam\">\n <CardContent className=\"pt-6\">\n <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n <div className=\"rounded-full bg-bg-sunken p-6 mb-4\">\n <FolderKanban size={48} className=\"text-text-secondary\" />\n </div>\n <h3 className=\"text-lg font-semibold mb-2\">\n {tWithFallback(t, 'projectsList.noProjectsFound', 'No projects found')}\n </h3>\n <p className=\"text-text-secondary max-w-sm mb-4\">\n {projectFilters.search\n ? tWithFallback(\n t,\n 'projectsList.adjustSearchOrFilters',\n 'Try adjusting your search or filters'\n )\n : tWithFallback(\n t,\n 'projectsList.createFirstProject',\n 'Create your first project to get started organizing your work'\n )}\n </p>\n {!projectFilters.search && canCreateProject && (\n <Button onClick={handleCreateProject}>\n <Plus size={16} className=\"mr-2\" />\n {tWithFallback(t, 'projectsList.createProject', 'Create Project')}\n </Button>\n )}\n </div>\n </CardContent>\n </GlassCard>\n )}\n\n {/* Projects Grid */}\n {!isLoading && !error && data && data.items.length > 0 && (\n <>\n <div className=\"flex flex-col gap-3 rounded-lg border border-border-seam bg-bg-surface p-4 sm:flex-row sm:items-center sm:justify-between\">\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={allVisibleSelected}\n ref={(input) => {\n if (input) {\n input.indeterminate = partiallySelected && !allVisibleSelected;\n }\n }}\n onChange={(event) => toggleAllProjects(event.target.checked)}\n aria-label={tWithFallback(\n t,\n 'projectsList.selectAllAriaLabel',\n 'Select all projects'\n )}\n className=\"size-4 rounded border-border-subtle text-primary\"\n />\n <span>\n {tWithFallback(t, 'projectsList.selectAllVisible', 'Select all visible')}\n </span>\n </label>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-sm text-text-secondary\">\n {selectedProjectIds.size} {tWithFallback(t, 'projectsList.selected', 'selected')}\n </span>\n {selectedProjectIds.size > 0 && (\n <Button variant=\"ghost\" size=\"sm\" onClick={clearProjectSelection}>\n {tWithFallback(t, 'projectsList.clearSelection', 'Clear selection')}\n </Button>\n )}\n {/* Bulk mutations are bound to a single workspaceId via the\n archive/unarchive/delete hooks. On the cross-workspace\n /workspaces/projects route (workspaceId is undefined),\n hide them — selections can span workspaces and the bound\n hook would fail authorization for projects outside the\n caller's context. The per-card actions in ProjectCard\n still work because they resolve per-project. */}\n {workspaceId && canArchiveProject && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={handleBulkArchive}\n disabled={archivableProjects.length === 0}\n >\n {tWithFallback(t, 'projectsList.archiveSelected', 'Archive Selected')}\n {archivableProjects.length > 0 ? ` (${archivableProjects.length})` : ''}\n </Button>\n )}\n {workspaceId && canArchiveProject && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={handleBulkUnarchive}\n disabled={unarchivableProjects.length === 0}\n >\n {tWithFallback(t, 'projectsList.unarchiveSelected', 'Unarchive Selected')}\n {unarchivableProjects.length > 0 ? ` (${unarchivableProjects.length})` : ''}\n </Button>\n )}\n {workspaceId && canDeleteProject && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={handleBulkDelete}\n disabled={selectedProjects.length === 0}\n className=\"text-status-error-text\"\n >\n {tWithFallback(t, 'projectsList.deleteSelected', 'Delete Selected')}\n {selectedProjects.length > 0 ? ` (${selectedProjects.length})` : ''}\n </Button>\n )}\n </div>\n </div>\n\n <div\n className={\n viewMode === 'list'\n ? 'space-y-3'\n : 'grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4'\n }\n >\n {data.items.map((project) => (\n <ProjectCard\n key={project.id}\n project={project}\n selected={selectedProjectIds.has(project.id)}\n canArchive={canArchiveProject}\n canDelete={canDeleteProject}\n onTagClick={toggleTagFilter}\n onToggleSelect={(checked) => {\n const isSelected = selectedProjectIds.has(project.id);\n if (checked !== isSelected) {\n toggleProjectSelection(project.id);\n }\n }}\n />\n ))}\n </div>\n\n {/* Pagination Info */}\n {data.total > data.items.length && (\n <div className=\"text-center text-sm text-text-secondary\">\n {tWithFallback(t, 'projectsList.showingPrefix', 'Showing')} {data.items.length}{' '}\n {tWithFallback(t, 'projectsList.showingOf', 'of')} {data.total}{' '}\n {tWithFallback(t, 'projectsList.showingProjects', 'projects')}\n </div>\n )}\n </>\n )}\n\n {/* Create Project Dialog */}\n {isCreateProjectDialogOpen && <CreateProjectDialog workspaceId={workspaceId} />}\n </div>\n </PageContainer>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProjectsListPage.js","names":[],"sources":["../../src/pages/ProjectsListPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { Plus, Grid3x3, List, Search } from 'lucide-react';\nimport { TagBadge } from '@burdenoff/fe-libs/tag';\nimport {\n Button,\n Input,\n Card,\n CardContent,\n GlassCard,\n PageContainer,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Skeleton,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport { useDebounce } from '@burdenoff/fe-libs/shared/hooks';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useWorkspacesStore } from '../store/workspacesStore';\nimport { nativeConfirm, nativeImpact } from '../utils/nativeBridge';\nimport { useMyProjects } from '../hooks/useProjects';\nimport {\n useArchiveProject,\n useDeleteProject,\n useUnarchiveProject,\n} from '../hooks/useProjectMutations';\nimport { ProjectCard } from '../components/ProjectCard';\nimport { CreateProjectDialog } from '../components/CreateProjectDialog';\nimport { useWorkspacePermissions } from '../hooks/useWorkspacePermissions';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\nimport { tWithFallback } from '../utils/i18n';\nimport { getProjectDisplayTags } from '../utils/projectTags';\nimport type { ProjectTag, Workspace } from '../types';\n\nexport function ProjectsListPage() {\n const { t } = useI18n();\n\n // The URL path `:workspaceId` (when present) intentionally OVERRIDES the active\n // context — it scopes this page to a specific workspace for deep-links/drill-in.\n // The cross-workspace route (`/workspaces/projects`, no path param) leaves it\n // undefined so the list stays cross-workspace. The stale-path-after-context-switch\n // problem is handled by WorkspaceContextPathSync (it rewrites this segment when the\n // active context changes), so the override never points at a stale workspace.\n // (BOFF-2805 follow-up)\n const { workspaceId } = useParams<{ workspaceId: string }>();\n useWorkspacesPageView('workspaces.projects', { workspaceId, requireParams: true });\n const {\n viewMode,\n setViewMode,\n projectFilters,\n setProjectFilters,\n clearProjectFilters,\n selectedProjectIds,\n toggleProjectSelection,\n clearProjectSelection,\n openCreateProjectDialog,\n isCreateProjectDialogOpen,\n } = useWorkspacesStore();\n\n const [searchValue, setSearchValue] = useState(projectFilters.search || '');\n const [activeTagFilters, setActiveTagFilters] = useState<ProjectTag[]>([]);\n const [matchAllTagFilters, setMatchAllTagFilters] = useState(true);\n const debouncedSearch = useDebounce(searchValue, 500);\n\n // Update filters when debounced search changes\n useEffect(() => {\n setProjectFilters({ search: debouncedSearch });\n }, [debouncedSearch, setProjectFilters]);\n\n useEffect(() => {\n if (activeTagFilters.length === 0) {\n setProjectFilters({\n tagIds: undefined,\n tagHierarchyPaths: undefined,\n matchAllTags: undefined,\n includeDescendants: undefined,\n });\n return;\n }\n\n setProjectFilters({\n tagIds: activeTagFilters.map((tag) => tag.tagId),\n tagHierarchyPaths: activeTagFilters\n .map((tag) => tag.hierarchyPath)\n .filter((tagPath): tagPath is string => typeof tagPath === 'string' && tagPath.length > 0),\n matchAllTags: matchAllTagFilters,\n includeDescendants: true,\n });\n }, [activeTagFilters, matchAllTagFilters, setProjectFilters]);\n\n useEffect(() => {\n return () => clearProjectSelection();\n }, [clearProjectSelection]);\n\n // Membership-scoped query: backend filters by actorId, so no client-side\n // permission gate is needed. Page is shared by `/workspaces/projects` and\n // `/workspaces/:workspaceId/projects`; the latter narrows the list below.\n const PROJECTS_PAGE_SIZE = 500;\n const {\n data: myData,\n isLoading,\n error,\n } = useMyProjects({\n limit: PROJECTS_PAGE_SIZE,\n offset: 0,\n });\n\n const data = useMemo(() => {\n const all = myData?.items ?? [];\n const q = (projectFilters.search ?? '').trim().toLowerCase();\n const tagIds = projectFilters.tagIds ?? [];\n const matchAllTags = projectFilters.matchAllTags ?? true;\n\n const matchesWorkspace = (project: (typeof all)[number]) =>\n !workspaceId || project.workspaceId === workspaceId;\n const matchesSearch = (project: (typeof all)[number]) =>\n !q || project.name.toLowerCase().includes(q) || project.key.toLowerCase().includes(q);\n const matchesStatus = (project: (typeof all)[number]) =>\n !projectFilters.status || project.status === projectFilters.status;\n const matchesArchived = (project: (typeof all)[number]) =>\n typeof projectFilters.archived === 'undefined' ||\n project.archived === projectFilters.archived;\n const matchesTags = (project: (typeof all)[number]) => {\n if (tagIds.length === 0) return true;\n const projectTagIds = new Set(getProjectDisplayTags(project).map((t) => t.tagId));\n return matchAllTags\n ? tagIds.every((id) => projectTagIds.has(id))\n : tagIds.some((id) => projectTagIds.has(id));\n };\n\n const filtered = all.filter(\n (project) =>\n matchesWorkspace(project) &&\n matchesSearch(project) &&\n matchesStatus(project) &&\n matchesArchived(project) &&\n matchesTags(project)\n );\n\n // Surface the real backend totals so the \"Showing X of Y\" footer can flag\n // truncation. For narrowed views (workspace/tag filter), the backend total\n // doesn't reflect the post-filter count, so we report the filtered size as\n // total. But we MUST preserve `hasMore` from upstream — if the upstream\n // query was capped (hasMore=true), matches outside the first page are\n // silently hidden, and the footer needs to keep flagging that.\n const apiTotal = myData?.total ?? filtered.length;\n const apiHasMore = myData?.hasMore ?? false;\n const isNarrowed = !!workspaceId || tagIds.length > 0;\n return {\n items: filtered,\n total: isNarrowed ? filtered.length : apiTotal,\n hasMore: apiHasMore,\n };\n }, [myData?.items, myData?.total, myData?.hasMore, projectFilters, workspaceId]);\n\n const { mutateAsync: archiveProject } = useArchiveProject(workspaceId);\n const { mutateAsync: unarchiveProject } = useUnarchiveProject(workspaceId);\n const { mutateAsync: deleteProject } = useDeleteProject(workspaceId);\n\n const selectedProjects = useMemo(\n () => data?.items.filter((project) => selectedProjectIds.has(project.id)) ?? [],\n [data?.items, selectedProjectIds]\n );\n\n const archivableProjects = useMemo(\n () => selectedProjects.filter((project) => !project.archived),\n [selectedProjects]\n );\n\n const unarchivableProjects = useMemo(\n () => selectedProjects.filter((project) => project.archived),\n [selectedProjects]\n );\n\n const allVisibleSelected =\n (data?.items.length ?? 0) > 0 &&\n (data?.items ?? []).every((project) => selectedProjectIds.has(project.id));\n const partiallySelected =\n (data?.items ?? []).some((project) => selectedProjectIds.has(project.id)) &&\n !allVisibleSelected;\n\n const {\n canCreateProject,\n canDeleteProject,\n canArchiveProject,\n isLoading: permissionsLoading,\n } = useWorkspacePermissions();\n\n if (permissionsLoading) {\n return (\n <div className=\"space-y-4 p-6\">\n <Skeleton className=\"h-10 w-1/3\" />\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-4\">\n <Skeleton className=\"h-32\" />\n <Skeleton className=\"h-32\" />\n <Skeleton className=\"h-32\" />\n </div>\n </div>\n );\n }\n\n // No client-side list gate: this page calls `myProjects` which is\n // membership-scoped — the resolver filters by actorId on the backend, so the\n // data set returned IS the access list. If the user is in zero projects, the\n // page renders the empty state below. Per-action checks (canCreateProject,\n // canDeleteProject, canArchiveProject) remain in place to hide buttons the\n // user isn't authorized for on their workspace.\n\n const handleCreateProject = () => {\n openCreateProjectDialog({} as Workspace);\n };\n\n const handleStatusFilter = (value: string) => {\n if (value === 'all') {\n const nextFilters = { ...projectFilters };\n delete nextFilters.status;\n setProjectFilters(nextFilters);\n } else {\n setProjectFilters({ status: value as 'ACTIVE' | 'INACTIVE' | 'ARCHIVED' });\n }\n };\n\n const handleArchivedFilter = (value: string) => {\n if (value === 'all') {\n const nextFilters = { ...projectFilters };\n delete nextFilters.archived;\n setProjectFilters(nextFilters);\n } else {\n setProjectFilters({ archived: value === 'true' });\n }\n };\n\n const toggleTagFilter = (tag: ProjectTag) => {\n setActiveTagFilters((current) => {\n const exists = current.some((entry) => entry.tagId === tag.tagId);\n if (exists) {\n return current.filter((entry) => entry.tagId !== tag.tagId);\n }\n\n return [...current, tag];\n });\n };\n\n const clearAllProjectFilters = () => {\n setSearchValue('');\n setActiveTagFilters([]);\n setMatchAllTagFilters(true);\n clearProjectFilters();\n };\n\n const toggleAllProjects = (checked: boolean) => {\n (data?.items ?? []).forEach((project) => {\n const isSelected = selectedProjectIds.has(project.id);\n if (checked !== isSelected) {\n toggleProjectSelection(project.id);\n }\n });\n };\n\n const handleBulkArchive = async () => {\n if (archivableProjects.length === 0) return;\n await Promise.allSettled(archivableProjects.map((project) => archiveProject(project.id)));\n clearProjectSelection();\n };\n\n const handleBulkUnarchive = async () => {\n if (unarchivableProjects.length === 0) return;\n await Promise.allSettled(unarchivableProjects.map((project) => unarchiveProject(project.id)));\n clearProjectSelection();\n };\n\n const handleBulkDelete = async () => {\n if (selectedProjects.length === 0) return;\n void nativeImpact('medium');\n const message =\n tWithFallback(t, 'projectsList.bulkDeleteConfirmPrefix', 'Delete ') +\n selectedProjects.length +\n ' ' +\n tWithFallback(t, 'projectsList.bulkDeleteConfirmProject', 'selected project') +\n (selectedProjects.length === 1\n ? ''\n : tWithFallback(t, 'projectsList.bulkDeleteConfirmPluralSuffix', 's')) +\n tWithFallback(t, 'projectsList.bulkDeleteConfirmSuffix', '? This cannot be undone.');\n const native = await nativeConfirm({\n title: 'Delete projects',\n message,\n okButtonTitle: 'Delete',\n cancelButtonTitle: 'Cancel',\n });\n const confirmed = native === null ? window.confirm(message) : native;\n if (!confirmed) return;\n\n await Promise.allSettled(selectedProjects.map((project) => deleteProject(project.id)));\n clearProjectSelection();\n };\n\n return (\n <PageContainer padded={false}>\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n {/* Header */}\n <div className=\"flex flex-col sm:flex-row sm:items-center justify-between gap-4\">\n <div>\n <h1 className=\"text-2xl sm:text-3xl font-bold\">\n {tWithFallback(t, 'projectsList.pageTitle', 'Projects')}\n </h1>\n <p className=\"text-text-secondary\">\n {tWithFallback(t, 'projectsList.pageSubtitle', 'Manage and organize your projects')}\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n {/* View mode toggle */}\n <div className=\"flex items-center gap-1 border rounded-lg p-1\">\n <Button\n variant={viewMode === 'grid' ? 'secondary' : 'ghost'}\n size=\"sm\"\n onClick={() => setViewMode('grid')}\n className=\"size-8 p-0\"\n aria-label={tWithFallback(\n t,\n 'projectsList.gridViewAriaLabel',\n 'Show projects in grid view'\n )}\n >\n <Grid3x3 size={16} />\n </Button>\n <Button\n variant={viewMode === 'list' ? 'secondary' : 'ghost'}\n size=\"sm\"\n onClick={() => setViewMode('list')}\n className=\"size-8 p-0\"\n aria-label={tWithFallback(\n t,\n 'projectsList.listViewAriaLabel',\n 'Show projects in list view'\n )}\n >\n <List size={16} />\n </Button>\n </div>\n {canCreateProject && (\n <Button onClick={handleCreateProject}>\n <Plus className=\"size-4 sm:mr-2\" />\n <span className=\"hidden sm:inline\">\n {tWithFallback(t, 'projectsList.createProject', 'Create Project')}\n </span>\n </Button>\n )}\n </div>\n </div>\n\n {/* Filters & Search */}\n <div className=\"flex flex-col sm:flex-row gap-3\">\n <div className=\"relative flex-1\">\n <Search\n className=\"absolute left-3 top-1/2 transform -translate-y-1/2 text-text-secondary\"\n size={18}\n />\n <Input\n placeholder={tWithFallback(t, 'projectsList.searchPlaceholder', 'Search projects...')}\n value={searchValue}\n onChange={(e) => setSearchValue(e.target.value)}\n className=\"pl-[3.25rem]\"\n />\n </div>\n <Select value={projectFilters.status || 'all'} onValueChange={handleStatusFilter}>\n <SelectTrigger className=\"w-full sm:w-[180px]\">\n <SelectValue\n placeholder={tWithFallback(t, 'projectsList.filterByStatus', 'Filter by status')}\n />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">\n {tWithFallback(t, 'projectsList.statusAll', 'All Statuses')}\n </SelectItem>\n <SelectItem value=\"ACTIVE\">\n {tWithFallback(t, 'projectsList.statusActive', 'Active')}\n </SelectItem>\n <SelectItem value=\"INACTIVE\">\n {tWithFallback(t, 'projectsList.statusInactive', 'Inactive')}\n </SelectItem>\n </SelectContent>\n </Select>\n <Select\n value={\n projectFilters.archived === undefined\n ? 'all'\n : projectFilters.archived\n ? 'true'\n : 'false'\n }\n onValueChange={handleArchivedFilter}\n >\n <SelectTrigger className=\"w-full sm:w-[180px]\">\n <SelectValue\n placeholder={tWithFallback(t, 'projectsList.filterByArchive', 'Filter by archive')}\n />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">\n {tWithFallback(t, 'projectsList.archiveAll', 'All Projects')}\n </SelectItem>\n <SelectItem value=\"false\">\n {tWithFallback(t, 'projectsList.archiveActiveProjects', 'Active Projects')}\n </SelectItem>\n <SelectItem value=\"true\">\n {tWithFallback(t, 'projectsList.archiveArchived', 'Archived')}\n </SelectItem>\n </SelectContent>\n </Select>\n {(projectFilters.search ||\n projectFilters.status ||\n projectFilters.archived !== undefined ||\n activeTagFilters.length > 0) && (\n <Button variant=\"outline\" onClick={clearAllProjectFilters}>\n {tWithFallback(t, 'projectsList.clearFilters', 'Clear Filters')}\n </Button>\n )}\n </div>\n\n {activeTagFilters.length > 0 && (\n <div className=\"flex flex-col gap-3 rounded-lg border border-border-seam bg-bg-surface px-4 py-3\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <p className=\"text-sm font-medium\">\n {tWithFallback(t, 'projectsList.activeTagFilters', 'Active tag filters')}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {tWithFallback(\n t,\n 'projectsList.activeTagFiltersDescription',\n 'Click a project tag to add or remove it from the current filter set.'\n )}\n </p>\n </div>\n <Select\n value={matchAllTagFilters ? 'all' : 'any'}\n onValueChange={(value) => setMatchAllTagFilters(value === 'all')}\n >\n <SelectTrigger className=\"w-full sm:w-[180px]\">\n <SelectValue\n placeholder={tWithFallback(t, 'projectsList.tagMatchMode', 'Tag match mode')}\n />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">\n {tWithFallback(t, 'projectsList.tagMatchAll', 'Match all tags')}\n </SelectItem>\n <SelectItem value=\"any\">\n {tWithFallback(t, 'projectsList.tagMatchAny', 'Match any tag')}\n </SelectItem>\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"flex flex-wrap gap-2\">\n {activeTagFilters.map((tag) => (\n <TagBadge\n key={tag.tagId}\n id={tag.tagId}\n label={tag.label}\n color={tag.color ?? undefined}\n icon={tag.icon ?? undefined}\n description={tag.description ?? undefined}\n removable\n onRemove={() => toggleTagFilter(tag)}\n size=\"sm\"\n />\n ))}\n </div>\n </div>\n )}\n\n {/* Error State */}\n {error && (\n <GlassCard treatment=\"plain\" className=\"border-status-error-border\">\n <CardContent className=\"pt-6\">\n <div className=\"flex flex-col items-center justify-center py-8 text-center\">\n <p className=\"text-status-error-text font-semibold mb-2\">\n {tWithFallback(t, 'projectsList.errorLoading', 'Error loading projects')}\n </p>\n <p className=\"text-sm text-text-secondary\">\n {error instanceof Error\n ? error.message\n : tWithFallback(t, 'projectsList.unknownError', 'An unknown error occurred')}\n </p>\n </div>\n </CardContent>\n </GlassCard>\n )}\n\n {/* Loading State */}\n {isLoading && (\n <div className=\"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4\">\n {Array.from({ length: 6 }).map((_, i) => (\n <Card key={i} className=\"border-border-seam\">\n <CardContent className=\"pt-6\">\n <div className=\"space-y-3\">\n <Skeleton className=\"size-12 rounded-lg\" />\n <Skeleton className=\"h-4 w-3/4\" />\n <Skeleton className=\"h-4 w-1/2\" />\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n )}\n\n {/* Empty State */}\n {!isLoading && !error && data?.items.length === 0 && (\n <GlassCard treatment=\"plain\" className=\"border-border-seam\">\n <CardContent className=\"pt-6\">\n <IllustratedEmptyState\n illustration={projectFilters.search ? 'empty-search' : 'empty-data'}\n title={tWithFallback(t, 'projectsList.noProjectsFound', 'No projects found')}\n description={\n projectFilters.search\n ? tWithFallback(\n t,\n 'projectsList.adjustSearchOrFilters',\n 'Try adjusting your search or filters'\n )\n : tWithFallback(\n t,\n 'projectsList.createFirstProject',\n 'Create your first project to get started organizing your work'\n )\n }\n action={\n !projectFilters.search && canCreateProject ? (\n <Button onClick={handleCreateProject}>\n <Plus size={16} className=\"mr-2\" />\n {tWithFallback(t, 'projectsList.createProject', 'Create Project')}\n </Button>\n ) : undefined\n }\n />\n </CardContent>\n </GlassCard>\n )}\n\n {/* Projects Grid */}\n {!isLoading && !error && data && data.items.length > 0 && (\n <>\n <div className=\"flex flex-col gap-3 rounded-lg border border-border-seam bg-bg-surface p-4 sm:flex-row sm:items-center sm:justify-between\">\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={allVisibleSelected}\n ref={(input) => {\n if (input) {\n input.indeterminate = partiallySelected && !allVisibleSelected;\n }\n }}\n onChange={(event) => toggleAllProjects(event.target.checked)}\n aria-label={tWithFallback(\n t,\n 'projectsList.selectAllAriaLabel',\n 'Select all projects'\n )}\n className=\"size-4 rounded border-border-subtle text-primary\"\n />\n <span>\n {tWithFallback(t, 'projectsList.selectAllVisible', 'Select all visible')}\n </span>\n </label>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-sm text-text-secondary\">\n {selectedProjectIds.size} {tWithFallback(t, 'projectsList.selected', 'selected')}\n </span>\n {selectedProjectIds.size > 0 && (\n <Button variant=\"ghost\" size=\"sm\" onClick={clearProjectSelection}>\n {tWithFallback(t, 'projectsList.clearSelection', 'Clear selection')}\n </Button>\n )}\n {/* Bulk mutations are bound to a single workspaceId via the\n archive/unarchive/delete hooks. On the cross-workspace\n /workspaces/projects route (workspaceId is undefined),\n hide them — selections can span workspaces and the bound\n hook would fail authorization for projects outside the\n caller's context. The per-card actions in ProjectCard\n still work because they resolve per-project. */}\n {workspaceId && canArchiveProject && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={handleBulkArchive}\n disabled={archivableProjects.length === 0}\n >\n {tWithFallback(t, 'projectsList.archiveSelected', 'Archive Selected')}\n {archivableProjects.length > 0 ? ` (${archivableProjects.length})` : ''}\n </Button>\n )}\n {workspaceId && canArchiveProject && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={handleBulkUnarchive}\n disabled={unarchivableProjects.length === 0}\n >\n {tWithFallback(t, 'projectsList.unarchiveSelected', 'Unarchive Selected')}\n {unarchivableProjects.length > 0 ? ` (${unarchivableProjects.length})` : ''}\n </Button>\n )}\n {workspaceId && canDeleteProject && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={handleBulkDelete}\n disabled={selectedProjects.length === 0}\n className=\"text-status-error-text\"\n >\n {tWithFallback(t, 'projectsList.deleteSelected', 'Delete Selected')}\n {selectedProjects.length > 0 ? ` (${selectedProjects.length})` : ''}\n </Button>\n )}\n </div>\n </div>\n\n <div\n className={\n viewMode === 'list'\n ? 'space-y-3'\n : 'grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4'\n }\n >\n {data.items.map((project) => (\n <ProjectCard\n key={project.id}\n project={project}\n selected={selectedProjectIds.has(project.id)}\n canArchive={canArchiveProject}\n canDelete={canDeleteProject}\n onTagClick={toggleTagFilter}\n onToggleSelect={(checked) => {\n const isSelected = selectedProjectIds.has(project.id);\n if (checked !== isSelected) {\n toggleProjectSelection(project.id);\n }\n }}\n />\n ))}\n </div>\n\n {/* Pagination Info */}\n {data.total > data.items.length && (\n <div className=\"text-center text-sm text-text-secondary\">\n {tWithFallback(t, 'projectsList.showingPrefix', 'Showing')} {data.items.length}{' '}\n {tWithFallback(t, 'projectsList.showingOf', 'of')} {data.total}{' '}\n {tWithFallback(t, 'projectsList.showingProjects', 'projects')}\n </div>\n )}\n </>\n )}\n\n {/* Create Project Dialog */}\n {isCreateProjectDialogOpen && <CreateProjectDialog workspaceId={workspaceId} />}\n </div>\n </PageContainer>\n );\n}\n"],"mappings":"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"}
|
|
@@ -9,69 +9,67 @@ import { useProjects as s } from "../hooks/useProjects.js";
|
|
|
9
9
|
import { useWorkspaceActivity as c } from "../hooks/useWorkspaceActivity.js";
|
|
10
10
|
import { Activity as l, ArrowLeft as u, ChevronRight as d, FolderKanban as f, Settings as p, UserPlus as m, Users as h } from "lucide-react";
|
|
11
11
|
import { Fragment as g, jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
12
|
-
import { Badge as y, Button as b, CTAOverflowMenu as x, CardContent as S, CardHeader as C, CardTitle as w, GlassCard as T,
|
|
13
|
-
import { useNavigate as
|
|
14
|
-
import { useI18n as
|
|
12
|
+
import { Badge as y, Button as b, CTAOverflowMenu as x, CardContent as S, CardHeader as C, CardTitle as w, GlassCard as T, IllustratedEmptyState as E, Skeleton as D } from "@burdenoff/fe-libs/ui";
|
|
13
|
+
import { useNavigate as O, useParams as k } from "react-router-dom";
|
|
14
|
+
import { useI18n as A } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
15
15
|
//#region src/pages/WorkspaceDetailPage.tsx
|
|
16
|
-
function
|
|
17
|
-
let { workspaceId:
|
|
16
|
+
function j() {
|
|
17
|
+
let { workspaceId: j } = k();
|
|
18
18
|
i("workspaces.detail", {
|
|
19
|
-
workspaceId:
|
|
19
|
+
workspaceId: j,
|
|
20
20
|
requireParams: !0
|
|
21
21
|
});
|
|
22
|
-
let { t:
|
|
23
|
-
if (
|
|
22
|
+
let { t: M } = A(), N = O(), { basePath: P = "/" } = e(), { withCurrentSearch: F } = n(), { data: I, isLoading: L, error: R } = t(j || ""), { data: z } = s({ workspaceId: j }, { limit: 1 }, j), { data: B } = a(j || "", { limit: 1 }), { data: V } = c(j), { canViewWorkspace: H, canUpdateWorkspace: U, canInviteMember: W, isLoading: G } = o();
|
|
23
|
+
if (G) return /* @__PURE__ */ v("div", {
|
|
24
24
|
className: "space-y-4 sm:space-y-6 p-3 sm:p-6",
|
|
25
|
-
children: [/* @__PURE__ */ _(
|
|
25
|
+
children: [/* @__PURE__ */ _(D, { className: "h-10 w-1/3" }), /* @__PURE__ */ v("div", {
|
|
26
26
|
className: "grid grid-cols-1 md:grid-cols-3 gap-4",
|
|
27
27
|
children: [
|
|
28
|
-
/* @__PURE__ */ _(
|
|
29
|
-
/* @__PURE__ */ _(
|
|
30
|
-
/* @__PURE__ */ _(
|
|
28
|
+
/* @__PURE__ */ _(D, { className: "h-32" }),
|
|
29
|
+
/* @__PURE__ */ _(D, { className: "h-32" }),
|
|
30
|
+
/* @__PURE__ */ _(D, { className: "h-32" })
|
|
31
31
|
]
|
|
32
32
|
})]
|
|
33
33
|
});
|
|
34
|
-
if (!
|
|
34
|
+
if (!H) return /* @__PURE__ */ _("div", {
|
|
35
35
|
className: "flex items-center justify-center min-h-[400px]",
|
|
36
36
|
children: /* @__PURE__ */ v("div", {
|
|
37
37
|
className: "text-center space-y-2",
|
|
38
38
|
children: [/* @__PURE__ */ _("h2", {
|
|
39
39
|
className: "text-lg font-semibold",
|
|
40
|
-
children: r(
|
|
40
|
+
children: r(M, "workspaceDetail.accessDenied", "Access Denied")
|
|
41
41
|
}), /* @__PURE__ */ _("p", {
|
|
42
42
|
className: "text-sm text-text-secondary",
|
|
43
|
-
children: r(
|
|
43
|
+
children: r(M, "workspaceDetail.accessDeniedDesc", "You don't have permission to view this workspace.")
|
|
44
44
|
})]
|
|
45
45
|
})
|
|
46
46
|
});
|
|
47
|
-
if (
|
|
47
|
+
if (L) return /* @__PURE__ */ v("div", {
|
|
48
48
|
className: "space-y-4 sm:space-y-6 p-3 sm:p-6",
|
|
49
|
-
children: [/* @__PURE__ */ _(
|
|
49
|
+
children: [/* @__PURE__ */ _(D, { className: "h-10 w-1/3" }), /* @__PURE__ */ v("div", {
|
|
50
50
|
className: "grid grid-cols-1 md:grid-cols-3 gap-4",
|
|
51
51
|
children: [
|
|
52
|
-
/* @__PURE__ */ _(
|
|
53
|
-
/* @__PURE__ */ _(
|
|
54
|
-
/* @__PURE__ */ _(
|
|
52
|
+
/* @__PURE__ */ _(D, { className: "h-32" }),
|
|
53
|
+
/* @__PURE__ */ _(D, { className: "h-32" }),
|
|
54
|
+
/* @__PURE__ */ _(D, { className: "h-32" })
|
|
55
55
|
]
|
|
56
56
|
})]
|
|
57
57
|
});
|
|
58
|
-
if (
|
|
58
|
+
if (R || !I) return /* @__PURE__ */ v("div", {
|
|
59
59
|
className: "space-y-4 p-6",
|
|
60
60
|
children: [/* @__PURE__ */ v(b, {
|
|
61
61
|
variant: "ghost",
|
|
62
|
-
onClick: () =>
|
|
62
|
+
onClick: () => N(F(`${P}/list`)),
|
|
63
63
|
className: "gap-2",
|
|
64
|
-
children: [/* @__PURE__ */ _(u, { size: 20 }), r(
|
|
65
|
-
}), /* @__PURE__ */ _(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
children: r(j, "workspaceDetail.notFound", "Workspace not found")
|
|
70
|
-
})
|
|
64
|
+
children: [/* @__PURE__ */ _(u, { size: 20 }), r(M, "workspaceDetail.backToWorkspaces", "Back to Workspaces")]
|
|
65
|
+
}), /* @__PURE__ */ _(E, {
|
|
66
|
+
illustration: "empty-search",
|
|
67
|
+
title: r(M, "workspaceDetail.notFound", "Workspace not found"),
|
|
68
|
+
description: r(M, "workspaceDetail.notFoundDescription", "This workspace doesn't exist or you don't have permission to view it.")
|
|
71
69
|
})]
|
|
72
70
|
});
|
|
73
|
-
let
|
|
74
|
-
(e.key === "Enter" || e.key === " ") && (e.preventDefault(),
|
|
71
|
+
let K = (e, t) => {
|
|
72
|
+
(e.key === "Enter" || e.key === " ") && (e.preventDefault(), N(t));
|
|
75
73
|
};
|
|
76
74
|
return /* @__PURE__ */ v("div", {
|
|
77
75
|
className: "space-y-4 sm:space-y-6 p-3 sm:p-6",
|
|
@@ -80,35 +78,35 @@ function A() {
|
|
|
80
78
|
children: [/* @__PURE__ */ v("div", { children: [
|
|
81
79
|
/* @__PURE__ */ v(b, {
|
|
82
80
|
variant: "ghost",
|
|
83
|
-
onClick: () =>
|
|
81
|
+
onClick: () => N(F(`${P}/list`)),
|
|
84
82
|
className: "gap-2 mb-2 -ml-2",
|
|
85
|
-
children: [/* @__PURE__ */ _(u, { size: 20 }), r(
|
|
83
|
+
children: [/* @__PURE__ */ _(u, { size: 20 }), r(M, "workspaceDetail.backToWorkspaces", "Back to Workspaces")]
|
|
86
84
|
}),
|
|
87
85
|
/* @__PURE__ */ _("h1", {
|
|
88
86
|
className: "text-2xl sm:text-3xl font-bold",
|
|
89
|
-
children:
|
|
87
|
+
children: I.name
|
|
90
88
|
}),
|
|
91
89
|
/* @__PURE__ */ v("div", {
|
|
92
90
|
className: "flex items-center gap-2 mt-2",
|
|
93
91
|
children: [/* @__PURE__ */ _(y, {
|
|
94
92
|
variant: "outline",
|
|
95
|
-
children:
|
|
96
|
-
}), /* @__PURE__ */ _(y, { children:
|
|
93
|
+
children: I.type
|
|
94
|
+
}), /* @__PURE__ */ _(y, { children: I.status })]
|
|
97
95
|
})
|
|
98
96
|
] }), /* @__PURE__ */ _(x, {
|
|
99
|
-
primary:
|
|
100
|
-
label: r(
|
|
97
|
+
primary: U ? {
|
|
98
|
+
label: r(M, "workspaceDetail.settings", "Settings"),
|
|
101
99
|
icon: /* @__PURE__ */ _(p, { size: 18 }),
|
|
102
|
-
onSelect: () =>
|
|
103
|
-
} :
|
|
104
|
-
label: r(
|
|
100
|
+
onSelect: () => N(F(`${P}/${j}/settings`))
|
|
101
|
+
} : W ? {
|
|
102
|
+
label: r(M, "workspaceDetail.inviteMembers", "Invite Member"),
|
|
105
103
|
icon: /* @__PURE__ */ _(m, { size: 18 }),
|
|
106
|
-
onSelect: () =>
|
|
104
|
+
onSelect: () => N(F(`${P}/${j}/invite`))
|
|
107
105
|
} : void 0,
|
|
108
|
-
actions: [...
|
|
109
|
-
label: r(
|
|
106
|
+
actions: [...U && W ? [{
|
|
107
|
+
label: r(M, "workspaceDetail.inviteMembers", "Invite Member"),
|
|
110
108
|
icon: /* @__PURE__ */ _(m, { size: 16 }),
|
|
111
|
-
onSelect: () =>
|
|
109
|
+
onSelect: () => N(F(`${P}/${j}/invite`))
|
|
112
110
|
}] : []]
|
|
113
111
|
})]
|
|
114
112
|
}), /* @__PURE__ */ v("div", {
|
|
@@ -121,10 +119,10 @@ function A() {
|
|
|
121
119
|
/* @__PURE__ */ v(T, {
|
|
122
120
|
treatment: "metric",
|
|
123
121
|
className: "cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg/50",
|
|
124
|
-
onClick: () =>
|
|
122
|
+
onClick: () => N(F(`${P}/${j}/projects`)),
|
|
125
123
|
role: "button",
|
|
126
124
|
tabIndex: 0,
|
|
127
|
-
onKeyDown: (e) =>
|
|
125
|
+
onKeyDown: (e) => K(e, F(`${P}/${j}/projects`)),
|
|
128
126
|
children: [/* @__PURE__ */ v(C, {
|
|
129
127
|
className: "flex flex-row items-center justify-between pb-2",
|
|
130
128
|
children: [/* @__PURE__ */ v(w, {
|
|
@@ -132,26 +130,26 @@ function A() {
|
|
|
132
130
|
children: [/* @__PURE__ */ _(f, {
|
|
133
131
|
size: 18,
|
|
134
132
|
className: "text-text-muted"
|
|
135
|
-
}), r(
|
|
133
|
+
}), r(M, "workspaceDetail.projects", "Projects")]
|
|
136
134
|
}), /* @__PURE__ */ _(d, {
|
|
137
135
|
size: 18,
|
|
138
136
|
className: "text-text-muted"
|
|
139
137
|
})]
|
|
140
138
|
}), /* @__PURE__ */ v(S, { children: [/* @__PURE__ */ _("p", {
|
|
141
139
|
className: "text-3xl font-bold tabular-nums",
|
|
142
|
-
children:
|
|
140
|
+
children: z?.total ?? I.projectCount ?? 0
|
|
143
141
|
}), /* @__PURE__ */ _("p", {
|
|
144
142
|
className: "text-sm text-text-muted",
|
|
145
|
-
children: r(
|
|
143
|
+
children: r(M, "workspaceDetail.projectsDesc", "Projects in this workspace")
|
|
146
144
|
})] })]
|
|
147
145
|
}),
|
|
148
146
|
/* @__PURE__ */ v(T, {
|
|
149
147
|
treatment: "metric",
|
|
150
148
|
className: "cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg/50",
|
|
151
|
-
onClick: () =>
|
|
149
|
+
onClick: () => N(F(`${P}/${j}/members`)),
|
|
152
150
|
role: "button",
|
|
153
151
|
tabIndex: 0,
|
|
154
|
-
onKeyDown: (e) =>
|
|
152
|
+
onKeyDown: (e) => K(e, F(`${P}/${j}/members`)),
|
|
155
153
|
children: [/* @__PURE__ */ v(C, {
|
|
156
154
|
className: "flex flex-row items-center justify-between pb-2",
|
|
157
155
|
children: [/* @__PURE__ */ v(w, {
|
|
@@ -159,26 +157,26 @@ function A() {
|
|
|
159
157
|
children: [/* @__PURE__ */ _(h, {
|
|
160
158
|
size: 18,
|
|
161
159
|
className: "text-text-muted"
|
|
162
|
-
}), r(
|
|
160
|
+
}), r(M, "workspaceDetail.members", "Members")]
|
|
163
161
|
}), /* @__PURE__ */ _(d, {
|
|
164
162
|
size: 18,
|
|
165
163
|
className: "text-text-muted"
|
|
166
164
|
})]
|
|
167
165
|
}), /* @__PURE__ */ v(S, { children: [/* @__PURE__ */ _("p", {
|
|
168
166
|
className: "text-3xl font-bold tabular-nums",
|
|
169
|
-
children:
|
|
167
|
+
children: B?.total ?? I.memberCount ?? 0
|
|
170
168
|
}), /* @__PURE__ */ _("p", {
|
|
171
169
|
className: "text-sm text-text-muted",
|
|
172
|
-
children: r(
|
|
170
|
+
children: r(M, "workspaceDetail.membersDesc", "Workspace members")
|
|
173
171
|
})] })]
|
|
174
172
|
}),
|
|
175
173
|
/* @__PURE__ */ v(T, {
|
|
176
174
|
treatment: "metric",
|
|
177
175
|
className: "cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg/50",
|
|
178
|
-
onClick: () =>
|
|
176
|
+
onClick: () => N(F(`${P}/${j}/activity`)),
|
|
179
177
|
role: "button",
|
|
180
178
|
tabIndex: 0,
|
|
181
|
-
onKeyDown: (e) =>
|
|
179
|
+
onKeyDown: (e) => K(e, F(`${P}/${j}/activity`)),
|
|
182
180
|
children: [/* @__PURE__ */ v(C, {
|
|
183
181
|
className: "flex flex-row items-center justify-between pb-2",
|
|
184
182
|
children: [/* @__PURE__ */ v(w, {
|
|
@@ -186,17 +184,17 @@ function A() {
|
|
|
186
184
|
children: [/* @__PURE__ */ _(l, {
|
|
187
185
|
size: 18,
|
|
188
186
|
className: "text-text-muted"
|
|
189
|
-
}), r(
|
|
187
|
+
}), r(M, "workspaceDetail.activity", "Activity")]
|
|
190
188
|
}), /* @__PURE__ */ _(d, {
|
|
191
189
|
size: 18,
|
|
192
190
|
className: "text-text-muted"
|
|
193
191
|
})]
|
|
194
192
|
}), /* @__PURE__ */ v(S, { children: [/* @__PURE__ */ _("p", {
|
|
195
193
|
className: "text-3xl font-bold tabular-nums",
|
|
196
|
-
children:
|
|
194
|
+
children: V?.length ?? 0
|
|
197
195
|
}), /* @__PURE__ */ _("p", {
|
|
198
196
|
className: "text-sm text-text-muted",
|
|
199
|
-
children: r(
|
|
197
|
+
children: r(M, "workspaceDetail.activityDesc", "Recent activity items")
|
|
200
198
|
})] })]
|
|
201
199
|
})
|
|
202
200
|
]
|
|
@@ -206,45 +204,45 @@ function A() {
|
|
|
206
204
|
children: /* @__PURE__ */ v(T, {
|
|
207
205
|
treatment: "plain",
|
|
208
206
|
className: "border-border-seam",
|
|
209
|
-
children: [/* @__PURE__ */ _(C, { children: /* @__PURE__ */ _(w, { children: r(
|
|
207
|
+
children: [/* @__PURE__ */ _(C, { children: /* @__PURE__ */ _(w, { children: r(M, "workspaceDetail.workspaceInformation", "Workspace Information") }) }), /* @__PURE__ */ _(S, {
|
|
210
208
|
className: "space-y-2",
|
|
211
209
|
children: /* @__PURE__ */ v("div", {
|
|
212
210
|
className: "grid grid-cols-2 gap-2 text-sm",
|
|
213
211
|
children: [
|
|
214
212
|
/* @__PURE__ */ _("span", {
|
|
215
213
|
className: "text-text-muted",
|
|
216
|
-
children: r(
|
|
214
|
+
children: r(M, "workspaceDetail.id", "ID:")
|
|
217
215
|
}),
|
|
218
216
|
/* @__PURE__ */ _("span", {
|
|
219
217
|
className: "font-mono text-xs break-all",
|
|
220
|
-
children:
|
|
218
|
+
children: I.id
|
|
221
219
|
}),
|
|
222
|
-
|
|
220
|
+
I.domain && /* @__PURE__ */ v(g, { children: [/* @__PURE__ */ _("span", {
|
|
223
221
|
className: "text-text-muted",
|
|
224
|
-
children: r(
|
|
225
|
-
}), /* @__PURE__ */ _("span", { children:
|
|
226
|
-
|
|
222
|
+
children: r(M, "workspaceDetail.domain", "Domain:")
|
|
223
|
+
}), /* @__PURE__ */ _("span", { children: I.domain })] }),
|
|
224
|
+
I.contactEmail && /* @__PURE__ */ v(g, { children: [/* @__PURE__ */ _("span", {
|
|
227
225
|
className: "text-text-muted",
|
|
228
|
-
children: r(
|
|
226
|
+
children: r(M, "workspaceDetail.contactEmail", "Contact Email:")
|
|
229
227
|
}), /* @__PURE__ */ _("span", {
|
|
230
228
|
className: "break-all",
|
|
231
|
-
children:
|
|
229
|
+
children: I.contactEmail
|
|
232
230
|
})] }),
|
|
233
|
-
|
|
231
|
+
I.website && /* @__PURE__ */ v(g, { children: [/* @__PURE__ */ _("span", {
|
|
234
232
|
className: "text-text-muted",
|
|
235
|
-
children: r(
|
|
233
|
+
children: r(M, "workspaceDetail.website", "Website:")
|
|
236
234
|
}), /* @__PURE__ */ _("a", {
|
|
237
|
-
href:
|
|
235
|
+
href: I.website,
|
|
238
236
|
target: "_blank",
|
|
239
237
|
rel: "noopener noreferrer",
|
|
240
238
|
className: "text-primary hover:underline transition-colors duration-200 break-all",
|
|
241
|
-
children:
|
|
239
|
+
children: I.website
|
|
242
240
|
})] }),
|
|
243
241
|
/* @__PURE__ */ _("span", {
|
|
244
242
|
className: "text-text-muted",
|
|
245
|
-
children: r(
|
|
243
|
+
children: r(M, "workspaceDetail.created", "Created:")
|
|
246
244
|
}),
|
|
247
|
-
/* @__PURE__ */ _("span", { children: new Date(
|
|
245
|
+
/* @__PURE__ */ _("span", { children: new Date(I.createdAt).toLocaleDateString() })
|
|
248
246
|
]
|
|
249
247
|
})
|
|
250
248
|
})]
|
|
@@ -254,6 +252,6 @@ function A() {
|
|
|
254
252
|
});
|
|
255
253
|
}
|
|
256
254
|
//#endregion
|
|
257
|
-
export {
|
|
255
|
+
export { j as WorkspaceDetailPage };
|
|
258
256
|
|
|
259
257
|
//# sourceMappingURL=WorkspaceDetailPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkspaceDetailPage.js","names":[],"sources":["../../src/pages/WorkspaceDetailPage.tsx"],"sourcesContent":["import { useParams, useNavigate } from 'react-router-dom';\nimport {\n Settings,\n ArrowLeft,\n FolderKanban,\n Users,\n Activity,\n ChevronRight,\n UserPlus,\n} from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\nimport {\n Button,\n GlassCard,\n CardHeader,\n CardTitle,\n CardContent,\n Badge,\n Skeleton,\n CTAOverflowMenu,\n} from '@burdenoff/fe-libs/ui';\nimport { useWorkspace } from '../hooks/useWorkspaces';\nimport { useProjects } from '../hooks/useProjects';\nimport { useWorkspaceMembers } from '../hooks/useWorkspaceMembers';\nimport { useWorkspaceActivity } from '../hooks/useWorkspaceActivity';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { useScopedNavigation } from '../hooks/useScopedNavigation';\nimport { useWorkspacePermissions } from '../hooks/useWorkspacePermissions';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\n\nexport function WorkspaceDetailPage() {\n const { workspaceId } = useParams<{ workspaceId: string }>();\n useWorkspacesPageView('workspaces.detail', { workspaceId, requireParams: true });\n const { t } = useI18n();\n const navigate = useNavigate();\n const { basePath = '/' } = useWorkspacesContext();\n const { withCurrentSearch } = useScopedNavigation();\n const { data: workspace, isLoading, error } = useWorkspace(workspaceId || '');\n const { data: projects } = useProjects({ workspaceId }, { limit: 1 }, workspaceId);\n const { data: members } = useWorkspaceMembers(workspaceId || '', { limit: 1 });\n const { data: activities } = useWorkspaceActivity(workspaceId);\n const {\n canViewWorkspace,\n canUpdateWorkspace,\n canInviteMember,\n isLoading: permissionsLoading,\n } = useWorkspacePermissions();\n\n if (permissionsLoading) {\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n <Skeleton className=\"h-10 w-1/3\" />\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-4\">\n <Skeleton className=\"h-32\" />\n <Skeleton className=\"h-32\" />\n <Skeleton className=\"h-32\" />\n </div>\n </div>\n );\n }\n\n if (!canViewWorkspace) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <h2 className=\"text-lg font-semibold\">\n {tWithFallback(t, 'workspaceDetail.accessDenied', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n 'workspaceDetail.accessDeniedDesc',\n \"You don't have permission to view this workspace.\"\n )}\n </p>\n </div>\n </div>\n );\n }\n\n if (isLoading) {\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n <Skeleton className=\"h-10 w-1/3\" />\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-4\">\n <Skeleton className=\"h-32\" />\n <Skeleton className=\"h-32\" />\n <Skeleton className=\"h-32\" />\n </div>\n </div>\n );\n }\n\n if (error || !workspace) {\n return (\n <div className=\"space-y-4 p-6\">\n <Button\n variant=\"ghost\"\n onClick={() => navigate(withCurrentSearch(`${basePath}/list`))}\n className=\"gap-2\"\n >\n <ArrowLeft size={20} />\n {tWithFallback(t, 'workspaceDetail.backToWorkspaces', 'Back to Workspaces')}\n </Button>\n <div className=\"text-center py-12\">\n <p className=\"text-status-error-text\">\n {tWithFallback(t, 'workspaceDetail.notFound', 'Workspace not found')}\n </p>\n </div>\n </div>\n );\n }\n\n const handleCardKeyDown = (e: React.KeyboardEvent, path: string) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n navigate(path);\n }\n };\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n {/* Header */}\n <div className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3\">\n <div>\n <Button\n variant=\"ghost\"\n onClick={() => navigate(withCurrentSearch(`${basePath}/list`))}\n className=\"gap-2 mb-2 -ml-2\"\n >\n <ArrowLeft size={20} />\n {tWithFallback(t, 'workspaceDetail.backToWorkspaces', 'Back to Workspaces')}\n </Button>\n <h1 className=\"text-2xl sm:text-3xl font-bold\">{workspace.name}</h1>\n <div className=\"flex items-center gap-2 mt-2\">\n <Badge variant=\"outline\">{workspace.type}</Badge>\n <Badge>{workspace.status}</Badge>\n </div>\n </div>\n <CTAOverflowMenu\n primary={\n canUpdateWorkspace\n ? {\n label: tWithFallback(t, 'workspaceDetail.settings', 'Settings'),\n icon: <Settings size={18} />,\n onSelect: () =>\n navigate(withCurrentSearch(`${basePath}/${workspaceId}/settings`)),\n }\n : canInviteMember\n ? {\n label: tWithFallback(t, 'workspaceDetail.inviteMembers', 'Invite Member'),\n icon: <UserPlus size={18} />,\n onSelect: () =>\n navigate(withCurrentSearch(`${basePath}/${workspaceId}/invite`)),\n }\n : undefined\n }\n actions={[\n ...(canUpdateWorkspace && canInviteMember\n ? [\n {\n label: tWithFallback(t, 'workspaceDetail.inviteMembers', 'Invite Member'),\n icon: <UserPlus size={16} />,\n onSelect: () =>\n navigate(withCurrentSearch(`${basePath}/${workspaceId}/invite`)),\n },\n ]\n : []),\n ]}\n />\n </div>\n\n {/* Main + detail rail */}\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-4 sm:gap-6\">\n {/* Main: stat metrics */}\n <div className=\"lg:col-span-2 space-y-4 sm:space-y-6\">\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-4\">\n <GlassCard\n treatment=\"metric\"\n className=\"cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg/50\"\n onClick={() => navigate(withCurrentSearch(`${basePath}/${workspaceId}/projects`))}\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) =>\n handleCardKeyDown(e, withCurrentSearch(`${basePath}/${workspaceId}/projects`))\n }\n >\n <CardHeader className=\"flex flex-row items-center justify-between pb-2\">\n <CardTitle className=\"flex items-center gap-2 text-text-secondary text-sm font-medium\">\n <FolderKanban size={18} className=\"text-text-muted\" />\n {tWithFallback(t, 'workspaceDetail.projects', 'Projects')}\n </CardTitle>\n <ChevronRight size={18} className=\"text-text-muted\" />\n </CardHeader>\n <CardContent>\n <p className=\"text-3xl font-bold tabular-nums\">\n {projects?.total ?? workspace.projectCount ?? 0}\n </p>\n <p className=\"text-sm text-text-muted\">\n {tWithFallback(t, 'workspaceDetail.projectsDesc', 'Projects in this workspace')}\n </p>\n </CardContent>\n </GlassCard>\n\n <GlassCard\n treatment=\"metric\"\n className=\"cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg/50\"\n onClick={() => navigate(withCurrentSearch(`${basePath}/${workspaceId}/members`))}\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) =>\n handleCardKeyDown(e, withCurrentSearch(`${basePath}/${workspaceId}/members`))\n }\n >\n <CardHeader className=\"flex flex-row items-center justify-between pb-2\">\n <CardTitle className=\"flex items-center gap-2 text-text-secondary text-sm font-medium\">\n <Users size={18} className=\"text-text-muted\" />\n {tWithFallback(t, 'workspaceDetail.members', 'Members')}\n </CardTitle>\n <ChevronRight size={18} className=\"text-text-muted\" />\n </CardHeader>\n <CardContent>\n <p className=\"text-3xl font-bold tabular-nums\">\n {members?.total ?? workspace.memberCount ?? 0}\n </p>\n <p className=\"text-sm text-text-muted\">\n {tWithFallback(t, 'workspaceDetail.membersDesc', 'Workspace members')}\n </p>\n </CardContent>\n </GlassCard>\n\n <GlassCard\n treatment=\"metric\"\n className=\"cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg/50\"\n onClick={() => navigate(withCurrentSearch(`${basePath}/${workspaceId}/activity`))}\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) =>\n handleCardKeyDown(e, withCurrentSearch(`${basePath}/${workspaceId}/activity`))\n }\n >\n <CardHeader className=\"flex flex-row items-center justify-between pb-2\">\n <CardTitle className=\"flex items-center gap-2 text-text-secondary text-sm font-medium\">\n <Activity size={18} className=\"text-text-muted\" />\n {tWithFallback(t, 'workspaceDetail.activity', 'Activity')}\n </CardTitle>\n <ChevronRight size={18} className=\"text-text-muted\" />\n </CardHeader>\n <CardContent>\n <p className=\"text-3xl font-bold tabular-nums\">{activities?.length ?? 0}</p>\n <p className=\"text-sm text-text-muted\">\n {tWithFallback(t, 'workspaceDetail.activityDesc', 'Recent activity items')}\n </p>\n </CardContent>\n </GlassCard>\n </div>\n </div>\n\n {/* Detail rail: workspace info */}\n <aside className=\"lg:sticky lg:top-6 lg:self-start\">\n <GlassCard treatment=\"plain\" className=\"border-border-seam\">\n <CardHeader>\n <CardTitle>\n {tWithFallback(t, 'workspaceDetail.workspaceInformation', 'Workspace Information')}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n <div className=\"grid grid-cols-2 gap-2 text-sm\">\n <span className=\"text-text-muted\">\n {tWithFallback(t, 'workspaceDetail.id', 'ID:')}\n </span>\n <span className=\"font-mono text-xs break-all\">{workspace.id}</span>\n\n {workspace.domain && (\n <>\n <span className=\"text-text-muted\">\n {tWithFallback(t, 'workspaceDetail.domain', 'Domain:')}\n </span>\n <span>{workspace.domain}</span>\n </>\n )}\n\n {workspace.contactEmail && (\n <>\n <span className=\"text-text-muted\">\n {tWithFallback(t, 'workspaceDetail.contactEmail', 'Contact Email:')}\n </span>\n <span className=\"break-all\">{workspace.contactEmail}</span>\n </>\n )}\n\n {workspace.website && (\n <>\n <span className=\"text-text-muted\">\n {tWithFallback(t, 'workspaceDetail.website', 'Website:')}\n </span>\n <a\n href={workspace.website}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-primary hover:underline transition-colors duration-200 break-all\"\n >\n {workspace.website}\n </a>\n </>\n )}\n\n <span className=\"text-text-muted\">\n {tWithFallback(t, 'workspaceDetail.created', 'Created:')}\n </span>\n <span>{new Date(workspace.createdAt).toLocaleDateString()}</span>\n </div>\n </CardContent>\n </GlassCard>\n </aside>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WorkspaceDetailPage.js","names":[],"sources":["../../src/pages/WorkspaceDetailPage.tsx"],"sourcesContent":["import { useParams, useNavigate } from 'react-router-dom';\nimport {\n Settings,\n ArrowLeft,\n FolderKanban,\n Users,\n Activity,\n ChevronRight,\n UserPlus,\n} from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\nimport {\n Button,\n GlassCard,\n CardHeader,\n CardTitle,\n CardContent,\n Badge,\n Skeleton,\n CTAOverflowMenu,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport { useWorkspace } from '../hooks/useWorkspaces';\nimport { useProjects } from '../hooks/useProjects';\nimport { useWorkspaceMembers } from '../hooks/useWorkspaceMembers';\nimport { useWorkspaceActivity } from '../hooks/useWorkspaceActivity';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { useScopedNavigation } from '../hooks/useScopedNavigation';\nimport { useWorkspacePermissions } from '../hooks/useWorkspacePermissions';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\n\nexport function WorkspaceDetailPage() {\n const { workspaceId } = useParams<{ workspaceId: string }>();\n useWorkspacesPageView('workspaces.detail', { workspaceId, requireParams: true });\n const { t } = useI18n();\n const navigate = useNavigate();\n const { basePath = '/' } = useWorkspacesContext();\n const { withCurrentSearch } = useScopedNavigation();\n const { data: workspace, isLoading, error } = useWorkspace(workspaceId || '');\n const { data: projects } = useProjects({ workspaceId }, { limit: 1 }, workspaceId);\n const { data: members } = useWorkspaceMembers(workspaceId || '', { limit: 1 });\n const { data: activities } = useWorkspaceActivity(workspaceId);\n const {\n canViewWorkspace,\n canUpdateWorkspace,\n canInviteMember,\n isLoading: permissionsLoading,\n } = useWorkspacePermissions();\n\n if (permissionsLoading) {\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n <Skeleton className=\"h-10 w-1/3\" />\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-4\">\n <Skeleton className=\"h-32\" />\n <Skeleton className=\"h-32\" />\n <Skeleton className=\"h-32\" />\n </div>\n </div>\n );\n }\n\n if (!canViewWorkspace) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <h2 className=\"text-lg font-semibold\">\n {tWithFallback(t, 'workspaceDetail.accessDenied', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n 'workspaceDetail.accessDeniedDesc',\n \"You don't have permission to view this workspace.\"\n )}\n </p>\n </div>\n </div>\n );\n }\n\n if (isLoading) {\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n <Skeleton className=\"h-10 w-1/3\" />\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-4\">\n <Skeleton className=\"h-32\" />\n <Skeleton className=\"h-32\" />\n <Skeleton className=\"h-32\" />\n </div>\n </div>\n );\n }\n\n if (error || !workspace) {\n return (\n <div className=\"space-y-4 p-6\">\n <Button\n variant=\"ghost\"\n onClick={() => navigate(withCurrentSearch(`${basePath}/list`))}\n className=\"gap-2\"\n >\n <ArrowLeft size={20} />\n {tWithFallback(t, 'workspaceDetail.backToWorkspaces', 'Back to Workspaces')}\n </Button>\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title={tWithFallback(t, 'workspaceDetail.notFound', 'Workspace not found')}\n description={tWithFallback(\n t,\n 'workspaceDetail.notFoundDescription',\n \"This workspace doesn't exist or you don't have permission to view it.\"\n )}\n />\n </div>\n );\n }\n\n const handleCardKeyDown = (e: React.KeyboardEvent, path: string) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n navigate(path);\n }\n };\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n {/* Header */}\n <div className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3\">\n <div>\n <Button\n variant=\"ghost\"\n onClick={() => navigate(withCurrentSearch(`${basePath}/list`))}\n className=\"gap-2 mb-2 -ml-2\"\n >\n <ArrowLeft size={20} />\n {tWithFallback(t, 'workspaceDetail.backToWorkspaces', 'Back to Workspaces')}\n </Button>\n <h1 className=\"text-2xl sm:text-3xl font-bold\">{workspace.name}</h1>\n <div className=\"flex items-center gap-2 mt-2\">\n <Badge variant=\"outline\">{workspace.type}</Badge>\n <Badge>{workspace.status}</Badge>\n </div>\n </div>\n <CTAOverflowMenu\n primary={\n canUpdateWorkspace\n ? {\n label: tWithFallback(t, 'workspaceDetail.settings', 'Settings'),\n icon: <Settings size={18} />,\n onSelect: () =>\n navigate(withCurrentSearch(`${basePath}/${workspaceId}/settings`)),\n }\n : canInviteMember\n ? {\n label: tWithFallback(t, 'workspaceDetail.inviteMembers', 'Invite Member'),\n icon: <UserPlus size={18} />,\n onSelect: () =>\n navigate(withCurrentSearch(`${basePath}/${workspaceId}/invite`)),\n }\n : undefined\n }\n actions={[\n ...(canUpdateWorkspace && canInviteMember\n ? [\n {\n label: tWithFallback(t, 'workspaceDetail.inviteMembers', 'Invite Member'),\n icon: <UserPlus size={16} />,\n onSelect: () =>\n navigate(withCurrentSearch(`${basePath}/${workspaceId}/invite`)),\n },\n ]\n : []),\n ]}\n />\n </div>\n\n {/* Main + detail rail */}\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-4 sm:gap-6\">\n {/* Main: stat metrics */}\n <div className=\"lg:col-span-2 space-y-4 sm:space-y-6\">\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-4\">\n <GlassCard\n treatment=\"metric\"\n className=\"cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg/50\"\n onClick={() => navigate(withCurrentSearch(`${basePath}/${workspaceId}/projects`))}\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) =>\n handleCardKeyDown(e, withCurrentSearch(`${basePath}/${workspaceId}/projects`))\n }\n >\n <CardHeader className=\"flex flex-row items-center justify-between pb-2\">\n <CardTitle className=\"flex items-center gap-2 text-text-secondary text-sm font-medium\">\n <FolderKanban size={18} className=\"text-text-muted\" />\n {tWithFallback(t, 'workspaceDetail.projects', 'Projects')}\n </CardTitle>\n <ChevronRight size={18} className=\"text-text-muted\" />\n </CardHeader>\n <CardContent>\n <p className=\"text-3xl font-bold tabular-nums\">\n {projects?.total ?? workspace.projectCount ?? 0}\n </p>\n <p className=\"text-sm text-text-muted\">\n {tWithFallback(t, 'workspaceDetail.projectsDesc', 'Projects in this workspace')}\n </p>\n </CardContent>\n </GlassCard>\n\n <GlassCard\n treatment=\"metric\"\n className=\"cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg/50\"\n onClick={() => navigate(withCurrentSearch(`${basePath}/${workspaceId}/members`))}\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) =>\n handleCardKeyDown(e, withCurrentSearch(`${basePath}/${workspaceId}/members`))\n }\n >\n <CardHeader className=\"flex flex-row items-center justify-between pb-2\">\n <CardTitle className=\"flex items-center gap-2 text-text-secondary text-sm font-medium\">\n <Users size={18} className=\"text-text-muted\" />\n {tWithFallback(t, 'workspaceDetail.members', 'Members')}\n </CardTitle>\n <ChevronRight size={18} className=\"text-text-muted\" />\n </CardHeader>\n <CardContent>\n <p className=\"text-3xl font-bold tabular-nums\">\n {members?.total ?? workspace.memberCount ?? 0}\n </p>\n <p className=\"text-sm text-text-muted\">\n {tWithFallback(t, 'workspaceDetail.membersDesc', 'Workspace members')}\n </p>\n </CardContent>\n </GlassCard>\n\n <GlassCard\n treatment=\"metric\"\n className=\"cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg/50\"\n onClick={() => navigate(withCurrentSearch(`${basePath}/${workspaceId}/activity`))}\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) =>\n handleCardKeyDown(e, withCurrentSearch(`${basePath}/${workspaceId}/activity`))\n }\n >\n <CardHeader className=\"flex flex-row items-center justify-between pb-2\">\n <CardTitle className=\"flex items-center gap-2 text-text-secondary text-sm font-medium\">\n <Activity size={18} className=\"text-text-muted\" />\n {tWithFallback(t, 'workspaceDetail.activity', 'Activity')}\n </CardTitle>\n <ChevronRight size={18} className=\"text-text-muted\" />\n </CardHeader>\n <CardContent>\n <p className=\"text-3xl font-bold tabular-nums\">{activities?.length ?? 0}</p>\n <p className=\"text-sm text-text-muted\">\n {tWithFallback(t, 'workspaceDetail.activityDesc', 'Recent activity items')}\n </p>\n </CardContent>\n </GlassCard>\n </div>\n </div>\n\n {/* Detail rail: workspace info */}\n <aside className=\"lg:sticky lg:top-6 lg:self-start\">\n <GlassCard treatment=\"plain\" className=\"border-border-seam\">\n <CardHeader>\n <CardTitle>\n {tWithFallback(t, 'workspaceDetail.workspaceInformation', 'Workspace Information')}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n <div className=\"grid grid-cols-2 gap-2 text-sm\">\n <span className=\"text-text-muted\">\n {tWithFallback(t, 'workspaceDetail.id', 'ID:')}\n </span>\n <span className=\"font-mono text-xs break-all\">{workspace.id}</span>\n\n {workspace.domain && (\n <>\n <span className=\"text-text-muted\">\n {tWithFallback(t, 'workspaceDetail.domain', 'Domain:')}\n </span>\n <span>{workspace.domain}</span>\n </>\n )}\n\n {workspace.contactEmail && (\n <>\n <span className=\"text-text-muted\">\n {tWithFallback(t, 'workspaceDetail.contactEmail', 'Contact Email:')}\n </span>\n <span className=\"break-all\">{workspace.contactEmail}</span>\n </>\n )}\n\n {workspace.website && (\n <>\n <span className=\"text-text-muted\">\n {tWithFallback(t, 'workspaceDetail.website', 'Website:')}\n </span>\n <a\n href={workspace.website}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-primary hover:underline transition-colors duration-200 break-all\"\n >\n {workspace.website}\n </a>\n </>\n )}\n\n <span className=\"text-text-muted\">\n {tWithFallback(t, 'workspaceDetail.created', 'Created:')}\n </span>\n <span>{new Date(workspace.createdAt).toLocaleDateString()}</span>\n </div>\n </CardContent>\n </GlassCard>\n </aside>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|