@burdenoff/microfe-workspaces 2026.625.1 → 2026.625.3
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/components/MemberListItem.js +7 -6
- package/dist/components/MemberListItem.js.map +1 -1
- package/dist/components/ProjectCard.js +13 -12
- package/dist/components/ProjectCard.js.map +1 -1
- package/dist/components/WorkspaceCard.js +8 -7
- package/dist/components/WorkspaceCard.js.map +1 -1
- package/dist/pages/ActivityListPage.js +67 -55
- package/dist/pages/ActivityListPage.js.map +1 -1
- package/dist/pages/CreateWorkspacePage.js +5 -4
- package/dist/pages/CreateWorkspacePage.js.map +1 -1
- package/dist/pages/InviteAcceptancePage.js +45 -36
- package/dist/pages/InviteAcceptancePage.js.map +1 -1
- package/dist/pages/MembersListPage.js +2 -2
- package/dist/pages/MembersListPage.js.map +1 -1
- package/dist/pages/PendingInvitesPage.js +68 -67
- package/dist/pages/PendingInvitesPage.js.map +1 -1
- package/dist/pages/ProjectDetailPage.js +333 -302
- package/dist/pages/ProjectDetailPage.js.map +1 -1
- package/dist/pages/ProjectMembersPage.js +4 -4
- package/dist/pages/ProjectMembersPage.js.map +1 -1
- package/dist/pages/ProjectsListPage.js +234 -226
- package/dist/pages/ProjectsListPage.js.map +1 -1
- package/dist/pages/WorkspaceDetailPage.js +196 -178
- package/dist/pages/WorkspaceDetailPage.js.map +1 -1
- package/dist/pages/WorkspaceInvitePage.js +372 -353
- package/dist/pages/WorkspaceInvitePage.js.map +1 -1
- package/dist/pages/WorkspaceSettingsPage.js +98 -90
- package/dist/pages/WorkspaceSettingsPage.js.map +1 -1
- package/dist/pages/WorkspacesListPage.js +132 -121
- package/dist/pages/WorkspacesListPage.js.map +1 -1
- package/dist/pages/dashboard/WidgetWrapper.js +7 -6
- package/dist/pages/dashboard/WidgetWrapper.js.map +1 -1
- package/dist/pages/dashboard/widgets/MembersWidget.js +1 -1
- package/dist/pages/dashboard/widgets/MembersWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/WelcomeWidget.js +45 -37
- package/dist/pages/dashboard/widgets/WelcomeWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/WorkflowStatsWidget.js +2 -2
- package/dist/pages/dashboard/widgets/WorkflowStatsWidget.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkspaceSettingsPage.js","names":[],"sources":["../../src/pages/WorkspaceSettingsPage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { useParams, useNavigate } from 'react-router-dom';\nimport { ArrowLeft, Save } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { type TagItem, TagsWidget } from '@burdenoff/fe-libs/tag';\nimport { tWithFallback } from '../utils/i18n';\nimport {\n Button,\n Card,\n CardHeader,\n CardTitle,\n CardContent,\n OptionalFields,\n} from '@burdenoff/fe-libs/ui';\nimport {\n useGetWorkspaceTagsQuery,\n useReplaceWorkspaceTagsMutation,\n type WorkspaceTagAssignmentFieldsFragment,\n} from '../generated/wspace-operations';\nimport {\n Form,\n FormField,\n FormItem,\n FormLabel,\n FormControl,\n FormMessage,\n Input,\n z,\n} from '@burdenoff/fe-libs/form';\nimport { PermissionGate, AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { WORKSPACE_PERMISSIONS } from '../constants/permissions';\nimport { useWorkspace } from '../hooks/useWorkspaces';\nimport { useUpdateWorkspace } from '../hooks/useWorkspaceMutations';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\n\nconst updateWorkspaceSchema = z.object({\n name: z.string().min(2).max(100),\n contactEmail: z.string().email().optional().or(z.literal('')),\n domain: z.string().optional(),\n website: z.string().url().optional().or(z.literal('')),\n});\n\ntype UpdateWorkspaceFormData = z.infer<typeof updateWorkspaceSchema>;\n\nexport function WorkspaceSettingsPage() {\n const { t } = useI18n();\n const { workspaceId } = useParams<{ workspaceId: string }>();\n useWorkspacesPageView('workspaces.settings', { workspaceId, requireParams: true });\n const navigate = useNavigate();\n const { basePath = '/' } = useWorkspacesContext();\n const { data: workspace, isLoading } = useWorkspace(workspaceId || '');\n const { mutate: updateWorkspace, isPending } = useUpdateWorkspace();\n\n // Workspace tags (universal-tags assignments) — backend exposed via\n // wspace-workspace-svc workspace-tags.gql; this UI is the canonical\n // editor for them. Identical pattern to ProjectDetailPage.\n const { data: tagsData, refetch: refetchTags } = useGetWorkspaceTagsQuery({\n variables: { input: { workspaceId: workspaceId ?? '' } },\n skip: !workspaceId,\n fetchPolicy: 'cache-and-network',\n });\n const [replaceWorkspaceTags, { loading: tagsUpdating }] = useReplaceWorkspaceTagsMutation();\n // The widget keys tags by `id` for add/remove. We store the\n // assignment's tagId there so the round-trip back to\n // replaceWorkspaceTags has the right value to send. Display fields\n // come from the embedded `tag: UniversalTag` resolved by the\n // assignment — assignments without a hydrated tag (deleted or\n // unresolved) are dropped so the widget never renders empty badges.\n const widgetTags = useMemo<TagItem[]>(() => {\n const assignments = tagsData?.getWorkspaceTags ?? [];\n return assignments.flatMap((a: WorkspaceTagAssignmentFieldsFragment) => {\n if (!a.tag) return [];\n return [\n {\n id: a.tagId,\n key: a.tag.key,\n label: a.tag.label,\n value: a.tag.value ?? null,\n vocabularyId: a.tag.vocabularyId ?? null,\n parentId: a.tag.parentId ?? null,\n hierarchyPath: a.tag.hierarchyPath ?? null,\n color: a.tag.color ?? null,\n icon: a.tag.icon ?? null,\n description: a.tag.description ?? null,\n } satisfies TagItem,\n ];\n });\n }, [tagsData?.getWorkspaceTags]);\n\n const persistTags = async (next: TagItem[]) => {\n if (!workspaceId) return;\n await replaceWorkspaceTags({\n variables: {\n input: {\n workspaceId,\n tagIds: next.map((t) => t.id),\n },\n },\n });\n await refetchTags();\n };\n\n const handleAddTag = (tag: TagItem) => persistTags([...widgetTags, tag]);\n const handleRemoveTag = (tagId: string) => persistTags(widgetTags.filter((t) => t.id !== tagId));\n\n const handleSubmit = (data: UpdateWorkspaceFormData) => {\n if (!workspaceId) return;\n\n updateWorkspace(\n {\n id: workspaceId,\n input: {\n ...data,\n contactEmail: data.contactEmail || undefined,\n domain: data.domain || undefined,\n website: data.website || undefined,\n },\n },\n {\n onSuccess: () => {\n navigate(`${basePath}/${workspaceId}`);\n },\n }\n );\n };\n\n if (isLoading) {\n return <div className=\"p-6\">{tWithFallback(t, 'workspaceSettings.loading', 'Loading...')}</div>;\n }\n\n if (!workspace) {\n return (\n <div className=\"p-6\">\n {tWithFallback(t, 'workspaceSettings.notFound', 'Workspace not found')}\n </div>\n );\n }\n\n return (\n <PermissionGate\n permission={WORKSPACE_PERMISSIONS.WORKSPACE_UPDATE}\n fallback={\n <div className=\"p-6\">\n <Button\n variant=\"ghost\"\n onClick={() => navigate(`${basePath}/${workspaceId}`)}\n className=\"gap-2 mb-6 -ml-2\"\n >\n <ArrowLeft size={20} />\n {tWithFallback(t, 'workspaceSettings.backToWorkspace', 'Back to Workspace')}\n </Button>\n <AccessDenied\n variant=\"card\"\n title={tWithFallback(t, 'workspaceSettings.permissionRequired', 'Permission Required')}\n description={tWithFallback(\n t,\n 'workspaceSettings.permissionRequiredDesc',\n \"You don't have permission to edit workspace settings.\"\n )}\n details={tWithFallback(\n t,\n 'workspaceSettings.permissionRequiredDetails',\n 'Required: workspace:update'\n )}\n primaryAction={{\n label: tWithFallback(t, 'workspaceSettings.goBack', 'Go Back'),\n onClick: () => navigate(`${basePath}/${workspaceId}`),\n }}\n />\n </div>\n }\n >\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n <div>\n <Button\n variant=\"ghost\"\n onClick={() => navigate(`${basePath}/${workspaceId}`)}\n className=\"gap-2 mb-2 -ml-2\"\n >\n <ArrowLeft size={20} />\n {tWithFallback(t, 'workspaceSettings.backToWorkspace', 'Back to Workspace')}\n </Button>\n <h1 className=\"text-2xl sm:text-3xl font-bold\">\n {tWithFallback(t, 'workspaceSettings.title', 'Workspace Settings')}\n </h1>\n <p className=\"text-text-secondary\">\n {tWithFallback(t, 'workspaceSettings.subtitle', 'Manage workspace configuration')}\n </p>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>\n {tWithFallback(t, 'workspaceSettings.generalSettings', 'General Settings')}\n </CardTitle>\n </CardHeader>\n <CardContent>\n <Form\n schema={updateWorkspaceSchema}\n onSubmit={handleSubmit}\n formOptions={{\n defaultValues: {\n name: workspace?.name || '',\n contactEmail: workspace?.contactEmail || '',\n domain: workspace?.domain || '',\n website: workspace?.website || '',\n },\n }}\n >\n {(form) => (\n <div className=\"space-y-4\">\n <FormField\n control={form.control}\n name=\"name\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>\n {tWithFallback(t, 'workspaceSettings.workspaceName', 'Workspace Name')}\n </FormLabel>\n <FormControl>\n <Input {...field} />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <FormField\n control={form.control}\n name=\"contactEmail\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>\n {tWithFallback(t, 'workspaceSettings.contactEmail', 'Contact Email')}\n </FormLabel>\n <FormControl>\n <Input type=\"email\" {...field} />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <OptionalFields\n title={tWithFallback(t, 'workspaceSettings.advancedTitle', 'Advanced settings')}\n description={tWithFallback(\n t,\n 'workspaceSettings.advancedDescription',\n 'Custom domain + website. Most workspaces leave these blank.'\n )}\n >\n <FormField\n control={form.control}\n name=\"domain\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>\n {tWithFallback(t, 'workspaceSettings.domain', 'Domain')}\n </FormLabel>\n <FormControl>\n <Input {...field} />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <FormField\n control={form.control}\n name=\"website\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>\n {tWithFallback(t, 'workspaceSettings.website', 'Website')}\n </FormLabel>\n <FormControl>\n <Input type=\"url\" {...field} />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n </OptionalFields>\n\n <div className=\"flex gap-2\">\n <Button type=\"submit\" disabled={isPending} className=\"gap-2\">\n <Save size={20} />\n {isPending\n ? tWithFallback(t, 'workspaceSettings.saving', 'Saving...')\n : tWithFallback(t, 'workspaceSettings.saveChanges', 'Save Changes')}\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => navigate(`${basePath}/${workspaceId}`)}\n >\n {tWithFallback(t, 'workspaceSettings.cancel', 'Cancel')}\n </Button>\n </div>\n </div>\n )}\n </Form>\n </CardContent>\n </Card>\n\n {workspaceId && (\n <Card>\n <CardHeader>\n <CardTitle>{tWithFallback(t, 'workspaceSettings.tags', 'Tags')}</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n 'workspaceSettings.tagsDescription',\n 'Use universal tags to classify and filter this workspace.'\n )}\n </p>\n <TagsWidget\n entityType=\"Workspace\"\n entityId={workspaceId}\n workspaceId={workspaceId}\n tags={widgetTags}\n onAdd={handleAddTag}\n onRemove={handleRemoveTag}\n syncGraphNotifications={false}\n className={tagsUpdating ? 'pointer-events-none opacity-75' : undefined}\n />\n </CardContent>\n </Card>\n )}\n </div>\n </PermissionGate>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WorkspaceSettingsPage.js","names":[],"sources":["../../src/pages/WorkspaceSettingsPage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { useParams, useNavigate } from 'react-router-dom';\nimport { ArrowLeft, Save } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { type TagItem, TagsWidget } from '@burdenoff/fe-libs/tag';\nimport { tWithFallback } from '../utils/i18n';\nimport {\n Button,\n GlassCard,\n CardHeader,\n CardTitle,\n CardContent,\n OptionalFields,\n} from '@burdenoff/fe-libs/ui';\nimport {\n useGetWorkspaceTagsQuery,\n useReplaceWorkspaceTagsMutation,\n type WorkspaceTagAssignmentFieldsFragment,\n} from '../generated/wspace-operations';\nimport {\n Form,\n FormField,\n FormItem,\n FormLabel,\n FormControl,\n FormMessage,\n Input,\n z,\n} from '@burdenoff/fe-libs/form';\nimport { PermissionGate, AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { WORKSPACE_PERMISSIONS } from '../constants/permissions';\nimport { useWorkspace } from '../hooks/useWorkspaces';\nimport { useUpdateWorkspace } from '../hooks/useWorkspaceMutations';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\n\nconst updateWorkspaceSchema = z.object({\n name: z.string().min(2).max(100),\n contactEmail: z.string().email().optional().or(z.literal('')),\n domain: z.string().optional(),\n website: z.string().url().optional().or(z.literal('')),\n});\n\ntype UpdateWorkspaceFormData = z.infer<typeof updateWorkspaceSchema>;\n\nexport function WorkspaceSettingsPage() {\n const { t } = useI18n();\n const { workspaceId } = useParams<{ workspaceId: string }>();\n useWorkspacesPageView('workspaces.settings', { workspaceId, requireParams: true });\n const navigate = useNavigate();\n const { basePath = '/' } = useWorkspacesContext();\n const { data: workspace, isLoading } = useWorkspace(workspaceId || '');\n const { mutate: updateWorkspace, isPending } = useUpdateWorkspace();\n\n // Workspace tags (universal-tags assignments) — backend exposed via\n // wspace-workspace-svc workspace-tags.gql; this UI is the canonical\n // editor for them. Identical pattern to ProjectDetailPage.\n const { data: tagsData, refetch: refetchTags } = useGetWorkspaceTagsQuery({\n variables: { input: { workspaceId: workspaceId ?? '' } },\n skip: !workspaceId,\n fetchPolicy: 'cache-and-network',\n });\n const [replaceWorkspaceTags, { loading: tagsUpdating }] = useReplaceWorkspaceTagsMutation();\n // The widget keys tags by `id` for add/remove. We store the\n // assignment's tagId there so the round-trip back to\n // replaceWorkspaceTags has the right value to send. Display fields\n // come from the embedded `tag: UniversalTag` resolved by the\n // assignment — assignments without a hydrated tag (deleted or\n // unresolved) are dropped so the widget never renders empty badges.\n const widgetTags = useMemo<TagItem[]>(() => {\n const assignments = tagsData?.getWorkspaceTags ?? [];\n return assignments.flatMap((a: WorkspaceTagAssignmentFieldsFragment) => {\n if (!a.tag) return [];\n return [\n {\n id: a.tagId,\n key: a.tag.key,\n label: a.tag.label,\n value: a.tag.value ?? null,\n vocabularyId: a.tag.vocabularyId ?? null,\n parentId: a.tag.parentId ?? null,\n hierarchyPath: a.tag.hierarchyPath ?? null,\n color: a.tag.color ?? null,\n icon: a.tag.icon ?? null,\n description: a.tag.description ?? null,\n } satisfies TagItem,\n ];\n });\n }, [tagsData?.getWorkspaceTags]);\n\n const persistTags = async (next: TagItem[]) => {\n if (!workspaceId) return;\n await replaceWorkspaceTags({\n variables: {\n input: {\n workspaceId,\n tagIds: next.map((t) => t.id),\n },\n },\n });\n await refetchTags();\n };\n\n const handleAddTag = (tag: TagItem) => persistTags([...widgetTags, tag]);\n const handleRemoveTag = (tagId: string) => persistTags(widgetTags.filter((t) => t.id !== tagId));\n\n const handleSubmit = (data: UpdateWorkspaceFormData) => {\n if (!workspaceId) return;\n\n updateWorkspace(\n {\n id: workspaceId,\n input: {\n ...data,\n contactEmail: data.contactEmail || undefined,\n domain: data.domain || undefined,\n website: data.website || undefined,\n },\n },\n {\n onSuccess: () => {\n navigate(`${basePath}/${workspaceId}`);\n },\n }\n );\n };\n\n if (isLoading) {\n return <div className=\"p-6\">{tWithFallback(t, 'workspaceSettings.loading', 'Loading...')}</div>;\n }\n\n if (!workspace) {\n return (\n <div className=\"p-6\">\n {tWithFallback(t, 'workspaceSettings.notFound', 'Workspace not found')}\n </div>\n );\n }\n\n return (\n <PermissionGate\n permission={WORKSPACE_PERMISSIONS.WORKSPACE_UPDATE}\n fallback={\n <div className=\"p-6\">\n <Button\n variant=\"ghost\"\n onClick={() => navigate(`${basePath}/${workspaceId}`)}\n className=\"gap-2 mb-6 -ml-2\"\n >\n <ArrowLeft size={20} />\n {tWithFallback(t, 'workspaceSettings.backToWorkspace', 'Back to Workspace')}\n </Button>\n <AccessDenied\n variant=\"card\"\n title={tWithFallback(t, 'workspaceSettings.permissionRequired', 'Permission Required')}\n description={tWithFallback(\n t,\n 'workspaceSettings.permissionRequiredDesc',\n \"You don't have permission to edit workspace settings.\"\n )}\n details={tWithFallback(\n t,\n 'workspaceSettings.permissionRequiredDetails',\n 'Required: workspace:update'\n )}\n primaryAction={{\n label: tWithFallback(t, 'workspaceSettings.goBack', 'Go Back'),\n onClick: () => navigate(`${basePath}/${workspaceId}`),\n }}\n />\n </div>\n }\n >\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n <div>\n <Button\n variant=\"ghost\"\n onClick={() => navigate(`${basePath}/${workspaceId}`)}\n className=\"gap-2 mb-2 -ml-2\"\n >\n <ArrowLeft size={20} />\n {tWithFallback(t, 'workspaceSettings.backToWorkspace', 'Back to Workspace')}\n </Button>\n <h1 className=\"text-2xl sm:text-3xl font-bold\">\n {tWithFallback(t, 'workspaceSettings.title', 'Workspace Settings')}\n </h1>\n <p className=\"text-text-secondary\">\n {tWithFallback(t, 'workspaceSettings.subtitle', 'Manage workspace configuration')}\n </p>\n </div>\n\n <GlassCard treatment=\"plain\" className=\"border-border-seam\">\n <CardHeader>\n <CardTitle>\n {tWithFallback(t, 'workspaceSettings.generalSettings', 'General Settings')}\n </CardTitle>\n </CardHeader>\n <CardContent>\n <Form\n schema={updateWorkspaceSchema}\n onSubmit={handleSubmit}\n formOptions={{\n defaultValues: {\n name: workspace?.name || '',\n contactEmail: workspace?.contactEmail || '',\n domain: workspace?.domain || '',\n website: workspace?.website || '',\n },\n }}\n >\n {(form) => (\n <div className=\"space-y-4\">\n <FormField\n control={form.control}\n name=\"name\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>\n {tWithFallback(t, 'workspaceSettings.workspaceName', 'Workspace Name')}\n </FormLabel>\n <FormControl>\n <Input {...field} />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <FormField\n control={form.control}\n name=\"contactEmail\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>\n {tWithFallback(t, 'workspaceSettings.contactEmail', 'Contact Email')}\n </FormLabel>\n <FormControl>\n <Input type=\"email\" {...field} />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <OptionalFields\n title={tWithFallback(t, 'workspaceSettings.advancedTitle', 'Advanced settings')}\n description={tWithFallback(\n t,\n 'workspaceSettings.advancedDescription',\n 'Custom domain + website. Most workspaces leave these blank.'\n )}\n >\n <FormField\n control={form.control}\n name=\"domain\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>\n {tWithFallback(t, 'workspaceSettings.domain', 'Domain')}\n </FormLabel>\n <FormControl>\n <Input {...field} />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <FormField\n control={form.control}\n name=\"website\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>\n {tWithFallback(t, 'workspaceSettings.website', 'Website')}\n </FormLabel>\n <FormControl>\n <Input type=\"url\" {...field} />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n </OptionalFields>\n\n <div className=\"flex gap-2\">\n <Button type=\"submit\" disabled={isPending} className=\"gap-2\">\n <Save size={20} />\n {isPending\n ? tWithFallback(t, 'workspaceSettings.saving', 'Saving...')\n : tWithFallback(t, 'workspaceSettings.saveChanges', 'Save Changes')}\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => navigate(`${basePath}/${workspaceId}`)}\n >\n {tWithFallback(t, 'workspaceSettings.cancel', 'Cancel')}\n </Button>\n </div>\n </div>\n )}\n </Form>\n </CardContent>\n </GlassCard>\n\n {workspaceId && (\n <GlassCard treatment=\"plain\" className=\"border-border-seam\">\n <CardHeader>\n <CardTitle>{tWithFallback(t, 'workspaceSettings.tags', 'Tags')}</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <p className=\"text-sm text-text-muted\">\n {tWithFallback(\n t,\n 'workspaceSettings.tagsDescription',\n 'Use universal tags to classify and filter this workspace.'\n )}\n </p>\n <TagsWidget\n entityType=\"Workspace\"\n entityId={workspaceId}\n workspaceId={workspaceId}\n tags={widgetTags}\n onAdd={handleAddTag}\n onRemove={handleRemoveTag}\n syncGraphNotifications={false}\n className={tagsUpdating ? 'pointer-events-none opacity-75' : undefined}\n />\n </CardContent>\n </GlassCard>\n )}\n </div>\n </PermissionGate>\n );\n}\n"],"mappings":"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"}
|
|
@@ -8,124 +8,135 @@ import { WORKSPACE_PERMISSIONS as o } from "../constants/permissions.js";
|
|
|
8
8
|
import { tWithFallback as s } from "../utils/i18n.js";
|
|
9
9
|
import { useWorkspacesPageView as c } from "../hooks/useWorkspacesPageView.js";
|
|
10
10
|
import { useEffect as l, useState as u } from "react";
|
|
11
|
-
import { Grid3X3 as d, List as f, Mail as p,
|
|
12
|
-
import { Fragment as
|
|
13
|
-
import { Badge as
|
|
14
|
-
import { useNavigate as
|
|
15
|
-
import { useDebounce as
|
|
16
|
-
import { PermissionButton as
|
|
17
|
-
import { useI18n as
|
|
11
|
+
import { Grid3X3 as d, List as f, Mail as p, MoreHorizontal as m, Plus as h, Search as g, UserPlus as _ } from "lucide-react";
|
|
12
|
+
import { Fragment as v, jsx as y, jsxs as b } from "react/jsx-runtime";
|
|
13
|
+
import { Badge as ee, Button as x, DropdownMenu as S, DropdownMenuContent as C, DropdownMenuItem as w, DropdownMenuTrigger as T, Input as E, Skeleton as D } from "@burdenoff/fe-libs/ui";
|
|
14
|
+
import { useNavigate as O } from "react-router-dom";
|
|
15
|
+
import { useDebounce as k } from "@burdenoff/fe-libs/shared/hooks";
|
|
16
|
+
import { PermissionButton as A } from "@burdenoff/fe-libs/shared/components";
|
|
17
|
+
import { useI18n as j } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
18
18
|
//#region src/pages/WorkspacesListPage.tsx
|
|
19
|
-
function
|
|
19
|
+
function M() {
|
|
20
20
|
c("workspaces.list");
|
|
21
|
-
let { t:
|
|
22
|
-
|
|
23
|
-
}, [
|
|
21
|
+
let { t: M } = j(), N = O(), { basePath: P = "/", currentUser: F, workspaceId: I } = e(), { withCurrentSearch: L } = i(), { viewMode: R, setViewMode: z, workspaceFilters: B, setWorkspaceFilters: V } = t(), H = () => {
|
|
22
|
+
N(L(`${P}/new`));
|
|
23
|
+
}, [U, W] = u(B.search || ""), G = k(U, 500);
|
|
24
24
|
l(() => {
|
|
25
|
-
|
|
25
|
+
G !== B.search && V({ search: G });
|
|
26
26
|
}, [
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
G,
|
|
28
|
+
B.search,
|
|
29
|
+
V
|
|
30
30
|
]);
|
|
31
|
-
let { data:
|
|
32
|
-
|
|
33
|
-
},
|
|
34
|
-
|
|
35
|
-
},
|
|
36
|
-
...
|
|
37
|
-
items:
|
|
38
|
-
total:
|
|
31
|
+
let { data: K, isLoading: q, error: J } = n(), { data: Y = [] } = r(F?.email), X = Y.length, Z = () => {
|
|
32
|
+
N(L(`${P}/invites`));
|
|
33
|
+
}, Q = () => {
|
|
34
|
+
I && N(L(`${P}/${I}/invite`));
|
|
35
|
+
}, $ = K ? {
|
|
36
|
+
...K,
|
|
37
|
+
items: G ? K.items.filter((e) => e.name.toLowerCase().includes(G.toLowerCase())) : K.items,
|
|
38
|
+
total: G ? K.items.filter((e) => e.name.toLowerCase().includes(G.toLowerCase())).length : K.total
|
|
39
39
|
} : void 0;
|
|
40
|
-
return
|
|
40
|
+
return J ? /* @__PURE__ */ y("div", {
|
|
41
41
|
className: "space-y-4 sm:space-y-6 p-3 sm:p-6",
|
|
42
|
-
children: /* @__PURE__ */
|
|
42
|
+
children: /* @__PURE__ */ b("div", {
|
|
43
43
|
className: "flex flex-col items-center justify-center py-12",
|
|
44
|
-
children: [/* @__PURE__ */
|
|
44
|
+
children: [/* @__PURE__ */ y("p", {
|
|
45
45
|
className: "text-status-error-text text-lg font-semibold",
|
|
46
|
-
children: s(
|
|
47
|
-
}), /* @__PURE__ */
|
|
46
|
+
children: s(M, "workspacesList.loadError", "Error loading workspaces")
|
|
47
|
+
}), /* @__PURE__ */ y("p", {
|
|
48
48
|
className: "text-text-secondary text-sm mt-2",
|
|
49
|
-
children:
|
|
49
|
+
children: J instanceof Error ? J.message : s(M, "workspacesList.unknownError", "Unknown error")
|
|
50
50
|
})]
|
|
51
51
|
})
|
|
52
|
-
}) : /* @__PURE__ */
|
|
52
|
+
}) : /* @__PURE__ */ b("div", {
|
|
53
53
|
className: "space-y-4 sm:space-y-6 p-3 sm:p-6",
|
|
54
54
|
children: [
|
|
55
|
-
/* @__PURE__ */
|
|
55
|
+
/* @__PURE__ */ b("div", {
|
|
56
56
|
className: "flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3",
|
|
57
57
|
"data-tour": "workspaces-list-header",
|
|
58
|
-
children: [/* @__PURE__ */
|
|
58
|
+
children: [/* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("h1", {
|
|
59
59
|
className: "text-2xl sm:text-3xl font-bold",
|
|
60
|
-
children: s(
|
|
61
|
-
}), /* @__PURE__ */
|
|
60
|
+
children: s(M, "workspacesList.title", "Workspaces")
|
|
61
|
+
}), /* @__PURE__ */ y("p", {
|
|
62
62
|
className: "text-sm sm:text-base text-text-secondary",
|
|
63
|
-
children: s(
|
|
64
|
-
})] }), /* @__PURE__ */
|
|
63
|
+
children: s(M, "workspacesList.description", "Manage and organize your workspaces")
|
|
64
|
+
})] }), /* @__PURE__ */ b("div", {
|
|
65
65
|
className: "flex items-center gap-2",
|
|
66
66
|
"data-tour": "workspaces-list-actions",
|
|
67
67
|
children: [
|
|
68
|
-
/* @__PURE__ */
|
|
69
|
-
variant:
|
|
68
|
+
/* @__PURE__ */ y(x, {
|
|
69
|
+
variant: R === "grid" ? "default" : "outline",
|
|
70
70
|
size: "icon",
|
|
71
|
-
onClick: () =>
|
|
71
|
+
onClick: () => z("grid"),
|
|
72
72
|
className: "hidden sm:flex",
|
|
73
|
-
"aria-label": s(
|
|
74
|
-
title: s(
|
|
75
|
-
children: /* @__PURE__ */
|
|
73
|
+
"aria-label": s(M, "workspacesList.gridViewAriaLabel", "Show workspaces in grid view"),
|
|
74
|
+
title: s(M, "workspacesList.gridViewTitle", "Grid view"),
|
|
75
|
+
children: /* @__PURE__ */ y(d, { size: 20 })
|
|
76
76
|
}),
|
|
77
|
-
/* @__PURE__ */
|
|
78
|
-
variant:
|
|
77
|
+
/* @__PURE__ */ y(x, {
|
|
78
|
+
variant: R === "list" ? "default" : "outline",
|
|
79
79
|
size: "icon",
|
|
80
|
-
onClick: () =>
|
|
80
|
+
onClick: () => z("list"),
|
|
81
81
|
className: "hidden sm:flex",
|
|
82
|
-
"aria-label": s(
|
|
83
|
-
title: s(
|
|
84
|
-
children: /* @__PURE__ */
|
|
82
|
+
"aria-label": s(M, "workspacesList.listViewAriaLabel", "Show workspaces in list view"),
|
|
83
|
+
title: s(M, "workspacesList.listViewTitle", "List view"),
|
|
84
|
+
children: /* @__PURE__ */ y(f, { size: 20 })
|
|
85
85
|
}),
|
|
86
|
-
/* @__PURE__ */
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
deniedTooltip: s(O, "workspacesList.inviteDeniedTooltip", "You don't have permission to invite members into the current workspace"),
|
|
90
|
-
children: /* @__PURE__ */ y(x, {
|
|
86
|
+
/* @__PURE__ */ b(S, { children: [/* @__PURE__ */ y(T, {
|
|
87
|
+
asChild: !0,
|
|
88
|
+
children: /* @__PURE__ */ b(x, {
|
|
91
89
|
variant: "outline",
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
children: [/* @__PURE__ */ v(g, { size: 18 }), /* @__PURE__ */ v("span", { children: s(O, "workspacesList.inviteMemberButton", "Invite Member") })]
|
|
90
|
+
size: "icon",
|
|
91
|
+
"aria-label": "More actions",
|
|
92
|
+
className: "relative",
|
|
93
|
+
children: [/* @__PURE__ */ y(m, { size: 18 }), X > 0 ? /* @__PURE__ */ y("span", { className: "absolute -top-1 -right-1 flex size-2 rounded-full bg-action-primary-bg" }) : null]
|
|
97
94
|
})
|
|
98
|
-
}),
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
/* @__PURE__ */
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
children:
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
95
|
+
}), /* @__PURE__ */ b(C, {
|
|
96
|
+
align: "end",
|
|
97
|
+
children: [/* @__PURE__ */ y(A, {
|
|
98
|
+
permission: o.MEMBER_CREATE,
|
|
99
|
+
deniedBehavior: "disable",
|
|
100
|
+
deniedTooltip: s(M, "workspacesList.inviteDeniedTooltip", "You don't have permission to invite members into the current workspace"),
|
|
101
|
+
children: /* @__PURE__ */ b(w, {
|
|
102
|
+
onSelect: Q,
|
|
103
|
+
disabled: !I,
|
|
104
|
+
className: "gap-2",
|
|
105
|
+
children: [/* @__PURE__ */ y(_, { size: 16 }), s(M, "workspacesList.inviteMemberButton", "Invite Member")]
|
|
106
|
+
})
|
|
107
|
+
}), /* @__PURE__ */ b(w, {
|
|
108
|
+
onSelect: Z,
|
|
109
|
+
className: "gap-2",
|
|
110
|
+
children: [
|
|
111
|
+
/* @__PURE__ */ y(p, { size: 16 }),
|
|
112
|
+
/* @__PURE__ */ y("span", {
|
|
113
|
+
className: "flex-1",
|
|
114
|
+
children: s(M, "workspacesList.pendingInvitesButton", "Pending Invites")
|
|
115
|
+
}),
|
|
116
|
+
X > 0 ? /* @__PURE__ */ y(ee, {
|
|
117
|
+
variant: "secondary",
|
|
118
|
+
children: X
|
|
119
|
+
}) : null
|
|
120
|
+
]
|
|
121
|
+
})]
|
|
122
|
+
})] }),
|
|
123
|
+
/* @__PURE__ */ y(A, {
|
|
113
124
|
permission: o.WORKSPACE_CREATE,
|
|
114
125
|
deniedBehavior: "disable",
|
|
115
|
-
deniedTooltip: s(
|
|
116
|
-
children: /* @__PURE__ */
|
|
117
|
-
onClick:
|
|
126
|
+
deniedTooltip: s(M, "workspacesList.createDeniedTooltip", "You don't have permission to create workspaces"),
|
|
127
|
+
children: /* @__PURE__ */ b(x, {
|
|
128
|
+
onClick: H,
|
|
118
129
|
className: "gap-2",
|
|
119
130
|
"data-tour": "workspaces-create-button",
|
|
120
131
|
children: [
|
|
121
|
-
/* @__PURE__ */
|
|
122
|
-
/* @__PURE__ */
|
|
132
|
+
/* @__PURE__ */ y(h, { size: 20 }),
|
|
133
|
+
/* @__PURE__ */ y("span", {
|
|
123
134
|
className: "hidden sm:inline",
|
|
124
|
-
children: s(
|
|
135
|
+
children: s(M, "workspacesList.createButton", "Create Workspace")
|
|
125
136
|
}),
|
|
126
|
-
/* @__PURE__ */
|
|
137
|
+
/* @__PURE__ */ y("span", {
|
|
127
138
|
className: "sm:hidden",
|
|
128
|
-
children: s(
|
|
139
|
+
children: s(M, "workspacesList.createButtonShort", "Create")
|
|
129
140
|
})
|
|
130
141
|
]
|
|
131
142
|
})
|
|
@@ -133,86 +144,86 @@ function O() {
|
|
|
133
144
|
]
|
|
134
145
|
})]
|
|
135
146
|
}),
|
|
136
|
-
/* @__PURE__ */
|
|
147
|
+
/* @__PURE__ */ b("div", {
|
|
137
148
|
className: "relative",
|
|
138
149
|
"data-tour": "workspaces-search-input",
|
|
139
|
-
children: [/* @__PURE__ */
|
|
150
|
+
children: [/* @__PURE__ */ y(g, {
|
|
140
151
|
className: "absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary",
|
|
141
152
|
size: 20
|
|
142
|
-
}), /* @__PURE__ */
|
|
143
|
-
placeholder: s(
|
|
144
|
-
value:
|
|
153
|
+
}), /* @__PURE__ */ y(E, {
|
|
154
|
+
placeholder: s(M, "workspacesList.searchPlaceholder", "Search workspaces..."),
|
|
155
|
+
value: U,
|
|
145
156
|
onChange: (e) => {
|
|
146
|
-
|
|
157
|
+
W(e.target.value);
|
|
147
158
|
},
|
|
148
159
|
className: "pl-[3.25rem]"
|
|
149
160
|
})]
|
|
150
161
|
}),
|
|
151
|
-
/* @__PURE__ */
|
|
162
|
+
/* @__PURE__ */ y("div", {
|
|
152
163
|
className: "space-y-4 sm:space-y-6",
|
|
153
164
|
"data-tour": "workspaces-view-container",
|
|
154
|
-
children:
|
|
165
|
+
children: q ? /* @__PURE__ */ y("div", {
|
|
155
166
|
className: "grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4",
|
|
156
|
-
children: Array.from({ length: 6 }).map((e, t) => /* @__PURE__ */
|
|
157
|
-
className: "space-y-3 p-6 border rounded-lg",
|
|
158
|
-
children: [/* @__PURE__ */
|
|
167
|
+
children: Array.from({ length: 6 }).map((e, t) => /* @__PURE__ */ b("div", {
|
|
168
|
+
className: "space-y-3 p-6 border border-border-seam rounded-lg",
|
|
169
|
+
children: [/* @__PURE__ */ b("div", {
|
|
159
170
|
className: "flex items-center gap-3",
|
|
160
|
-
children: [/* @__PURE__ */
|
|
171
|
+
children: [/* @__PURE__ */ y(D, { className: "size-12 rounded-lg" }), /* @__PURE__ */ b("div", {
|
|
161
172
|
className: "space-y-2 flex-1",
|
|
162
|
-
children: [/* @__PURE__ */
|
|
173
|
+
children: [/* @__PURE__ */ y(D, { className: "h-4 w-3/4" }), /* @__PURE__ */ y(D, { className: "h-3 w-1/2" })]
|
|
163
174
|
})]
|
|
164
|
-
}), /* @__PURE__ */
|
|
175
|
+
}), /* @__PURE__ */ y(D, { className: "h-3 w-1/3" })]
|
|
165
176
|
}, t))
|
|
166
|
-
}) :
|
|
177
|
+
}) : $?.items.length === 0 ? /* @__PURE__ */ b("div", {
|
|
167
178
|
className: "flex flex-col items-center justify-center py-12 text-center",
|
|
168
179
|
children: [
|
|
169
|
-
/* @__PURE__ */
|
|
180
|
+
/* @__PURE__ */ y("div", {
|
|
170
181
|
className: "rounded-full bg-bg-sunken p-6 mb-4",
|
|
171
|
-
children: /* @__PURE__ */
|
|
182
|
+
children: /* @__PURE__ */ y(d, {
|
|
172
183
|
size: 48,
|
|
173
184
|
className: "text-text-secondary"
|
|
174
185
|
})
|
|
175
186
|
}),
|
|
176
|
-
/* @__PURE__ */
|
|
187
|
+
/* @__PURE__ */ y("h3", {
|
|
177
188
|
className: "text-lg font-semibold mb-2",
|
|
178
|
-
children: s(
|
|
189
|
+
children: s(M, "workspacesList.emptyTitle", "No workspaces found")
|
|
179
190
|
}),
|
|
180
|
-
/* @__PURE__ */
|
|
191
|
+
/* @__PURE__ */ y("p", {
|
|
181
192
|
className: "text-text-secondary mb-4 max-w-sm",
|
|
182
|
-
children:
|
|
193
|
+
children: B.search ? s(M, "workspacesList.emptySearchHint", "Try adjusting your search criteria") : s(M, "workspacesList.emptyCreateHint", "Get started by creating your first workspace")
|
|
183
194
|
}),
|
|
184
|
-
!
|
|
195
|
+
!B.search && /* @__PURE__ */ y(A, {
|
|
185
196
|
permission: o.WORKSPACE_CREATE,
|
|
186
197
|
deniedBehavior: "disable",
|
|
187
|
-
deniedTooltip: s(
|
|
188
|
-
children: /* @__PURE__ */
|
|
189
|
-
onClick:
|
|
198
|
+
deniedTooltip: s(M, "workspacesList.createDeniedTooltip", "You don't have permission to create workspaces"),
|
|
199
|
+
children: /* @__PURE__ */ b(x, {
|
|
200
|
+
onClick: H,
|
|
190
201
|
className: "gap-2",
|
|
191
|
-
children: [/* @__PURE__ */
|
|
202
|
+
children: [/* @__PURE__ */ y(h, { size: 20 }), s(M, "workspacesList.createFirstButton", "Create Your First Workspace")]
|
|
192
203
|
})
|
|
193
204
|
})
|
|
194
205
|
]
|
|
195
|
-
}) : /* @__PURE__ */
|
|
206
|
+
}) : /* @__PURE__ */ b(v, { children: [R === "list" ? /* @__PURE__ */ y("div", {
|
|
196
207
|
className: "space-y-3",
|
|
197
|
-
children:
|
|
198
|
-
}) : /* @__PURE__ */
|
|
208
|
+
children: $?.items.map((e) => /* @__PURE__ */ y(a, { workspace: e }, e.id))
|
|
209
|
+
}) : /* @__PURE__ */ y("div", {
|
|
199
210
|
className: "grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4",
|
|
200
|
-
children:
|
|
201
|
-
}),
|
|
211
|
+
children: $?.items.map((e) => /* @__PURE__ */ y(a, { workspace: e }, e.id))
|
|
212
|
+
}), $ && $.total > 0 && /* @__PURE__ */ b("div", {
|
|
202
213
|
className: "flex items-center justify-between text-sm text-text-secondary",
|
|
203
|
-
children: [/* @__PURE__ */
|
|
204
|
-
s(
|
|
214
|
+
children: [/* @__PURE__ */ b("p", { children: [
|
|
215
|
+
s(M, "workspacesList.showing", "Showing"),
|
|
205
216
|
" ",
|
|
206
|
-
|
|
217
|
+
$.items.length,
|
|
207
218
|
" ",
|
|
208
|
-
s(
|
|
219
|
+
s(M, "workspacesList.of", "of"),
|
|
209
220
|
" ",
|
|
210
|
-
|
|
221
|
+
$.total,
|
|
211
222
|
" ",
|
|
212
|
-
|
|
213
|
-
] }),
|
|
223
|
+
$.total === 1 ? s(M, "workspacesList.workspace", "workspace") : s(M, "workspacesList.workspaces", "workspaces")
|
|
224
|
+
] }), $.hasMore && /* @__PURE__ */ y("p", {
|
|
214
225
|
className: "text-xs",
|
|
215
|
-
children: s(
|
|
226
|
+
children: s(M, "workspacesList.moreAvailable", "More workspaces available - pagination coming soon")
|
|
216
227
|
})]
|
|
217
228
|
})] })
|
|
218
229
|
})
|
|
@@ -220,6 +231,6 @@ function O() {
|
|
|
220
231
|
});
|
|
221
232
|
}
|
|
222
233
|
//#endregion
|
|
223
|
-
export {
|
|
234
|
+
export { M as WorkspacesListPage };
|
|
224
235
|
|
|
225
236
|
//# sourceMappingURL=WorkspacesListPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkspacesListPage.js","names":[],"sources":["../../src/pages/WorkspacesListPage.tsx"],"sourcesContent":["import { Plus, Grid3X3, List as ListIcon, Search, Mail, UserPlus } from 'lucide-react';\nimport { Badge, Button, Input, Skeleton } from '@burdenoff/fe-libs/ui';\nimport { useNavigate } from 'react-router-dom';\nimport { useWorkspacesStore } from '../store/workspacesStore';\nimport { useMyWorkspaces } from '../hooks/useWorkspaces';\nimport { useMyPendingInvitations } from '../hooks/usePendingInvitations';\nimport { WorkspaceCard } from '../components/WorkspaceCard';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { useDebounce } from '@burdenoff/fe-libs/shared/hooks';\nimport { PermissionButton } from '@burdenoff/fe-libs/shared/components';\nimport { WORKSPACE_PERMISSIONS } from '../constants/permissions';\nimport { useState, useEffect } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\nimport { useScopedNavigation } from '../hooks/useScopedNavigation';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\n\nexport function WorkspacesListPage() {\n useWorkspacesPageView('workspaces.list');\n const { t } = useI18n();\n\n const navigate = useNavigate();\n const { basePath = '/', currentUser, workspaceId: currentWorkspaceId } = useWorkspacesContext();\n const { withCurrentSearch } = useScopedNavigation();\n const { viewMode, setViewMode, workspaceFilters, setWorkspaceFilters } = useWorkspacesStore();\n\n // Navigate to unified workspace creation page\n const handleCreateWorkspace = () => {\n navigate(withCurrentSearch(`${basePath}/new`));\n };\n\n const [searchValue, setSearchValue] = useState(workspaceFilters.search || '');\n const debouncedSearch = useDebounce(searchValue, 500);\n\n // Update filters when debounced search changes\n useEffect(() => {\n if (debouncedSearch !== workspaceFilters.search) {\n setWorkspaceFilters({ search: debouncedSearch });\n }\n }, [debouncedSearch, workspaceFilters.search, setWorkspaceFilters]);\n\n const { data: rawData, isLoading, error } = useMyWorkspaces();\n const { data: pendingInvitations = [] } = useMyPendingInvitations(currentUser?.email);\n const pendingInvitesCount = pendingInvitations.length;\n\n const handleOpenPendingInvites = () => {\n navigate(withCurrentSearch(`${basePath}/invites`));\n };\n\n const handleInviteMember = () => {\n if (!currentWorkspaceId) {\n return;\n }\n\n navigate(withCurrentSearch(`${basePath}/${currentWorkspaceId}/invite`));\n };\n\n // Client-side search filtering (myWorkspaces doesn't accept filter args)\n const data = rawData\n ? {\n ...rawData,\n items: debouncedSearch\n ? rawData.items.filter((ws) =>\n ws.name.toLowerCase().includes(debouncedSearch.toLowerCase())\n )\n : rawData.items,\n total: debouncedSearch\n ? rawData.items.filter((ws) =>\n ws.name.toLowerCase().includes(debouncedSearch.toLowerCase())\n ).length\n : rawData.total,\n }\n : undefined;\n\n const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n setSearchValue(e.target.value);\n };\n\n if (error) {\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n <div className=\"flex flex-col items-center justify-center py-12\">\n <p className=\"text-status-error-text text-lg font-semibold\">\n {tWithFallback(t, 'workspacesList.loadError', 'Error loading workspaces')}\n </p>\n <p className=\"text-text-secondary text-sm mt-2\">\n {error instanceof Error\n ? error.message\n : tWithFallback(t, 'workspacesList.unknownError', 'Unknown error')}\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n {/* Header with title + actions */}\n <div\n className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3\"\n data-tour=\"workspaces-list-header\"\n >\n <div>\n <h1 className=\"text-2xl sm:text-3xl font-bold\">\n {tWithFallback(t, 'workspacesList.title', 'Workspaces')}\n </h1>\n <p className=\"text-sm sm:text-base text-text-secondary\">\n {tWithFallback(t, 'workspacesList.description', 'Manage and organize your workspaces')}\n </p>\n </div>\n <div className=\"flex items-center gap-2\" data-tour=\"workspaces-list-actions\">\n {/* View mode toggle */}\n <Button\n variant={viewMode === 'grid' ? 'default' : 'outline'}\n size=\"icon\"\n onClick={() => setViewMode('grid')}\n className=\"hidden sm:flex\"\n aria-label={tWithFallback(\n t,\n 'workspacesList.gridViewAriaLabel',\n 'Show workspaces in grid view'\n )}\n title={tWithFallback(t, 'workspacesList.gridViewTitle', 'Grid view')}\n >\n <Grid3X3 size={20} />\n </Button>\n <Button\n variant={viewMode === 'list' ? 'default' : 'outline'}\n size=\"icon\"\n onClick={() => setViewMode('list')}\n className=\"hidden sm:flex\"\n aria-label={tWithFallback(\n t,\n 'workspacesList.listViewAriaLabel',\n 'Show workspaces in list view'\n )}\n title={tWithFallback(t, 'workspacesList.listViewTitle', 'List view')}\n >\n <ListIcon size={20} />\n </Button>\n <PermissionButton\n permission={WORKSPACE_PERMISSIONS.MEMBER_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tWithFallback(\n t,\n 'workspacesList.inviteDeniedTooltip',\n \"You don't have permission to invite members into the current workspace\"\n )}\n >\n <Button\n variant=\"outline\"\n onClick={handleInviteMember}\n className=\"gap-2\"\n disabled={!currentWorkspaceId}\n title={\n currentWorkspaceId\n ? tWithFallback(\n t,\n 'workspacesList.inviteMemberTitle',\n 'Invite members into the current workspace'\n )\n : tWithFallback(\n t,\n 'workspacesList.inviteMemberNoWorkspaceTitle',\n 'Select a current workspace first'\n )\n }\n >\n <UserPlus size={18} />\n <span>{tWithFallback(t, 'workspacesList.inviteMemberButton', 'Invite Member')}</span>\n </Button>\n </PermissionButton>\n <Button variant=\"outline\" onClick={handleOpenPendingInvites} className=\"gap-2\">\n <Mail size={18} />\n <span>\n {tWithFallback(t, 'workspacesList.pendingInvitesButton', 'Pending Invites')}\n </span>\n {pendingInvitesCount > 0 ? (\n <Badge variant=\"secondary\">{pendingInvitesCount}</Badge>\n ) : null}\n </Button>\n <PermissionButton\n permission={WORKSPACE_PERMISSIONS.WORKSPACE_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tWithFallback(\n t,\n 'workspacesList.createDeniedTooltip',\n \"You don't have permission to create workspaces\"\n )}\n >\n <Button\n onClick={handleCreateWorkspace}\n className=\"gap-2\"\n data-tour=\"workspaces-create-button\"\n >\n <Plus size={20} />\n <span className=\"hidden sm:inline\">\n {tWithFallback(t, 'workspacesList.createButton', 'Create Workspace')}\n </span>\n <span className=\"sm:hidden\">\n {tWithFallback(t, 'workspacesList.createButtonShort', 'Create')}\n </span>\n </Button>\n </PermissionButton>\n </div>\n </div>\n\n {/* Search */}\n <div className=\"relative\" data-tour=\"workspaces-search-input\">\n <Search\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary\"\n size={20}\n />\n <Input\n placeholder={tWithFallback(t, 'workspacesList.searchPlaceholder', 'Search workspaces...')}\n value={searchValue}\n onChange={handleSearchChange}\n className=\"pl-[3.25rem]\"\n />\n </div>\n\n {/* Content */}\n <div className=\"space-y-4 sm:space-y-6\" data-tour=\"workspaces-view-container\">\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 <div key={i} className=\"space-y-3 p-6 border rounded-lg\">\n <div className=\"flex items-center gap-3\">\n <Skeleton className=\"size-12 rounded-lg\" />\n <div className=\"space-y-2 flex-1\">\n <Skeleton className=\"h-4 w-3/4\" />\n <Skeleton className=\"h-3 w-1/2\" />\n </div>\n </div>\n <Skeleton className=\"h-3 w-1/3\" />\n </div>\n ))}\n </div>\n ) : data?.items.length === 0 ? (\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 <Grid3X3 size={48} className=\"text-text-secondary\" />\n </div>\n <h3 className=\"text-lg font-semibold mb-2\">\n {tWithFallback(t, 'workspacesList.emptyTitle', 'No workspaces found')}\n </h3>\n <p className=\"text-text-secondary mb-4 max-w-sm\">\n {workspaceFilters.search\n ? tWithFallback(\n t,\n 'workspacesList.emptySearchHint',\n 'Try adjusting your search criteria'\n )\n : tWithFallback(\n t,\n 'workspacesList.emptyCreateHint',\n 'Get started by creating your first workspace'\n )}\n </p>\n {!workspaceFilters.search && (\n <PermissionButton\n permission={WORKSPACE_PERMISSIONS.WORKSPACE_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tWithFallback(\n t,\n 'workspacesList.createDeniedTooltip',\n \"You don't have permission to create workspaces\"\n )}\n >\n <Button onClick={handleCreateWorkspace} className=\"gap-2\">\n <Plus size={20} />\n {tWithFallback(\n t,\n 'workspacesList.createFirstButton',\n 'Create Your First Workspace'\n )}\n </Button>\n </PermissionButton>\n )}\n </div>\n ) : (\n <>\n {/* View */}\n {viewMode === 'list' ? (\n <div className=\"space-y-3\">\n {data?.items.map((workspace) => (\n <WorkspaceCard key={workspace.id} workspace={workspace} />\n ))}\n </div>\n ) : (\n <div className=\"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4\">\n {data?.items.map((workspace) => (\n <WorkspaceCard key={workspace.id} workspace={workspace} />\n ))}\n </div>\n )}\n\n {/* Pagination info */}\n {data && data.total > 0 && (\n <div className=\"flex items-center justify-between text-sm text-text-secondary\">\n <p>\n {tWithFallback(t, 'workspacesList.showing', 'Showing')} {data.items.length}{' '}\n {tWithFallback(t, 'workspacesList.of', 'of')} {data.total}{' '}\n {data.total !== 1\n ? tWithFallback(t, 'workspacesList.workspaces', 'workspaces')\n : tWithFallback(t, 'workspacesList.workspace', 'workspace')}\n </p>\n {data.hasMore && (\n <p className=\"text-xs\">\n {tWithFallback(\n t,\n 'workspacesList.moreAvailable',\n 'More workspaces available - pagination coming soon'\n )}\n </p>\n )}\n </div>\n )}\n </>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WorkspacesListPage.js","names":[],"sources":["../../src/pages/WorkspacesListPage.tsx"],"sourcesContent":["import { Plus, Grid3X3, List as ListIcon, Search, Mail, UserPlus } from 'lucide-react';\nimport {\n Badge,\n Button,\n Input,\n Skeleton,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { MoreHorizontal } from 'lucide-react';\nimport { useNavigate } from 'react-router-dom';\nimport { useWorkspacesStore } from '../store/workspacesStore';\nimport { useMyWorkspaces } from '../hooks/useWorkspaces';\nimport { useMyPendingInvitations } from '../hooks/usePendingInvitations';\nimport { WorkspaceCard } from '../components/WorkspaceCard';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { useDebounce } from '@burdenoff/fe-libs/shared/hooks';\nimport { PermissionButton } from '@burdenoff/fe-libs/shared/components';\nimport { WORKSPACE_PERMISSIONS } from '../constants/permissions';\nimport { useState, useEffect } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\nimport { useScopedNavigation } from '../hooks/useScopedNavigation';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\n\nexport function WorkspacesListPage() {\n useWorkspacesPageView('workspaces.list');\n const { t } = useI18n();\n\n const navigate = useNavigate();\n const { basePath = '/', currentUser, workspaceId: currentWorkspaceId } = useWorkspacesContext();\n const { withCurrentSearch } = useScopedNavigation();\n const { viewMode, setViewMode, workspaceFilters, setWorkspaceFilters } = useWorkspacesStore();\n\n // Navigate to unified workspace creation page\n const handleCreateWorkspace = () => {\n navigate(withCurrentSearch(`${basePath}/new`));\n };\n\n const [searchValue, setSearchValue] = useState(workspaceFilters.search || '');\n const debouncedSearch = useDebounce(searchValue, 500);\n\n // Update filters when debounced search changes\n useEffect(() => {\n if (debouncedSearch !== workspaceFilters.search) {\n setWorkspaceFilters({ search: debouncedSearch });\n }\n }, [debouncedSearch, workspaceFilters.search, setWorkspaceFilters]);\n\n const { data: rawData, isLoading, error } = useMyWorkspaces();\n const { data: pendingInvitations = [] } = useMyPendingInvitations(currentUser?.email);\n const pendingInvitesCount = pendingInvitations.length;\n\n const handleOpenPendingInvites = () => {\n navigate(withCurrentSearch(`${basePath}/invites`));\n };\n\n const handleInviteMember = () => {\n if (!currentWorkspaceId) {\n return;\n }\n\n navigate(withCurrentSearch(`${basePath}/${currentWorkspaceId}/invite`));\n };\n\n // Client-side search filtering (myWorkspaces doesn't accept filter args)\n const data = rawData\n ? {\n ...rawData,\n items: debouncedSearch\n ? rawData.items.filter((ws) =>\n ws.name.toLowerCase().includes(debouncedSearch.toLowerCase())\n )\n : rawData.items,\n total: debouncedSearch\n ? rawData.items.filter((ws) =>\n ws.name.toLowerCase().includes(debouncedSearch.toLowerCase())\n ).length\n : rawData.total,\n }\n : undefined;\n\n const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n setSearchValue(e.target.value);\n };\n\n if (error) {\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n <div className=\"flex flex-col items-center justify-center py-12\">\n <p className=\"text-status-error-text text-lg font-semibold\">\n {tWithFallback(t, 'workspacesList.loadError', 'Error loading workspaces')}\n </p>\n <p className=\"text-text-secondary text-sm mt-2\">\n {error instanceof Error\n ? error.message\n : tWithFallback(t, 'workspacesList.unknownError', 'Unknown error')}\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n {/* Header with title + actions */}\n <div\n className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3\"\n data-tour=\"workspaces-list-header\"\n >\n <div>\n <h1 className=\"text-2xl sm:text-3xl font-bold\">\n {tWithFallback(t, 'workspacesList.title', 'Workspaces')}\n </h1>\n <p className=\"text-sm sm:text-base text-text-secondary\">\n {tWithFallback(t, 'workspacesList.description', 'Manage and organize your workspaces')}\n </p>\n </div>\n <div className=\"flex items-center gap-2\" data-tour=\"workspaces-list-actions\">\n {/* View mode toggle */}\n <Button\n variant={viewMode === 'grid' ? 'default' : 'outline'}\n size=\"icon\"\n onClick={() => setViewMode('grid')}\n className=\"hidden sm:flex\"\n aria-label={tWithFallback(\n t,\n 'workspacesList.gridViewAriaLabel',\n 'Show workspaces in grid view'\n )}\n title={tWithFallback(t, 'workspacesList.gridViewTitle', 'Grid view')}\n >\n <Grid3X3 size={20} />\n </Button>\n <Button\n variant={viewMode === 'list' ? 'default' : 'outline'}\n size=\"icon\"\n onClick={() => setViewMode('list')}\n className=\"hidden sm:flex\"\n aria-label={tWithFallback(\n t,\n 'workspacesList.listViewAriaLabel',\n 'Show workspaces in list view'\n )}\n title={tWithFallback(t, 'workspacesList.listViewTitle', 'List view')}\n >\n <ListIcon size={20} />\n </Button>\n {/* Secondary actions collapsed into an overflow \"More\" menu */}\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" size=\"icon\" aria-label=\"More actions\" className=\"relative\">\n <MoreHorizontal size={18} />\n {pendingInvitesCount > 0 ? (\n <span className=\"absolute -top-1 -right-1 flex size-2 rounded-full bg-action-primary-bg\" />\n ) : null}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <PermissionButton\n permission={WORKSPACE_PERMISSIONS.MEMBER_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tWithFallback(\n t,\n 'workspacesList.inviteDeniedTooltip',\n \"You don't have permission to invite members into the current workspace\"\n )}\n >\n <DropdownMenuItem\n onSelect={handleInviteMember}\n disabled={!currentWorkspaceId}\n className=\"gap-2\"\n >\n <UserPlus size={16} />\n {tWithFallback(t, 'workspacesList.inviteMemberButton', 'Invite Member')}\n </DropdownMenuItem>\n </PermissionButton>\n <DropdownMenuItem onSelect={handleOpenPendingInvites} className=\"gap-2\">\n <Mail size={16} />\n <span className=\"flex-1\">\n {tWithFallback(t, 'workspacesList.pendingInvitesButton', 'Pending Invites')}\n </span>\n {pendingInvitesCount > 0 ? (\n <Badge variant=\"secondary\">{pendingInvitesCount}</Badge>\n ) : null}\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n <PermissionButton\n permission={WORKSPACE_PERMISSIONS.WORKSPACE_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tWithFallback(\n t,\n 'workspacesList.createDeniedTooltip',\n \"You don't have permission to create workspaces\"\n )}\n >\n <Button\n onClick={handleCreateWorkspace}\n className=\"gap-2\"\n data-tour=\"workspaces-create-button\"\n >\n <Plus size={20} />\n <span className=\"hidden sm:inline\">\n {tWithFallback(t, 'workspacesList.createButton', 'Create Workspace')}\n </span>\n <span className=\"sm:hidden\">\n {tWithFallback(t, 'workspacesList.createButtonShort', 'Create')}\n </span>\n </Button>\n </PermissionButton>\n </div>\n </div>\n\n {/* Search */}\n <div className=\"relative\" data-tour=\"workspaces-search-input\">\n <Search\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary\"\n size={20}\n />\n <Input\n placeholder={tWithFallback(t, 'workspacesList.searchPlaceholder', 'Search workspaces...')}\n value={searchValue}\n onChange={handleSearchChange}\n className=\"pl-[3.25rem]\"\n />\n </div>\n\n {/* Content */}\n <div className=\"space-y-4 sm:space-y-6\" data-tour=\"workspaces-view-container\">\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 <div key={i} className=\"space-y-3 p-6 border border-border-seam rounded-lg\">\n <div className=\"flex items-center gap-3\">\n <Skeleton className=\"size-12 rounded-lg\" />\n <div className=\"space-y-2 flex-1\">\n <Skeleton className=\"h-4 w-3/4\" />\n <Skeleton className=\"h-3 w-1/2\" />\n </div>\n </div>\n <Skeleton className=\"h-3 w-1/3\" />\n </div>\n ))}\n </div>\n ) : data?.items.length === 0 ? (\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 <Grid3X3 size={48} className=\"text-text-secondary\" />\n </div>\n <h3 className=\"text-lg font-semibold mb-2\">\n {tWithFallback(t, 'workspacesList.emptyTitle', 'No workspaces found')}\n </h3>\n <p className=\"text-text-secondary mb-4 max-w-sm\">\n {workspaceFilters.search\n ? tWithFallback(\n t,\n 'workspacesList.emptySearchHint',\n 'Try adjusting your search criteria'\n )\n : tWithFallback(\n t,\n 'workspacesList.emptyCreateHint',\n 'Get started by creating your first workspace'\n )}\n </p>\n {!workspaceFilters.search && (\n <PermissionButton\n permission={WORKSPACE_PERMISSIONS.WORKSPACE_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tWithFallback(\n t,\n 'workspacesList.createDeniedTooltip',\n \"You don't have permission to create workspaces\"\n )}\n >\n <Button onClick={handleCreateWorkspace} className=\"gap-2\">\n <Plus size={20} />\n {tWithFallback(\n t,\n 'workspacesList.createFirstButton',\n 'Create Your First Workspace'\n )}\n </Button>\n </PermissionButton>\n )}\n </div>\n ) : (\n <>\n {/* View */}\n {viewMode === 'list' ? (\n <div className=\"space-y-3\">\n {data?.items.map((workspace) => (\n <WorkspaceCard key={workspace.id} workspace={workspace} />\n ))}\n </div>\n ) : (\n <div className=\"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4\">\n {data?.items.map((workspace) => (\n <WorkspaceCard key={workspace.id} workspace={workspace} />\n ))}\n </div>\n )}\n\n {/* Pagination info */}\n {data && data.total > 0 && (\n <div className=\"flex items-center justify-between text-sm text-text-secondary\">\n <p>\n {tWithFallback(t, 'workspacesList.showing', 'Showing')} {data.items.length}{' '}\n {tWithFallback(t, 'workspacesList.of', 'of')} {data.total}{' '}\n {data.total !== 1\n ? tWithFallback(t, 'workspacesList.workspaces', 'workspaces')\n : tWithFallback(t, 'workspacesList.workspace', 'workspace')}\n </p>\n {data.hasMore && (\n <p className=\"text-xs\">\n {tWithFallback(\n t,\n 'workspacesList.moreAvailable',\n 'More workspaces available - pagination coming soon'\n )}\n </p>\n )}\n </div>\n )}\n </>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { tWithFallback as e } from "../../utils/i18n.js";
|
|
2
2
|
import { AlertCircle as t, WifiOff as n } from "lucide-react";
|
|
3
3
|
import { jsx as r, jsxs as i } from "react/jsx-runtime";
|
|
4
|
-
import {
|
|
4
|
+
import { CardContent as a, CardHeader as o, CardTitle as s, GlassCard as c, Skeleton as l } from "@burdenoff/fe-libs/ui";
|
|
5
5
|
import { useI18n as u } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
6
6
|
//#region src/pages/dashboard/WidgetWrapper.tsx
|
|
7
7
|
function d(e) {
|
|
@@ -49,15 +49,16 @@ function m() {
|
|
|
49
49
|
});
|
|
50
50
|
}
|
|
51
51
|
function h({ title: e, icon: t, size: n, isLoading: l, error: u, unavailable: h, headerAction: g, children: _, className: v = "" }) {
|
|
52
|
-
return /* @__PURE__ */ i(
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
return /* @__PURE__ */ i(c, {
|
|
53
|
+
treatment: "insight",
|
|
54
|
+
className: `${d(n)} border-border-seam bg-bg-surface ${v}`,
|
|
55
|
+
children: [/* @__PURE__ */ i(o, {
|
|
55
56
|
className: "flex flex-row items-center justify-between pb-2 gap-y-0",
|
|
56
|
-
children: [/* @__PURE__ */ i(
|
|
57
|
+
children: [/* @__PURE__ */ i(s, {
|
|
57
58
|
className: "text-sm font-semibold text-text-primary flex items-center gap-2",
|
|
58
59
|
children: [t, e]
|
|
59
60
|
}), g]
|
|
60
|
-
}), /* @__PURE__ */ r(
|
|
61
|
+
}), /* @__PURE__ */ r(a, {
|
|
61
62
|
className: "pt-0",
|
|
62
63
|
children: l ? /* @__PURE__ */ r(f, {}) : u ? /* @__PURE__ */ r(p, { message: u }) : h ? /* @__PURE__ */ r(m, {}) : _
|
|
63
64
|
})]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WidgetWrapper.js","names":[],"sources":["../../../src/pages/dashboard/WidgetWrapper.tsx"],"sourcesContent":["/**\n * WidgetWrapper — Common wrapper for all dashboard widgets.\n *\n * Provides consistent card styling, loading skeleton, error state,\n * and unavailable state.\n */\nimport { type ReactNode } from 'react';\nimport {
|
|
1
|
+
{"version":3,"file":"WidgetWrapper.js","names":[],"sources":["../../../src/pages/dashboard/WidgetWrapper.tsx"],"sourcesContent":["/**\n * WidgetWrapper — Common wrapper for all dashboard widgets.\n *\n * Provides consistent card styling, loading skeleton, error state,\n * and unavailable state.\n */\nimport { type ReactNode } from 'react';\nimport { GlassCard, CardContent, CardHeader, CardTitle } from '@burdenoff/fe-libs/ui';\nimport { Skeleton } from '@burdenoff/fe-libs/ui';\nimport { AlertCircle, WifiOff } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../../utils/i18n';\nimport type { WidgetSize } from './types';\n\ninterface WidgetWrapperProps {\n title: string;\n icon?: ReactNode;\n size: WidgetSize;\n isLoading?: boolean;\n error?: string | null;\n unavailable?: boolean;\n headerAction?: ReactNode;\n children: ReactNode;\n className?: string;\n}\n\nfunction getColSpan(size: WidgetSize): string {\n switch (size) {\n case 'large':\n return 'col-span-1 sm:col-span-2 lg:col-span-3';\n case 'medium':\n return 'col-span-1 sm:col-span-2 lg:col-span-2 xl:col-span-2 2xl:col-span-1';\n case 'small':\n return 'col-span-1';\n default:\n return 'col-span-1';\n }\n}\n\nfunction LoadingSkeleton() {\n return (\n <div className=\"space-y-3 p-1\">\n <Skeleton className=\"h-4 w-3/4\" />\n <Skeleton className=\"h-4 w-1/2\" />\n <Skeleton className=\"h-20 w-full\" />\n <Skeleton className=\"h-4 w-2/3\" />\n </div>\n );\n}\n\nfunction ErrorState({ message }: { message: string }) {\n return (\n <div className=\"flex flex-col items-center justify-center py-6 text-center\">\n <div className=\"rounded-full bg-status-error-bg/10 p-2 mb-2\">\n <AlertCircle className=\"size-5 text-status-error-text\" />\n </div>\n <p className=\"text-sm text-text-secondary\">{message}</p>\n </div>\n );\n}\n\nfunction UnavailableState() {\n const { t } = useI18n();\n\n return (\n <div className=\"flex flex-col items-center justify-center py-6 text-center\">\n <div className=\"rounded-full bg-bg-sunken p-2 mb-2\">\n <WifiOff className=\"size-5 text-text-secondary\" />\n </div>\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(t, 'widgetWrapper.serviceUnavailable', 'Service unavailable')}\n </p>\n </div>\n );\n}\n\nexport function WidgetWrapper({\n title,\n icon,\n size,\n isLoading,\n error,\n unavailable,\n headerAction,\n children,\n className = '',\n}: WidgetWrapperProps) {\n const colSpan = getColSpan(size);\n\n return (\n <GlassCard\n treatment=\"insight\"\n className={`${colSpan} border-border-seam bg-bg-surface ${className}`}\n >\n <CardHeader className=\"flex flex-row items-center justify-between pb-2 gap-y-0\">\n <CardTitle className=\"text-sm font-semibold text-text-primary flex items-center gap-2\">\n {icon}\n {title}\n </CardTitle>\n {headerAction}\n </CardHeader>\n <CardContent className=\"pt-0\">\n {isLoading ? (\n <LoadingSkeleton />\n ) : error ? (\n <ErrorState message={error} />\n ) : unavailable ? (\n <UnavailableState />\n ) : (\n children\n )}\n </CardContent>\n </GlassCard>\n );\n}\n"],"mappings":";;;;;;AA0BA,SAAS,EAAW,GAA0B;AAC5C,SAAQ,GAAR;EACE,KAAK,QACH,QAAO;EACT,KAAK,SACH,QAAO;EACT,KAAK,QACH,QAAO;EACT,QACE,QAAO;;;AAIb,SAAS,IAAkB;AACzB,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD,EAAU,WAAU,aAAc,CAAA;GAClC,kBAAC,GAAD,EAAU,WAAU,aAAc,CAAA;GAClC,kBAAC,GAAD,EAAU,WAAU,eAAgB,CAAA;GACpC,kBAAC,GAAD,EAAU,WAAU,aAAc,CAAA;GAC9B;;;AAIV,SAAS,EAAW,EAAE,cAAgC;AACpD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD,EAAa,WAAU,iCAAkC,CAAA;GACrD,CAAA,EACN,kBAAC,KAAD;GAAG,WAAU;aAA+B;GAAY,CAAA,CACpD;;;AAIV,SAAS,IAAmB;CAC1B,IAAM,EAAE,MAAM,GAAS;AAEvB,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD,EAAS,WAAU,8BAA+B,CAAA;GAC9C,CAAA,EACN,kBAAC,KAAD;GAAG,WAAU;aACV,EAAc,GAAG,oCAAoC,sBAAsB;GAC1E,CAAA,CACA;;;AAIV,SAAgB,EAAc,EAC5B,UACA,SACA,SACA,cACA,UACA,gBACA,iBACA,aACA,eAAY,MACS;AAGrB,QACE,kBAAC,GAAD;EACE,WAAU;EACV,WAAW,GALC,EAAW,EAAK,CAKN,oCAAoC;YAF5D,CAIE,kBAAC,GAAD;GAAY,WAAU;aAAtB,CACE,kBAAC,GAAD;IAAW,WAAU;cAArB,CACG,GACA,EACS;OACX,EACU;MACb,kBAAC,GAAD;GAAa,WAAU;aACpB,IACC,kBAAC,GAAD,EAAmB,CAAA,GACjB,IACF,kBAAC,GAAD,EAAY,SAAS,GAAS,CAAA,GAC5B,IACF,kBAAC,GAAD,EAAoB,CAAA,GAEpB;GAEU,CAAA,CACJ"}
|