@burdenoff/microfe-timecampus 2026.626.2 → 2026.703.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/generated/wspace-operations.js +996 -816
  2. package/dist/generated/wspace-operations.js.map +1 -1
  3. package/dist/generated/wspace-types.js +28 -0
  4. package/dist/generated/wspace-types.js.map +1 -0
  5. package/dist/index.js +4 -4
  6. package/dist/timecampus/TimeCampusRoutes.js +16 -12
  7. package/dist/timecampus/TimeCampusRoutes.js.map +1 -1
  8. package/dist/timecampus/components/Modal.js +65 -0
  9. package/dist/timecampus/components/Modal.js.map +1 -0
  10. package/dist/timecampus/components/StatusBadge.js +28 -0
  11. package/dist/timecampus/components/StatusBadge.js.map +1 -0
  12. package/dist/timecampus/components/Toast.js +87 -0
  13. package/dist/timecampus/components/Toast.js.map +1 -0
  14. package/dist/timecampus/index.js +1 -1
  15. package/dist/timecampus/pages/HomePage.js +143 -135
  16. package/dist/timecampus/pages/HomePage.js.map +1 -1
  17. package/dist/timecampus/pages/applets/AppletStorePage.js +14 -19
  18. package/dist/timecampus/pages/applets/AppletStorePage.js.map +1 -1
  19. package/dist/timecampus/pages/applets/InstalledAppletsPage.js +49 -37
  20. package/dist/timecampus/pages/applets/InstalledAppletsPage.js.map +1 -1
  21. package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js +315 -87
  22. package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js.map +1 -1
  23. package/dist/timecampus/pages/channels/ChannelsPage.js +340 -46
  24. package/dist/timecampus/pages/channels/ChannelsPage.js.map +1 -1
  25. package/dist/timecampus/pages/client-portal/ClientPortalPage.js +374 -92
  26. package/dist/timecampus/pages/client-portal/ClientPortalPage.js.map +1 -1
  27. package/dist/timecampus/pages/dashboard/ReportsPage.js +70 -37
  28. package/dist/timecampus/pages/dashboard/ReportsPage.js.map +1 -1
  29. package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js +366 -60
  30. package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js.map +1 -1
  31. package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js +461 -87
  32. package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js.map +1 -1
  33. package/dist/timecampus/pages/geofencing/GeofencingPage.js +314 -55
  34. package/dist/timecampus/pages/geofencing/GeofencingPage.js.map +1 -1
  35. package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js +30 -27
  36. package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js.map +1 -1
  37. package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js +269 -74
  38. package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js.map +1 -1
  39. package/dist/timecampus/pages/meetings/ActionItemFormModal.js +127 -0
  40. package/dist/timecampus/pages/meetings/ActionItemFormModal.js.map +1 -0
  41. package/dist/timecampus/pages/meetings/MeetingCreatePage.js +62 -52
  42. package/dist/timecampus/pages/meetings/MeetingCreatePage.js.map +1 -1
  43. package/dist/timecampus/pages/meetings/MeetingDetailPage.js +361 -74
  44. package/dist/timecampus/pages/meetings/MeetingDetailPage.js.map +1 -1
  45. package/dist/timecampus/pages/meetings/MeetingFormModal.js +156 -0
  46. package/dist/timecampus/pages/meetings/MeetingFormModal.js.map +1 -0
  47. package/dist/timecampus/pages/meetings/MeetingsPage.js +175 -74
  48. package/dist/timecampus/pages/meetings/MeetingsPage.js.map +1 -1
  49. package/dist/timecampus/pages/meetings/ParticipantFormModal.js +105 -0
  50. package/dist/timecampus/pages/meetings/ParticipantFormModal.js.map +1 -0
  51. package/dist/timecampus/pages/pomodoro/PomodoroPage.js +307 -67
  52. package/dist/timecampus/pages/pomodoro/PomodoroPage.js.map +1 -1
  53. package/dist/timecampus/pages/projects/ProjectCreatePage.js +51 -48
  54. package/dist/timecampus/pages/projects/ProjectCreatePage.js.map +1 -1
  55. package/dist/timecampus/pages/projects/ProjectDetailPage.js +248 -51
  56. package/dist/timecampus/pages/projects/ProjectDetailPage.js.map +1 -1
  57. package/dist/timecampus/pages/projects/ProjectsPage.js +354 -87
  58. package/dist/timecampus/pages/projects/ProjectsPage.js.map +1 -1
  59. package/dist/timecampus/pages/projects/TasksPage.js +367 -65
  60. package/dist/timecampus/pages/projects/TasksPage.js.map +1 -1
  61. package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js +674 -104
  62. package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js.map +1 -1
  63. package/dist/timecampus/pages/team/TeamAnalyticsPage.js +46 -34
  64. package/dist/timecampus/pages/team/TeamAnalyticsPage.js.map +1 -1
  65. package/dist/timecampus/pages/team/TeamMembersPage.js +93 -69
  66. package/dist/timecampus/pages/team/TeamMembersPage.js.map +1 -1
  67. package/dist/timecampus/pages/team/TeamPage.js +4 -3
  68. package/dist/timecampus/pages/team/TeamPage.js.map +1 -1
  69. package/dist/timecampus/pages/templates/TemplateDetailPage.js +72 -53
  70. package/dist/timecampus/pages/templates/TemplateDetailPage.js.map +1 -1
  71. package/dist/timecampus/pages/templates/TemplatesPage.js +40 -35
  72. package/dist/timecampus/pages/templates/TemplatesPage.js.map +1 -1
  73. package/dist/timecampus/pages/time/TimeEntriesPage.js +484 -97
  74. package/dist/timecampus/pages/time/TimeEntriesPage.js.map +1 -1
  75. package/dist/timecampus/pages/time/TimeTrackingPage.js +210 -99
  76. package/dist/timecampus/pages/time/TimeTrackingPage.js.map +1 -1
  77. package/dist/timecampus/pages/time/TimerPage.js +229 -97
  78. package/dist/timecampus/pages/time/TimerPage.js.map +1 -1
  79. package/dist/timecampus/pages/time-approval/TimeApprovalPage.js +315 -110
  80. package/dist/timecampus/pages/time-approval/TimeApprovalPage.js.map +1 -1
  81. package/dist/timecampus/pages/timeline/TimelinePage.js +250 -0
  82. package/dist/timecampus/pages/timeline/TimelinePage.js.map +1 -0
  83. package/dist/timecampus/pages/widgets/WidgetDetailPage.js +158 -76
  84. package/dist/timecampus/pages/widgets/WidgetDetailPage.js.map +1 -1
  85. package/dist/timecampus/pages/widgets/WidgetsPage.js +181 -52
  86. package/dist/timecampus/pages/widgets/WidgetsPage.js.map +1 -1
  87. package/dist/timecampus/pages/workflows/WorkflowsPage.js +212 -61
  88. package/dist/timecampus/pages/workflows/WorkflowsPage.js.map +1 -1
  89. package/dist/timecampus/store/timecampusStore.js +13 -1
  90. package/dist/timecampus/store/timecampusStore.js.map +1 -1
  91. package/dist/timecampus/utils/helpers.js +20 -10
  92. package/dist/timecampus/utils/helpers.js.map +1 -1
  93. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ExpenseTrackingPage.js","names":[],"sources":["../../../../src/timecampus/pages/expense-tracking/ExpenseTrackingPage.tsx"],"sourcesContent":["import { Receipt, Plus, DollarSign } from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport {\n getStatusColor,\n formatCurrency,\n formatDate,\n} from \"../../utils/helpers\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useListExpensesQuery,\n useGetExpenseStatsQuery,\n type ExpenseFieldsFragment,\n} from \"@/generated/wspace-operations\";\n\nexport function ExpenseTrackingPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n\n const {\n data: expensesData,\n loading: expensesLoading,\n error: expensesError,\n } = useListExpensesQuery({\n variables: { workspaceId },\n });\n const {\n data: statsData,\n loading: statsLoading,\n error: statsError,\n } = useGetExpenseStatsQuery({\n variables: { workspaceId },\n });\n\n const expenses = expensesData?.expenses ?? [];\n const stats = statsData?.expenseStats;\n\n if (expensesLoading && statsLoading) {\n return (\n <PageLayout\n title=\"Expense Tracking\"\n description=\"Track and manage work expenses\"\n >\n <LoadingSkeleton variant=\"list\" count={4} />\n </PageLayout>\n );\n }\n\n if (expensesError && statsError) {\n return (\n <PageLayout\n title=\"Expense Tracking\"\n description=\"Track and manage work expenses\"\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 expense data. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Expense Tracking\"\n description=\"Track and manage work expenses\"\n purpose=\"Capture project-related costs alongside the hours you log — receipts, categories and approvals in one place — so the bill you send a client reflects the full cost of the work, not just time.\"\n actions={\n <button 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 <Plus className=\"h-4 w-4\" />\n Add Expense\n </button>\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=\"Total This Month\"\n value={formatCurrency(stats?.totalAmount ?? 0)}\n icon={<DollarSign className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Pending\"\n value={String(stats?.pendingCount ?? 0)}\n description=\"expenses awaiting approval\"\n />\n <StatCard\n title=\"Approved\"\n value={String(stats?.approvedCount ?? 0)}\n description=\"expenses approved\"\n />\n <StatCard\n title=\"Average\"\n value={formatCurrency(stats?.averageAmount ?? 0)}\n icon={<Receipt className=\"h-5 w-5\" />}\n description=\"per expense\"\n />\n </div>\n\n {expenses.length === 0 && !expensesLoading ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No expenses recorded yet\"\n description=\"Add your first expense to track project costs and route them for approval.\"\n action={\n <button 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 <Plus className=\"h-4 w-4\" />\n Add Expense\n </button>\n }\n />\n ) : (\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop\">\n <div className=\"divide-y divide-border\">\n {expenses.map((expense: ExpenseFieldsFragment) => (\n <div\n key={expense.id}\n className=\"flex items-center justify-between p-4 hover:bg-bg-sunken/50 transition-colors\"\n >\n <div className=\"flex items-center gap-3\">\n <Receipt className=\"h-5 w-5 text-text-secondary\" />\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {expense.description ?? \"Untitled expense\"}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {expense.category} &middot;{\" \"}\n {expense.projectId ?? \"No project\"} &middot;{\" \"}\n {formatDate(expense.date)}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n <p className=\"text-sm font-medium text-text-primary\">\n {formatCurrency(expense.amount)}\n </p>\n <span\n className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${getStatusColor(expense.status)}`}\n >\n {expense.status}\n </span>\n </div>\n </div>\n ))}\n </div>\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;AAiBA,SAAgB,IAAsB;CACpC,IAAM,EAAE,iBAAc,OAAO,GAAe,EAEtC,EACJ,MAAM,GACN,SAAS,GACT,OAAO,MACL,EAAqB,EACvB,WAAW,EAAE,gBAAa,EAC3B,CAAC,EACI,EACJ,MAAM,GACN,SAAS,GACT,OAAO,MACL,EAAwB,EAC1B,WAAW,EAAE,gBAAa,EAC3B,CAAC,EAEI,IAAW,GAAc,YAAY,EAAE,EACvC,IAAQ,GAAW;AA0BzB,QAxBI,KAAmB,IAEnB,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;YAEZ,kBAAC,GAAD;GAAiB,SAAQ;GAAO,OAAO;GAAK,CAAA;EACjC,CAAA,GAIb,KAAiB,IAEjB,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;YAEZ,kBAAC,OAAD;GAAK,WAAU;aAAoG;GAE7G,CAAA;EACK,CAAA,GAKf,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SAAQ;EACR,SACE,kBAAC,UAAD;GAAQ,WAAU;aAAlB,CACE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,cAErB;;YARb,CAWE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,GAAD;KACE,UAAA;KACA,OAAM;KACN,OAAO,EAAe,GAAO,eAAe,EAAE;KAC9C,MAAM,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA;KACxC,CAAA;IACF,kBAAC,GAAD;KACE,OAAM;KACN,OAAO,OAAO,GAAO,gBAAgB,EAAE;KACvC,aAAY;KACZ,CAAA;IACF,kBAAC,GAAD;KACE,OAAM;KACN,OAAO,OAAO,GAAO,iBAAiB,EAAE;KACxC,aAAY;KACZ,CAAA;IACF,kBAAC,GAAD;KACE,OAAM;KACN,OAAO,EAAe,GAAO,iBAAiB,EAAE;KAChD,MAAM,kBAAC,GAAD,EAAS,WAAU,WAAY,CAAA;KACrC,aAAY;KACZ,CAAA;IACE;MAEL,EAAS,WAAW,KAAK,CAAC,IACzB,kBAAC,GAAD;GACE,cAAa;GACb,OAAM;GACN,aAAY;GACZ,QACE,kBAAC,UAAD;IAAQ,WAAU;cAAlB,CACE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,cAErB;;GAEX,CAAA,GAEF,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAS,KAAK,MACb,kBAAC,OAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD,EAAS,WAAU,+BAAgC,CAAA,EACnD,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAQ,eAAe;OACtB,CAAA,EACJ,kBAAC,KAAD;OAAG,WAAU;iBAAb;QACG,EAAQ;QAAS;QAAU;QAC3B,EAAQ,aAAa;QAAa;QAAU;QAC5C,EAAW,EAAQ,KAAK;QACvB;SACA,EAAA,CAAA,CACF;SACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAe,EAAQ,OAAO;OAC7B,CAAA,EACJ,kBAAC,QAAD;OACE,WAAW,4DAA4D,EAAe,EAAQ,OAAO;iBAEpG,EAAQ;OACJ,CAAA,CACH;QACF;OA1BC,EAAQ,GA0BT,CACN;IACE,CAAA;GACF,CAAA,CAEG"}
