@burdenoff/microfe-housingvista 2026.1023.2 → 2026.1024.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/housingvista/pages/amc/AMCManagementPage.js +1 -1
- package/dist/housingvista/pages/amc/AMCManagementPage.js.map +1 -1
- package/dist/housingvista/pages/amc/AmcContractFormDialog.d.ts +0 -2
- package/dist/housingvista/pages/amc/AmcContractFormDialog.d.ts.map +1 -1
- package/dist/housingvista/pages/amc/AmcContractFormDialog.js +25 -55
- package/dist/housingvista/pages/amc/AmcContractFormDialog.js.map +1 -1
- package/package.json +1 -1
|
@@ -89,7 +89,7 @@ function z() {
|
|
|
89
89
|
monthlyCost: e.monthlyCost,
|
|
90
90
|
startDate: e.startDate,
|
|
91
91
|
endDate: e.endDate,
|
|
92
|
-
status:
|
|
92
|
+
status: "active",
|
|
93
93
|
nextServiceDate: e.nextServiceDate || void 0,
|
|
94
94
|
servicesIncluded: e.servicesIncluded.length > 0 ? e.servicesIncluded : void 0
|
|
95
95
|
}), l.success("Contract created", `${e.packageName} added for ${e.propertyTitle}.`), Y(!1);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AMCManagementPage.js","names":[],"sources":["../../../../src/housingvista/pages/amc/AMCManagementPage.tsx"],"sourcesContent":["import { useState, useMemo, useEffect } from 'react';\nimport { useNavigate } from 'react-router';\nimport {\n Shield,\n DollarSign,\n Calendar,\n Plus,\n Download,\n Clock,\n AlertTriangle,\n Wrench,\n RotateCcw,\n MapPin,\n Pencil,\n Ban,\n Trash2,\n} from 'lucide-react';\nimport { Button, EmphasisPanel, NextSteps, Pagination } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components';\nimport { StatCard } from '../../components';\nimport { StatusBadge } from '../../components';\nimport { FilterBar } from '../../components';\nimport { EmptyState } from '../../components';\nimport { useAMCContracts, useCreateAmcContract, useMaintenanceRequests } from '../../hooks';\nimport { useServerPagination } from '../../hooks/useServerPagination';\nimport { cn, formatCurrency } from '../../utils/helpers';\nimport { notify } from '../../lib/toast';\nimport type { AMCContract } from '../../types';\nimport { useTrackPageView } from '../../hooks/useHousingVistaAnalytics';\nimport { AmcContractFormDialog, type AmcContractFormValues } from './AmcContractFormDialog';\nimport { AmcCancelDialog, AmcDeleteDialog, AmcRenewDialog } from './AmcContractActionDialogs';\nimport { useAmcManagement } from './useAmcManagement';\nimport { calendarDaysUntil, formatCalendarDate } from './amcDates';\n\nfunction getStatusVariant(status: AMCContract['status']): 'success' | 'warning' | 'error' | 'muted' {\n switch (status) {\n case 'active':\n return 'success';\n case 'expiring_soon':\n return 'warning';\n case 'expired':\n return 'error';\n case 'cancelled':\n return 'muted';\n default:\n return 'muted';\n }\n}\n\nexport function AMCManagementPage() {\n useTrackPageView('housingvista.amc.viewed');\n const navigate = useNavigate();\n const pagination = useServerPagination(10);\n const [search, setSearch] = useState('');\n const [debouncedSearch, setDebouncedSearch] = useState('');\n\n // Debounce the raw search input into the value that feeds the server filter.\n useEffect(() => {\n const timer = setTimeout(() => setDebouncedSearch(search), 300);\n return () => clearTimeout(timer);\n }, [search]);\n\n const { data: amcContracts, loading: contractsLoading } = useAMCContracts({\n filter: { search: debouncedSearch.trim() || undefined },\n pagination: pagination.pagination,\n });\n\n // Deleting the last row on page 2+ leaves an empty page (the Pagination control is hidden when the list is\n // empty) — step back to the previous page instead of stranding the user (Codex P2 on mfe#22).\n const { page: currentPage, setPage: setCurrentPage } = pagination;\n useEffect(() => {\n if (!contractsLoading && amcContracts.length === 0 && currentPage > 1) {\n setCurrentPage(currentPage - 1);\n }\n }, [contractsLoading, amcContracts.length, currentPage, setCurrentPage]);\n\n const { createAmcContract, loading: creating } = useCreateAmcContract();\n\n // Open maintenance requests across the portfolio — real count for the sidebar stat.\n const { data: maintenanceRequests } = useMaintenanceRequests({\n filter: { status: 'open' },\n pagination: { limit: 100, offset: 0 },\n });\n const openMaintenanceCount = maintenanceRequests.length;\n\n const handleSearchChange = (value: string) => {\n setSearch(value);\n pagination.setPage(1);\n };\n\n const handleExport = () => {\n if (amcContracts.length === 0) {\n notify.warning('Nothing to export', 'There are no contracts to export yet.');\n return;\n }\n const escape = (value: string) => `\"${value.replace(/\"/g, '\"\"')}\"`;\n const headers = ['Package', 'Property', 'Provider', 'Monthly Cost', 'Start', 'End', 'Status', 'Services'];\n const rows = amcContracts.map((c) =>\n [\n c.packageName,\n c.propertyTitle,\n c.providerName,\n String(c.monthlyCost),\n c.startDate,\n c.endDate,\n c.status,\n c.servicesIncluded.join('; '),\n ]\n .map((cell) => escape(String(cell)))\n .join(',')\n );\n const csv = [headers.map(escape).join(','), ...rows].join('\\n');\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = `housingvista-amc-contracts-${new Date().toISOString().slice(0, 10)}.csv`;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n notify.success('Export ready', `${amcContracts.length} contracts exported to CSV.`);\n };\n\n const [createOpen, setCreateOpen] = useState(false);\n const manage = useAmcManagement();\n\n const handleCreate = async (values: AmcContractFormValues) => {\n try {\n await createAmcContract({\n propertyTitle: values.propertyTitle,\n packageName: values.packageName,\n providerName: values.providerName,\n monthlyCost: values.monthlyCost,\n startDate: values.startDate,\n endDate: values.endDate,\n status: values.status,\n nextServiceDate: values.nextServiceDate || undefined,\n servicesIncluded: values.servicesIncluded.length > 0 ? values.servicesIncluded : undefined,\n });\n notify.success('Contract created', `${values.packageName} added for ${values.propertyTitle}.`);\n setCreateOpen(false);\n } catch (err) {\n notify.error('Could not create contract', (err as Error).message);\n }\n };\n\n const handleEdit = async (values: AmcContractFormValues) => {\n const target = manage.editTarget;\n if (!target) return;\n try {\n await manage.updateAmcContract(target.id, {\n propertyTitle: values.propertyTitle,\n packageName: values.packageName,\n providerName: values.providerName,\n monthlyCost: values.monthlyCost,\n startDate: values.startDate,\n endDate: values.endDate,\n // null clears a previously scheduled service.\n nextServiceDate: values.nextServiceDate || null,\n servicesIncluded: values.servicesIncluded,\n });\n notify.success('Contract updated', `${values.packageName} saved.`);\n manage.setEditTarget(null);\n } catch (err) {\n notify.error('Could not update contract', (err as Error).message);\n }\n };\n\n const openCreate = () => setCreateOpen(true);\n\n // Only live contracts feed the schedule AND the \"Upcoming Services\" stat, so the two always agree.\n const scheduledServices = useMemo(\n () =>\n amcContracts\n .filter((c) => c.nextServiceDate && (c.status === 'active' || c.status === 'expiring_soon'))\n .sort((a, b) => new Date(a.nextServiceDate).getTime() - new Date(b.nextServiceDate).getTime()),\n [amcContracts]\n );\n\n const stats = useMemo(() => {\n const activeContracts = amcContracts.filter((c) => c.status === 'active' || c.status === 'expiring_soon').length;\n const pendingRenewals = amcContracts.filter((c) => c.status === 'expiring_soon').length;\n const totalMonthlySpent = amcContracts\n .filter((c) => c.status === 'active' || c.status === 'expiring_soon')\n .reduce((sum, c) => sum + c.monthlyCost, 0);\n const upcomingServices = scheduledServices.filter((c) => {\n const daysUntil = calendarDaysUntil(c.nextServiceDate);\n return daysUntil >= 0 && daysUntil <= 30;\n }).length;\n return { activeContracts, pendingRenewals, totalMonthlySpent, upcomingServices };\n }, [amcContracts, scheduledServices]);\n\n // Accent styling per contract status for the card treatment.\n const statusAccent: Record<AMCContract['status'], string> = {\n active: 'border-l-4 border-l-status-success-border',\n expiring_soon: 'border-l-4 border-l-status-warning-border bg-status-warning-bg-subtle/30',\n expired: 'border-l-4 border-l-status-error-border',\n cancelled: 'border-l-4 border-l-border-strong',\n };\n\n return (\n <PageLayout\n title=\"AMC Management\"\n description=\"Manage Annual Maintenance Contracts and service packages\"\n purpose=\"Keep your properties maintained without surprises. Track every Annual Maintenance Contract, see what each covers and costs, get ahead of renewals, and know which services are due in the next 30 days so nothing slips.\"\n actions={\n <div className=\"flex items-center gap-2\">\n <button\n onClick={handleExport}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Download className=\"h-4 w-4\" />\n Export\n </button>\n <Button type=\"button\" onClick={openCreate} className=\"gap-2\">\n <Plus className=\"h-4 w-4\" />\n New Contract\n </Button>\n </div>\n }\n >\n {/* Stats — primary insight zone (one EmphasisPanel per page) */}\n <EmphasisPanel className=\"p-4\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard label=\"Active Contracts\" value={stats.activeContracts} icon={<Shield className=\"h-4 w-4\" />} />\n <StatCard label=\"Pending Renewals\" value={stats.pendingRenewals} icon={<RotateCcw className=\"h-4 w-4\" />} />\n <StatCard\n label=\"Total Monthly Spent\"\n value={stats.totalMonthlySpent}\n prefix=\"$\"\n icon={<DollarSign className=\"h-4 w-4\" />}\n />\n <StatCard label=\"Upcoming Services\" value={stats.upcomingServices} icon={<Calendar className=\"h-4 w-4\" />} />\n </div>\n </EmphasisPanel>\n\n <NextSteps\n storageKey=\"housingvista-amc\"\n steps={[\n {\n id: 'pick-property',\n label: 'Add coverage for a property',\n description: 'Choose a property to set up a new maintenance contract.',\n onClick: () => navigate('../properties'),\n },\n {\n id: 'compare-providers',\n label: 'Compare your providers',\n description: 'Search by provider name to weigh coverage and monthly cost.',\n onClick: () => {\n const firstProvider = amcContracts[0]?.providerName;\n if (firstProvider) {\n setSearch(firstProvider);\n pagination.setPage(1);\n }\n },\n },\n {\n id: 'check-legal',\n label: 'Review contract paperwork',\n description: 'Open the legal hub to manage the underlying documents.',\n onClick: () => navigate('../legal'),\n },\n ]}\n />\n\n {/* Contract cards (left) + service schedule & open-maintenance sidebar (right) */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"space-y-4 lg:col-span-2\">\n <div className=\"flex flex-col gap-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">All Contracts</h2>\n <FilterBar\n searchValue={search}\n onSearchChange={handleSearchChange}\n searchPlaceholder=\"Search contracts, providers, properties...\"\n />\n </div>\n {amcContracts.length > 0 ? (\n <>\n <div className=\"space-y-3\">\n {amcContracts.map((contract) => (\n <div\n key={contract.id}\n className={cn(\n 'rounded-lg border border-border-subtle bg-bg-surface p-5 shadow-sm',\n statusAccent[contract.status]\n )}\n >\n <div className=\"flex flex-wrap items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <p className=\"flex items-center gap-1 text-xs text-text-secondary\">\n <MapPin className=\"h-3 w-3\" />\n Property\n </p>\n <h3 className=\"truncate text-base font-semibold text-text-primary\">{contract.propertyTitle}</h3>\n <p className=\"text-xs text-text-secondary\">{contract.packageName}</p>\n </div>\n <StatusBadge\n label={contract.status.replace('_', ' ')}\n variant={getStatusVariant(contract.status)}\n dot\n />\n </div>\n <div className=\"mt-4 grid grid-cols-2 gap-4 border-t border-border-subtle pt-4 sm:grid-cols-4\">\n <div>\n <p className=\"text-xs text-text-secondary\">Provider</p>\n <p className=\"mt-0.5 text-sm font-medium text-text-primary\">{contract.providerName}</p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Coverage</p>\n <p className=\"mt-0.5 truncate text-sm font-medium text-text-primary\">\n {contract.servicesIncluded.length > 0\n ? contract.servicesIncluded.slice(0, 2).join(', ')\n : '—'}\n {contract.servicesIncluded.length > 2 ? ` +${contract.servicesIncluded.length - 2}` : ''}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Monthly Cost</p>\n <p className=\"mt-0.5 text-sm font-semibold text-text-primary\">\n {formatCurrency(contract.monthlyCost)}\n <span className=\"text-xs font-normal text-text-secondary\">/mo</span>\n </p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Renewal Date</p>\n <p className=\"mt-0.5 text-sm font-medium text-text-primary\">\n {formatCalendarDate(contract.endDate)}\n </p>\n </div>\n </div>\n {contract.nextServiceDate && (\n <p className=\"mt-3 flex items-center gap-1.5 text-xs text-text-secondary\">\n <Calendar className=\"h-3.5 w-3.5\" />\n Next service {formatCalendarDate(contract.nextServiceDate)}\n </p>\n )}\n <div className=\"mt-3 flex flex-wrap items-center gap-2 border-t border-border-subtle pt-3\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => manage.setEditTarget(contract)}>\n <Pencil className=\"mr-1.5 h-3.5 w-3.5\" />\n Edit\n </Button>\n {contract.status !== 'cancelled' && (\n <>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => manage.openRenew(contract)}>\n <RotateCcw className=\"mr-1.5 h-3.5 w-3.5\" />\n Renew\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => manage.setCancelTarget(contract)}\n >\n <Ban className=\"mr-1.5 h-3.5 w-3.5\" />\n Cancel\n </Button>\n </>\n )}\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"ml-auto text-status-error-text\"\n onClick={() => manage.setDeleteTarget(contract)}\n >\n <Trash2 className=\"mr-1.5 h-3.5 w-3.5\" />\n Delete\n </Button>\n </div>\n </div>\n ))}\n </div>\n <Pagination\n page={pagination.page}\n pageSize={pagination.pageSize}\n hasNextPage={amcContracts.length >= pagination.pageSize}\n hasPreviousPage={pagination.page > 1}\n onPageChange={pagination.setPage}\n onPageSizeChange={pagination.setPageSize}\n />\n </>\n ) : (\n <EmptyState\n illustration=\"empty-search\"\n icon={<Shield className=\"h-12 w-12\" />}\n title=\"No contracts found\"\n description=\"Try adjusting your search criteria or create a new contract.\"\n action={\n <Button type=\"button\" onClick={openCreate} className=\"gap-2\">\n <Plus className=\"h-4 w-4\" />\n New Contract\n </Button>\n }\n />\n )}\n </div>\n\n {/* Sidebar */}\n <aside className=\"space-y-6 lg:col-span-1\">\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-5 shadow-sm\">\n <h3 className=\"mb-4 text-base font-semibold text-text-primary\">Upcoming Service Schedule</h3>\n {scheduledServices.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No services scheduled\"\n description=\"Upcoming service dates from your active contracts will appear here.\"\n className=\"border-0 bg-transparent p-4\"\n />\n ) : (\n <ol className=\"space-y-4\">\n {scheduledServices.map((contract) => {\n const daysUntil = calendarDaysUntil(contract.nextServiceDate);\n const isUrgent = daysUntil <= 7;\n const isPast = daysUntil < 0;\n return (\n <li key={contract.id} className=\"flex gap-3\">\n <div\n className={cn(\n 'flex h-9 w-9 shrink-0 items-center justify-center rounded-full',\n isPast\n ? 'bg-status-error-bg-subtle'\n : isUrgent\n ? 'bg-status-warning-bg-subtle'\n : 'bg-status-info-bg-subtle'\n )}\n >\n {isPast ? (\n <AlertTriangle className=\"h-4 w-4 text-status-error-text\" />\n ) : isUrgent ? (\n <Clock className=\"h-4 w-4 text-status-warning-text\" />\n ) : (\n <Wrench className=\"h-4 w-4 text-status-info-text\" />\n )}\n </div>\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-text-primary\">\n {formatCalendarDate(contract.nextServiceDate)}\n </p>\n <p className=\"truncate text-sm text-text-primary\">\n {contract.packageName} — {contract.propertyTitle}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">{contract.providerName}</p>\n <p\n className={cn(\n 'text-xs font-medium',\n isPast\n ? 'text-status-error-text'\n : isUrgent\n ? 'text-status-warning-text'\n : 'text-text-secondary'\n )}\n >\n {isPast\n ? `${Math.abs(daysUntil)} days overdue`\n : daysUntil === 0\n ? 'Today'\n : `In ${daysUntil} days`}\n </p>\n </div>\n </li>\n );\n })}\n </ol>\n )}\n </div>\n\n <button\n type=\"button\"\n onClick={() => navigate('../rental')}\n className=\"flex w-full items-center gap-3 rounded-lg border border-border-subtle bg-bg-surface p-4 text-left shadow-sm transition-colors hover:bg-bg-sunken focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <div className=\"rounded-lg bg-status-info-bg-subtle p-2.5\">\n <Wrench className=\"h-5 w-5 text-status-info-text\" />\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">Open maintenance requests</p>\n <p className=\"text-xl font-bold text-text-primary\">{openMaintenanceCount}</p>\n </div>\n </button>\n </aside>\n </div>\n\n <AmcContractFormDialog\n open={createOpen || manage.editTarget !== null}\n onOpenChange={(open) => {\n if (!open) {\n setCreateOpen(false);\n manage.setEditTarget(null);\n }\n }}\n contract={manage.editTarget}\n saving={creating || manage.updating}\n onSubmit={manage.editTarget ? handleEdit : handleCreate}\n />\n <AmcRenewDialog\n contract={manage.renewTarget}\n endDate={manage.renewEnd}\n onEndDateChange={manage.setRenewEnd}\n onClose={() => manage.setRenewTarget(null)}\n onSubmit={manage.handleRenew}\n busy={manage.renewing}\n />\n <AmcCancelDialog\n contract={manage.cancelTarget}\n onClose={() => manage.setCancelTarget(null)}\n onConfirm={manage.confirmCancel}\n busy={manage.updating}\n />\n <AmcDeleteDialog\n contract={manage.deleteTarget}\n onClose={() => manage.setDeleteTarget(null)}\n onConfirm={manage.confirmDelete}\n busy={manage.deleting}\n />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AMCManagementPage.js","names":[],"sources":["../../../../src/housingvista/pages/amc/AMCManagementPage.tsx"],"sourcesContent":["import { useState, useMemo, useEffect } from 'react';\nimport { useNavigate } from 'react-router';\nimport {\n Shield,\n DollarSign,\n Calendar,\n Plus,\n Download,\n Clock,\n AlertTriangle,\n Wrench,\n RotateCcw,\n MapPin,\n Pencil,\n Ban,\n Trash2,\n} from 'lucide-react';\nimport { Button, EmphasisPanel, NextSteps, Pagination } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components';\nimport { StatCard } from '../../components';\nimport { StatusBadge } from '../../components';\nimport { FilterBar } from '../../components';\nimport { EmptyState } from '../../components';\nimport { useAMCContracts, useCreateAmcContract, useMaintenanceRequests } from '../../hooks';\nimport { useServerPagination } from '../../hooks/useServerPagination';\nimport { cn, formatCurrency } from '../../utils/helpers';\nimport { notify } from '../../lib/toast';\nimport type { AMCContract } from '../../types';\nimport { useTrackPageView } from '../../hooks/useHousingVistaAnalytics';\nimport { AmcContractFormDialog, type AmcContractFormValues } from './AmcContractFormDialog';\nimport { AmcCancelDialog, AmcDeleteDialog, AmcRenewDialog } from './AmcContractActionDialogs';\nimport { useAmcManagement } from './useAmcManagement';\nimport { calendarDaysUntil, formatCalendarDate } from './amcDates';\n\nfunction getStatusVariant(status: AMCContract['status']): 'success' | 'warning' | 'error' | 'muted' {\n switch (status) {\n case 'active':\n return 'success';\n case 'expiring_soon':\n return 'warning';\n case 'expired':\n return 'error';\n case 'cancelled':\n return 'muted';\n default:\n return 'muted';\n }\n}\n\nexport function AMCManagementPage() {\n useTrackPageView('housingvista.amc.viewed');\n const navigate = useNavigate();\n const pagination = useServerPagination(10);\n const [search, setSearch] = useState('');\n const [debouncedSearch, setDebouncedSearch] = useState('');\n\n // Debounce the raw search input into the value that feeds the server filter.\n useEffect(() => {\n const timer = setTimeout(() => setDebouncedSearch(search), 300);\n return () => clearTimeout(timer);\n }, [search]);\n\n const { data: amcContracts, loading: contractsLoading } = useAMCContracts({\n filter: { search: debouncedSearch.trim() || undefined },\n pagination: pagination.pagination,\n });\n\n // Deleting the last row on page 2+ leaves an empty page (the Pagination control is hidden when the list is\n // empty) — step back to the previous page instead of stranding the user (Codex P2 on mfe#22).\n const { page: currentPage, setPage: setCurrentPage } = pagination;\n useEffect(() => {\n if (!contractsLoading && amcContracts.length === 0 && currentPage > 1) {\n setCurrentPage(currentPage - 1);\n }\n }, [contractsLoading, amcContracts.length, currentPage, setCurrentPage]);\n\n const { createAmcContract, loading: creating } = useCreateAmcContract();\n\n // Open maintenance requests across the portfolio — real count for the sidebar stat.\n const { data: maintenanceRequests } = useMaintenanceRequests({\n filter: { status: 'open' },\n pagination: { limit: 100, offset: 0 },\n });\n const openMaintenanceCount = maintenanceRequests.length;\n\n const handleSearchChange = (value: string) => {\n setSearch(value);\n pagination.setPage(1);\n };\n\n const handleExport = () => {\n if (amcContracts.length === 0) {\n notify.warning('Nothing to export', 'There are no contracts to export yet.');\n return;\n }\n const escape = (value: string) => `\"${value.replace(/\"/g, '\"\"')}\"`;\n const headers = ['Package', 'Property', 'Provider', 'Monthly Cost', 'Start', 'End', 'Status', 'Services'];\n const rows = amcContracts.map((c) =>\n [\n c.packageName,\n c.propertyTitle,\n c.providerName,\n String(c.monthlyCost),\n c.startDate,\n c.endDate,\n c.status,\n c.servicesIncluded.join('; '),\n ]\n .map((cell) => escape(String(cell)))\n .join(',')\n );\n const csv = [headers.map(escape).join(','), ...rows].join('\\n');\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = `housingvista-amc-contracts-${new Date().toISOString().slice(0, 10)}.csv`;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n notify.success('Export ready', `${amcContracts.length} contracts exported to CSV.`);\n };\n\n const [createOpen, setCreateOpen] = useState(false);\n const manage = useAmcManagement();\n\n const handleCreate = async (values: AmcContractFormValues) => {\n try {\n await createAmcContract({\n propertyTitle: values.propertyTitle,\n packageName: values.packageName,\n providerName: values.providerName,\n monthlyCost: values.monthlyCost,\n startDate: values.startDate,\n endDate: values.endDate,\n status: 'active',\n nextServiceDate: values.nextServiceDate || undefined,\n servicesIncluded: values.servicesIncluded.length > 0 ? values.servicesIncluded : undefined,\n });\n notify.success('Contract created', `${values.packageName} added for ${values.propertyTitle}.`);\n setCreateOpen(false);\n } catch (err) {\n notify.error('Could not create contract', (err as Error).message);\n }\n };\n\n const handleEdit = async (values: AmcContractFormValues) => {\n const target = manage.editTarget;\n if (!target) return;\n try {\n await manage.updateAmcContract(target.id, {\n propertyTitle: values.propertyTitle,\n packageName: values.packageName,\n providerName: values.providerName,\n monthlyCost: values.monthlyCost,\n startDate: values.startDate,\n endDate: values.endDate,\n // null clears a previously scheduled service.\n nextServiceDate: values.nextServiceDate || null,\n servicesIncluded: values.servicesIncluded,\n });\n notify.success('Contract updated', `${values.packageName} saved.`);\n manage.setEditTarget(null);\n } catch (err) {\n notify.error('Could not update contract', (err as Error).message);\n }\n };\n\n const openCreate = () => setCreateOpen(true);\n\n // Only live contracts feed the schedule AND the \"Upcoming Services\" stat, so the two always agree.\n const scheduledServices = useMemo(\n () =>\n amcContracts\n .filter((c) => c.nextServiceDate && (c.status === 'active' || c.status === 'expiring_soon'))\n .sort((a, b) => new Date(a.nextServiceDate).getTime() - new Date(b.nextServiceDate).getTime()),\n [amcContracts]\n );\n\n const stats = useMemo(() => {\n const activeContracts = amcContracts.filter((c) => c.status === 'active' || c.status === 'expiring_soon').length;\n const pendingRenewals = amcContracts.filter((c) => c.status === 'expiring_soon').length;\n const totalMonthlySpent = amcContracts\n .filter((c) => c.status === 'active' || c.status === 'expiring_soon')\n .reduce((sum, c) => sum + c.monthlyCost, 0);\n const upcomingServices = scheduledServices.filter((c) => {\n const daysUntil = calendarDaysUntil(c.nextServiceDate);\n return daysUntil >= 0 && daysUntil <= 30;\n }).length;\n return { activeContracts, pendingRenewals, totalMonthlySpent, upcomingServices };\n }, [amcContracts, scheduledServices]);\n\n // Accent styling per contract status for the card treatment.\n const statusAccent: Record<AMCContract['status'], string> = {\n active: 'border-l-4 border-l-status-success-border',\n expiring_soon: 'border-l-4 border-l-status-warning-border bg-status-warning-bg-subtle/30',\n expired: 'border-l-4 border-l-status-error-border',\n cancelled: 'border-l-4 border-l-border-strong',\n };\n\n return (\n <PageLayout\n title=\"AMC Management\"\n description=\"Manage Annual Maintenance Contracts and service packages\"\n purpose=\"Keep your properties maintained without surprises. Track every Annual Maintenance Contract, see what each covers and costs, get ahead of renewals, and know which services are due in the next 30 days so nothing slips.\"\n actions={\n <div className=\"flex items-center gap-2\">\n <button\n onClick={handleExport}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Download className=\"h-4 w-4\" />\n Export\n </button>\n <Button type=\"button\" onClick={openCreate} className=\"gap-2\">\n <Plus className=\"h-4 w-4\" />\n New Contract\n </Button>\n </div>\n }\n >\n {/* Stats — primary insight zone (one EmphasisPanel per page) */}\n <EmphasisPanel className=\"p-4\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard label=\"Active Contracts\" value={stats.activeContracts} icon={<Shield className=\"h-4 w-4\" />} />\n <StatCard label=\"Pending Renewals\" value={stats.pendingRenewals} icon={<RotateCcw className=\"h-4 w-4\" />} />\n <StatCard\n label=\"Total Monthly Spent\"\n value={stats.totalMonthlySpent}\n prefix=\"$\"\n icon={<DollarSign className=\"h-4 w-4\" />}\n />\n <StatCard label=\"Upcoming Services\" value={stats.upcomingServices} icon={<Calendar className=\"h-4 w-4\" />} />\n </div>\n </EmphasisPanel>\n\n <NextSteps\n storageKey=\"housingvista-amc\"\n steps={[\n {\n id: 'pick-property',\n label: 'Add coverage for a property',\n description: 'Choose a property to set up a new maintenance contract.',\n onClick: () => navigate('../properties'),\n },\n {\n id: 'compare-providers',\n label: 'Compare your providers',\n description: 'Search by provider name to weigh coverage and monthly cost.',\n onClick: () => {\n const firstProvider = amcContracts[0]?.providerName;\n if (firstProvider) {\n setSearch(firstProvider);\n pagination.setPage(1);\n }\n },\n },\n {\n id: 'check-legal',\n label: 'Review contract paperwork',\n description: 'Open the legal hub to manage the underlying documents.',\n onClick: () => navigate('../legal'),\n },\n ]}\n />\n\n {/* Contract cards (left) + service schedule & open-maintenance sidebar (right) */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"space-y-4 lg:col-span-2\">\n <div className=\"flex flex-col gap-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">All Contracts</h2>\n <FilterBar\n searchValue={search}\n onSearchChange={handleSearchChange}\n searchPlaceholder=\"Search contracts, providers, properties...\"\n />\n </div>\n {amcContracts.length > 0 ? (\n <>\n <div className=\"space-y-3\">\n {amcContracts.map((contract) => (\n <div\n key={contract.id}\n className={cn(\n 'rounded-lg border border-border-subtle bg-bg-surface p-5 shadow-sm',\n statusAccent[contract.status]\n )}\n >\n <div className=\"flex flex-wrap items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <p className=\"flex items-center gap-1 text-xs text-text-secondary\">\n <MapPin className=\"h-3 w-3\" />\n Property\n </p>\n <h3 className=\"truncate text-base font-semibold text-text-primary\">{contract.propertyTitle}</h3>\n <p className=\"text-xs text-text-secondary\">{contract.packageName}</p>\n </div>\n <StatusBadge\n label={contract.status.replace('_', ' ')}\n variant={getStatusVariant(contract.status)}\n dot\n />\n </div>\n <div className=\"mt-4 grid grid-cols-2 gap-4 border-t border-border-subtle pt-4 sm:grid-cols-4\">\n <div>\n <p className=\"text-xs text-text-secondary\">Provider</p>\n <p className=\"mt-0.5 text-sm font-medium text-text-primary\">{contract.providerName}</p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Coverage</p>\n <p className=\"mt-0.5 truncate text-sm font-medium text-text-primary\">\n {contract.servicesIncluded.length > 0\n ? contract.servicesIncluded.slice(0, 2).join(', ')\n : '—'}\n {contract.servicesIncluded.length > 2 ? ` +${contract.servicesIncluded.length - 2}` : ''}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Monthly Cost</p>\n <p className=\"mt-0.5 text-sm font-semibold text-text-primary\">\n {formatCurrency(contract.monthlyCost)}\n <span className=\"text-xs font-normal text-text-secondary\">/mo</span>\n </p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Renewal Date</p>\n <p className=\"mt-0.5 text-sm font-medium text-text-primary\">\n {formatCalendarDate(contract.endDate)}\n </p>\n </div>\n </div>\n {contract.nextServiceDate && (\n <p className=\"mt-3 flex items-center gap-1.5 text-xs text-text-secondary\">\n <Calendar className=\"h-3.5 w-3.5\" />\n Next service {formatCalendarDate(contract.nextServiceDate)}\n </p>\n )}\n <div className=\"mt-3 flex flex-wrap items-center gap-2 border-t border-border-subtle pt-3\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => manage.setEditTarget(contract)}>\n <Pencil className=\"mr-1.5 h-3.5 w-3.5\" />\n Edit\n </Button>\n {contract.status !== 'cancelled' && (\n <>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => manage.openRenew(contract)}>\n <RotateCcw className=\"mr-1.5 h-3.5 w-3.5\" />\n Renew\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => manage.setCancelTarget(contract)}\n >\n <Ban className=\"mr-1.5 h-3.5 w-3.5\" />\n Cancel\n </Button>\n </>\n )}\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"ml-auto text-status-error-text\"\n onClick={() => manage.setDeleteTarget(contract)}\n >\n <Trash2 className=\"mr-1.5 h-3.5 w-3.5\" />\n Delete\n </Button>\n </div>\n </div>\n ))}\n </div>\n <Pagination\n page={pagination.page}\n pageSize={pagination.pageSize}\n hasNextPage={amcContracts.length >= pagination.pageSize}\n hasPreviousPage={pagination.page > 1}\n onPageChange={pagination.setPage}\n onPageSizeChange={pagination.setPageSize}\n />\n </>\n ) : (\n <EmptyState\n illustration=\"empty-search\"\n icon={<Shield className=\"h-12 w-12\" />}\n title=\"No contracts found\"\n description=\"Try adjusting your search criteria or create a new contract.\"\n action={\n <Button type=\"button\" onClick={openCreate} className=\"gap-2\">\n <Plus className=\"h-4 w-4\" />\n New Contract\n </Button>\n }\n />\n )}\n </div>\n\n {/* Sidebar */}\n <aside className=\"space-y-6 lg:col-span-1\">\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-5 shadow-sm\">\n <h3 className=\"mb-4 text-base font-semibold text-text-primary\">Upcoming Service Schedule</h3>\n {scheduledServices.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No services scheduled\"\n description=\"Upcoming service dates from your active contracts will appear here.\"\n className=\"border-0 bg-transparent p-4\"\n />\n ) : (\n <ol className=\"space-y-4\">\n {scheduledServices.map((contract) => {\n const daysUntil = calendarDaysUntil(contract.nextServiceDate);\n const isUrgent = daysUntil <= 7;\n const isPast = daysUntil < 0;\n return (\n <li key={contract.id} className=\"flex gap-3\">\n <div\n className={cn(\n 'flex h-9 w-9 shrink-0 items-center justify-center rounded-full',\n isPast\n ? 'bg-status-error-bg-subtle'\n : isUrgent\n ? 'bg-status-warning-bg-subtle'\n : 'bg-status-info-bg-subtle'\n )}\n >\n {isPast ? (\n <AlertTriangle className=\"h-4 w-4 text-status-error-text\" />\n ) : isUrgent ? (\n <Clock className=\"h-4 w-4 text-status-warning-text\" />\n ) : (\n <Wrench className=\"h-4 w-4 text-status-info-text\" />\n )}\n </div>\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-text-primary\">\n {formatCalendarDate(contract.nextServiceDate)}\n </p>\n <p className=\"truncate text-sm text-text-primary\">\n {contract.packageName} — {contract.propertyTitle}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">{contract.providerName}</p>\n <p\n className={cn(\n 'text-xs font-medium',\n isPast\n ? 'text-status-error-text'\n : isUrgent\n ? 'text-status-warning-text'\n : 'text-text-secondary'\n )}\n >\n {isPast\n ? `${Math.abs(daysUntil)} days overdue`\n : daysUntil === 0\n ? 'Today'\n : `In ${daysUntil} days`}\n </p>\n </div>\n </li>\n );\n })}\n </ol>\n )}\n </div>\n\n <button\n type=\"button\"\n onClick={() => navigate('../rental')}\n className=\"flex w-full items-center gap-3 rounded-lg border border-border-subtle bg-bg-surface p-4 text-left shadow-sm transition-colors hover:bg-bg-sunken focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <div className=\"rounded-lg bg-status-info-bg-subtle p-2.5\">\n <Wrench className=\"h-5 w-5 text-status-info-text\" />\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">Open maintenance requests</p>\n <p className=\"text-xl font-bold text-text-primary\">{openMaintenanceCount}</p>\n </div>\n </button>\n </aside>\n </div>\n\n <AmcContractFormDialog\n open={createOpen || manage.editTarget !== null}\n onOpenChange={(open) => {\n if (!open) {\n setCreateOpen(false);\n manage.setEditTarget(null);\n }\n }}\n contract={manage.editTarget}\n saving={creating || manage.updating}\n onSubmit={manage.editTarget ? handleEdit : handleCreate}\n />\n <AmcRenewDialog\n contract={manage.renewTarget}\n endDate={manage.renewEnd}\n onEndDateChange={manage.setRenewEnd}\n onClose={() => manage.setRenewTarget(null)}\n onSubmit={manage.handleRenew}\n busy={manage.renewing}\n />\n <AmcCancelDialog\n contract={manage.cancelTarget}\n onClose={() => manage.setCancelTarget(null)}\n onConfirm={manage.confirmCancel}\n busy={manage.updating}\n />\n <AmcDeleteDialog\n contract={manage.deleteTarget}\n onClose={() => manage.setDeleteTarget(null)}\n onConfirm={manage.confirmDelete}\n busy={manage.deleting}\n />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { AMCContract } from '../../types';
|
|
2
2
|
|
|
3
|
-
export type AmcStatus = 'active' | 'expiring_soon' | 'expired' | 'cancelled';
|
|
4
3
|
export interface AmcContractFormValues {
|
|
5
4
|
propertyTitle: string;
|
|
6
5
|
packageName: string;
|
|
@@ -10,7 +9,6 @@ export interface AmcContractFormValues {
|
|
|
10
9
|
endDate: string;
|
|
11
10
|
/** ISO date, or '' when no upcoming service is scheduled. */
|
|
12
11
|
nextServiceDate: string;
|
|
13
|
-
status: AmcStatus;
|
|
14
12
|
servicesIncluded: string[];
|
|
15
13
|
}
|
|
16
14
|
interface AmcContractFormDialogProps {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AmcContractFormDialog.d.ts","sourceRoot":"","sources":["../../../../src/housingvista/pages/amc/AmcContractFormDialog.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AmcContractFormDialog.d.ts","sourceRoot":"","sources":["../../../../src/housingvista/pages/amc/AmcContractFormDialog.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C,MAAM,WAAW,qBAAqB;IACpC,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,UAAU,0BAA0B;IAClC,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,6DAA6D;IAC7D,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAC;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5D;AAED,wBAAgB,qBAAqB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,0BAA0B,+BA0LnH"}
|
|
@@ -6,9 +6,9 @@ import { jsx as s, jsxs as c } from "react/jsx-runtime";
|
|
|
6
6
|
import { Button as l, ResponsiveDialog as u, ResponsiveDialogContent as d, ResponsiveDialogDescription as f, ResponsiveDialogFooter as p, ResponsiveDialogHeader as m, ResponsiveDialogTitle as h } from "@burdenoff/fe-libs/ui";
|
|
7
7
|
//#region src/housingvista/pages/amc/AmcContractFormDialog.tsx
|
|
8
8
|
function g({ open: g, onOpenChange: _, contract: v, saving: y, onSubmit: b }) {
|
|
9
|
-
let x = v !== null, [S, C] = o(""), [w, T] = o(""), [E, D] = o(""), [O, k] = o(""), [A, j] = o(""), [M, N] = o(""), [P, F] = o(""), [I, L] = o("
|
|
9
|
+
let x = v !== null, [S, C] = o(""), [w, T] = o(""), [E, D] = o(""), [O, k] = o(""), [A, j] = o(""), [M, N] = o(""), [P, F] = o(""), [I, L] = o("");
|
|
10
10
|
return a(() => {
|
|
11
|
-
g && (C(v?.propertyTitle ?? ""), T(v?.packageName ?? ""), D(v?.providerName ?? ""), k(v ? String(v.monthlyCost) : ""), j(v ? n(v.startDate) : ""), N(v ? n(v.endDate) : ""), F(v ? n(v.nextServiceDate ?? "") : ""), L(
|
|
11
|
+
g && (C(v?.propertyTitle ?? ""), T(v?.packageName ?? ""), D(v?.providerName ?? ""), k(v ? String(v.monthlyCost) : ""), j(v ? n(v.startDate) : ""), N(v ? n(v.endDate) : ""), F(v ? n(v.nextServiceDate ?? "") : ""), L(v ? v.servicesIncluded.join(", ") : ""));
|
|
12
12
|
}, [g, v]), /* @__PURE__ */ s(u, {
|
|
13
13
|
open: g,
|
|
14
14
|
onOpenChange: _,
|
|
@@ -42,8 +42,7 @@ function g({ open: g, onOpenChange: _, contract: v, saving: y, onSubmit: b }) {
|
|
|
42
42
|
startDate: t(A),
|
|
43
43
|
endDate: t(M),
|
|
44
44
|
nextServiceDate: t(P),
|
|
45
|
-
|
|
46
|
-
servicesIncluded: R.split(",").map((e) => e.trim()).filter((e) => e.length > 0)
|
|
45
|
+
servicesIncluded: I.split(",").map((e) => e.trim()).filter((e) => e.length > 0)
|
|
47
46
|
});
|
|
48
47
|
},
|
|
49
48
|
children: [
|
|
@@ -102,55 +101,26 @@ function g({ open: g, onOpenChange: _, contract: v, saving: y, onSubmit: b }) {
|
|
|
102
101
|
})] })]
|
|
103
102
|
}),
|
|
104
103
|
/* @__PURE__ */ c("div", {
|
|
105
|
-
className:
|
|
106
|
-
children: [
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
className: i
|
|
126
|
-
})] }),
|
|
127
|
-
!x && /* @__PURE__ */ c("div", { children: [/* @__PURE__ */ s("label", {
|
|
128
|
-
className: "mb-1.5 block text-sm font-medium text-text-primary",
|
|
129
|
-
children: "Status"
|
|
130
|
-
}), /* @__PURE__ */ c("select", {
|
|
131
|
-
value: I,
|
|
132
|
-
onChange: (e) => L(e.target.value),
|
|
133
|
-
className: "h-10 w-full rounded-md border border-border-subtle bg-bg-surface px-3 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
|
|
134
|
-
children: [
|
|
135
|
-
/* @__PURE__ */ s("option", {
|
|
136
|
-
value: "active",
|
|
137
|
-
children: "Active"
|
|
138
|
-
}),
|
|
139
|
-
/* @__PURE__ */ s("option", {
|
|
140
|
-
value: "expiring_soon",
|
|
141
|
-
children: "Expiring soon"
|
|
142
|
-
}),
|
|
143
|
-
/* @__PURE__ */ s("option", {
|
|
144
|
-
value: "expired",
|
|
145
|
-
children: "Expired"
|
|
146
|
-
}),
|
|
147
|
-
/* @__PURE__ */ s("option", {
|
|
148
|
-
value: "cancelled",
|
|
149
|
-
children: "Cancelled"
|
|
150
|
-
})
|
|
151
|
-
]
|
|
152
|
-
})] })
|
|
153
|
-
]
|
|
104
|
+
className: "grid grid-cols-1 gap-3 sm:grid-cols-2",
|
|
105
|
+
children: [/* @__PURE__ */ c("div", { children: [/* @__PURE__ */ s("label", {
|
|
106
|
+
className: "mb-1.5 block text-sm font-medium text-text-primary",
|
|
107
|
+
children: "Start date"
|
|
108
|
+
}), /* @__PURE__ */ s("input", {
|
|
109
|
+
type: "date",
|
|
110
|
+
required: x,
|
|
111
|
+
value: A,
|
|
112
|
+
onChange: (e) => j(e.target.value),
|
|
113
|
+
className: i
|
|
114
|
+
})] }), /* @__PURE__ */ c("div", { children: [/* @__PURE__ */ s("label", {
|
|
115
|
+
className: "mb-1.5 block text-sm font-medium text-text-primary",
|
|
116
|
+
children: "End date"
|
|
117
|
+
}), /* @__PURE__ */ s("input", {
|
|
118
|
+
type: "date",
|
|
119
|
+
required: x,
|
|
120
|
+
value: M,
|
|
121
|
+
onChange: (e) => N(e.target.value),
|
|
122
|
+
className: i
|
|
123
|
+
})] })]
|
|
154
124
|
}),
|
|
155
125
|
/* @__PURE__ */ s("div", {
|
|
156
126
|
className: "grid grid-cols-1 gap-3 sm:grid-cols-2",
|
|
@@ -179,8 +149,8 @@ function g({ open: g, onOpenChange: _, contract: v, saving: y, onSubmit: b }) {
|
|
|
179
149
|
}), /* @__PURE__ */ s("input", {
|
|
180
150
|
type: "text",
|
|
181
151
|
placeholder: "Comma-separated, e.g. Plumbing, Electrical, HVAC",
|
|
182
|
-
value:
|
|
183
|
-
onChange: (e) =>
|
|
152
|
+
value: I,
|
|
153
|
+
onChange: (e) => L(e.target.value),
|
|
184
154
|
className: i
|
|
185
155
|
})] })
|
|
186
156
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AmcContractFormDialog.js","names":[],"sources":["../../../../src/housingvista/pages/amc/AmcContractFormDialog.tsx"],"sourcesContent":["/**\n * New / edit contract dialog for the AMC page (BOFF-9600). One form for both\n * modes: `contract` null = create, otherwise edit with the fields prefilled.\n * Status is only chosen on create; an existing contract is cancelled through\n * the Cancel action and reactivated through Renew.\n */\nimport { useEffect, useState, type FormEvent } from 'react';\nimport {\n Button,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n} from '@burdenoff/fe-libs/ui';\nimport { notify } from '../../lib/toast';\nimport type { AMCContract } from '../../types';\nimport { dateInputToIso, toDateInput, withinContractWindow } from './amcDates';\nimport { inputClass } from './amcStyles';\n\nexport type AmcStatus = 'active' | 'expiring_soon' | 'expired' | 'cancelled';\n\nexport interface AmcContractFormValues {\n propertyTitle: string;\n packageName: string;\n providerName: string;\n monthlyCost: number;\n startDate: string;\n endDate: string;\n /** ISO date, or '' when no upcoming service is scheduled. */\n nextServiceDate: string;\n status: AmcStatus;\n servicesIncluded: string[];\n}\n\ninterface AmcContractFormDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n /** null = create a new contract; otherwise edit this one. */\n contract: AMCContract | null;\n saving: boolean;\n onSubmit: (values: AmcContractFormValues) => Promise<void>;\n}\n\nexport function AmcContractFormDialog({ open, onOpenChange, contract, saving, onSubmit }: AmcContractFormDialogProps) {\n const isEdit = contract !== null;\n const [propertyTitle, setPropertyTitle] = useState('');\n const [packageName, setPackageName] = useState('');\n const [providerName, setProviderName] = useState('');\n const [monthlyCost, setMonthlyCost] = useState('');\n const [startDate, setStartDate] = useState('');\n const [endDate, setEndDate] = useState('');\n const [nextServiceDate, setNextServiceDate] = useState('');\n const [status, setStatus] = useState<AmcStatus>('active');\n const [servicesIncluded, setServicesIncluded] = useState('');\n\n // Reset (create) or prefill (edit) every time the dialog opens.\n useEffect(() => {\n if (!open) return;\n setPropertyTitle(contract?.propertyTitle ?? '');\n setPackageName(contract?.packageName ?? '');\n setProviderName(contract?.providerName ?? '');\n setMonthlyCost(contract ? String(contract.monthlyCost) : '');\n setStartDate(contract ? toDateInput(contract.startDate) : '');\n setEndDate(contract ? toDateInput(contract.endDate) : '');\n // The column is nullable: a contract without a scheduled service reads back null.\n setNextServiceDate(contract ? toDateInput(contract.nextServiceDate ?? '') : '');\n setStatus('active');\n setServicesIncluded(contract ? contract.servicesIncluded.join(', ') : '');\n }, [open, contract]);\n\n const handleSubmit = async (event: FormEvent) => {\n event.preventDefault();\n const cost = Number(monthlyCost);\n if (!propertyTitle.trim() || !packageName.trim() || !providerName.trim()) {\n notify.warning('Missing details', 'Property, package and provider are required.');\n return;\n }\n if (!Number.isFinite(cost) || cost <= 0) {\n notify.warning('Enter a valid monthly cost');\n return;\n }\n if (isEdit && (!startDate || !endDate)) {\n notify.warning('Dates are required', 'Start and end dates cannot be cleared on an existing contract.');\n return;\n }\n if (\n nextServiceDate &&\n !withinContractWindow(dateInputToIso(startDate), dateInputToIso(endDate), dateInputToIso(nextServiceDate))\n ) {\n notify.warning('Next service date out of range', 'It must fall between the contract start and end dates.');\n return;\n }\n await onSubmit({\n propertyTitle: propertyTitle.trim(),\n packageName: packageName.trim(),\n providerName: providerName.trim(),\n monthlyCost: cost,\n startDate: dateInputToIso(startDate),\n endDate: dateInputToIso(endDate),\n nextServiceDate: dateInputToIso(nextServiceDate),\n status,\n servicesIncluded: servicesIncluded\n .split(',')\n .map((s) => s.trim())\n .filter((s) => s.length > 0),\n });\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"sm:max-w-lg\">\n <form onSubmit={handleSubmit}>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {isEdit ? 'Edit maintenance contract' : 'New maintenance contract'}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {isEdit\n ? 'Update the coverage, cost or dates of this Annual Maintenance Contract.'\n : 'Set up an Annual Maintenance Contract to track coverage, cost and renewal dates.'}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-3 py-2\">\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Property *</label>\n <input\n type=\"text\"\n required\n placeholder=\"e.g. Maple Court\"\n value={propertyTitle}\n onChange={(e) => setPropertyTitle(e.target.value)}\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Package *</label>\n <input\n type=\"text\"\n required\n placeholder=\"e.g. Premium Care\"\n value={packageName}\n onChange={(e) => setPackageName(e.target.value)}\n className={inputClass}\n />\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Provider *</label>\n <input\n type=\"text\"\n required\n placeholder=\"Service company\"\n value={providerName}\n onChange={(e) => setProviderName(e.target.value)}\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Monthly cost *</label>\n <input\n type=\"number\"\n required\n min={0}\n step=\"0.01\"\n placeholder=\"0.00\"\n value={monthlyCost}\n onChange={(e) => setMonthlyCost(e.target.value)}\n className={inputClass}\n />\n </div>\n </div>\n <div className={isEdit ? 'grid grid-cols-1 gap-3 sm:grid-cols-2' : 'grid grid-cols-1 gap-3 sm:grid-cols-3'}>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Start date</label>\n <input\n type=\"date\"\n required={isEdit}\n value={startDate}\n onChange={(e) => setStartDate(e.target.value)}\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">End date</label>\n <input\n type=\"date\"\n required={isEdit}\n value={endDate}\n onChange={(e) => setEndDate(e.target.value)}\n className={inputClass}\n />\n </div>\n {!isEdit && (\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Status</label>\n <select\n value={status}\n onChange={(e) => setStatus(e.target.value as AmcStatus)}\n className={inputClass}\n >\n <option value=\"active\">Active</option>\n <option value=\"expiring_soon\">Expiring soon</option>\n <option value=\"expired\">Expired</option>\n <option value=\"cancelled\">Cancelled</option>\n </select>\n </div>\n )}\n </div>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Next service date</label>\n <input\n type=\"date\"\n min={startDate || undefined}\n max={endDate || undefined}\n value={nextServiceDate}\n onChange={(e) => setNextServiceDate(e.target.value)}\n className={inputClass}\n />\n <p className=\"mt-1 text-xs text-text-secondary\">Optional — shows in the upcoming service schedule.</p>\n </div>\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Services included</label>\n <input\n type=\"text\"\n placeholder=\"Comma-separated, e.g. Plumbing, Electrical, HVAC\"\n value={servicesIncluded}\n onChange={(e) => setServicesIncluded(e.target.value)}\n className={inputClass}\n />\n </div>\n </div>\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => onOpenChange(false)}>\n Cancel\n </Button>\n <Button type=\"submit\" disabled={saving}>\n {saving ? 'Saving…' : isEdit ? 'Save changes' : 'Create contract'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AmcContractFormDialog.js","names":[],"sources":["../../../../src/housingvista/pages/amc/AmcContractFormDialog.tsx"],"sourcesContent":["/**\n * New / edit contract dialog for the AMC page (BOFF-9600). One form for both\n * modes: `contract` null = create, otherwise edit with the fields prefilled.\n * There is no status field: it is derived from the end date on the server\n * (BOFF-9602). A contract is cancelled through the Cancel action and\n * reactivated through Renew.\n */\nimport { useEffect, useState, type FormEvent } from 'react';\nimport {\n Button,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n} from '@burdenoff/fe-libs/ui';\nimport { notify } from '../../lib/toast';\nimport type { AMCContract } from '../../types';\nimport { dateInputToIso, toDateInput, withinContractWindow } from './amcDates';\nimport { inputClass } from './amcStyles';\n\nexport interface AmcContractFormValues {\n propertyTitle: string;\n packageName: string;\n providerName: string;\n monthlyCost: number;\n startDate: string;\n endDate: string;\n /** ISO date, or '' when no upcoming service is scheduled. */\n nextServiceDate: string;\n servicesIncluded: string[];\n}\n\ninterface AmcContractFormDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n /** null = create a new contract; otherwise edit this one. */\n contract: AMCContract | null;\n saving: boolean;\n onSubmit: (values: AmcContractFormValues) => Promise<void>;\n}\n\nexport function AmcContractFormDialog({ open, onOpenChange, contract, saving, onSubmit }: AmcContractFormDialogProps) {\n const isEdit = contract !== null;\n const [propertyTitle, setPropertyTitle] = useState('');\n const [packageName, setPackageName] = useState('');\n const [providerName, setProviderName] = useState('');\n const [monthlyCost, setMonthlyCost] = useState('');\n const [startDate, setStartDate] = useState('');\n const [endDate, setEndDate] = useState('');\n const [nextServiceDate, setNextServiceDate] = useState('');\n const [servicesIncluded, setServicesIncluded] = useState('');\n\n // Reset (create) or prefill (edit) every time the dialog opens.\n useEffect(() => {\n if (!open) return;\n setPropertyTitle(contract?.propertyTitle ?? '');\n setPackageName(contract?.packageName ?? '');\n setProviderName(contract?.providerName ?? '');\n setMonthlyCost(contract ? String(contract.monthlyCost) : '');\n setStartDate(contract ? toDateInput(contract.startDate) : '');\n setEndDate(contract ? toDateInput(contract.endDate) : '');\n // The column is nullable: a contract without a scheduled service reads back null.\n setNextServiceDate(contract ? toDateInput(contract.nextServiceDate ?? '') : '');\n setServicesIncluded(contract ? contract.servicesIncluded.join(', ') : '');\n }, [open, contract]);\n\n const handleSubmit = async (event: FormEvent) => {\n event.preventDefault();\n const cost = Number(monthlyCost);\n if (!propertyTitle.trim() || !packageName.trim() || !providerName.trim()) {\n notify.warning('Missing details', 'Property, package and provider are required.');\n return;\n }\n if (!Number.isFinite(cost) || cost <= 0) {\n notify.warning('Enter a valid monthly cost');\n return;\n }\n if (isEdit && (!startDate || !endDate)) {\n notify.warning('Dates are required', 'Start and end dates cannot be cleared on an existing contract.');\n return;\n }\n if (\n nextServiceDate &&\n !withinContractWindow(dateInputToIso(startDate), dateInputToIso(endDate), dateInputToIso(nextServiceDate))\n ) {\n notify.warning('Next service date out of range', 'It must fall between the contract start and end dates.');\n return;\n }\n await onSubmit({\n propertyTitle: propertyTitle.trim(),\n packageName: packageName.trim(),\n providerName: providerName.trim(),\n monthlyCost: cost,\n startDate: dateInputToIso(startDate),\n endDate: dateInputToIso(endDate),\n nextServiceDate: dateInputToIso(nextServiceDate),\n servicesIncluded: servicesIncluded\n .split(',')\n .map((s) => s.trim())\n .filter((s) => s.length > 0),\n });\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"sm:max-w-lg\">\n <form onSubmit={handleSubmit}>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {isEdit ? 'Edit maintenance contract' : 'New maintenance contract'}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {isEdit\n ? 'Update the coverage, cost or dates of this Annual Maintenance Contract.'\n : 'Set up an Annual Maintenance Contract to track coverage, cost and renewal dates.'}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-3 py-2\">\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Property *</label>\n <input\n type=\"text\"\n required\n placeholder=\"e.g. Maple Court\"\n value={propertyTitle}\n onChange={(e) => setPropertyTitle(e.target.value)}\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Package *</label>\n <input\n type=\"text\"\n required\n placeholder=\"e.g. Premium Care\"\n value={packageName}\n onChange={(e) => setPackageName(e.target.value)}\n className={inputClass}\n />\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Provider *</label>\n <input\n type=\"text\"\n required\n placeholder=\"Service company\"\n value={providerName}\n onChange={(e) => setProviderName(e.target.value)}\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Monthly cost *</label>\n <input\n type=\"number\"\n required\n min={0}\n step=\"0.01\"\n placeholder=\"0.00\"\n value={monthlyCost}\n onChange={(e) => setMonthlyCost(e.target.value)}\n className={inputClass}\n />\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Start date</label>\n <input\n type=\"date\"\n required={isEdit}\n value={startDate}\n onChange={(e) => setStartDate(e.target.value)}\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">End date</label>\n <input\n type=\"date\"\n required={isEdit}\n value={endDate}\n onChange={(e) => setEndDate(e.target.value)}\n className={inputClass}\n />\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Next service date</label>\n <input\n type=\"date\"\n min={startDate || undefined}\n max={endDate || undefined}\n value={nextServiceDate}\n onChange={(e) => setNextServiceDate(e.target.value)}\n className={inputClass}\n />\n <p className=\"mt-1 text-xs text-text-secondary\">Optional — shows in the upcoming service schedule.</p>\n </div>\n </div>\n <div>\n <label className=\"mb-1.5 block text-sm font-medium text-text-primary\">Services included</label>\n <input\n type=\"text\"\n placeholder=\"Comma-separated, e.g. Plumbing, Electrical, HVAC\"\n value={servicesIncluded}\n onChange={(e) => setServicesIncluded(e.target.value)}\n className={inputClass}\n />\n </div>\n </div>\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => onOpenChange(false)}>\n Cancel\n </Button>\n <Button type=\"submit\" disabled={saving}>\n {saving ? 'Saving…' : isEdit ? 'Save changes' : 'Create contract'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burdenoff/microfe-housingvista",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.1024.1",
|
|
4
4
|
"description": "Burdenoff HousingVista Microfrontend - Comprehensive real estate platform for property management, investment, and transactions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|