@burdenoff/microfe-ecoimpacthub 2026.819.4 → 2026.830.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/ecoimpacthub/hooks/index.d.ts +2 -0
- package/dist/ecoimpacthub/hooks/index.d.ts.map +1 -1
- package/dist/ecoimpacthub/hooks/index.js +2 -0
- package/dist/ecoimpacthub/hooks/useActiveEcoEntity.d.ts +11 -0
- package/dist/ecoimpacthub/hooks/useActiveEcoEntity.d.ts.map +1 -0
- package/dist/ecoimpacthub/hooks/useActiveEcoEntity.js +21 -0
- package/dist/ecoimpacthub/hooks/useActiveEcoEntity.js.map +1 -0
- package/dist/ecoimpacthub/hooks/useTr.d.ts +4 -0
- package/dist/ecoimpacthub/hooks/useTr.d.ts.map +1 -0
- package/dist/ecoimpacthub/hooks/useTr.js +20 -0
- package/dist/ecoimpacthub/hooks/useTr.js.map +1 -0
- package/dist/ecoimpacthub/pages/DashboardPage.d.ts.map +1 -1
- package/dist/ecoimpacthub/pages/DashboardPage.js +162 -138
- package/dist/ecoimpacthub/pages/DashboardPage.js.map +1 -1
- package/dist/ecoimpacthub/pages/_shared/MethodPicker.d.ts +15 -0
- package/dist/ecoimpacthub/pages/_shared/MethodPicker.d.ts.map +1 -0
- package/dist/ecoimpacthub/pages/_shared/MethodPicker.js +36 -0
- package/dist/ecoimpacthub/pages/_shared/MethodPicker.js.map +1 -0
- package/dist/ecoimpacthub/pages/_shared/UseMyLocationButton.d.ts +15 -0
- package/dist/ecoimpacthub/pages/_shared/UseMyLocationButton.d.ts.map +1 -0
- package/dist/ecoimpacthub/pages/_shared/UseMyLocationButton.js +66 -0
- package/dist/ecoimpacthub/pages/_shared/UseMyLocationButton.js.map +1 -0
- package/dist/ecoimpacthub/pages/habitat/HabitatProjectDetailPage.d.ts.map +1 -1
- package/dist/ecoimpacthub/pages/habitat/HabitatProjectDetailPage.js +127 -124
- package/dist/ecoimpacthub/pages/habitat/HabitatProjectDetailPage.js.map +1 -1
- package/dist/ecoimpacthub/pages/observations/ObservationsPage.d.ts.map +1 -1
- package/dist/ecoimpacthub/pages/observations/ObservationsPage.js +226 -224
- package/dist/ecoimpacthub/pages/observations/ObservationsPage.js.map +1 -1
- package/dist/ecoimpacthub/pages/reports/ReportDetailPage.d.ts.map +1 -1
- package/dist/ecoimpacthub/pages/reports/ReportDetailPage.js +159 -156
- package/dist/ecoimpacthub/pages/reports/ReportDetailPage.js.map +1 -1
- package/dist/ecoimpacthub/pages/sites/SiteDetailPage.d.ts.map +1 -1
- package/dist/ecoimpacthub/pages/sites/SiteDetailPage.js +40 -37
- package/dist/ecoimpacthub/pages/sites/SiteDetailPage.js.map +1 -1
- package/dist/ecoimpacthub/pages/sites/SitesPage.d.ts.map +1 -1
- package/dist/ecoimpacthub/pages/sites/SitesPage.js +208 -171
- package/dist/ecoimpacthub/pages/sites/SitesPage.js.map +1 -1
- package/dist/ecoimpacthub/pages/species/SpeciesDetailPage.d.ts.map +1 -1
- package/dist/ecoimpacthub/pages/species/SpeciesDetailPage.js +211 -214
- package/dist/ecoimpacthub/pages/species/SpeciesDetailPage.js.map +1 -1
- package/dist/ecoimpacthub/pages/species/SpeciesPage.d.ts.map +1 -1
- package/dist/ecoimpacthub/pages/species/SpeciesPage.js +174 -157
- package/dist/ecoimpacthub/pages/species/SpeciesPage.js.map +1 -1
- package/dist/ecoimpacthub/utils/activeEntity.d.ts +30 -0
- package/dist/ecoimpacthub/utils/activeEntity.d.ts.map +1 -0
- package/dist/ecoimpacthub/utils/activeEntity.js +12 -0
- package/dist/ecoimpacthub/utils/activeEntity.js.map +1 -0
- package/dist/ecoimpacthub/utils/pageContext.d.ts.map +1 -1
- package/dist/ecoimpacthub/utils/pageContext.js +30 -25
- package/dist/ecoimpacthub/utils/pageContext.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HabitatProjectDetailPage.js","names":[],"sources":["../../../../src/ecoimpacthub/pages/habitat/HabitatProjectDetailPage.tsx"],"sourcesContent":["/**\n * Habitat project detail — overview, lifecycle-progress, transitions & activities.\n * @module ecoimpacthub/pages/habitat\n *\n * Reads `ecoHabitatProject` (+ nested activities); writes\n * `ecoTransitionHabitatProject`, `ecoLogRestorationActivity`. The hero is\n * balanced into three tidy cards — overview, lifecycle-progress (a status-derived\n * radial ring + stepper, since the schema has no numeric progress) and\n * advance-project. Tags are driven via the platform tag store (no eco-svc change).\n */\n\nimport { useMemo, useState, type ReactNode } from 'react';\nimport { useParams } from 'react-router';\nimport { ArrowLeft, ArrowRight, Plus, Sprout } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n GlassCard,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { DataTable, type Column } from '../../components/DataTable';\nimport { LoadingView, ErrorView, Section } from '../../components/StateViews';\nimport { EmptyState } from '../../components/EmptyState';\nimport { EcoTags } from '../../components/EcoTags';\nimport { useEcoImpactHub } from '../../context';\nimport {\n useEcoHabitatProject,\n useEcoTransitionHabitatProject,\n useEcoLogRestorationActivity,\n useEcoSites,\n} from '../../hooks';\nimport { formatDate, formatDateTime, formatEnumLabel, formatNumber } from '../../utils/formatters';\nimport type {\n EcoHabitatProjectStatus,\n EcoRestorationActivity,\n EcoRestorationActivityType,\n} from '../../types';\nimport { ActivityTypeBadge, LifecycleBadge } from '../badges';\nimport { LifecycleProgress } from './LifecycleProgress';\n\n/** Allowed forward lifecycle transitions per current status. */\nconst NEXT_STATUSES: Record<EcoHabitatProjectStatus, EcoHabitatProjectStatus[]> = {\n PLANNED: ['IN_PROGRESS', 'ON_HOLD', 'CANCELLED'],\n IN_PROGRESS: ['MONITORING', 'ON_HOLD', 'COMPLETED', 'CANCELLED'],\n MONITORING: ['COMPLETED', 'IN_PROGRESS'],\n ON_HOLD: ['IN_PROGRESS', 'CANCELLED'],\n COMPLETED: [],\n CANCELLED: [],\n};\n\nconst ACTIVITY_TYPES: EcoRestorationActivityType[] = [\n 'PLANTING',\n 'INVASIVE_REMOVAL',\n 'REWILDING',\n 'WATER_MANAGEMENT',\n 'SOIL_REMEDIATION',\n 'COMMUNITY_ENGAGEMENT',\n 'SURVEY',\n];\n\n/** Local `YYYY-MM-DDTHH:mm` string for a datetime-local default of \"now\". */\nconst nowLocal = (): string => {\n const d = new Date();\n const local = new Date(d.getTime() - d.getTimezoneOffset() * 60000);\n return local.toISOString().slice(0, 16);\n};\n\ninterface ActivityFormState {\n type: EcoRestorationActivityType;\n summary: string;\n details: string;\n quantity: string;\n unit: string;\n performedAt: string;\n}\n\nconst emptyActivityForm = (): ActivityFormState => ({\n type: 'PLANTING',\n summary: '',\n details: '',\n quantity: '',\n unit: '',\n performedAt: nowLocal(),\n});\n\n/** A labelled key/value cell for the overview grid. */\nfunction Fact({ label, children }: { label: string; children: ReactNode }) {\n return (\n <div>\n <dt className=\"text-xs text-text-tertiary\">{label}</dt>\n <dd className=\"mt-1 text-sm text-text-primary\">{children}</dd>\n </div>\n );\n}\n\nexport function HabitatProjectDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useEcoImpactHub();\n const { data: project, isLoading, isError, refetch } = useEcoHabitatProject(id);\n const sites = useEcoSites({ pagination: { limit: 100, offset: 0 } });\n\n const transitionProject = useEcoTransitionHabitatProject();\n const logActivity = useEcoLogRestorationActivity();\n\n const [activityOpen, setActivityOpen] = useState(false);\n const [activityForm, setActivityForm] = useState<ActivityFormState>(emptyActivityForm);\n const [error, setError] = useState<string | null>(null);\n\n const projectSiteId = project?.siteId ?? null;\n const siteItems = sites.data?.items;\n const siteName = useMemo(\n () => (projectSiteId ? (siteItems?.find((s) => s.id === projectSiteId)?.name ?? null) : null),\n [projectSiteId, siteItems]\n );\n\n if (isLoading) {\n return (\n <PageLayout title=\"Habitat project\">\n <LoadingView />\n </PageLayout>\n );\n }\n if (isError || !project) {\n return (\n <PageLayout title=\"Habitat project\">\n <ErrorView message=\"This project could not be loaded.\" onRetry={() => refetch()} />\n </PageLayout>\n );\n }\n\n const activities = project.activities ?? [];\n const nextStatuses = NEXT_STATUSES[project.status];\n const timeline = [formatDate(project.startDate), formatDate(project.endDate)].join(' – ');\n const subtitle = [siteName, timeline].filter((v) => v && v !== '— – —').join(' · ');\n\n const handleTransition = async (status: EcoHabitatProjectStatus) => {\n if (!id) return;\n setError(null);\n try {\n await transitionProject.mutateAsync({ id, status });\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to update project status.');\n }\n };\n\n const handleLogActivity = async () => {\n if (!id || !activityForm.summary.trim()) {\n setError('Activity summary is required.');\n return;\n }\n setError(null);\n try {\n await logActivity.mutateAsync({\n projectId: id,\n type: activityForm.type,\n summary: activityForm.summary.trim(),\n details: activityForm.details.trim() || null,\n quantity: activityForm.quantity ? Number(activityForm.quantity) : null,\n unit: activityForm.unit.trim() || null,\n performedAt: activityForm.performedAt\n ? new Date(activityForm.performedAt).toISOString()\n : new Date().toISOString(),\n });\n setActivityOpen(false);\n setActivityForm(emptyActivityForm());\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to log activity.');\n }\n };\n\n const activityColumns: Column<EcoRestorationActivity>[] = [\n {\n key: 'type',\n header: 'Type',\n render: (a) => <ActivityTypeBadge type={a.type} />,\n },\n {\n key: 'summary',\n header: 'Summary',\n render: (a) => (\n <div className=\"min-w-0\">\n <p className=\"text-text-primary\">{a.summary}</p>\n {a.details && (\n <p className=\"mt-0.5 max-w-md truncate text-xs text-text-tertiary\">{a.details}</p>\n )}\n </div>\n ),\n },\n {\n key: 'quantity',\n header: 'Quantity',\n align: 'right',\n hideOnMobile: true,\n sortValue: (a) => a.quantity ?? 0,\n render: (a) =>\n a.quantity != null ? (\n <span className=\"tabular-nums text-text-secondary\">\n {formatNumber(a.quantity)}\n {a.unit ? ` ${a.unit}` : ''}\n </span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'performedAt',\n header: 'Performed',\n align: 'right',\n hideOnMobile: true,\n sortValue: (a) => a.performedAt,\n render: (a) => <span className=\"text-text-tertiary\">{formatDateTime(a.performedAt)}</span>,\n },\n ];\n\n return (\n <PageLayout\n title={\n <span className=\"flex flex-wrap items-center gap-x-3 gap-y-2\">\n <span className=\"text-text-primary\">{project.name}</span>\n <LifecycleBadge status={project.status} />\n </span>\n }\n description={subtitle || undefined}\n actions={\n <Button variant=\"ghost\" onClick={() => navigateTo('habitat')}>\n <ArrowLeft className=\"mr-1.5 h-4 w-4\" /> Back\n </Button>\n }\n >\n {/* Hero — overview · lifecycle-progress · advance-project */}\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-4\">\n {/* Overview */}\n <GlassCard treatment=\"glass\" className=\"p-6 lg:col-span-2\">\n <dl className=\"grid grid-cols-2 gap-x-6 gap-y-4 sm:grid-cols-3\">\n <Fact label=\"Status\">\n <LifecycleBadge status={project.status} />\n </Fact>\n <Fact label=\"Target area\">\n <span className=\"tabular-nums\">\n {project.targetHectares != null\n ? `${formatNumber(project.targetHectares)} ha`\n : '—'}\n </span>\n </Fact>\n <Fact label=\"Budget\">\n <span className=\"tabular-nums\">\n {project.budgetAmount > 0\n ? `${formatNumber(project.budgetAmount)} ${project.currency}`\n : '—'}\n </span>\n </Fact>\n <Fact label=\"Start\">{formatDate(project.startDate)}</Fact>\n <Fact label=\"End\">{formatDate(project.endDate)}</Fact>\n <Fact label={project.completedAt ? 'Completed' : 'Site'}>\n {project.completedAt ? formatDate(project.completedAt) : (siteName ?? '—')}\n </Fact>\n {project.completedAt && <Fact label=\"Site\">{siteName ?? '—'}</Fact>}\n </dl>\n\n {project.description && (\n <p className=\"mt-5 rounded-lg bg-bg-sunken/60 p-3 text-sm text-text-secondary\">\n {project.description}\n </p>\n )}\n\n <div className=\"mt-5 border-t border-border-seam pt-4\">\n <EcoTags entityType=\"ecoimpacthub-habitat\" entityId={project.id} />\n </div>\n </GlassCard>\n\n {/* Lifecycle progress */}\n <GlassCard treatment=\"glass\" className=\"flex flex-col p-6\">\n <p className=\"mb-2 text-xs font-medium uppercase tracking-wide text-text-tertiary\">\n Lifecycle progress\n </p>\n <div className=\"flex flex-1 items-center justify-center\">\n <LifecycleProgress status={project.status} />\n </div>\n </GlassCard>\n\n {/* Advance project */}\n <GlassCard treatment=\"glass\" className=\"flex flex-col gap-3 p-6\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-tertiary\">\n Advance project\n </p>\n {nextStatuses.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n This project is {formatEnumLabel(project.status).toLowerCase()} — no further\n transitions.\n </p>\n ) : (\n nextStatuses.map((next) => (\n <Button\n key={next}\n variant=\"outline\"\n onClick={() => handleTransition(next)}\n disabled={transitionProject.isPending}\n className=\"justify-between\"\n >\n {formatEnumLabel(next)}\n <ArrowRight className=\"h-4 w-4 opacity-60\" />\n </Button>\n ))\n )}\n <Button className=\"mt-auto\" onClick={() => setActivityOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Log activity\n </Button>\n </GlassCard>\n </div>\n\n {error && <p className=\"mt-4 text-sm text-status-error-text\">{error}</p>}\n\n {/* Restoration activities */}\n <Section\n title=\"Restoration activities\"\n description=\"Work logged against this project — planting, removals, surveys & engagement.\"\n action={\n <Button size=\"sm\" onClick={() => setActivityOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Log activity\n </Button>\n }\n >\n {activities.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Sprout className=\"h-10 w-10\" />}\n title=\"No activities yet\"\n description=\"Log restoration work to build this project's field record.\"\n action={\n <Button size=\"sm\" onClick={() => setActivityOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Log activity\n </Button>\n }\n />\n ) : (\n <DataTable\n columns={activityColumns}\n rows={activities}\n rowKey={(a) => a.id}\n defaultSort={{ key: 'performedAt', dir: 'desc' }}\n />\n )}\n </Section>\n\n {/* Log activity dialog */}\n <ResponsiveDialog open={activityOpen} onOpenChange={setActivityOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Log restoration activity</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Record a piece of work performed for {project.name}.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"actType\">Type</Label>\n <Select\n value={activityForm.type}\n onValueChange={(v) =>\n setActivityForm({ ...activityForm, type: v as EcoRestorationActivityType })\n }\n >\n <SelectTrigger id=\"actType\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {ACTIVITY_TYPES.map((t) => (\n <SelectItem key={t} value={t}>\n {formatEnumLabel(t)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"actPerformedAt\">Performed at</Label>\n <Input\n id=\"actPerformedAt\"\n type=\"datetime-local\"\n value={activityForm.performedAt}\n onChange={(e) =>\n setActivityForm({ ...activityForm, performedAt: e.target.value })\n }\n />\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"actSummary\">Summary *</Label>\n <Input\n id=\"actSummary\"\n value={activityForm.summary}\n onChange={(e) => setActivityForm({ ...activityForm, summary: e.target.value })}\n placeholder=\"Planted 500 mangrove saplings\"\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"actDetails\">Details</Label>\n <Textarea\n id=\"actDetails\"\n value={activityForm.details}\n onChange={(e) => setActivityForm({ ...activityForm, details: e.target.value })}\n placeholder=\"Optional notes about the work.\"\n rows={2}\n />\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"actQuantity\">Quantity</Label>\n <Input\n id=\"actQuantity\"\n type=\"number\"\n step=\"any\"\n value={activityForm.quantity}\n onChange={(e) => setActivityForm({ ...activityForm, quantity: e.target.value })}\n placeholder=\"500\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"actUnit\">Unit</Label>\n <Input\n id=\"actUnit\"\n value={activityForm.unit}\n onChange={(e) => setActivityForm({ ...activityForm, unit: e.target.value })}\n placeholder=\"saplings\"\n />\n </div>\n </div>\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setActivityOpen(false)}>\n Cancel\n </Button>\n <Button onClick={handleLogActivity} disabled={logActivity.isPending}>\n {logActivity.isPending ? 'Logging…' : 'Log activity'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HabitatProjectDetailPage.js","names":[],"sources":["../../../../src/ecoimpacthub/pages/habitat/HabitatProjectDetailPage.tsx"],"sourcesContent":["/**\n * Habitat project detail — overview, lifecycle-progress, transitions & activities.\n * @module ecoimpacthub/pages/habitat\n *\n * Reads `ecoHabitatProject` (+ nested activities); writes\n * `ecoTransitionHabitatProject`, `ecoLogRestorationActivity`. The hero is\n * balanced into three tidy cards — overview, lifecycle-progress (a status-derived\n * radial ring + stepper, since the schema has no numeric progress) and\n * advance-project. Tags are driven via the platform tag store (no eco-svc change).\n */\n\nimport { useMemo, useState, type ReactNode } from 'react';\nimport { useParams } from 'react-router';\nimport { ArrowLeft, ArrowRight, Plus, Sprout } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n GlassCard,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { DataTable, type Column } from '../../components/DataTable';\nimport { LoadingView, ErrorView, Section } from '../../components/StateViews';\nimport { EmptyState } from '../../components/EmptyState';\nimport { EcoTags } from '../../components/EcoTags';\nimport { useEcoImpactHub } from '../../context';\nimport {\n useEcoHabitatProject,\n useEcoTransitionHabitatProject,\n useEcoLogRestorationActivity,\n useEcoSites,\n useActiveEcoEntity,\n} from '../../hooks';\nimport { formatDate, formatDateTime, formatEnumLabel, formatNumber } from '../../utils/formatters';\nimport type {\n EcoHabitatProjectStatus,\n EcoRestorationActivity,\n EcoRestorationActivityType,\n} from '../../types';\nimport { ActivityTypeBadge, LifecycleBadge } from '../badges';\nimport { LifecycleProgress } from './LifecycleProgress';\n\n/** Allowed forward lifecycle transitions per current status. */\nconst NEXT_STATUSES: Record<EcoHabitatProjectStatus, EcoHabitatProjectStatus[]> = {\n PLANNED: ['IN_PROGRESS', 'ON_HOLD', 'CANCELLED'],\n IN_PROGRESS: ['MONITORING', 'ON_HOLD', 'COMPLETED', 'CANCELLED'],\n MONITORING: ['COMPLETED', 'IN_PROGRESS'],\n ON_HOLD: ['IN_PROGRESS', 'CANCELLED'],\n COMPLETED: [],\n CANCELLED: [],\n};\n\nconst ACTIVITY_TYPES: EcoRestorationActivityType[] = [\n 'PLANTING',\n 'INVASIVE_REMOVAL',\n 'REWILDING',\n 'WATER_MANAGEMENT',\n 'SOIL_REMEDIATION',\n 'COMMUNITY_ENGAGEMENT',\n 'SURVEY',\n];\n\n/** Local `YYYY-MM-DDTHH:mm` string for a datetime-local default of \"now\". */\nconst nowLocal = (): string => {\n const d = new Date();\n const local = new Date(d.getTime() - d.getTimezoneOffset() * 60000);\n return local.toISOString().slice(0, 16);\n};\n\ninterface ActivityFormState {\n type: EcoRestorationActivityType;\n summary: string;\n details: string;\n quantity: string;\n unit: string;\n performedAt: string;\n}\n\nconst emptyActivityForm = (): ActivityFormState => ({\n type: 'PLANTING',\n summary: '',\n details: '',\n quantity: '',\n unit: '',\n performedAt: nowLocal(),\n});\n\n/** A labelled key/value cell for the overview grid. */\nfunction Fact({ label, children }: { label: string; children: ReactNode }) {\n return (\n <div>\n <dt className=\"text-xs text-text-tertiary\">{label}</dt>\n <dd className=\"mt-1 text-sm text-text-primary\">{children}</dd>\n </div>\n );\n}\n\nexport function HabitatProjectDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useEcoImpactHub();\n const { data: project, isLoading, isError, refetch } = useEcoHabitatProject(id);\n\n // Let the assistant refer to this record by name (see utils/activeEntity).\n useActiveEcoEntity(project?.id, project?.name);\n const sites = useEcoSites({ pagination: { limit: 100, offset: 0 } });\n\n const transitionProject = useEcoTransitionHabitatProject();\n const logActivity = useEcoLogRestorationActivity();\n\n const [activityOpen, setActivityOpen] = useState(false);\n const [activityForm, setActivityForm] = useState<ActivityFormState>(emptyActivityForm);\n const [error, setError] = useState<string | null>(null);\n\n const projectSiteId = project?.siteId ?? null;\n const siteItems = sites.data?.items;\n const siteName = useMemo(\n () => (projectSiteId ? (siteItems?.find((s) => s.id === projectSiteId)?.name ?? null) : null),\n [projectSiteId, siteItems]\n );\n\n if (isLoading) {\n return (\n <PageLayout title=\"Habitat project\">\n <LoadingView />\n </PageLayout>\n );\n }\n if (isError || !project) {\n return (\n <PageLayout title=\"Habitat project\">\n <ErrorView message=\"This project could not be loaded.\" onRetry={() => refetch()} />\n </PageLayout>\n );\n }\n\n const activities = project.activities ?? [];\n const nextStatuses = NEXT_STATUSES[project.status];\n const timeline = [formatDate(project.startDate), formatDate(project.endDate)].join(' – ');\n const subtitle = [siteName, timeline].filter((v) => v && v !== '— – —').join(' · ');\n\n const handleTransition = async (status: EcoHabitatProjectStatus) => {\n if (!id) return;\n setError(null);\n try {\n await transitionProject.mutateAsync({ id, status });\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to update project status.');\n }\n };\n\n const handleLogActivity = async () => {\n if (!id || !activityForm.summary.trim()) {\n setError('Activity summary is required.');\n return;\n }\n setError(null);\n try {\n await logActivity.mutateAsync({\n projectId: id,\n type: activityForm.type,\n summary: activityForm.summary.trim(),\n details: activityForm.details.trim() || null,\n quantity: activityForm.quantity ? Number(activityForm.quantity) : null,\n unit: activityForm.unit.trim() || null,\n performedAt: activityForm.performedAt\n ? new Date(activityForm.performedAt).toISOString()\n : new Date().toISOString(),\n });\n setActivityOpen(false);\n setActivityForm(emptyActivityForm());\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to log activity.');\n }\n };\n\n const activityColumns: Column<EcoRestorationActivity>[] = [\n {\n key: 'type',\n header: 'Type',\n render: (a) => <ActivityTypeBadge type={a.type} />,\n },\n {\n key: 'summary',\n header: 'Summary',\n render: (a) => (\n <div className=\"min-w-0\">\n <p className=\"text-text-primary\">{a.summary}</p>\n {a.details && (\n <p className=\"mt-0.5 max-w-md truncate text-xs text-text-tertiary\">{a.details}</p>\n )}\n </div>\n ),\n },\n {\n key: 'quantity',\n header: 'Quantity',\n align: 'right',\n hideOnMobile: true,\n sortValue: (a) => a.quantity ?? 0,\n render: (a) =>\n a.quantity != null ? (\n <span className=\"tabular-nums text-text-secondary\">\n {formatNumber(a.quantity)}\n {a.unit ? ` ${a.unit}` : ''}\n </span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'performedAt',\n header: 'Performed',\n align: 'right',\n hideOnMobile: true,\n sortValue: (a) => a.performedAt,\n render: (a) => <span className=\"text-text-tertiary\">{formatDateTime(a.performedAt)}</span>,\n },\n ];\n\n return (\n <PageLayout\n title={\n <span className=\"flex flex-wrap items-center gap-x-3 gap-y-2\">\n <span className=\"text-text-primary\">{project.name}</span>\n <LifecycleBadge status={project.status} />\n </span>\n }\n description={subtitle || undefined}\n actions={\n <Button variant=\"ghost\" onClick={() => navigateTo('habitat')}>\n <ArrowLeft className=\"mr-1.5 h-4 w-4\" /> Back\n </Button>\n }\n >\n {/* Hero — overview · lifecycle-progress · advance-project */}\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-4\">\n {/* Overview */}\n <GlassCard treatment=\"glass\" className=\"p-6 lg:col-span-2\">\n <dl className=\"grid grid-cols-2 gap-x-6 gap-y-4 sm:grid-cols-3\">\n <Fact label=\"Status\">\n <LifecycleBadge status={project.status} />\n </Fact>\n <Fact label=\"Target area\">\n <span className=\"tabular-nums\">\n {project.targetHectares != null\n ? `${formatNumber(project.targetHectares)} ha`\n : '—'}\n </span>\n </Fact>\n <Fact label=\"Budget\">\n <span className=\"tabular-nums\">\n {project.budgetAmount > 0\n ? `${formatNumber(project.budgetAmount)} ${project.currency}`\n : '—'}\n </span>\n </Fact>\n <Fact label=\"Start\">{formatDate(project.startDate)}</Fact>\n <Fact label=\"End\">{formatDate(project.endDate)}</Fact>\n <Fact label={project.completedAt ? 'Completed' : 'Site'}>\n {project.completedAt ? formatDate(project.completedAt) : (siteName ?? '—')}\n </Fact>\n {project.completedAt && <Fact label=\"Site\">{siteName ?? '—'}</Fact>}\n </dl>\n\n {project.description && (\n <p className=\"mt-5 rounded-lg bg-bg-sunken/60 p-3 text-sm text-text-secondary\">\n {project.description}\n </p>\n )}\n\n <div className=\"mt-5 border-t border-border-seam pt-4\">\n <EcoTags entityType=\"ecoimpacthub-habitat\" entityId={project.id} />\n </div>\n </GlassCard>\n\n {/* Lifecycle progress */}\n <GlassCard treatment=\"glass\" className=\"flex flex-col p-6\">\n <p className=\"mb-2 text-xs font-medium uppercase tracking-wide text-text-tertiary\">\n Lifecycle progress\n </p>\n <div className=\"flex flex-1 items-center justify-center\">\n <LifecycleProgress status={project.status} />\n </div>\n </GlassCard>\n\n {/* Advance project */}\n <GlassCard treatment=\"glass\" className=\"flex flex-col gap-3 p-6\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-tertiary\">\n Advance project\n </p>\n {nextStatuses.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n This project is {formatEnumLabel(project.status).toLowerCase()} — no further\n transitions.\n </p>\n ) : (\n nextStatuses.map((next) => (\n <Button\n key={next}\n variant=\"outline\"\n onClick={() => handleTransition(next)}\n disabled={transitionProject.isPending}\n className=\"justify-between\"\n >\n {formatEnumLabel(next)}\n <ArrowRight className=\"h-4 w-4 opacity-60\" />\n </Button>\n ))\n )}\n <Button className=\"mt-auto\" onClick={() => setActivityOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Log activity\n </Button>\n </GlassCard>\n </div>\n\n {error && <p className=\"mt-4 text-sm text-status-error-text\">{error}</p>}\n\n {/* Restoration activities */}\n <Section\n title=\"Restoration activities\"\n description=\"Work logged against this project — planting, removals, surveys & engagement.\"\n action={\n <Button size=\"sm\" onClick={() => setActivityOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Log activity\n </Button>\n }\n >\n {activities.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Sprout className=\"h-10 w-10\" />}\n title=\"No activities yet\"\n description=\"Log restoration work to build this project's field record.\"\n action={\n <Button size=\"sm\" onClick={() => setActivityOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Log activity\n </Button>\n }\n />\n ) : (\n <DataTable\n columns={activityColumns}\n rows={activities}\n rowKey={(a) => a.id}\n defaultSort={{ key: 'performedAt', dir: 'desc' }}\n />\n )}\n </Section>\n\n {/* Log activity dialog */}\n <ResponsiveDialog open={activityOpen} onOpenChange={setActivityOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Log restoration activity</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Record a piece of work performed for {project.name}.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"actType\">Type</Label>\n <Select\n value={activityForm.type}\n onValueChange={(v) =>\n setActivityForm({ ...activityForm, type: v as EcoRestorationActivityType })\n }\n >\n <SelectTrigger id=\"actType\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {ACTIVITY_TYPES.map((t) => (\n <SelectItem key={t} value={t}>\n {formatEnumLabel(t)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"actPerformedAt\">Performed at</Label>\n <Input\n id=\"actPerformedAt\"\n type=\"datetime-local\"\n value={activityForm.performedAt}\n onChange={(e) =>\n setActivityForm({ ...activityForm, performedAt: e.target.value })\n }\n />\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"actSummary\">Summary *</Label>\n <Input\n id=\"actSummary\"\n value={activityForm.summary}\n onChange={(e) => setActivityForm({ ...activityForm, summary: e.target.value })}\n placeholder=\"Planted 500 mangrove saplings\"\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"actDetails\">Details</Label>\n <Textarea\n id=\"actDetails\"\n value={activityForm.details}\n onChange={(e) => setActivityForm({ ...activityForm, details: e.target.value })}\n placeholder=\"Optional notes about the work.\"\n rows={2}\n />\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"actQuantity\">Quantity</Label>\n <Input\n id=\"actQuantity\"\n type=\"number\"\n step=\"any\"\n value={activityForm.quantity}\n onChange={(e) => setActivityForm({ ...activityForm, quantity: e.target.value })}\n placeholder=\"500\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"actUnit\">Unit</Label>\n <Input\n id=\"actUnit\"\n value={activityForm.unit}\n onChange={(e) => setActivityForm({ ...activityForm, unit: e.target.value })}\n placeholder=\"saplings\"\n />\n </div>\n </div>\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setActivityOpen(false)}>\n Cancel\n </Button>\n <Button onClick={handleLogActivity} disabled={logActivity.isPending}>\n {logActivity.isPending ? 'Logging…' : 'Log activity'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ObservationsPage.d.ts","sourceRoot":"","sources":["../../../../src/ecoimpacthub/pages/observations/ObservationsPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"ObservationsPage.d.ts","sourceRoot":"","sources":["../../../../src/ecoimpacthub/pages/observations/ObservationsPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA2FH,wBAAgB,gBAAgB,4CAua/B"}
|