1
+ {"version":3,"file":"ExpenseTrackingPage.js","names":[],"sources":["../../../../src/timecampus/pages/expense-tracking/ExpenseTrackingPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport {\n Receipt,\n Plus,\n DollarSign,\n Pencil,\n Trash2,\n Send,\n Check,\n X,\n} from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { StatusBadge } from \"../../components/StatusBadge\";\nimport { Modal } from \"../../components/Modal\";\nimport { ToastContainer, useToast } from \"../../components/Toast\";\nimport { formatCurrency, formatDate } from \"../../utils/helpers\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useListExpensesQuery,\n useGetExpenseStatsQuery,\n useCreateExpenseMutation,\n useUpdateExpenseMutation,\n useDeleteExpenseMutation,\n useSubmitExpenseForApprovalMutation,\n useApproveExpenseMutation,\n useRejectExpenseMutation,\n type ExpenseFieldsFragment,\n} from \"@/generated/wspace-operations\";\nimport { ExpenseStatus } from \"@/generated/wspace-types\";\n\nconst PAGE_SIZE = 25;\n\nconst CATEGORY_OPTIONS = [\n \"Travel\",\n \"Meals\",\n \"Software\",\n \"Hardware\",\n \"Office\",\n \"Training\",\n \"Other\",\n];\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 ExpenseFormState {\n amount: string;\n category: string;\n description: string;\n projectId: string;\n date: string;\n}\n\nfunction todayIso(): string {\n return new Date().toISOString().slice(0, 10);\n}\n\nconst emptyForm: ExpenseFormState = {\n amount: \"\",\n category: CATEGORY_OPTIONS[0],\n description: \"\",\n projectId: \"\",\n date: todayIso(),\n};\n\nexport function ExpenseTrackingPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n const { toasts, addToast, dismissToast } = useToast();\n\n const {\n data: expensesData,\n loading: expensesLoading,\n error: expensesError,\n refetch,\n fetchMore,\n } = useListExpensesQuery({\n variables: { workspaceId, limit: PAGE_SIZE, offset: 0 },\n notifyOnNetworkStatusChange: true,\n });\n const {\n data: statsData,\n loading: statsLoading,\n error: statsError,\n refetch: refetchStats,\n } = useGetExpenseStatsQuery({\n variables: { workspaceId },\n });\n\n const [createExpense, { loading: creating }] = useCreateExpenseMutation();\n const [updateExpense, { loading: updating }] = useUpdateExpenseMutation();\n const [deleteExpense, { loading: deleting }] = useDeleteExpenseMutation();\n const [submitExpense] = useSubmitExpenseForApprovalMutation();\n const [approveExpense] = useApproveExpenseMutation();\n const [rejectExpense, { loading: rejecting }] = useRejectExpenseMutation();\n\n const expenses = expensesData?.expenses ?? [];\n const stats = statsData?.expenseStats;\n\n const [loadingMore, setLoadingMore] = useState(false);\n const [canLoadMore, setCanLoadMore] = useState(true);\n\n // Create / edit modal\n const [formOpen, setFormOpen] = useState(false);\n const [editing, setEditing] = useState<ExpenseFieldsFragment | null>(null);\n const [form, setForm] = useState<ExpenseFormState>(emptyForm);\n\n // Delete confirm\n const [deleteTarget, setDeleteTarget] =\n useState<ExpenseFieldsFragment | null>(null);\n\n // Reject reason\n const [rejectTarget, setRejectTarget] =\n useState<ExpenseFieldsFragment | null>(null);\n const [rejectReason, setRejectReason] = useState(\"\");\n\n // Per-row busy state for status transitions\n const [busyId, setBusyId] = useState<string | null>(null);\n\n const refreshAll = async () => {\n await Promise.all([refetch(), refetchStats()]);\n };\n\n const openCreate = () => {\n setEditing(null);\n setForm({ ...emptyForm, date: todayIso() });\n setFormOpen(true);\n };\n\n const openEdit = (expense: ExpenseFieldsFragment) => {\n setEditing(expense);\n setForm({\n amount: String(expense.amount),\n category: expense.category,\n description: expense.description ?? \"\",\n projectId: expense.projectId ?? \"\",\n date: expense.date ? expense.date.slice(0, 10) : todayIso(),\n });\n setFormOpen(true);\n };\n\n const closeForm = () => {\n setFormOpen(false);\n setEditing(null);\n };\n\n const handleSubmit = async () => {\n const amount = Number(form.amount);\n if (!form.amount || Number.isNaN(amount) || !form.category.trim()) return;\n const isoDate = new Date(form.date).toISOString();\n try {\n if (editing) {\n await updateExpense({\n variables: {\n id: editing.id,\n workspaceId,\n input: {\n amount,\n category: form.category.trim(),\n description: form.description.trim() || undefined,\n projectId: form.projectId.trim() || undefined,\n date: isoDate,\n },\n },\n });\n addToast({ title: \"Expense updated\", variant: \"success\" });\n } else {\n await createExpense({\n variables: {\n input: {\n workspaceId,\n amount,\n category: form.category.trim(),\n description: form.description.trim() || undefined,\n projectId: form.projectId.trim() || undefined,\n date: isoDate,\n },\n },\n });\n addToast({ title: \"Expense added\", variant: \"success\" });\n }\n closeForm();\n await refreshAll();\n } catch {\n addToast({\n title: editing ? \"Failed to update expense\" : \"Failed to add expense\",\n variant: \"error\",\n });\n }\n };\n\n const handleDelete = async () => {\n if (!deleteTarget) return;\n try {\n await deleteExpense({\n variables: { id: deleteTarget.id, workspaceId },\n });\n addToast({ title: \"Expense deleted\", variant: \"success\" });\n setDeleteTarget(null);\n await refreshAll();\n } catch {\n addToast({ title: \"Failed to delete expense\", variant: \"error\" });\n }\n };\n\n const handleSubmitForApproval = async (expense: ExpenseFieldsFragment) => {\n setBusyId(expense.id);\n try {\n await submitExpense({ variables: { id: expense.id, workspaceId } });\n addToast({ title: \"Submitted for approval\", variant: \"success\" });\n await refreshAll();\n } catch {\n addToast({ title: \"Failed to submit expense\", variant: \"error\" });\n } finally {\n setBusyId(null);\n }\n };\n\n const handleApprove = async (expense: ExpenseFieldsFragment) => {\n setBusyId(expense.id);\n try {\n await approveExpense({ variables: { id: expense.id, workspaceId } });\n addToast({ title: \"Expense approved\", variant: \"success\" });\n await refreshAll();\n } catch {\n addToast({ title: \"Failed to approve expense\", variant: \"error\" });\n } finally {\n setBusyId(null);\n }\n };\n\n const handleReject = async () => {\n if (!rejectTarget) return;\n try {\n await rejectExpense({\n variables: {\n id: rejectTarget.id,\n workspaceId,\n reason: rejectReason.trim() || undefined,\n },\n });\n addToast({ title: \"Expense rejected\", variant: \"success\" });\n setRejectTarget(null);\n setRejectReason(\"\");\n await refreshAll();\n } catch {\n addToast({ title: \"Failed to reject expense\", variant: \"error\" });\n }\n };\n\n const handleLoadMore = async () => {\n setLoadingMore(true);\n try {\n const res = await fetchMore({\n variables: { workspaceId, limit: PAGE_SIZE, offset: expenses.length },\n updateQuery: (prev, { fetchMoreResult }) => {\n if (!fetchMoreResult) return prev;\n return {\n ...prev,\n expenses: [...prev.expenses, ...fetchMoreResult.expenses],\n };\n },\n });\n if ((res.data?.expenses?.length ?? 0) < PAGE_SIZE) {\n setCanLoadMore(false);\n }\n } finally {\n setLoadingMore(false);\n }\n };\n\n if (expensesLoading && statsLoading && expenses.length === 0) {\n return (\n <PageLayout\n title=\"Expense Tracking\"\n description=\"Track and manage work expenses\"\n >\n <LoadingSkeleton variant=\"list\" count={4} />\n </PageLayout>\n );\n }\n\n if (expensesError && statsError) {\n return (\n <PageLayout\n title=\"Expense Tracking\"\n description=\"Track and manage work expenses\"\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 expense data. Please try again later.\n </div>\n </PageLayout>\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 Expense\n </button>\n );\n\n return (\n <PageLayout\n title=\"Expense Tracking\"\n description=\"Track and manage work expenses\"\n purpose=\"Capture project-related costs alongside the hours you log — receipts, categories and approvals in one place — so the bill you send a client reflects the full cost of the work, not just time.\"\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 This Month\"\n value={formatCurrency(stats?.totalAmount ?? 0)}\n icon={<DollarSign className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Pending\"\n value={String(stats?.pendingCount ?? 0)}\n description=\"expenses awaiting approval\"\n />\n <StatCard\n title=\"Approved\"\n value={String(stats?.approvedCount ?? 0)}\n description=\"expenses approved\"\n />\n <StatCard\n title=\"Average\"\n value={formatCurrency(stats?.averageAmount ?? 0)}\n icon={<Receipt className=\"h-5 w-5\" />}\n description=\"per expense\"\n />\n </div>\n\n {expenses.length === 0 && !expensesLoading ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No expenses recorded yet\"\n description=\"Add your first expense to track project costs and route them for approval.\"\n action={addButton}\n />\n ) : (\n <>\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop\">\n <div className=\"divide-y divide-border\">\n {expenses.map((expense: ExpenseFieldsFragment) => {\n const rowBusy = busyId === expense.id;\n const isPending = expense.status === ExpenseStatus.Pending;\n const isRejected = expense.status === ExpenseStatus.Rejected;\n return (\n <div\n key={expense.id}\n className=\"flex flex-col gap-3 p-4 hover:bg-bg-sunken/50 transition-colors sm:flex-row sm:items-center sm:justify-between\"\n >\n <div className=\"flex items-center gap-3\">\n <Receipt className=\"h-5 w-5 shrink-0 text-text-secondary\" />\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {expense.description ?? \"Untitled expense\"}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {expense.category} &middot;{\" \"}\n {expense.projectId ?? \"No project\"} &middot;{\" \"}\n {formatDate(expense.date)}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n <p className=\"text-sm font-medium text-text-primary\">\n {formatCurrency(expense.amount, expense.currency)}\n </p>\n <StatusBadge\n label={expense.status.toLowerCase()}\n status={expense.status.toLowerCase()}\n />\n <div className=\"flex items-center gap-1\">\n {isPending && (\n <>\n <button\n type=\"button\"\n disabled={rowBusy}\n onClick={() => handleApprove(expense)}\n title=\"Approve expense\"\n className=\"rounded-md p-1.5 text-status-success-text hover:bg-status-success-bg disabled:opacity-50\"\n >\n <Check className=\"h-4 w-4\" />\n </button>\n <button\n type=\"button\"\n disabled={rowBusy}\n onClick={() => {\n setRejectTarget(expense);\n setRejectReason(\"\");\n }}\n title=\"Reject expense\"\n className=\"rounded-md p-1.5 text-status-error-text hover:bg-status-error-bg disabled:opacity-50\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </>\n )}\n {isRejected && (\n <button\n type=\"button\"\n disabled={rowBusy}\n onClick={() => handleSubmitForApproval(expense)}\n title=\"Resubmit for approval\"\n className=\"rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken disabled:opacity-50\"\n >\n <Send className=\"h-4 w-4\" />\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => openEdit(expense)}\n title=\"Edit expense\"\n className=\"rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken\"\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setDeleteTarget(expense)}\n title=\"Delete expense\"\n className=\"rounded-md 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 </div>\n );\n })}\n </div>\n </div>\n\n {canLoadMore && expenses.length >= PAGE_SIZE && (\n <div className=\"mt-4 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={closeForm}\n title={editing ? \"Edit Expense\" : \"Add Expense\"}\n description={\n editing\n ? \"Update this expense's details.\"\n : \"Record a project-related cost to track alongside your logged hours.\"\n }\n footer={\n <>\n <button\n onClick={closeForm}\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={\n creating ||\n updating ||\n !form.amount ||\n Number.isNaN(Number(form.amount)) ||\n !form.category.trim()\n }\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 || updating\n ? \"Saving...\"\n : editing\n ? \"Save Changes\"\n : \"Add Expense\"}\n </button>\n </>\n }\n >\n <div className=\"space-y-4\">\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 Amount\n </label>\n <input\n type=\"number\"\n min={0}\n step=\"0.01\"\n placeholder=\"0.00\"\n value={form.amount}\n onChange={(e) => setForm({ ...form, amount: e.target.value })}\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Category\n </label>\n <select\n value={form.category}\n onChange={(e) => setForm({ ...form, category: e.target.value })}\n className={inputClass}\n >\n {CATEGORY_OPTIONS.map((c) => (\n <option key={c} value={c}>\n {c}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Description\n </label>\n <input\n type=\"text\"\n placeholder=\"What was this expense for?\"\n value={form.description}\n onChange={(e) =>\n setForm({ ...form, description: e.target.value })\n }\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 Project ID\n </label>\n <input\n type=\"text\"\n placeholder=\"Optional project ID\"\n value={form.projectId}\n onChange={(e) =>\n setForm({ ...form, projectId: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Date\n </label>\n <input\n type=\"date\"\n value={form.date}\n onChange={(e) => setForm({ ...form, date: e.target.value })}\n className={inputClass}\n />\n </div>\n </div>\n </div>\n </Modal>\n\n <Modal\n open={deleteTarget !== null}\n onClose={() => setDeleteTarget(null)}\n title=\"Delete expense?\"\n description={\n deleteTarget\n ? `${formatCurrency(deleteTarget.amount, deleteTarget.currency)} · ${deleteTarget.category} will be removed.`\n : undefined\n }\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeleteTarget(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 This permanently removes the expense. This action cannot be undone.\n </p>\n </Modal>\n\n <Modal\n open={rejectTarget !== null}\n onClose={() => setRejectTarget(null)}\n title=\"Reject expense\"\n description=\"Let the submitter know why this expense was rejected.\"\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setRejectTarget(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={handleReject}\n disabled={rejecting}\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 {rejecting ? \"Rejecting...\" : \"Reject Expense\"}\n </button>\n </>\n }\n >\n <label className=\"block text-sm font-medium text-text-primary\">\n Reason\n </label>\n <textarea\n rows={3}\n placeholder=\"Reason for rejection (optional)\"\n value={rejectReason}\n onChange={(e) => setRejectReason(e.target.value)}\n className={inputClass}\n />\n </Modal>\n\n <ToastContainer toasts={toasts} onDismiss={dismissToast} />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1,77 +1,336 @@
1
- import { useTimeCampus as e } from "../../providers/TimeCampusProvider.js";
2
- import { getStatusColor as t } from "../../utils/helpers.js";
3
- import { LoadingSkeleton as n } from "../../components/LoadingSkeleton.js";
4
- import { useListGeofencesQuery as r } from "../../../generated/wspace-operations.js";
5
- import { PageLayout as i } from "../../components/PageLayout.js";
6
- import { EmptyState as a } from "../../components/EmptyState.js";
7
- import { MapPin as o, Plus as s } from "lucide-react";
8
- import { jsx as c, jsxs as l } from "react/jsx-runtime";
1
+ import { getStatusColor as e } from "../../utils/helpers.js";
2
+ import { LoadingSkeleton as t } from "../../components/LoadingSkeleton.js";
3
+ import { useCreateGeofenceMutation as n, useDeleteGeofenceMutation as r, useListGeofencesQuery as i, useToggleGeofenceMutation as a, useUpdateGeofenceMutation as o } from "../../../generated/wspace-operations.js";
4
+ import { PageLayout as s } from "../../components/PageLayout.js";
5
+ import { EmptyState as c } from "../../components/EmptyState.js";
6
+ import { GeofenceTrigger as l } from "../../../generated/wspace-types.js";
7
+ import { Modal as u } from "../../components/Modal.js";
8
+ import { useState as d } from "react";
9
+ import { MapPin as f, Pencil as p, Plus as m, Power as h, Trash2 as g } from "lucide-react";
10
+ import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
9
11
  //#region src/timecampus/pages/geofencing/GeofencingPage.tsx
10
- function u() {
11
- let { workspaceId: u = "" } = e(), { data: d, loading: f, error: p } = r(), m = d?.geofences?.items ?? [];
12
- return f ? /* @__PURE__ */ c(i, {
12
+ var b = 25, x = {
13
+ name: "",
14
+ latitude: "",
15
+ longitude: "",
16
+ radius: "100",
17
+ trigger: l.Both,
18
+ address: "",
19
+ isActive: !0
20
+ }, S = "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";
21
+ function C() {
22
+ let { data: C, loading: w, error: T, refetch: E, fetchMore: D } = i({ variables: {
23
+ limit: b,
24
+ offset: 0
25
+ } }), [ee, { loading: O, error: k }] = n(), [A, { loading: j, error: M }] = o(), [N, { loading: P }] = r(), [F] = a(), [I, L] = d(!1), [R, z] = d(null), [B, V] = d(x), [H, U] = d(null), [W, G] = d(!1), K = C?.geofences?.items ?? [], q = C?.geofences?.hasMore ?? !1, J = () => {
26
+ z(null), V(x), L(!0);
27
+ }, Y = (e) => {
28
+ z(e), V({
29
+ name: e.name,
30
+ latitude: String(e.latitude),
31
+ longitude: String(e.longitude),
32
+ radius: String(e.radius),
33
+ trigger: e.trigger,
34
+ address: e.address ?? "",
35
+ isActive: e.isActive
36
+ }), L(!0);
37
+ }, X = async () => {
38
+ if (!B.name.trim()) return;
39
+ let e = {
40
+ name: B.name.trim(),
41
+ latitude: Number(B.latitude),
42
+ longitude: Number(B.longitude),
43
+ radius: Number(B.radius),
44
+ trigger: B.trigger,
45
+ address: B.address.trim() || void 0,
46
+ isActive: B.isActive
47
+ };
48
+ R ? await A({ variables: {
49
+ id: R.id,
50
+ input: e
51
+ } }) : await ee({ variables: { input: e } }), L(!1), await E();
52
+ }, Z = async () => {
53
+ H && (await N({ variables: { id: H.id } }), U(null), await E());
54
+ }, Q = async (e) => {
55
+ await F({ variables: {
56
+ id: e.id,
57
+ isActive: !e.isActive
58
+ } }), await E();
59
+ }, te = async () => {
60
+ G(!0);
61
+ try {
62
+ await D({
63
+ variables: {
64
+ limit: b,
65
+ offset: K.length
66
+ },
67
+ updateQuery: (e, { fetchMoreResult: t }) => t ? { geofences: {
68
+ ...t.geofences,
69
+ items: [...e.geofences.items, ...t.geofences.items]
70
+ } } : e
71
+ });
72
+ } finally {
73
+ G(!1);
74
+ }
75
+ }, $ = /* @__PURE__ */ y("button", {
76
+ onClick: J,
77
+ 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",
78
+ children: [/* @__PURE__ */ v(m, { className: "h-4 w-4" }), "Create Geofence"]
79
+ });
80
+ return w ? /* @__PURE__ */ v(s, {
13
81
  title: "Geofencing",
14
82
  description: "Set up location-based triggers and zones",
15
- children: /* @__PURE__ */ c(n, {
83
+ children: /* @__PURE__ */ v(t, {
16
84
  variant: "card",
17
85
  count: 4
18
86
  })
19
- }) : p ? /* @__PURE__ */ c(i, {
87
+ }) : T ? /* @__PURE__ */ v(s, {
20
88
  title: "Geofencing",
21
89
  description: "Set up location-based triggers and zones",
22
- children: /* @__PURE__ */ c("div", {
90
+ children: /* @__PURE__ */ v("div", {
23
91
  className: "rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text",
24
92
  children: "Failed to load geofences. Please try again later."
25
93
  })
26
- }) : /* @__PURE__ */ c(i, {
94
+ }) : /* @__PURE__ */ y(s, {
27
95
  title: "Geofencing",
28
96
  description: "Set up location-based triggers and zones",
29
97
  purpose: "Draw zones around your worksites — offices, client sites, job locations — and have time tracking start and stop automatically as people move in and out, so on-site hours record themselves.",
30
- actions: /* @__PURE__ */ l("button", {
31
- 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",
32
- children: [/* @__PURE__ */ c(s, { className: "h-4 w-4" }), "Create Geofence"]
33
- }),
34
- children: m.length === 0 ? /* @__PURE__ */ c(a, {
35
- illustration: "empty-generic",
36
- title: "No geofences configured yet",
37
- description: "Create your first zone to trigger automatic time tracking when people arrive or leave a location.",
38
- action: /* @__PURE__ */ l("button", {
39
- 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",
40
- children: [/* @__PURE__ */ c(s, { className: "h-4 w-4" }), "Create Geofence"]
41
- })
42
- }) : /* @__PURE__ */ c("div", {
43
- className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
44
- children: m.map((e) => /* @__PURE__ */ l("div", {
45
- className: "rounded-xl border border-border-seam bg-gloss-card shadow-pop p-5 transition-colors hover:bg-bg-sunken/50 cursor-pointer",
46
- children: [/* @__PURE__ */ l("div", {
47
- className: "flex items-start justify-between",
48
- children: [/* @__PURE__ */ l("div", {
49
- className: "flex items-center gap-2",
50
- children: [/* @__PURE__ */ c(o, { className: "h-5 w-5 text-text-secondary" }), /* @__PURE__ */ l("div", { children: [/* @__PURE__ */ c("p", {
51
- className: "text-sm font-semibold text-text-primary",
52
- children: e.name
53
- }), /* @__PURE__ */ l("p", {
54
- className: "text-xs text-text-secondary",
98
+ actions: $,
99
+ children: [
100
+ K.length === 0 ? /* @__PURE__ */ v(c, {
101
+ illustration: "empty-generic",
102
+ title: "No geofences configured yet",
103
+ description: "Create your first zone to trigger automatic time tracking when people arrive or leave a location.",
104
+ action: $
105
+ }) : /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v("div", {
106
+ className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
107
+ children: K.map((t) => /* @__PURE__ */ y("div", {
108
+ className: "flex flex-col rounded-xl border border-border-seam bg-gloss-card shadow-pop p-5 transition-colors hover:bg-bg-sunken/50",
109
+ children: [
110
+ /* @__PURE__ */ y("div", {
111
+ className: "flex items-start justify-between",
112
+ children: [/* @__PURE__ */ y("div", {
113
+ className: "flex items-center gap-2",
114
+ children: [/* @__PURE__ */ v(f, { className: "h-5 w-5 text-text-secondary" }), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("p", {
115
+ className: "text-sm font-semibold text-text-primary",
116
+ children: t.name
117
+ }), /* @__PURE__ */ y("p", {
118
+ className: "text-xs text-text-secondary",
119
+ children: [
120
+ t.trigger,
121
+ " · ",
122
+ t.radius,
123
+ "m radius"
124
+ ]
125
+ })] })]
126
+ }), /* @__PURE__ */ v("span", {
127
+ className: `inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${e(t.isActive ? "active" : "inactive")}`,
128
+ children: t.isActive ? "Active" : "Inactive"
129
+ })]
130
+ }),
131
+ /* @__PURE__ */ y("div", {
132
+ className: "mt-3 flex items-center justify-between text-xs text-text-secondary",
133
+ children: [/* @__PURE__ */ v("span", { children: t.address ?? "No address" }), /* @__PURE__ */ y("span", { children: [t.events?.length ?? 0, " events"] })]
134
+ }),
135
+ /* @__PURE__ */ y("div", {
136
+ className: "mt-4 flex items-center gap-2 border-t border-border-seam pt-3",
55
137
  children: [
56
- e.trigger,
57
- " · ",
58
- e.radius,
59
- "m radius"
138
+ /* @__PURE__ */ y("button", {
139
+ onClick: () => Q(t),
140
+ className: "inline-flex items-center gap-1 rounded-md border border-border-subtle px-2 py-1 text-xs font-medium text-text-primary hover:bg-bg-sunken/50",
141
+ children: [/* @__PURE__ */ v(h, { className: "h-3.5 w-3.5" }), t.isActive ? "Deactivate" : "Activate"]
142
+ }),
143
+ /* @__PURE__ */ y("button", {
144
+ onClick: () => Y(t),
145
+ className: "inline-flex items-center gap-1 rounded-md border border-border-subtle px-2 py-1 text-xs font-medium text-text-primary hover:bg-bg-sunken/50",
146
+ children: [/* @__PURE__ */ v(p, { className: "h-3.5 w-3.5" }), "Edit"]
147
+ }),
148
+ /* @__PURE__ */ y("button", {
149
+ onClick: () => U(t),
150
+ className: "inline-flex items-center gap-1 rounded-md border border-border-subtle px-2 py-1 text-xs font-medium text-status-error-text hover:bg-status-error-bg",
151
+ children: [/* @__PURE__ */ v(g, { className: "h-3.5 w-3.5" }), "Delete"]
152
+ })
60
153
  ]
61
- })] })]
62
- }), /* @__PURE__ */ c("span", {
63
- className: `inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${t(e.isActive ? "active" : "inactive")}`,
64
- children: e.isActive ? "Active" : "Inactive"
65
- })]
66
- }), /* @__PURE__ */ l("div", {
67
- className: "mt-3 flex items-center justify-between text-xs text-text-secondary",
68
- children: [/* @__PURE__ */ c("span", { children: e.address ?? "No address" }), /* @__PURE__ */ l("span", { children: [e.events?.length ?? 0, " events"] })]
154
+ })
155
+ ]
156
+ }, t.id))
157
+ }), q && /* @__PURE__ */ v("div", {
158
+ className: "mt-6 flex justify-center",
159
+ children: /* @__PURE__ */ v("button", {
160
+ onClick: te,
161
+ disabled: W,
162
+ 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",
163
+ children: W ? "Loading..." : "Load more"
164
+ })
165
+ })] }),
166
+ /* @__PURE__ */ y(u, {
167
+ open: I,
168
+ onClose: () => L(!1),
169
+ title: R ? "Edit Geofence" : "Create Geofence",
170
+ description: "Define a zone that triggers automatic time tracking.",
171
+ size: "lg",
172
+ footer: /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v("button", {
173
+ onClick: () => L(!1),
174
+ className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
175
+ children: "Cancel"
176
+ }), /* @__PURE__ */ v("button", {
177
+ onClick: X,
178
+ disabled: O || j || !B.name.trim(),
179
+ 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",
180
+ children: O || j ? "Saving..." : R ? "Save Changes" : "Create Geofence"
181
+ })] }),
182
+ children: [(k || M) && /* @__PURE__ */ v("div", {
183
+ className: "mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text",
184
+ children: "Failed to save geofence. Please check the values and try again."
185
+ }), /* @__PURE__ */ y("div", {
186
+ className: "space-y-4",
187
+ children: [
188
+ /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
189
+ className: "block text-sm font-medium text-text-primary",
190
+ children: "Name"
191
+ }), /* @__PURE__ */ v("input", {
192
+ type: "text",
193
+ placeholder: "e.g. Head Office",
194
+ value: B.name,
195
+ onChange: (e) => V({
196
+ ...B,
197
+ name: e.target.value
198
+ }),
199
+ className: S
200
+ })] }),
201
+ /* @__PURE__ */ y("div", {
202
+ className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
203
+ children: [/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
204
+ className: "block text-sm font-medium text-text-primary",
205
+ children: "Latitude"
206
+ }), /* @__PURE__ */ v("input", {
207
+ type: "number",
208
+ step: "any",
209
+ placeholder: "0.000000",
210
+ value: B.latitude,
211
+ onChange: (e) => V({
212
+ ...B,
213
+ latitude: e.target.value
214
+ }),
215
+ className: S
216
+ })] }), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
217
+ className: "block text-sm font-medium text-text-primary",
218
+ children: "Longitude"
219
+ }), /* @__PURE__ */ v("input", {
220
+ type: "number",
221
+ step: "any",
222
+ placeholder: "0.000000",
223
+ value: B.longitude,
224
+ onChange: (e) => V({
225
+ ...B,
226
+ longitude: e.target.value
227
+ }),
228
+ className: S
229
+ })] })]
230
+ }),
231
+ /* @__PURE__ */ y("div", {
232
+ className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
233
+ children: [/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
234
+ className: "block text-sm font-medium text-text-primary",
235
+ children: "Radius (meters)"
236
+ }), /* @__PURE__ */ v("input", {
237
+ type: "number",
238
+ placeholder: "100",
239
+ value: B.radius,
240
+ onChange: (e) => V({
241
+ ...B,
242
+ radius: e.target.value
243
+ }),
244
+ className: S
245
+ })] }), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
246
+ className: "block text-sm font-medium text-text-primary",
247
+ children: "Trigger"
248
+ }), /* @__PURE__ */ y("select", {
249
+ value: B.trigger,
250
+ onChange: (e) => V({
251
+ ...B,
252
+ trigger: e.target.value
253
+ }),
254
+ className: S,
255
+ children: [
256
+ /* @__PURE__ */ v("option", {
257
+ value: l.Enter,
258
+ children: "On entry"
259
+ }),
260
+ /* @__PURE__ */ v("option", {
261
+ value: l.Exit,
262
+ children: "On exit"
263
+ }),
264
+ /* @__PURE__ */ v("option", {
265
+ value: l.Both,
266
+ children: "Entry & exit"
267
+ })
268
+ ]
269
+ })] })]
270
+ }),
271
+ /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
272
+ className: "block text-sm font-medium text-text-primary",
273
+ children: "Address (optional)"
274
+ }), /* @__PURE__ */ v("input", {
275
+ type: "text",
276
+ placeholder: "Street address for reference",
277
+ value: B.address,
278
+ onChange: (e) => V({
279
+ ...B,
280
+ address: e.target.value
281
+ }),
282
+ className: S
283
+ })] }),
284
+ /* @__PURE__ */ y("label", {
285
+ className: "flex items-center justify-between",
286
+ children: [/* @__PURE__ */ v("span", {
287
+ className: "text-sm font-medium text-text-primary",
288
+ children: "Active"
289
+ }), /* @__PURE__ */ v("input", {
290
+ type: "checkbox",
291
+ checked: B.isActive,
292
+ onChange: (e) => V({
293
+ ...B,
294
+ isActive: e.target.checked
295
+ }),
296
+ className: "h-4 w-4 rounded border-border-subtle"
297
+ })]
298
+ })
299
+ ]
69
300
  })]
