@burdenoff/microfe-timecampus 2026.830.2 → 2026.904.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/timecampus/components/index.js +4 -4
- package/dist/timecampus/pages/HomePage.d.ts.map +1 -1
- package/dist/timecampus/pages/HomePage.js +149 -133
- package/dist/timecampus/pages/HomePage.js.map +1 -1
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js +7 -7
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js.map +1 -1
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js +7 -7
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js.map +1 -1
- package/dist/timecampus/pages/dashboard/ReportsPage.js +13 -13
- package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js +7 -7
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js +7 -7
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/focus-score/FocusScorePage.js +13 -13
- package/dist/timecampus/pages/geofencing/GeofencingPage.js +135 -135
- package/dist/timecampus/pages/geofencing/GeofencingPage.js.map +1 -1
- package/dist/timecampus/pages/invoicing/InvoicingPage.js +7 -7
- package/dist/timecampus/pages/invoicing/InvoicingPage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/DeadTimeRadarPage.js +8 -8
- package/dist/timecampus/pages/lifetime/FiniteLifePage.js +4 -4
- package/dist/timecampus/pages/lifetime/FiniteLifePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/LifeBalancePage.js +104 -104
- package/dist/timecampus/pages/lifetime/LifeBalancePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/LifeTimelinePage.js +7 -7
- package/dist/timecampus/pages/lifetime/LifeTimelinePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/PersonalCorporatePage.js +33 -33
- package/dist/timecampus/pages/lifetime/PersonalCorporatePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/RecommendationsPage.js +80 -80
- package/dist/timecampus/pages/lifetime/RecommendationsPage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/TimeWellSpentPage.js +16 -16
- package/dist/timecampus/pages/lifetime/ValuesGoalsPage.js +11 -11
- package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js +11 -11
- package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js +14 -14
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingsPage.js +12 -12
- package/dist/timecampus/pages/projects/ProjectsPage.js +5 -5
- package/dist/timecampus/pages/projects/ProjectsPage.js.map +1 -1
- package/dist/timecampus/pages/projects/TasksPage.js +8 -8
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js +8 -8
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamMembersPage.js +8 -8
- package/dist/timecampus/pages/team-activity/TeamActivityPage.js +13 -13
- package/dist/timecampus/pages/time/TimeEntriesPage.js +5 -5
- package/dist/timecampus/pages/time/TimeEntriesPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimeTrackingPage.js +138 -138
- package/dist/timecampus/pages/time/TimeTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimerPage.js +14 -14
- package/dist/timecampus/pages/time/TimerPage.js.map +1 -1
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js +26 -26
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js.map +1 -1
- package/dist/timecampus/pages/timeline/TimelinePage.js +8 -8
- package/dist/timecampus/pages/work-patterns/WorkPatternsPage.js +79 -79
- package/dist/timecampus/pages/work-patterns/WorkPatternsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocationTrackingPage.js","names":[],"sources":["../../../../src/timecampus/pages/location-tracking/LocationTrackingPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport {\n MapPin,\n Clock,\n Plus,\n Trash2,\n Building2,\n Satellite,\n} from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { StatusBadge } from \"../../components/StatusBadge\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport {\n ChartCard,\n DonutChart,\n BarsChart,\n ProgressMeter,\n type ChartDatum,\n} from \"../../components\";\nimport { formatHours, formatDate, formatTime } from \"../../utils/helpers\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useListLocationEntriesQuery,\n useGetLocationStatsQuery,\n useCreateLocationEntryMutation,\n useDeleteLocationEntryMutation,\n type LocationEntryFieldsFragment,\n type ListLocationEntriesQuery,\n} from \"@/generated/wspace-operations\";\n\nconst PAGE_SIZE = 25;\n\nconst inputClass =\n \"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\";\n\ninterface EntryFormState {\n address: string;\n latitude: string;\n longitude: string;\n startTime: string;\n endTime: string;\n}\n\nconst emptyForm: EntryFormState = {\n address: \"\",\n latitude: \"\",\n longitude: \"\",\n startTime: \"\",\n endTime: \"\",\n};\n\nexport function LocationTrackingPage() {\n const { currentUser } = useTimeCampus();\n\n const {\n data: entriesData,\n loading: entriesLoading,\n error: entriesError,\n refetch: refetchEntries,\n fetchMore,\n } = useListLocationEntriesQuery({\n variables: { pagination: { limit: PAGE_SIZE, offset: 0 } },\n });\n const {\n data: statsData,\n loading: statsLoading,\n error: statsError,\n refetch: refetchStats,\n } = useGetLocationStatsQuery();\n\n const [createEntry, { loading: creating, error: createError }] =\n useCreateLocationEntryMutation();\n const [deleteEntry, { loading: deleting }] = useDeleteLocationEntryMutation();\n\n const [formOpen, setFormOpen] = useState(false);\n const [form, setForm] = useState<EntryFormState>(emptyForm);\n const [confirmDelete, setConfirmDelete] =\n useState<LocationEntryFieldsFragment | null>(null);\n const [loadingMore, setLoadingMore] = useState(false);\n\n const locations = entriesData?.locationEntries?.items ?? [];\n const hasMore = entriesData?.locationEntries?.hasMore ?? false;\n const stats = statsData?.locationStats;\n\n // ---- Derived, real-data views over the loaded entries -------------------\n const geofencedCount = locations.filter(\n (l: LocationEntryFieldsFragment) => l.geofenceId,\n ).length;\n const manualCount = locations.length - geofencedCount;\n const sourceSlices = [\n { name: \"Geofenced\", value: geofencedCount },\n { name: \"Manual\", value: manualCount },\n ];\n\n const maxDuration = locations.reduce(\n (max: number, l: LocationEntryFieldsFragment) =>\n Math.max(max, l.duration ?? 0),\n 0,\n );\n\n const durationByAddress = new Map<string, number>();\n for (const l of locations as LocationEntryFieldsFragment[]) {\n if (!l.duration) continue;\n const key = l.address?.trim() || \"Unknown location\";\n durationByAddress.set(key, (durationByAddress.get(key) ?? 0) + l.duration);\n }\n const topLocations: ChartDatum[] = Array.from(durationByAddress.entries())\n .map(([location, seconds]) => ({ location, hours: seconds / 3600 }))\n .sort((a, b) => (b.hours as number) - (a.hours as number))\n .slice(0, 6);\n\n const openCreate = () => {\n const now = new Date();\n const localIso = new Date(now.getTime() - now.getTimezoneOffset() * 60000)\n .toISOString()\n .slice(0, 16);\n setForm({ ...emptyForm, startTime: localIso });\n setFormOpen(true);\n };\n\n const canSubmit =\n !!currentUser?.id &&\n form.latitude.trim() !== \"\" &&\n form.longitude.trim() !== \"\" &&\n form.startTime.trim() !== \"\";\n\n const handleSubmit = async () => {\n if (!canSubmit || !currentUser?.id) return;\n const startTime = new Date(form.startTime).toISOString();\n const endTime = form.endTime\n ? new Date(form.endTime).toISOString()\n : undefined;\n const duration = endTime\n ? Math.max(\n 0,\n Math.round(\n (new Date(endTime).getTime() - new Date(startTime).getTime()) /\n 1000,\n ),\n )\n : undefined;\n await createEntry({\n variables: {\n input: {\n userId: currentUser.id,\n latitude: Number(form.latitude),\n longitude: Number(form.longitude),\n address: form.address.trim() || undefined,\n startTime,\n endTime,\n duration,\n },\n },\n });\n setFormOpen(false);\n await Promise.all([refetchEntries(), refetchStats()]);\n };\n\n const handleDelete = async () => {\n if (!confirmDelete) return;\n await deleteEntry({ variables: { id: confirmDelete.id } });\n setConfirmDelete(null);\n await Promise.all([refetchEntries(), refetchStats()]);\n };\n\n const handleLoadMore = async () => {\n setLoadingMore(true);\n try {\n await fetchMore({\n variables: {\n pagination: { limit: PAGE_SIZE, offset: locations.length },\n },\n updateQuery: (\n prev: ListLocationEntriesQuery,\n { fetchMoreResult }: { fetchMoreResult?: ListLocationEntriesQuery },\n ) => {\n if (!fetchMoreResult) return prev;\n return {\n locationEntries: {\n ...fetchMoreResult.locationEntries,\n items: [\n ...prev.locationEntries.items,\n ...fetchMoreResult.locationEntries.items,\n ],\n },\n };\n },\n });\n } finally {\n setLoadingMore(false);\n }\n };\n\n const addButton = (\n <button\n onClick={openCreate}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />\n Add Location Entry\n </button>\n );\n\n if (entriesLoading && statsLoading) {\n return (\n <PageLayout\n title=\"Location Tracking\"\n description=\"Track work locations and productivity\"\n >\n <LoadingSkeleton variant=\"list\" count={4} />\n </PageLayout>\n );\n }\n\n if (entriesError && statsError) {\n return (\n <PageLayout\n title=\"Location Tracking\"\n description=\"Track work locations and productivity\"\n >\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n Failed to load location data. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Location Tracking\"\n description=\"Track work locations and productivity\"\n purpose=\"See where work actually happens — office, home, client sites — and how long you spend at each, so you can verify on-site time, support field teams, and back up location-based billing.\"\n actions={addButton}\n >\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Total Locations\"\n value={String(stats?.totalLocations ?? 0)}\n icon={<MapPin className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Unique Addresses\"\n value={String(stats?.uniqueAddresses ?? 0)}\n icon={<Building2 className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Most Frequent\"\n value={stats?.mostFrequentLocation ?? \"N/A\"}\n />\n <StatCard\n title=\"Total Duration\"\n value={formatHours((stats?.totalDuration ?? 0) / 3600)}\n icon={<Clock className=\"h-5 w-5\" />}\n description=\"Tracked time\"\n />\n </div>\n\n {locations.length === 0 && !entriesLoading ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No location entries found\"\n description=\"With location tracking enabled, the places you work from will show up here with time spent at each. You can also add an entry manually.\"\n action={addButton}\n />\n ) : (\n <>\n <div className=\"mt-6 grid grid-cols-1 gap-4 lg:grid-cols-2\">\n <ChartCard\n title=\"Tracking source\"\n description=\"How your loaded entries were captured\"\n >\n <DonutChart\n data={sourceSlices}\n centerLabel=\"Entries\"\n centerValue={locations.length}\n ariaLabel=\"Geofenced vs manual location entries\"\n emptyLabel=\"No entries to summarize yet\"\n />\n </ChartCard>\n <ChartCard\n title=\"Time by location\"\n description=\"Hours logged across your loaded entries\"\n >\n <BarsChart\n data={topLocations}\n xKey=\"location\"\n orientation=\"horizontal\"\n series={[{ key: \"hours\", label: \"Hours\" }]}\n categoryWidth={120}\n valueFormat={(v) => formatHours(v)}\n ariaLabel=\"Hours logged by location\"\n emptyLabel=\"No completed entries with a duration yet\"\n />\n </ChartCard>\n </div>\n\n <div className=\"mt-6 space-y-3\">\n {locations.map((location: LocationEntryFieldsFragment) => {\n const geofenced = Boolean(location.geofenceId);\n const durationSeconds = location.duration ?? 0;\n return (\n <div\n key={location.id}\n className=\"rounded-xl border border-border-seam bg-gloss-card p-4 shadow-pop transition-colors hover:bg-bg-sunken/50\"\n >\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"flex min-w-0 items-start gap-3\">\n <span className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n {geofenced ? (\n <Satellite className=\"h-5 w-5\" />\n ) : (\n <MapPin className=\"h-5 w-5\" />\n )}\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {location.address ?? \"Unknown Location\"}\n </p>\n <div className=\"mt-1 flex flex-wrap items-center gap-2 text-xs text-text-secondary\">\n <StatusBadge\n label={geofenced ? \"Geofenced\" : \"Manual\"}\n variant={geofenced ? \"info\" : \"muted\"}\n />\n <span>{formatDate(location.startTime)}</span>\n <span className=\"text-text-muted\">\n {formatTime(location.startTime)}\n {location.endTime\n ? ` – ${formatTime(location.endTime)}`\n : \"\"}\n </span>\n </div>\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n <div className=\"text-right\">\n <p className=\"text-sm font-semibold tabular-nums text-text-primary\">\n {location.duration\n ? formatHours(location.duration / 3600)\n : \"Active\"}\n </p>\n <p className=\"text-xs text-text-secondary\">duration</p>\n </div>\n <button\n onClick={() => setConfirmDelete(location)}\n aria-label=\"Delete entry\"\n className=\"rounded-md border border-border-subtle p-1.5 text-status-error-text hover:bg-status-error-bg\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n {location.duration && maxDuration > 0 ? (\n <div className=\"mt-3\">\n <ProgressMeter\n value={durationSeconds}\n max={maxDuration}\n tone=\"success\"\n showValue={false}\n size=\"sm\"\n />\n </div>\n ) : null}\n </div>\n );\n })}\n </div>\n {hasMore && (\n <div className=\"mt-6 flex justify-center\">\n <button\n onClick={handleLoadMore}\n disabled={loadingMore}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50\"\n >\n {loadingMore ? \"Loading...\" : \"Load more\"}\n </button>\n </div>\n )}\n </>\n )}\n\n <Modal\n open={formOpen}\n onClose={() => setFormOpen(false)}\n title=\"Add Location Entry\"\n description=\"Manually log a work location. Only you and people you share with can see this.\"\n size=\"lg\"\n footer={\n <>\n <button\n onClick={() => setFormOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleSubmit}\n disabled={creating || !canSubmit}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {creating ? \"Saving...\" : \"Add Entry\"}\n </button>\n </>\n }\n >\n {createError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to add location entry. Please check the values and try again.\n </div>\n )}\n {!currentUser?.id && (\n <div className=\"mb-4 rounded-lg border border-status-warning-bg/40 bg-status-warning-bg p-3 text-sm text-status-warning-text\">\n You must be signed in to add a location entry.\n </div>\n )}\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Address (optional)\n </label>\n <input\n type=\"text\"\n placeholder=\"e.g. Client site, Chennai\"\n value={form.address}\n onChange={(e) => setForm({ ...form, address: e.target.value })}\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Latitude\n </label>\n <input\n type=\"number\"\n step=\"any\"\n placeholder=\"0.000000\"\n value={form.latitude}\n onChange={(e) => setForm({ ...form, latitude: e.target.value })}\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Longitude\n </label>\n <input\n type=\"number\"\n step=\"any\"\n placeholder=\"0.000000\"\n value={form.longitude}\n onChange={(e) =>\n setForm({ ...form, longitude: e.target.value })\n }\n className={inputClass}\n />\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Start Time\n </label>\n <input\n type=\"datetime-local\"\n value={form.startTime}\n onChange={(e) =>\n setForm({ ...form, startTime: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n End Time (optional)\n </label>\n <input\n type=\"datetime-local\"\n value={form.endTime}\n onChange={(e) => setForm({ ...form, endTime: e.target.value })}\n className={inputClass}\n />\n </div>\n </div>\n </div>\n </Modal>\n\n <Modal\n open={confirmDelete !== null}\n onClose={() => setConfirmDelete(null)}\n title=\"Delete Location Entry\"\n description=\"This action cannot be undone.\"\n footer={\n <>\n <button\n onClick={() => setConfirmDelete(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleDelete}\n disabled={deleting}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/80 disabled:opacity-50\"\n >\n {deleting ? \"Deleting...\" : \"Delete\"}\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Are you sure you want to delete this location entry\n {confirmDelete?.address ? ` at ${confirmDelete.address}` : \"\"}?\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"LocationTrackingPage.js","names":[],"sources":["../../../../src/timecampus/pages/location-tracking/LocationTrackingPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport {\n MapPin,\n Clock,\n Plus,\n Trash2,\n Building2,\n Satellite,\n} from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { StatusBadge } from \"../../components/StatusBadge\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport {\n ChartCard,\n DonutChart,\n BarsChart,\n ProgressMeter,\n type ChartDatum,\n} from \"../../components\";\nimport { formatHours, formatDate, formatTime } from \"../../utils/helpers\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useListLocationEntriesQuery,\n useGetLocationStatsQuery,\n useCreateLocationEntryMutation,\n useDeleteLocationEntryMutation,\n type LocationEntryFieldsFragment,\n type ListLocationEntriesQuery,\n} from \"@/generated/wspace-operations\";\n\nconst PAGE_SIZE = 25;\n\nconst inputClass =\n \"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\";\n\ninterface EntryFormState {\n address: string;\n latitude: string;\n longitude: string;\n startTime: string;\n endTime: string;\n}\n\nconst emptyForm: EntryFormState = {\n address: \"\",\n latitude: \"\",\n longitude: \"\",\n startTime: \"\",\n endTime: \"\",\n};\n\nexport function LocationTrackingPage() {\n const { currentUser } = useTimeCampus();\n\n const {\n data: entriesData,\n loading: entriesLoading,\n error: entriesError,\n refetch: refetchEntries,\n fetchMore,\n } = useListLocationEntriesQuery({\n variables: { pagination: { limit: PAGE_SIZE, offset: 0 } },\n });\n const {\n data: statsData,\n loading: statsLoading,\n error: statsError,\n refetch: refetchStats,\n } = useGetLocationStatsQuery();\n\n const [createEntry, { loading: creating, error: createError }] =\n useCreateLocationEntryMutation();\n const [deleteEntry, { loading: deleting }] = useDeleteLocationEntryMutation();\n\n const [formOpen, setFormOpen] = useState(false);\n const [form, setForm] = useState<EntryFormState>(emptyForm);\n const [confirmDelete, setConfirmDelete] =\n useState<LocationEntryFieldsFragment | null>(null);\n const [loadingMore, setLoadingMore] = useState(false);\n\n const locations = entriesData?.locationEntries?.items ?? [];\n const hasMore = entriesData?.locationEntries?.hasMore ?? false;\n const stats = statsData?.locationStats;\n\n // ---- Derived, real-data views over the loaded entries -------------------\n const geofencedCount = locations.filter(\n (l: LocationEntryFieldsFragment) => l.geofenceId,\n ).length;\n const manualCount = locations.length - geofencedCount;\n const sourceSlices = [\n { name: \"Geofenced\", value: geofencedCount },\n { name: \"Manual\", value: manualCount },\n ];\n\n const maxDuration = locations.reduce(\n (max: number, l: LocationEntryFieldsFragment) =>\n Math.max(max, l.duration ?? 0),\n 0,\n );\n\n const durationByAddress = new Map<string, number>();\n for (const l of locations as LocationEntryFieldsFragment[]) {\n if (!l.duration) continue;\n const key = l.address?.trim() || \"Unknown location\";\n durationByAddress.set(key, (durationByAddress.get(key) ?? 0) + l.duration);\n }\n const topLocations: ChartDatum[] = Array.from(durationByAddress.entries())\n .map(([location, seconds]) => ({ location, hours: seconds / 3600 }))\n .sort((a, b) => (b.hours as number) - (a.hours as number))\n .slice(0, 6);\n\n const openCreate = () => {\n const now = new Date();\n const localIso = new Date(now.getTime() - now.getTimezoneOffset() * 60000)\n .toISOString()\n .slice(0, 16);\n setForm({ ...emptyForm, startTime: localIso });\n setFormOpen(true);\n };\n\n const canSubmit =\n !!currentUser?.id &&\n form.latitude.trim() !== \"\" &&\n form.longitude.trim() !== \"\" &&\n form.startTime.trim() !== \"\";\n\n const handleSubmit = async () => {\n if (!canSubmit || !currentUser?.id) return;\n const startTime = new Date(form.startTime).toISOString();\n const endTime = form.endTime\n ? new Date(form.endTime).toISOString()\n : undefined;\n const duration = endTime\n ? Math.max(\n 0,\n Math.round(\n (new Date(endTime).getTime() - new Date(startTime).getTime()) /\n 1000,\n ),\n )\n : undefined;\n await createEntry({\n variables: {\n input: {\n userId: currentUser.id,\n latitude: Number(form.latitude),\n longitude: Number(form.longitude),\n address: form.address.trim() || undefined,\n startTime,\n endTime,\n duration,\n },\n },\n });\n setFormOpen(false);\n await Promise.all([refetchEntries(), refetchStats()]);\n };\n\n const handleDelete = async () => {\n if (!confirmDelete) return;\n await deleteEntry({ variables: { id: confirmDelete.id } });\n setConfirmDelete(null);\n await Promise.all([refetchEntries(), refetchStats()]);\n };\n\n const handleLoadMore = async () => {\n setLoadingMore(true);\n try {\n await fetchMore({\n variables: {\n pagination: { limit: PAGE_SIZE, offset: locations.length },\n },\n updateQuery: (\n prev: ListLocationEntriesQuery,\n { fetchMoreResult }: { fetchMoreResult?: ListLocationEntriesQuery },\n ) => {\n if (!fetchMoreResult) return prev;\n return {\n locationEntries: {\n ...fetchMoreResult.locationEntries,\n items: [\n ...prev.locationEntries.items,\n ...fetchMoreResult.locationEntries.items,\n ],\n },\n };\n },\n });\n } finally {\n setLoadingMore(false);\n }\n };\n\n const addButton = (\n <button\n onClick={openCreate}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />\n Add Location Entry\n </button>\n );\n\n if (entriesLoading && statsLoading) {\n return (\n <PageLayout\n title=\"Location Tracking\"\n description=\"Track work locations and productivity\"\n >\n <LoadingSkeleton variant=\"list\" count={4} />\n </PageLayout>\n );\n }\n\n if (entriesError && statsError) {\n return (\n <PageLayout\n title=\"Location Tracking\"\n description=\"Track work locations and productivity\"\n >\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n Failed to load location data. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Location Tracking\"\n description=\"Track work locations and productivity\"\n purpose=\"See where work actually happens — office, home, client sites — and how long you spend at each, so you can verify on-site time, support field teams, and back up location-based billing.\"\n actions={addButton}\n >\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Total Locations\"\n value={String(stats?.totalLocations ?? 0)}\n icon={<MapPin className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Unique Addresses\"\n value={String(stats?.uniqueAddresses ?? 0)}\n icon={<Building2 className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Most Frequent\"\n value={stats?.mostFrequentLocation ?? \"N/A\"}\n />\n <StatCard\n title=\"Total Duration\"\n value={formatHours((stats?.totalDuration ?? 0) / 3600)}\n icon={<Clock className=\"h-5 w-5\" />}\n description=\"Tracked time\"\n />\n </div>\n\n {locations.length === 0 && !entriesLoading ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No location entries found\"\n description=\"With location tracking enabled, the places you work from will show up here with time spent at each. You can also add an entry manually.\"\n action={addButton}\n />\n ) : (\n <>\n <div className=\"mt-6 grid grid-cols-1 gap-4 lg:grid-cols-2\">\n <ChartCard\n title=\"Tracking source\"\n description=\"How your loaded entries were captured\"\n >\n <DonutChart\n data={sourceSlices}\n centerLabel=\"Entries\"\n centerValue={locations.length}\n ariaLabel=\"Geofenced vs manual location entries\"\n emptyLabel=\"No entries to summarize yet\"\n />\n </ChartCard>\n <ChartCard\n title=\"Time by location\"\n description=\"Hours logged across your loaded entries\"\n >\n <BarsChart\n data={topLocations}\n xKey=\"location\"\n orientation=\"horizontal\"\n series={[{ key: \"hours\", label: \"Hours\" }]}\n categoryWidth={120}\n valueFormat={(v) => formatHours(v)}\n ariaLabel=\"Hours logged by location\"\n emptyLabel=\"No completed entries with a duration yet\"\n />\n </ChartCard>\n </div>\n\n <div className=\"mt-6 space-y-3\">\n {locations.map((location: LocationEntryFieldsFragment) => {\n const geofenced = Boolean(location.geofenceId);\n const durationSeconds = location.duration ?? 0;\n return (\n <div\n key={location.id}\n className=\"rounded-xl border border-border-seam bg-gloss-card p-4 shadow-pop transition-colors hover:bg-bg-sunken/50\"\n >\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"flex min-w-0 items-start gap-3\">\n <span className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n {geofenced ? (\n <Satellite className=\"h-5 w-5\" />\n ) : (\n <MapPin className=\"h-5 w-5\" />\n )}\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {location.address ?? \"Unknown Location\"}\n </p>\n <div className=\"mt-1 flex flex-wrap items-center gap-2 text-xs text-text-secondary\">\n <StatusBadge\n label={geofenced ? \"Geofenced\" : \"Manual\"}\n variant={geofenced ? \"info\" : \"muted\"}\n />\n <span>{formatDate(location.startTime)}</span>\n <span className=\"text-text-muted\">\n {formatTime(location.startTime)}\n {location.endTime\n ? ` – ${formatTime(location.endTime)}`\n : \"\"}\n </span>\n </div>\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n <div className=\"text-right\">\n <p className=\"text-sm font-semibold tabular-nums text-text-primary\">\n {location.duration\n ? formatHours(location.duration / 3600)\n : \"Active\"}\n </p>\n <p className=\"text-xs text-text-secondary\">duration</p>\n </div>\n <button\n onClick={() => setConfirmDelete(location)}\n aria-label=\"Delete entry\"\n className=\"rounded-md border border-border-subtle p-1.5 text-status-error-text hover:bg-status-error-bg\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n {location.duration && maxDuration > 0 ? (\n <div className=\"mt-3\">\n <ProgressMeter\n value={durationSeconds}\n max={maxDuration}\n tone=\"success\"\n showValue={false}\n size=\"sm\"\n />\n </div>\n ) : null}\n </div>\n );\n })}\n </div>\n {hasMore && (\n <div className=\"mt-6 flex justify-center\">\n <button\n onClick={handleLoadMore}\n disabled={loadingMore}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50\"\n >\n {loadingMore ? \"Loading...\" : \"Load more\"}\n </button>\n </div>\n )}\n </>\n )}\n\n <Modal\n open={formOpen}\n onClose={() => setFormOpen(false)}\n title=\"Add Location Entry\"\n description=\"Manually log a work location. Only you and people you share with can see this.\"\n size=\"lg\"\n footer={\n <>\n <button\n onClick={() => setFormOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleSubmit}\n disabled={creating || !canSubmit}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {creating ? \"Saving...\" : \"Add Entry\"}\n </button>\n </>\n }\n >\n {createError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to add location entry. Please check the values and try again.\n </div>\n )}\n {!currentUser?.id && (\n <div className=\"mb-4 rounded-lg border border-status-warning-bg/40 bg-status-warning-bg p-3 text-sm text-status-warning-text\">\n You must be signed in to add a location entry.\n </div>\n )}\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Address (optional)\n </label>\n <input\n type=\"text\"\n placeholder=\"e.g. Client site, Chennai\"\n value={form.address}\n onChange={(e) => setForm({ ...form, address: e.target.value })}\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Latitude\n </label>\n <input\n type=\"number\"\n step=\"any\"\n placeholder=\"0.000000\"\n value={form.latitude}\n onChange={(e) => setForm({ ...form, latitude: e.target.value })}\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Longitude\n </label>\n <input\n type=\"number\"\n step=\"any\"\n placeholder=\"0.000000\"\n value={form.longitude}\n onChange={(e) =>\n setForm({ ...form, longitude: e.target.value })\n }\n className={inputClass}\n />\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Start Time\n </label>\n <input\n type=\"datetime-local\"\n value={form.startTime}\n onChange={(e) =>\n setForm({ ...form, startTime: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n End Time (optional)\n </label>\n <input\n type=\"datetime-local\"\n value={form.endTime}\n onChange={(e) => setForm({ ...form, endTime: e.target.value })}\n className={inputClass}\n />\n </div>\n </div>\n </div>\n </Modal>\n\n <Modal\n open={confirmDelete !== null}\n onClose={() => setConfirmDelete(null)}\n title=\"Delete Location Entry\"\n description=\"This action cannot be undone.\"\n footer={\n <>\n <button\n onClick={() => setConfirmDelete(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleDelete}\n disabled={deleting}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/80 disabled:opacity-50\"\n >\n {deleting ? \"Deleting...\" : \"Delete\"}\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Are you sure you want to delete this location entry\n {confirmDelete?.address ? ` at ${confirmDelete.address}` : \"\"}?\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -2,17 +2,17 @@ import { useTimeCampus as e, useTimeCampusNavigate as t } from "../../providers/
|
|
|
2
2
|
import { cn as ee, formatCurrency as te } from "../../utils/helpers.js";
|
|
3
3
|
import { StatCard as n } from "../../components/StatCard.js";
|
|
4
4
|
import { LoadingSkeleton as ne } from "../../components/LoadingSkeleton.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { EmptyState as r } from "../../components/EmptyState.js";
|
|
6
|
+
import { useAddParticipantMutation as i, useCompleteActionItemMutation as a, useCreateActionItemMutation as o, useDeleteActionItemMutation as s, useDeleteMeetingMutation as c, useGetMeetingQuery as l, useRateMeetingMutation as re, useRemoveParticipantMutation as ie, useUpdateActionItemMutation as ae, useUpdateMeetingMutation as oe, useUpdateParticipantStatusMutation as se } from "../../../generated/wspace-operations.js";
|
|
7
|
+
import { PageLayout as u } from "../../components/PageLayout.js";
|
|
7
8
|
import { StatusBadge as ce } from "../../components/StatusBadge.js";
|
|
8
|
-
import { EmptyState as u } from "../../components/EmptyState.js";
|
|
9
9
|
import { Modal as d } from "../../components/Modal.js";
|
|
10
10
|
import { MeetingActionItemStatus as f, MeetingParticipantStatus as p } from "../../../generated/wspace-types.js";
|
|
11
11
|
import { MeetingFormModal as le } from "./MeetingFormModal.js";
|
|
12
12
|
import { ParticipantFormModal as ue } from "./ParticipantFormModal.js";
|
|
13
13
|
import { ActionItemFormModal as de } from "./ActionItemFormModal.js";
|
|
14
14
|
import { useState as m } from "react";
|
|
15
|
-
import { CalendarDays as
|
|
15
|
+
import { CalendarDays as fe, Check as h, Clock as g, Pencil as _, Plus as v, Star as pe, Trash2 as y, UserMinus as b, Users as x } from "lucide-react";
|
|
16
16
|
import { Link as S, useParams as C } from "react-router";
|
|
17
17
|
import { Fragment as w, jsx as T, jsxs as E } from "react/jsx-runtime";
|
|
18
18
|
import { NextSteps as D } from "@burdenoff/fe-libs/ui";
|
|
@@ -36,19 +36,19 @@ function j(e) {
|
|
|
36
36
|
return t.charAt(0).toUpperCase() + t.slice(1);
|
|
37
37
|
}
|
|
38
38
|
function M() {
|
|
39
|
-
let { id: p } = C(), { workspaceId: M = "", currentUser: N } = e(), P = t(), F = p ?? "", { data: I, loading: L, error: R, refetch: z } =
|
|
39
|
+
let { id: p } = C(), { workspaceId: M = "", currentUser: N } = e(), P = t(), F = p ?? "", { data: I, loading: L, error: R, refetch: z } = l({
|
|
40
40
|
variables: {
|
|
41
41
|
id: F,
|
|
42
42
|
workspaceId: M
|
|
43
43
|
},
|
|
44
44
|
skip: !M || !p
|
|
45
|
-
}), [B, { loading: me, error: he }] = oe(), [ge, { loading: V }] =
|
|
46
|
-
if (L) return /* @__PURE__ */ T(
|
|
45
|
+
}), [B, { loading: me, error: he }] = oe(), [ge, { loading: V }] = c(), [_e, { loading: ve }] = re(), [ye, { loading: be, error: H }] = i(), [xe] = ie(), [Se] = se(), [Ce, { loading: we, error: Te }] = o(), [Ee, { loading: De, error: Oe }] = ae(), [ke] = a(), [Ae] = s(), [je, U] = m(!1), [Me, W] = m(!1), [Ne, G] = m(!1), [K, q] = m(null), [J, Y] = m(null), [X, Z] = m(null), Q = I?.meeting;
|
|
46
|
+
if (L) return /* @__PURE__ */ T(u, {
|
|
47
47
|
title: "Meeting Details",
|
|
48
48
|
description: `Meeting ID: ${p}`,
|
|
49
49
|
children: /* @__PURE__ */ T(ne, { variant: "detail" })
|
|
50
50
|
});
|
|
51
|
-
if (R || !Q) return /* @__PURE__ */ T(
|
|
51
|
+
if (R || !Q) return /* @__PURE__ */ T(u, {
|
|
52
52
|
title: "Meeting Details",
|
|
53
53
|
description: `Meeting ID: ${p}`,
|
|
54
54
|
children: /* @__PURE__ */ T("div", {
|
|
@@ -110,7 +110,7 @@ function M() {
|
|
|
110
110
|
workspaceId: M
|
|
111
111
|
} }), await z();
|
|
112
112
|
};
|
|
113
|
-
return /* @__PURE__ */ E(
|
|
113
|
+
return /* @__PURE__ */ E(u, {
|
|
114
114
|
title: "Meeting Details",
|
|
115
115
|
description: Q.title,
|
|
116
116
|
purpose: "The full record of one meeting — who attended, what it cost in people's time, and the action items it produced — so you can follow up on commitments and judge whether the meeting was worth it.",
|
|
@@ -146,7 +146,7 @@ function M() {
|
|
|
146
146
|
featured: !0,
|
|
147
147
|
title: "Duration",
|
|
148
148
|
value: `${Q.duration ?? 0} min`,
|
|
149
|
-
icon: /* @__PURE__ */ T(
|
|
149
|
+
icon: /* @__PURE__ */ T(g, { className: "h-5 w-5" })
|
|
150
150
|
}),
|
|
151
151
|
/* @__PURE__ */ T(n, {
|
|
152
152
|
title: "Participants",
|
|
@@ -168,7 +168,7 @@ function M() {
|
|
|
168
168
|
Q.calendarEventId && /* @__PURE__ */ E(S, {
|
|
169
169
|
to: `/calendar/event/${Q.calendarEventId}`,
|
|
170
170
|
className: "inline-flex w-fit items-center gap-1.5 rounded-full border border-border-subtle bg-bg-surface px-3 py-1.5 text-xs font-medium text-text-secondary hover:bg-bg-sunken/50 hover:text-text-primary",
|
|
171
|
-
children: [/* @__PURE__ */ T(
|
|
171
|
+
children: [/* @__PURE__ */ T(fe, { className: "h-3.5 w-3.5" }), "View in Calendar"]
|
|
172
172
|
}),
|
|
173
173
|
/* @__PURE__ */ E("div", {
|
|
174
174
|
className: "rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6",
|
|
@@ -254,7 +254,7 @@ function M() {
|
|
|
254
254
|
})]
|
|
255
255
|
})]
|
|
256
256
|
}, e.id))
|
|
257
|
-
}) : /* @__PURE__ */ T(
|
|
257
|
+
}) : /* @__PURE__ */ T(r, {
|
|
258
258
|
illustration: "empty-people",
|
|
259
259
|
title: "No participants added",
|
|
260
260
|
description: "Add attendees to capture who was in the room and what it cost.",
|
|
@@ -299,7 +299,7 @@ function M() {
|
|
|
299
299
|
onClick: () => Ue(e.id),
|
|
300
300
|
className: "rounded-md p-1 text-text-secondary hover:bg-status-success-bg hover:text-status-success-text",
|
|
301
301
|
"aria-label": "Complete action item",
|
|
302
|
-
children: /* @__PURE__ */ T(
|
|
302
|
+
children: /* @__PURE__ */ T(h, { className: "h-4 w-4" })
|
|
303
303
|
}),
|
|
304
304
|
/* @__PURE__ */ T("button", {
|
|
305
305
|
onClick: () => Y({
|
|
@@ -319,7 +319,7 @@ function M() {
|
|
|
319
319
|
]
|
|
320
320
|
})]
|
|
321
321
|
}, e.id))
|
|
322
|
-
}) : /* @__PURE__ */ T(
|
|
322
|
+
}) : /* @__PURE__ */ T(r, {
|
|
323
323
|
illustration: "empty-generic",
|
|
324
324
|
title: "No action items",
|
|
325
325
|
description: "Capture follow-ups here so commitments from the meeting don't get lost.",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MeetingDetailPage.js","names":[],"sources":["../../../../src/timecampus/pages/meetings/MeetingDetailPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useParams, Link } from \"react-router\";\nimport {\n Users,\n Clock,\n Pencil,\n Trash2,\n Plus,\n Star,\n Check,\n UserMinus,\n CalendarDays,\n} from \"lucide-react\";\nimport { NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { StatusBadge } from \"../../components/StatusBadge\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport { cn, type BadgeVariant } from \"../../utils/helpers\";\nimport {\n useTimeCampus,\n useTimeCampusNavigate,\n} from \"../../providers/TimeCampusProvider\";\nimport {\n useGetMeetingQuery,\n useUpdateMeetingMutation,\n useDeleteMeetingMutation,\n useRateMeetingMutation,\n useAddParticipantMutation,\n useRemoveParticipantMutation,\n useUpdateParticipantStatusMutation,\n useCreateActionItemMutation,\n useUpdateActionItemMutation,\n useCompleteActionItemMutation,\n useDeleteActionItemMutation,\n type MeetingParticipantFieldsFragment,\n type MeetingActionItemFieldsFragment,\n} from \"@/generated/wspace-operations\";\nimport {\n MeetingParticipantStatus,\n MeetingActionItemStatus,\n type UpdateMeetingInput,\n type AddMeetingParticipantInput,\n type CreateActionItemInput,\n type UpdateActionItemInput,\n} from \"@/generated/wspace-types\";\nimport { formatCurrency } from \"../../utils/helpers\";\nimport { MeetingFormModal } from \"./MeetingFormModal\";\nimport { ParticipantFormModal } from \"./ParticipantFormModal\";\nimport { ActionItemFormModal } from \"./ActionItemFormModal\";\n\nconst PARTICIPANT_STATUS_OPTIONS = [\n MeetingParticipantStatus.Pending,\n MeetingParticipantStatus.Accepted,\n MeetingParticipantStatus.Tentative,\n MeetingParticipantStatus.Declined,\n];\n\nconst ACTION_ITEM_VARIANT: Record<MeetingActionItemStatus, BadgeVariant> = {\n [MeetingActionItemStatus.Completed]: \"success\",\n [MeetingActionItemStatus.InProgress]: \"info\",\n [MeetingActionItemStatus.Pending]: \"warning\",\n [MeetingActionItemStatus.Cancelled]: \"muted\",\n};\n\n/** Deterministic 2-char monogram from an opaque participant id. */\nfunction idMonogram(id: string): string {\n return (\n id\n .replace(/[^a-zA-Z0-9]/g, \"\")\n .slice(0, 2)\n .toUpperCase() || \"?\"\n );\n}\n\nfunction prettyStatus(status: string): string {\n const lower = status.replace(/_/g, \" \").toLowerCase();\n return lower.charAt(0).toUpperCase() + lower.slice(1);\n}\n\nexport function MeetingDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { workspaceId = \"\", currentUser } = useTimeCampus();\n const navigate = useTimeCampusNavigate();\n const meetingId = id ?? \"\";\n\n const { data, loading, error, refetch } = useGetMeetingQuery({\n variables: { id: meetingId, workspaceId },\n skip: !workspaceId || !id,\n });\n\n const [updateMeeting, { loading: updating, error: updateError }] =\n useUpdateMeetingMutation();\n const [deleteMeeting, { loading: deletingMeeting }] =\n useDeleteMeetingMutation();\n const [rateMeeting, { loading: rating }] = useRateMeetingMutation();\n const [addParticipant, { loading: addingParticipant, error: addPError }] =\n useAddParticipantMutation();\n const [removeParticipant] = useRemoveParticipantMutation();\n const [updateParticipantStatus] = useUpdateParticipantStatusMutation();\n const [createActionItem, { loading: creatingItem, error: createItemError }] =\n useCreateActionItemMutation();\n const [updateActionItem, { loading: updatingItem, error: updateItemError }] =\n useUpdateActionItemMutation();\n const [completeActionItem] = useCompleteActionItemMutation();\n const [deleteActionItem] = useDeleteActionItemMutation();\n\n const [editOpen, setEditOpen] = useState(false);\n const [deleteOpen, setDeleteOpen] = useState(false);\n const [participantOpen, setParticipantOpen] = useState(false);\n const [removingParticipant, setRemovingParticipant] =\n useState<MeetingParticipantFieldsFragment | null>(null);\n const [actionItemModal, setActionItemModal] = useState<\n | { mode: \"create\" }\n | { mode: \"edit\"; item: MeetingActionItemFieldsFragment }\n | null\n >(null);\n const [deletingItem, setDeletingItem] =\n useState<MeetingActionItemFieldsFragment | null>(null);\n\n const meeting = data?.meeting;\n\n if (loading) {\n return (\n <PageLayout title=\"Meeting Details\" description={`Meeting ID: ${id}`}>\n <LoadingSkeleton variant=\"detail\" />\n </PageLayout>\n );\n }\n\n if (error || !meeting) {\n return (\n <PageLayout title=\"Meeting Details\" description={`Meeting ID: ${id}`}>\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n {error\n ? \"Failed to load meeting details. Please try again later.\"\n : \"Meeting not found.\"}\n </div>\n </PageLayout>\n );\n }\n\n const pendingActions = meeting.actionItems.filter(\n (a: MeetingActionItemFieldsFragment) => a.status !== \"COMPLETED\",\n ).length;\n const currentRating = meeting.rating ?? 0;\n\n const handleUpdateMeeting = async (input: UpdateMeetingInput) => {\n await updateMeeting({ variables: { id: meetingId, workspaceId, input } });\n await refetch();\n setEditOpen(false);\n };\n\n const handleDeleteMeeting = async () => {\n await deleteMeeting({ variables: { id: meetingId, workspaceId } });\n setDeleteOpen(false);\n navigate(\"/meetings\");\n };\n\n const handleRate = async (value: number) => {\n await rateMeeting({\n variables: { id: meetingId, workspaceId, rating: value },\n });\n await refetch();\n };\n\n const handleAddParticipant = async (input: AddMeetingParticipantInput) => {\n await addParticipant({\n variables: { meetingId, workspaceId, input },\n });\n await refetch();\n setParticipantOpen(false);\n };\n\n const handleRemoveParticipant = async () => {\n if (!removingParticipant) return;\n await removeParticipant({\n variables: {\n meetingId,\n participantId: removingParticipant.id,\n workspaceId,\n },\n });\n setRemovingParticipant(null);\n await refetch();\n };\n\n const handleParticipantStatus = async (\n participantId: string,\n status: MeetingParticipantStatus,\n ) => {\n await updateParticipantStatus({\n variables: { meetingId, participantId, workspaceId, status },\n });\n await refetch();\n };\n\n const handleCreateItem = async (input: CreateActionItemInput) => {\n await createActionItem({ variables: { meetingId, workspaceId, input } });\n await refetch();\n setActionItemModal(null);\n };\n\n const handleUpdateItem = async (\n itemId: string,\n input: UpdateActionItemInput,\n ) => {\n await updateActionItem({ variables: { id: itemId, workspaceId, input } });\n await refetch();\n setActionItemModal(null);\n };\n\n const handleCompleteItem = async (itemId: string) => {\n await completeActionItem({ variables: { id: itemId, workspaceId } });\n await refetch();\n };\n\n const handleDeleteItem = async () => {\n if (!deletingItem) return;\n await deleteActionItem({ variables: { id: deletingItem.id, workspaceId } });\n setDeletingItem(null);\n await refetch();\n };\n\n return (\n <PageLayout\n title=\"Meeting Details\"\n description={meeting.title}\n purpose=\"The full record of one meeting — who attended, what it cost in people's time, and the action items it produced — so you can follow up on commitments and judge whether the meeting was worth it.\"\n actions={\n <>\n <button\n onClick={() => setEditOpen(true)}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n <Pencil className=\"h-4 w-4\" />\n Edit\n </button>\n <button\n onClick={() => setDeleteOpen(true)}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/40\"\n >\n <Trash2 className=\"h-4 w-4\" />\n Delete\n </button>\n </>\n }\n >\n <NextSteps\n storageKey=\"timecampus-meeting-detail\"\n className=\"mb-6\"\n steps={[\n {\n id: \"all-meetings\",\n label: \"Back to all meetings\",\n description: \"Browse and schedule other meetings.\",\n onClick: () => navigate(\"/meetings\"),\n },\n {\n id: \"analytics\",\n label: \"See meeting analytics\",\n description: \"Understand your overall meeting load and cost.\",\n onClick: () => navigate(\"/meetings/analytics\"),\n },\n ]}\n />\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Duration\"\n value={`${meeting.duration ?? 0} min`}\n icon={<Clock className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Participants\"\n value={String(meeting.participants.length)}\n icon={<Users className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Cost\"\n value={meeting.cost != null ? formatCurrency(meeting.cost) : \"N/A\"}\n description=\"Based on participant rates\"\n />\n <StatCard\n title=\"Action Items\"\n value={String(meeting.actionItems.length)}\n description={`${pendingActions} pending`}\n />\n </div>\n\n {/* Calendar link — surfaces the wspace-calendar event auto-linked on\n create (MeetingMeta.calendarEventId). Hidden entirely when the\n meeting has no linked event (e.g. calendar-svc was unreachable at\n creation time), rather than showing a dead/broken state. */}\n {meeting.calendarEventId && (\n <Link\n to={`/calendar/event/${meeting.calendarEventId}`}\n className=\"inline-flex w-fit items-center gap-1.5 rounded-full border border-border-subtle bg-bg-surface px-3 py-1.5 text-xs font-medium text-text-secondary hover:bg-bg-sunken/50 hover:text-text-primary\"\n >\n <CalendarDays className=\"h-3.5 w-3.5\" />\n View in Calendar\n </Link>\n )}\n\n {/* Rating */}\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6\">\n <h3 className=\"mb-1 text-sm font-semibold text-text-primary\">\n Rate this meeting\n </h3>\n <p className=\"mb-3 text-xs text-text-secondary\">\n Was it worth the time? Your rating feeds meeting analytics.\n </p>\n <div className=\"flex items-center gap-1\">\n {[1, 2, 3, 4, 5].map((value) => (\n <button\n key={value}\n onClick={() => handleRate(value)}\n disabled={rating}\n aria-label={`Rate ${value} star${value > 1 ? \"s\" : \"\"}`}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken disabled:opacity-50\"\n >\n <Star\n className={cn(\n \"h-6 w-6\",\n value <= currentRating\n ? \"fill-status-warning-text text-status-warning-text\"\n : \"text-text-secondary\",\n )}\n />\n </button>\n ))}\n {currentRating > 0 && (\n <span className=\"ml-2 text-sm text-text-secondary\">\n {currentRating} / 5\n </span>\n )}\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n Participants\n </h3>\n <button\n onClick={() => setParticipantOpen(true)}\n className=\"inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-2.5 py-1.5 text-xs font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-3.5 w-3.5\" />\n Add\n </button>\n </div>\n {meeting.participants.length > 0 ? (\n <div className=\"space-y-2\">\n {meeting.participants.map(\n (p: MeetingParticipantFieldsFragment) => (\n <div\n key={p.id}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-subtle px-3 py-2\"\n >\n <div className=\"flex min-w-0 items-center gap-2.5\">\n <span className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-action-primary-bg/10 text-[11px] font-semibold text-primary\">\n {idMonogram(p.userId)}\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm text-text-primary\">\n {p.userId}\n </p>\n <p className=\"text-xs text-text-secondary\">{p.role}</p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <select\n value={p.status}\n onChange={(e) =>\n handleParticipantStatus(\n p.id,\n e.target.value as MeetingParticipantStatus,\n )\n }\n className=\"rounded-md border border-border-subtle bg-bg-surface px-2 py-1 text-xs text-text-primary\"\n aria-label=\"Participant status\"\n >\n {PARTICIPANT_STATUS_OPTIONS.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n <button\n onClick={() => setRemovingParticipant(p)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-status-error-bg hover:text-status-error-text\"\n aria-label=\"Remove participant\"\n >\n <UserMinus className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n ),\n )}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-people\"\n title=\"No participants added\"\n description=\"Add attendees to capture who was in the room and what it cost.\"\n action={\n <button\n onClick={() => setParticipantOpen(true)}\n className=\"inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />\n Add participant\n </button>\n }\n />\n )}\n </div>\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n Action Items\n </h3>\n <button\n onClick={() => setActionItemModal({ mode: \"create\" })}\n className=\"inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-2.5 py-1.5 text-xs font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-3.5 w-3.5\" />\n Add\n </button>\n </div>\n {meeting.actionItems.length > 0 ? (\n <div className=\"space-y-2\">\n {meeting.actionItems.map(\n (item: MeetingActionItemFieldsFragment) => (\n <div\n key={item.id}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-subtle px-3 py-2\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm text-text-primary\">\n {item.title}\n </p>\n <div className=\"mt-1\">\n <StatusBadge\n label={prettyStatus(item.status)}\n variant={ACTION_ITEM_VARIANT[item.status]}\n />\n </div>\n </div>\n <div className=\"flex items-center gap-1\">\n {item.status !== MeetingActionItemStatus.Completed && (\n <button\n onClick={() => handleCompleteItem(item.id)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-status-success-bg hover:text-status-success-text\"\n aria-label=\"Complete action item\"\n >\n <Check className=\"h-4 w-4\" />\n </button>\n )}\n <button\n onClick={() =>\n setActionItemModal({ mode: \"edit\", item })\n }\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n aria-label=\"Edit action item\"\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => setDeletingItem(item)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-status-error-bg hover:text-status-error-text\"\n aria-label=\"Delete action item\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n ),\n )}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No action items\"\n description=\"Capture follow-ups here so commitments from the meeting don't get lost.\"\n action={\n <button\n onClick={() => setActionItemModal({ mode: \"create\" })}\n className=\"inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />\n Add action item\n </button>\n }\n />\n )}\n </div>\n </div>\n\n {/* Edit meeting */}\n {editOpen && (\n <MeetingFormModal\n open\n onClose={() => setEditOpen(false)}\n workspaceId={workspaceId}\n meeting={meeting}\n submitting={updating}\n error={Boolean(updateError)}\n onUpdate={handleUpdateMeeting}\n />\n )}\n\n {/* Delete meeting */}\n <Modal\n open={deleteOpen}\n onClose={() => setDeleteOpen(false)}\n title=\"Delete meeting\"\n description=\"This permanently removes the meeting and its analytics.\"\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeleteOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleDeleteMeeting}\n disabled={deletingMeeting}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90 disabled:opacity-50\"\n >\n {deletingMeeting ? \"Deleting...\" : \"Delete\"}\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Are you sure you want to delete{\" \"}\n <span className=\"font-medium text-text-primary\">{meeting.title}</span>\n ? This action cannot be undone.\n </p>\n </Modal>\n\n {/* Add participant */}\n {participantOpen && (\n <ParticipantFormModal\n open\n onClose={() => setParticipantOpen(false)}\n defaultUserId={currentUser?.id}\n submitting={addingParticipant}\n error={Boolean(addPError)}\n onSubmit={handleAddParticipant}\n />\n )}\n\n {/* Remove participant confirm */}\n <Modal\n open={Boolean(removingParticipant)}\n onClose={() => setRemovingParticipant(null)}\n title=\"Remove participant\"\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setRemovingParticipant(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleRemoveParticipant}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90\"\n >\n Remove\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Remove{\" \"}\n <span className=\"font-medium text-text-primary\">\n {removingParticipant?.userId}\n </span>{\" \"}\n from this meeting?\n </p>\n </Modal>\n\n {/* Create / edit action item */}\n {actionItemModal && (\n <ActionItemFormModal\n open\n onClose={() => setActionItemModal(null)}\n item={\n actionItemModal.mode === \"edit\" ? actionItemModal.item : undefined\n }\n submitting={\n actionItemModal.mode === \"edit\" ? updatingItem : creatingItem\n }\n error={Boolean(\n actionItemModal.mode === \"edit\" ? updateItemError : createItemError,\n )}\n onCreate={handleCreateItem}\n onUpdate={\n actionItemModal.mode === \"edit\"\n ? (input) => handleUpdateItem(actionItemModal.item.id, input)\n : undefined\n }\n />\n )}\n\n {/* Delete action item confirm */}\n <Modal\n open={Boolean(deletingItem)}\n onClose={() => setDeletingItem(null)}\n title=\"Delete action item\"\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeletingItem(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleDeleteItem}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90\"\n >\n Delete\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Delete action item{\" \"}\n <span className=\"font-medium text-text-primary\">\n {deletingItem?.title}\n </span>\n ?\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MeetingDetailPage.js","names":[],"sources":["../../../../src/timecampus/pages/meetings/MeetingDetailPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useParams, Link } from \"react-router\";\nimport {\n Users,\n Clock,\n Pencil,\n Trash2,\n Plus,\n Star,\n Check,\n UserMinus,\n CalendarDays,\n} from \"lucide-react\";\nimport { NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { StatusBadge } from \"../../components/StatusBadge\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport { cn, type BadgeVariant } from \"../../utils/helpers\";\nimport {\n useTimeCampus,\n useTimeCampusNavigate,\n} from \"../../providers/TimeCampusProvider\";\nimport {\n useGetMeetingQuery,\n useUpdateMeetingMutation,\n useDeleteMeetingMutation,\n useRateMeetingMutation,\n useAddParticipantMutation,\n useRemoveParticipantMutation,\n useUpdateParticipantStatusMutation,\n useCreateActionItemMutation,\n useUpdateActionItemMutation,\n useCompleteActionItemMutation,\n useDeleteActionItemMutation,\n type MeetingParticipantFieldsFragment,\n type MeetingActionItemFieldsFragment,\n} from \"@/generated/wspace-operations\";\nimport {\n MeetingParticipantStatus,\n MeetingActionItemStatus,\n type UpdateMeetingInput,\n type AddMeetingParticipantInput,\n type CreateActionItemInput,\n type UpdateActionItemInput,\n} from \"@/generated/wspace-types\";\nimport { formatCurrency } from \"../../utils/helpers\";\nimport { MeetingFormModal } from \"./MeetingFormModal\";\nimport { ParticipantFormModal } from \"./ParticipantFormModal\";\nimport { ActionItemFormModal } from \"./ActionItemFormModal\";\n\nconst PARTICIPANT_STATUS_OPTIONS = [\n MeetingParticipantStatus.Pending,\n MeetingParticipantStatus.Accepted,\n MeetingParticipantStatus.Tentative,\n MeetingParticipantStatus.Declined,\n];\n\nconst ACTION_ITEM_VARIANT: Record<MeetingActionItemStatus, BadgeVariant> = {\n [MeetingActionItemStatus.Completed]: \"success\",\n [MeetingActionItemStatus.InProgress]: \"info\",\n [MeetingActionItemStatus.Pending]: \"warning\",\n [MeetingActionItemStatus.Cancelled]: \"muted\",\n};\n\n/** Deterministic 2-char monogram from an opaque participant id. */\nfunction idMonogram(id: string): string {\n return (\n id\n .replace(/[^a-zA-Z0-9]/g, \"\")\n .slice(0, 2)\n .toUpperCase() || \"?\"\n );\n}\n\nfunction prettyStatus(status: string): string {\n const lower = status.replace(/_/g, \" \").toLowerCase();\n return lower.charAt(0).toUpperCase() + lower.slice(1);\n}\n\nexport function MeetingDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { workspaceId = \"\", currentUser } = useTimeCampus();\n const navigate = useTimeCampusNavigate();\n const meetingId = id ?? \"\";\n\n const { data, loading, error, refetch } = useGetMeetingQuery({\n variables: { id: meetingId, workspaceId },\n skip: !workspaceId || !id,\n });\n\n const [updateMeeting, { loading: updating, error: updateError }] =\n useUpdateMeetingMutation();\n const [deleteMeeting, { loading: deletingMeeting }] =\n useDeleteMeetingMutation();\n const [rateMeeting, { loading: rating }] = useRateMeetingMutation();\n const [addParticipant, { loading: addingParticipant, error: addPError }] =\n useAddParticipantMutation();\n const [removeParticipant] = useRemoveParticipantMutation();\n const [updateParticipantStatus] = useUpdateParticipantStatusMutation();\n const [createActionItem, { loading: creatingItem, error: createItemError }] =\n useCreateActionItemMutation();\n const [updateActionItem, { loading: updatingItem, error: updateItemError }] =\n useUpdateActionItemMutation();\n const [completeActionItem] = useCompleteActionItemMutation();\n const [deleteActionItem] = useDeleteActionItemMutation();\n\n const [editOpen, setEditOpen] = useState(false);\n const [deleteOpen, setDeleteOpen] = useState(false);\n const [participantOpen, setParticipantOpen] = useState(false);\n const [removingParticipant, setRemovingParticipant] =\n useState<MeetingParticipantFieldsFragment | null>(null);\n const [actionItemModal, setActionItemModal] = useState<\n | { mode: \"create\" }\n | { mode: \"edit\"; item: MeetingActionItemFieldsFragment }\n | null\n >(null);\n const [deletingItem, setDeletingItem] =\n useState<MeetingActionItemFieldsFragment | null>(null);\n\n const meeting = data?.meeting;\n\n if (loading) {\n return (\n <PageLayout title=\"Meeting Details\" description={`Meeting ID: ${id}`}>\n <LoadingSkeleton variant=\"detail\" />\n </PageLayout>\n );\n }\n\n if (error || !meeting) {\n return (\n <PageLayout title=\"Meeting Details\" description={`Meeting ID: ${id}`}>\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n {error\n ? \"Failed to load meeting details. Please try again later.\"\n : \"Meeting not found.\"}\n </div>\n </PageLayout>\n );\n }\n\n const pendingActions = meeting.actionItems.filter(\n (a: MeetingActionItemFieldsFragment) => a.status !== \"COMPLETED\",\n ).length;\n const currentRating = meeting.rating ?? 0;\n\n const handleUpdateMeeting = async (input: UpdateMeetingInput) => {\n await updateMeeting({ variables: { id: meetingId, workspaceId, input } });\n await refetch();\n setEditOpen(false);\n };\n\n const handleDeleteMeeting = async () => {\n await deleteMeeting({ variables: { id: meetingId, workspaceId } });\n setDeleteOpen(false);\n navigate(\"/meetings\");\n };\n\n const handleRate = async (value: number) => {\n await rateMeeting({\n variables: { id: meetingId, workspaceId, rating: value },\n });\n await refetch();\n };\n\n const handleAddParticipant = async (input: AddMeetingParticipantInput) => {\n await addParticipant({\n variables: { meetingId, workspaceId, input },\n });\n await refetch();\n setParticipantOpen(false);\n };\n\n const handleRemoveParticipant = async () => {\n if (!removingParticipant) return;\n await removeParticipant({\n variables: {\n meetingId,\n participantId: removingParticipant.id,\n workspaceId,\n },\n });\n setRemovingParticipant(null);\n await refetch();\n };\n\n const handleParticipantStatus = async (\n participantId: string,\n status: MeetingParticipantStatus,\n ) => {\n await updateParticipantStatus({\n variables: { meetingId, participantId, workspaceId, status },\n });\n await refetch();\n };\n\n const handleCreateItem = async (input: CreateActionItemInput) => {\n await createActionItem({ variables: { meetingId, workspaceId, input } });\n await refetch();\n setActionItemModal(null);\n };\n\n const handleUpdateItem = async (\n itemId: string,\n input: UpdateActionItemInput,\n ) => {\n await updateActionItem({ variables: { id: itemId, workspaceId, input } });\n await refetch();\n setActionItemModal(null);\n };\n\n const handleCompleteItem = async (itemId: string) => {\n await completeActionItem({ variables: { id: itemId, workspaceId } });\n await refetch();\n };\n\n const handleDeleteItem = async () => {\n if (!deletingItem) return;\n await deleteActionItem({ variables: { id: deletingItem.id, workspaceId } });\n setDeletingItem(null);\n await refetch();\n };\n\n return (\n <PageLayout\n title=\"Meeting Details\"\n description={meeting.title}\n purpose=\"The full record of one meeting — who attended, what it cost in people's time, and the action items it produced — so you can follow up on commitments and judge whether the meeting was worth it.\"\n actions={\n <>\n <button\n onClick={() => setEditOpen(true)}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n <Pencil className=\"h-4 w-4\" />\n Edit\n </button>\n <button\n onClick={() => setDeleteOpen(true)}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/40\"\n >\n <Trash2 className=\"h-4 w-4\" />\n Delete\n </button>\n </>\n }\n >\n <NextSteps\n storageKey=\"timecampus-meeting-detail\"\n className=\"mb-6\"\n steps={[\n {\n id: \"all-meetings\",\n label: \"Back to all meetings\",\n description: \"Browse and schedule other meetings.\",\n onClick: () => navigate(\"/meetings\"),\n },\n {\n id: \"analytics\",\n label: \"See meeting analytics\",\n description: \"Understand your overall meeting load and cost.\",\n onClick: () => navigate(\"/meetings/analytics\"),\n },\n ]}\n />\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Duration\"\n value={`${meeting.duration ?? 0} min`}\n icon={<Clock className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Participants\"\n value={String(meeting.participants.length)}\n icon={<Users className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Cost\"\n value={meeting.cost != null ? formatCurrency(meeting.cost) : \"N/A\"}\n description=\"Based on participant rates\"\n />\n <StatCard\n title=\"Action Items\"\n value={String(meeting.actionItems.length)}\n description={`${pendingActions} pending`}\n />\n </div>\n\n {/* Calendar link — surfaces the wspace-calendar event auto-linked on\n create (MeetingMeta.calendarEventId). Hidden entirely when the\n meeting has no linked event (e.g. calendar-svc was unreachable at\n creation time), rather than showing a dead/broken state. */}\n {meeting.calendarEventId && (\n <Link\n to={`/calendar/event/${meeting.calendarEventId}`}\n className=\"inline-flex w-fit items-center gap-1.5 rounded-full border border-border-subtle bg-bg-surface px-3 py-1.5 text-xs font-medium text-text-secondary hover:bg-bg-sunken/50 hover:text-text-primary\"\n >\n <CalendarDays className=\"h-3.5 w-3.5\" />\n View in Calendar\n </Link>\n )}\n\n {/* Rating */}\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6\">\n <h3 className=\"mb-1 text-sm font-semibold text-text-primary\">\n Rate this meeting\n </h3>\n <p className=\"mb-3 text-xs text-text-secondary\">\n Was it worth the time? Your rating feeds meeting analytics.\n </p>\n <div className=\"flex items-center gap-1\">\n {[1, 2, 3, 4, 5].map((value) => (\n <button\n key={value}\n onClick={() => handleRate(value)}\n disabled={rating}\n aria-label={`Rate ${value} star${value > 1 ? \"s\" : \"\"}`}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken disabled:opacity-50\"\n >\n <Star\n className={cn(\n \"h-6 w-6\",\n value <= currentRating\n ? \"fill-status-warning-text text-status-warning-text\"\n : \"text-text-secondary\",\n )}\n />\n </button>\n ))}\n {currentRating > 0 && (\n <span className=\"ml-2 text-sm text-text-secondary\">\n {currentRating} / 5\n </span>\n )}\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n Participants\n </h3>\n <button\n onClick={() => setParticipantOpen(true)}\n className=\"inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-2.5 py-1.5 text-xs font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-3.5 w-3.5\" />\n Add\n </button>\n </div>\n {meeting.participants.length > 0 ? (\n <div className=\"space-y-2\">\n {meeting.participants.map(\n (p: MeetingParticipantFieldsFragment) => (\n <div\n key={p.id}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-subtle px-3 py-2\"\n >\n <div className=\"flex min-w-0 items-center gap-2.5\">\n <span className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-action-primary-bg/10 text-[11px] font-semibold text-primary\">\n {idMonogram(p.userId)}\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm text-text-primary\">\n {p.userId}\n </p>\n <p className=\"text-xs text-text-secondary\">{p.role}</p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <select\n value={p.status}\n onChange={(e) =>\n handleParticipantStatus(\n p.id,\n e.target.value as MeetingParticipantStatus,\n )\n }\n className=\"rounded-md border border-border-subtle bg-bg-surface px-2 py-1 text-xs text-text-primary\"\n aria-label=\"Participant status\"\n >\n {PARTICIPANT_STATUS_OPTIONS.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n <button\n onClick={() => setRemovingParticipant(p)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-status-error-bg hover:text-status-error-text\"\n aria-label=\"Remove participant\"\n >\n <UserMinus className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n ),\n )}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-people\"\n title=\"No participants added\"\n description=\"Add attendees to capture who was in the room and what it cost.\"\n action={\n <button\n onClick={() => setParticipantOpen(true)}\n className=\"inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />\n Add participant\n </button>\n }\n />\n )}\n </div>\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n Action Items\n </h3>\n <button\n onClick={() => setActionItemModal({ mode: \"create\" })}\n className=\"inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-2.5 py-1.5 text-xs font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-3.5 w-3.5\" />\n Add\n </button>\n </div>\n {meeting.actionItems.length > 0 ? (\n <div className=\"space-y-2\">\n {meeting.actionItems.map(\n (item: MeetingActionItemFieldsFragment) => (\n <div\n key={item.id}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-subtle px-3 py-2\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm text-text-primary\">\n {item.title}\n </p>\n <div className=\"mt-1\">\n <StatusBadge\n label={prettyStatus(item.status)}\n variant={ACTION_ITEM_VARIANT[item.status]}\n />\n </div>\n </div>\n <div className=\"flex items-center gap-1\">\n {item.status !== MeetingActionItemStatus.Completed && (\n <button\n onClick={() => handleCompleteItem(item.id)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-status-success-bg hover:text-status-success-text\"\n aria-label=\"Complete action item\"\n >\n <Check className=\"h-4 w-4\" />\n </button>\n )}\n <button\n onClick={() =>\n setActionItemModal({ mode: \"edit\", item })\n }\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n aria-label=\"Edit action item\"\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => setDeletingItem(item)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-status-error-bg hover:text-status-error-text\"\n aria-label=\"Delete action item\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n ),\n )}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No action items\"\n description=\"Capture follow-ups here so commitments from the meeting don't get lost.\"\n action={\n <button\n onClick={() => setActionItemModal({ mode: \"create\" })}\n className=\"inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />\n Add action item\n </button>\n }\n />\n )}\n </div>\n </div>\n\n {/* Edit meeting */}\n {editOpen && (\n <MeetingFormModal\n open\n onClose={() => setEditOpen(false)}\n workspaceId={workspaceId}\n meeting={meeting}\n submitting={updating}\n error={Boolean(updateError)}\n onUpdate={handleUpdateMeeting}\n />\n )}\n\n {/* Delete meeting */}\n <Modal\n open={deleteOpen}\n onClose={() => setDeleteOpen(false)}\n title=\"Delete meeting\"\n description=\"This permanently removes the meeting and its analytics.\"\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeleteOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleDeleteMeeting}\n disabled={deletingMeeting}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90 disabled:opacity-50\"\n >\n {deletingMeeting ? \"Deleting...\" : \"Delete\"}\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Are you sure you want to delete{\" \"}\n <span className=\"font-medium text-text-primary\">{meeting.title}</span>\n ? This action cannot be undone.\n </p>\n </Modal>\n\n {/* Add participant */}\n {participantOpen && (\n <ParticipantFormModal\n open\n onClose={() => setParticipantOpen(false)}\n defaultUserId={currentUser?.id}\n submitting={addingParticipant}\n error={Boolean(addPError)}\n onSubmit={handleAddParticipant}\n />\n )}\n\n {/* Remove participant confirm */}\n <Modal\n open={Boolean(removingParticipant)}\n onClose={() => setRemovingParticipant(null)}\n title=\"Remove participant\"\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setRemovingParticipant(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleRemoveParticipant}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90\"\n >\n Remove\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Remove{\" \"}\n <span className=\"font-medium text-text-primary\">\n {removingParticipant?.userId}\n </span>{\" \"}\n from this meeting?\n </p>\n </Modal>\n\n {/* Create / edit action item */}\n {actionItemModal && (\n <ActionItemFormModal\n open\n onClose={() => setActionItemModal(null)}\n item={\n actionItemModal.mode === \"edit\" ? actionItemModal.item : undefined\n }\n submitting={\n actionItemModal.mode === \"edit\" ? updatingItem : creatingItem\n }\n error={Boolean(\n actionItemModal.mode === \"edit\" ? updateItemError : createItemError,\n )}\n onCreate={handleCreateItem}\n onUpdate={\n actionItemModal.mode === \"edit\"\n ? (input) => handleUpdateItem(actionItemModal.item.id, input)\n : undefined\n }\n />\n )}\n\n {/* Delete action item confirm */}\n <Modal\n open={Boolean(deletingItem)}\n onClose={() => setDeletingItem(null)}\n title=\"Delete action item\"\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeletingItem(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleDeleteItem}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90\"\n >\n Delete\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Delete action item{\" \"}\n <span className=\"font-medium text-text-primary\">\n {deletingItem?.title}\n </span>\n ?\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { useTimeCampus as e, useTimeCampusNavigate as t } from "../../providers/TimeCampusProvider.js";
|
|
2
2
|
import { cn as n, formatCurrency as r, formatDate as i, formatTime as a } from "../../utils/helpers.js";
|
|
3
3
|
import { LoadingSkeleton as o } from "../../components/LoadingSkeleton.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
4
|
+
import { EmptyState as s } from "../../components/EmptyState.js";
|
|
5
|
+
import { useCreateMeetingMutation as c, useDeleteMeetingMutation as l, useListMeetingsQuery as u, useUpdateMeetingMutation as d } from "../../../generated/wspace-operations.js";
|
|
6
|
+
import { PageLayout as f } from "../../components/PageLayout.js";
|
|
7
|
+
import { StatusBadge as p } from "../../components/StatusBadge.js";
|
|
8
8
|
import { Modal as m } from "../../components/Modal.js";
|
|
9
9
|
import { MeetingType as h } from "../../../generated/wspace-types.js";
|
|
10
10
|
import { MeetingFormModal as g } from "./MeetingFormModal.js";
|
|
@@ -59,7 +59,7 @@ function M({ rating: e }) {
|
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
61
|
function N() {
|
|
62
|
-
let { workspaceId: n = "" } = e(), h = t(), [b, O] = _(D), [A, N] = _(!1), [P, F] = _(null), [I, L] = _(null), { data: R, loading: z, error: B, refetch: V, fetchMore: H } =
|
|
62
|
+
let { workspaceId: n = "" } = e(), h = t(), [b, O] = _(D), [A, N] = _(!1), [P, F] = _(null), [I, L] = _(null), { data: R, loading: z, error: B, refetch: V, fetchMore: H } = u({
|
|
63
63
|
variables: {
|
|
64
64
|
workspaceId: n,
|
|
65
65
|
pagination: {
|
|
@@ -69,7 +69,7 @@ function N() {
|
|
|
69
69
|
},
|
|
70
70
|
skip: !n,
|
|
71
71
|
notifyOnNetworkStatusChange: !0
|
|
72
|
-
}), [U, { loading: W, error: G }] =
|
|
72
|
+
}), [U, { loading: W, error: G }] = c(), [K, { loading: q, error: J }] = d(), [Y, { loading: X }] = l(), Z = R?.meetings ?? [], Q = Z.length >= b, te = async (e) => {
|
|
73
73
|
await U({ variables: { input: e } }), await V(), N(!1);
|
|
74
74
|
}, ne = async (e) => {
|
|
75
75
|
P && (await K({ variables: {
|
|
@@ -93,7 +93,7 @@ function N() {
|
|
|
93
93
|
className: "inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90",
|
|
94
94
|
children: [/* @__PURE__ */ w(y, { className: "h-4 w-4" }), "Schedule Meeting"]
|
|
95
95
|
});
|
|
96
|
-
return z && Z.length === 0 ? /* @__PURE__ */ w(
|
|
96
|
+
return z && Z.length === 0 ? /* @__PURE__ */ w(f, {
|
|
97
97
|
title: "Meetings",
|
|
98
98
|
description: "Schedule and track meetings",
|
|
99
99
|
actions: $,
|
|
@@ -101,20 +101,20 @@ function N() {
|
|
|
101
101
|
variant: "list",
|
|
102
102
|
count: 5
|
|
103
103
|
})
|
|
104
|
-
}) : B ? /* @__PURE__ */ w(
|
|
104
|
+
}) : B ? /* @__PURE__ */ w(f, {
|
|
105
105
|
title: "Meetings",
|
|
106
106
|
description: "Schedule and track meetings",
|
|
107
107
|
children: /* @__PURE__ */ w("div", {
|
|
108
108
|
className: "rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text",
|
|
109
109
|
children: "Failed to load meetings. Please try again later."
|
|
110
110
|
})
|
|
111
|
-
}) : /* @__PURE__ */ T(
|
|
111
|
+
}) : /* @__PURE__ */ T(f, {
|
|
112
112
|
title: "Meetings",
|
|
113
113
|
description: "Schedule and track meetings",
|
|
114
114
|
purpose: "Keep every meeting and its recording in one place and see how much of your week goes to them — so you can protect focus time, trim low-value calls, and account for meeting hours in your tracking.",
|
|
115
115
|
actions: $,
|
|
116
116
|
children: [
|
|
117
|
-
Z.length === 0 ? /* @__PURE__ */ w(
|
|
117
|
+
Z.length === 0 ? /* @__PURE__ */ w(s, {
|
|
118
118
|
illustration: "empty-data",
|
|
119
119
|
title: "No meetings found",
|
|
120
120
|
description: "Schedule your first meeting to start tracking calls and their time impact.",
|
|
@@ -166,10 +166,10 @@ function N() {
|
|
|
166
166
|
}),
|
|
167
167
|
/* @__PURE__ */ T("div", {
|
|
168
168
|
className: "mt-2 flex flex-wrap items-center gap-1.5",
|
|
169
|
-
children: [/* @__PURE__ */ w(
|
|
169
|
+
children: [/* @__PURE__ */ w(p, {
|
|
170
170
|
label: k(e.meetingType),
|
|
171
171
|
variant: "default"
|
|
172
|
-
}), /* @__PURE__ */ w(
|
|
172
|
+
}), /* @__PURE__ */ w(p, {
|
|
173
173
|
label: e.endTime ? "Completed" : "Scheduled",
|
|
174
174
|
variant: e.endTime ? "success" : "info",
|
|
175
175
|
dot: !0
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { useTimeCampusNavigate as e } from "../../providers/TimeCampusProvider.js";
|
|
2
2
|
import { formatHours as t, getStatusColor as n } from "../../utils/helpers.js";
|
|
3
3
|
import { LoadingSkeleton as ee } from "../../components/LoadingSkeleton.js";
|
|
4
|
-
import {
|
|
4
|
+
import { EmptyState as r } from "../../components/EmptyState.js";
|
|
5
|
+
import { useArchiveProjectMutation as i, useCreateProjectMutation as a, useDeleteProjectMutation as o, useListProjectsQuery as s, useUpdateProjectMutation as te } from "../../../generated/wspace-operations.js";
|
|
5
6
|
import { PageLayout as c } from "../../components/PageLayout.js";
|
|
6
|
-
import { EmptyState as te } from "../../components/EmptyState.js";
|
|
7
7
|
import { Modal as l } from "../../components/Modal.js";
|
|
8
8
|
import { useState as u } from "react";
|
|
9
9
|
import { Archive as d, Pencil as f, Plus as p, Trash2 as m } from "lucide-react";
|
|
@@ -27,13 +27,13 @@ function w() {
|
|
|
27
27
|
let { t: w } = v(), T = (e, t) => {
|
|
28
28
|
let n = w(e);
|
|
29
29
|
return n === e ? t : n;
|
|
30
|
-
}, E = e(), [D, O] = u(y), { data: k, loading: A, error: ne, refetch: j } =
|
|
30
|
+
}, E = e(), [D, O] = u(y), { data: k, loading: A, error: ne, refetch: j } = s({
|
|
31
31
|
variables: { pagination: {
|
|
32
32
|
limit: D,
|
|
33
33
|
offset: 0
|
|
34
34
|
} },
|
|
35
35
|
notifyOnNetworkStatusChange: !0
|
|
36
|
-
}), M = k?.projects?.items ?? [], N = k?.projects?.hasMore ?? !1, [P, F] = u(!1), [I, L] = u(null), [R, z] = u(null), [B, V] = u(x), [H, { loading: U, error: W }] =
|
|
36
|
+
}), M = k?.projects?.items ?? [], N = k?.projects?.hasMore ?? !1, [P, F] = u(!1), [I, L] = u(null), [R, z] = u(null), [B, V] = u(x), [H, { loading: U, error: W }] = a(), [G, { loading: K, error: q }] = te(), [J, { loading: Y }] = i(), [X, { loading: Z, error: re }] = o(), Q = () => {
|
|
37
37
|
V(x), F(!0);
|
|
38
38
|
}, ie = (e) => {
|
|
39
39
|
V({
|
|
@@ -178,7 +178,7 @@ function w() {
|
|
|
178
178
|
A && M.length === 0 ? /* @__PURE__ */ g(ee, {
|
|
179
179
|
variant: "card",
|
|
180
180
|
count: 4
|
|
181
|
-
}) : M.length === 0 ? /* @__PURE__ */ g(
|
|
181
|
+
}) : M.length === 0 ? /* @__PURE__ */ g(r, {
|
|
182
182
|
illustration: "empty-generic",
|
|
183
183
|
title: T("timecampus.projects.empty.title", "No projects yet"),
|
|
184
184
|
description: T("timecampus.projects.empty", "No projects yet. Create your first project to get started."),
|