Merge asset recurring templates into the main Recurring/Summary pages

Assets.tsx had its own bare-bones template-creation form (title/interval/
next_due only, no description/priority-choice/assignee/schedule_mode/
lead_days/group) with zero edit or delete path — a linked recurring task
could only ever be created from here, never managed. The backend already
sourced the asset detail's tasks/templates straight from the main tasks
and task_templates tables (no separate storage), so the fix is entirely
frontend: stop duplicating the editor, link back to the real one.

- Assets.tsx: removed the bespoke ServiceTaskForm entirely. Each linked
  template row now navigates to /recurring?edit=<id> (opens that
  template's full edit form there, including delete). "+ Add" navigates
  to /recurring?new_asset=&new_location= for a prefilled New Template
  form. Added a matching "+ Report fault" action navigating to
  /summary?new_asset=&new_location= for one-off tasks, since there was
  previously no way to create a plain task from the Assets page at all.
  Existing tasks still open in-place via TaskModal, unchanged.
- Recurring.tsx / Summary.tsx: read those query params on mount, open
  the appropriate form/modal prefilled, then strip the params from the
  URL (replace, no history pollution).
- NewTaskModal.tsx + Recurring.tsx template form: asset picker is no
  longer gated behind picking a location first — it always shows the
  full register (grouped by location) or the location-filtered list
  once one's chosen, and selecting an asset now back-fills its location
  automatically. Makes tagging routine servicing to a specific asset
  (fixed or floating recurring) the natural way in, for both one-off
  and recurring tasks.
- Asset list/detail counts (open tasks, recurring templates) are
  unchanged — still sourced from the same main-table queries.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-08-26 15:09:24 +00:00
parent d9b01a5981
commit 2d2541576d
4 changed files with 136 additions and 84 deletions

View file

@ -1,4 +1,5 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useSearchParams } from 'react-router-dom'
import { Plus, RefreshCw, Camera, BedDouble, Layers, ChevronDown, ArrowUpDown, ArrowUp, ArrowDown } from 'lucide-react'
import type { Task, Category, Location, AppConfig, TaskStatus, Priority } from '../types'
import { STATUS_LABELS, can } from '../types'
@ -47,7 +48,9 @@ export default function Summary() {
setSortMode(prev => SORT_CYCLE[(SORT_CYCLE.indexOf(prev) + 1) % SORT_CYCLE.length])
}
const [searchParams, setSearchParams] = useSearchParams()
const [showNew, setShowNew] = useState(false)
const [newTaskPreset, setNewTaskPreset] = useState<{ locationId?: number; assetId?: number }>({})
const [openTask, setOpenTask] = useState<number | null>(null)
const roomsCat = useMemo(() => categories.find(c => c.is_rooms) ?? null, [categories])
@ -80,6 +83,26 @@ export default function Summary() {
fetchConfig().then(setConfig).catch(() => {})
}, [])
// Deep link from the Assets page: ?new_asset=&new_location= opens a prefilled
// Report fault form instead of the asset page having its own creation form.
useEffect(() => {
const newAsset = searchParams.get('new_asset')
const newLocation = searchParams.get('new_location')
if (newAsset || newLocation) {
setNewTaskPreset({
locationId: newLocation ? parseInt(newLocation) : undefined,
assetId: newAsset ? parseInt(newAsset) : undefined,
})
setShowNew(true)
setSearchParams(prev => {
const next = new URLSearchParams(prev)
next.delete('new_asset'); next.delete('new_location')
return next
}, { replace: true })
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchParams])
const counts = useMemo(() => {
const c: Partial<Record<TaskStatus, number>> = {}
for (const t of tasks) c[t.status] = (c[t.status] || 0) + 1
@ -141,7 +164,7 @@ export default function Summary() {
<RefreshCw size={14} strokeWidth={1.75} /> Refresh
</button>
{can(user, 'report') && (
<button className="btn btn-primary" onClick={() => setShowNew(true)}>
<button className="btn btn-primary" onClick={() => { setNewTaskPreset({}); setShowNew(true) }}>
<Plus size={14} strokeWidth={1.75} /> Report fault
</button>
)}
@ -287,6 +310,8 @@ export default function Summary() {
categories={categories}
locations={locations}
config={config}
initialLocationId={newTaskPreset.locationId}
initialAssetId={newTaskPreset.assetId}
onClose={() => setShowNew(false)}
onCreated={load}
/>