70
- }, e.id))
71
- })
301
+ }),
302
+ /* @__PURE__ */ v(u, {
303
+ open: H !== null,
304
+ onClose: () => U(null),
305
+ title: "Delete Geofence",
306
+ description: "This action cannot be undone.",
307
+ footer: /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ v("button", {
308
+ onClick: () => U(null),
309
+ className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
310
+ children: "Cancel"
311
+ }), /* @__PURE__ */ v("button", {
312
+ onClick: Z,
313
+ disabled: P,
314
+ 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",
315
+ children: P ? "Deleting..." : "Delete"
316
+ })] }),
317
+ children: /* @__PURE__ */ y("p", {
318
+ className: "text-sm text-text-secondary",
319
+ children: [
320
+ "Are you sure you want to delete",
321
+ " ",
322
+ /* @__PURE__ */ v("span", {
323
+ className: "font-medium text-text-primary",
324
+ children: H?.name
325
+ }),
326
+ "? Any automatic tracking tied to this zone will stop."
327
+ ]
328
+ })
329
+ })
330
+ ]
72
331
  });
73
332
  }
74
333
  //#endregion
75
- export { u as GeofencingPage };
334
+ export { C as GeofencingPage };
76
335
 
77
336
  //# sourceMappingURL=GeofencingPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"GeofencingPage.js","names":[],"sources":["../../../../src/timecampus/pages/geofencing/GeofencingPage.tsx"],"sourcesContent":["import { MapPin, Plus } from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { getStatusColor } from \"../../utils/helpers\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useListGeofencesQuery,\n type ListGeofencesQuery,\n} from \"@/generated/wspace-operations\";\n\ntype GeofenceItem = NonNullable<\n ListGeofencesQuery[\"geofences\"]\n>[\"items\"][number];\n\nexport function GeofencingPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n\n const { data, loading, error } = useListGeofencesQuery();\n\n const geofences = data?.geofences?.items ?? [];\n\n if (loading) {\n return (\n <PageLayout\n title=\"Geofencing\"\n description=\"Set up location-based triggers and zones\"\n >\n <LoadingSkeleton variant=\"card\" count={4} />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title=\"Geofencing\"\n description=\"Set up location-based triggers and zones\"\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 geofences. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Geofencing\"\n description=\"Set up location-based triggers and zones\"\n purpose=\"Draw zones around your worksites — offices, client sites, job locations — and have time tracking start and stop automatically as people move in and out, so on-site hours record themselves.\"\n actions={\n <button 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 <Plus className=\"h-4 w-4\" />\n Create Geofence\n </button>\n }\n >\n {geofences.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No geofences configured yet\"\n description=\"Create your first zone to trigger automatic time tracking when people arrive or leave a location.\"\n action={\n <button 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 <Plus className=\"h-4 w-4\" />\n Create Geofence\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {geofences.map((fence: GeofenceItem) => (\n <div\n key={fence.id}\n className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-5 transition-colors hover:bg-bg-sunken/50 cursor-pointer\"\n >\n <div className=\"flex items-start justify-between\">\n <div className=\"flex items-center gap-2\">\n <MapPin className=\"h-5 w-5 text-text-secondary\" />\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {fence.name}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {fence.trigger} &middot; {fence.radius}m radius\n </p>\n </div>\n </div>\n <span\n className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${getStatusColor(fence.isActive ? \"active\" : \"inactive\")}`}\n >\n {fence.isActive ? \"Active\" : \"Inactive\"}\n </span>\n </div>\n <div className=\"mt-3 flex items-center justify-between text-xs text-text-secondary\">\n <span>{fence.address ?? \"No address\"}</span>\n <span>{fence.events?.length ?? 0} events</span>\n </div>\n </div>\n ))}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;AAeA,SAAgB,IAAiB;CAC/B,IAAM,EAAE,iBAAc,OAAO,GAAe,EAEtC,EAAE,SAAM,YAAS,aAAU,GAAuB,EAElD,IAAY,GAAM,WAAW,SAAS,EAAE;AA0B9C,QAxBI,IAEA,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;YAEZ,kBAAC,GAAD;GAAiB,SAAQ;GAAO,OAAO;GAAK,CAAA;EACjC,CAAA,GAIb,IAEA,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;YAEZ,kBAAC,OAAD;GAAK,WAAU;aAAoG;GAE7G,CAAA;EACK,CAAA,GAKf,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SAAQ;EACR,SACE,kBAAC,UAAD;GAAQ,WAAU;aAAlB,CACE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,kBAErB;;YAGV,EAAU,WAAW,IACpB,kBAAC,GAAD;GACE,cAAa;GACb,OAAM;GACN,aAAY;GACZ,QACE,kBAAC,UAAD;IAAQ,WAAU;cAAlB,CACE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,kBAErB;;GAEX,CAAA,GAEF,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAU,KAAK,MACd,kBAAC,OAAD;IAEE,WAAU;cAFZ,CAIE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,+BAAgC,CAAA,EAClD,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAM;OACL,CAAA,EACJ,kBAAC,KAAD;OAAG,WAAU;iBAAb;QACG,EAAM;QAAQ;QAAW,EAAM;QAAO;QACrC;SACA,EAAA,CAAA,CACF;SACN,kBAAC,QAAD;MACE,WAAW,4DAA4D,EAAe,EAAM,WAAW,WAAW,WAAW;gBAE5H,EAAM,WAAW,WAAW;MACxB,CAAA,CACH;QACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD,EAAA,UAAO,EAAM,WAAW,cAAoB,CAAA,EAC5C,kBAAC,QAAD,EAAA,UAAA,CAAO,EAAM,QAAQ,UAAU,GAAE,UAAc,EAAA,CAAA,CAC3C;OACF;MAzBC,EAAM,GAyBP,CACN;GACE,CAAA;EAEG,CAAA"}
1
+ {"version":3,"file":"GeofencingPage.js","names":[],"sources":["../../../../src/timecampus/pages/geofencing/GeofencingPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { MapPin, Plus, Pencil, Trash2, Power } from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport { getStatusColor } from \"../../utils/helpers\";\nimport {\n useListGeofencesQuery,\n useCreateGeofenceMutation,\n useUpdateGeofenceMutation,\n useDeleteGeofenceMutation,\n useToggleGeofenceMutation,\n type ListGeofencesQuery,\n} from \"@/generated/wspace-operations\";\nimport { GeofenceTrigger } from \"@/generated/wspace-types\";\n\ntype GeofenceItem = NonNullable<\n ListGeofencesQuery[\"geofences\"]\n>[\"items\"][number];\n\nconst PAGE_SIZE = 25;\n\ninterface GeofenceFormState {\n name: string;\n latitude: string;\n longitude: string;\n radius: string;\n trigger: GeofenceTrigger;\n address: string;\n isActive: boolean;\n}\n\nconst emptyForm: GeofenceFormState = {\n name: \"\",\n latitude: \"\",\n longitude: \"\",\n radius: \"100\",\n trigger: GeofenceTrigger.Both,\n address: \"\",\n isActive: true,\n};\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\nexport function GeofencingPage() {\n const { data, loading, error, refetch, fetchMore } = useListGeofencesQuery({\n variables: { limit: PAGE_SIZE, offset: 0 },\n });\n\n const [createGeofence, { loading: creating, error: createError }] =\n useCreateGeofenceMutation();\n const [updateGeofence, { loading: updating, error: updateError }] =\n useUpdateGeofenceMutation();\n const [deleteGeofence, { loading: deleting }] = useDeleteGeofenceMutation();\n const [toggleGeofence] = useToggleGeofenceMutation();\n\n const [formOpen, setFormOpen] = useState(false);\n const [editing, setEditing] = useState<GeofenceItem | null>(null);\n const [form, setForm] = useState<GeofenceFormState>(emptyForm);\n const [confirmDelete, setConfirmDelete] = useState<GeofenceItem | null>(null);\n const [loadingMore, setLoadingMore] = useState(false);\n\n const geofences = data?.geofences?.items ?? [];\n const hasMore = data?.geofences?.hasMore ?? false;\n\n const openCreate = () => {\n setEditing(null);\n setForm(emptyForm);\n setFormOpen(true);\n };\n\n const openEdit = (fence: GeofenceItem) => {\n setEditing(fence);\n setForm({\n name: fence.name,\n latitude: String(fence.latitude),\n longitude: String(fence.longitude),\n radius: String(fence.radius),\n trigger: fence.trigger,\n address: fence.address ?? \"\",\n isActive: fence.isActive,\n });\n setFormOpen(true);\n };\n\n const handleSubmit = async () => {\n if (!form.name.trim()) return;\n const base = {\n name: form.name.trim(),\n latitude: Number(form.latitude),\n longitude: Number(form.longitude),\n radius: Number(form.radius),\n trigger: form.trigger,\n address: form.address.trim() || undefined,\n isActive: form.isActive,\n };\n if (editing) {\n await updateGeofence({ variables: { id: editing.id, input: base } });\n } else {\n await createGeofence({ variables: { input: base } });\n }\n setFormOpen(false);\n await refetch();\n };\n\n const handleDelete = async () => {\n if (!confirmDelete) return;\n await deleteGeofence({ variables: { id: confirmDelete.id } });\n setConfirmDelete(null);\n await refetch();\n };\n\n const handleToggle = async (fence: GeofenceItem) => {\n await toggleGeofence({\n variables: { id: fence.id, isActive: !fence.isActive },\n });\n await refetch();\n };\n\n const handleLoadMore = async () => {\n setLoadingMore(true);\n try {\n await fetchMore({\n variables: { limit: PAGE_SIZE, offset: geofences.length },\n updateQuery: (prev, { fetchMoreResult }) => {\n if (!fetchMoreResult) return prev;\n return {\n geofences: {\n ...fetchMoreResult.geofences,\n items: [\n ...prev.geofences.items,\n ...fetchMoreResult.geofences.items,\n ],\n },\n };\n },\n });\n } finally {\n setLoadingMore(false);\n }\n };\n\n const createButton = (\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 Create Geofence\n </button>\n );\n\n if (loading) {\n return (\n <PageLayout\n title=\"Geofencing\"\n description=\"Set up location-based triggers and zones\"\n >\n <LoadingSkeleton variant=\"card\" count={4} />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title=\"Geofencing\"\n description=\"Set up location-based triggers and zones\"\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 geofences. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Geofencing\"\n description=\"Set up location-based triggers and zones\"\n purpose=\"Draw zones around your worksites — offices, client sites, job locations — and have time tracking start and stop automatically as people move in and out, so on-site hours record themselves.\"\n actions={createButton}\n >\n {geofences.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No geofences configured yet\"\n description=\"Create your first zone to trigger automatic time tracking when people arrive or leave a location.\"\n action={createButton}\n />\n ) : (\n <>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {geofences.map((fence: GeofenceItem) => (\n <div\n key={fence.id}\n className=\"flex flex-col rounded-xl border border-border-seam bg-gloss-card shadow-pop p-5 transition-colors hover:bg-bg-sunken/50\"\n >\n <div className=\"flex items-start justify-between\">\n <div className=\"flex items-center gap-2\">\n <MapPin className=\"h-5 w-5 text-text-secondary\" />\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {fence.name}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {fence.trigger} &middot; {fence.radius}m radius\n </p>\n </div>\n </div>\n <span\n className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${getStatusColor(fence.isActive ? \"active\" : \"inactive\")}`}\n >\n {fence.isActive ? \"Active\" : \"Inactive\"}\n </span>\n </div>\n <div className=\"mt-3 flex items-center justify-between text-xs text-text-secondary\">\n <span>{fence.address ?? \"No address\"}</span>\n <span>{fence.events?.length ?? 0} events</span>\n </div>\n <div className=\"mt-4 flex items-center gap-2 border-t border-border-seam pt-3\">\n <button\n onClick={() => handleToggle(fence)}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-subtle px-2 py-1 text-xs font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n <Power className=\"h-3.5 w-3.5\" />\n {fence.isActive ? \"Deactivate\" : \"Activate\"}\n </button>\n <button\n onClick={() => openEdit(fence)}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-subtle px-2 py-1 text-xs font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n <Pencil className=\"h-3.5 w-3.5\" />\n Edit\n </button>\n <button\n onClick={() => setConfirmDelete(fence)}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-subtle px-2 py-1 text-xs font-medium text-status-error-text hover:bg-status-error-bg\"\n >\n <Trash2 className=\"h-3.5 w-3.5\" />\n Delete\n </button>\n </div>\n </div>\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={editing ? \"Edit Geofence\" : \"Create Geofence\"}\n description=\"Define a zone that triggers automatic time tracking.\"\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 || updating || !form.name.trim()}\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 || updating\n ? \"Saving...\"\n : editing\n ? \"Save Changes\"\n : \"Create Geofence\"}\n </button>\n </>\n }\n >\n {(createError || updateError) && (\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 save geofence. Please check the values and try again.\n </div>\n )}\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Name\n </label>\n <input\n type=\"text\"\n placeholder=\"e.g. Head Office\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: 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 Radius (meters)\n </label>\n <input\n type=\"number\"\n placeholder=\"100\"\n value={form.radius}\n onChange={(e) => setForm({ ...form, radius: e.target.value })}\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Trigger\n </label>\n <select\n value={form.trigger}\n onChange={(e) =>\n setForm({\n ...form,\n trigger: e.target.value as GeofenceTrigger,\n })\n }\n className={inputClass}\n >\n <option value={GeofenceTrigger.Enter}>On entry</option>\n <option value={GeofenceTrigger.Exit}>On exit</option>\n <option value={GeofenceTrigger.Both}>Entry &amp; exit</option>\n </select>\n </div>\n </div>\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=\"Street address for reference\"\n value={form.address}\n onChange={(e) => setForm({ ...form, address: e.target.value })}\n className={inputClass}\n />\n </div>\n <label className=\"flex items-center justify-between\">\n <span className=\"text-sm font-medium text-text-primary\">\n Active\n </span>\n <input\n type=\"checkbox\"\n checked={form.isActive}\n onChange={(e) => setForm({ ...form, isActive: e.target.checked })}\n className=\"h-4 w-4 rounded border-border-subtle\"\n />\n </label>\n </div>\n </Modal>\n\n <Modal\n open={confirmDelete !== null}\n onClose={() => setConfirmDelete(null)}\n title=\"Delete Geofence\"\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{\" \"}\n <span className=\"font-medium text-text-primary\">\n {confirmDelete?.name}\n </span>\n ? Any automatic tracking tied to this zone will stop.\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}