Files
honeyDueWeb/docs/02-core-crud.md
T
Trey t 5a50d77515 feat: complete Phase 3 — advanced features for Casera web app
Adds sharing (residence share codes, join, user management, .casera file
export/import), subscription status with feature comparison, notification
preferences with bell icon, profile settings (edit info, change password,
theme picker, delete account), onboarding wizard with create/join paths,
enhanced dashboard with stats cards, Recharts completion chart, recent
activity feed, and task report PDF download.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 09:31:29 -06:00

12 KiB

Phase 2 — Core CRUD

Build the 4 primary domains: Residences, Tasks, Contractors, and Documents.

Checklist

  • Residences: list (with summary card), detail, create/edit form, delete
  • Tasks: kanban board (drag-and-drop columns), create/edit form, task actions (complete, cancel, archive, in-progress)
  • Task completion: form with file upload (photos), completion history with image viewer
  • Task templates: autocomplete search + template browser
  • Contractors: list (search, filter by favorite/specialty), detail (quick actions), create/edit form, delete
  • Documents: tabbed view (warranties/documents), detail, create/edit form (type-specific fields), file upload/download, image gallery

1. Residences

Screens

Route Screen Description
/app/residences Residence List Grid of residence cards with summary data
/app/residences/new Create Residence Form: name, address, type, photo
/app/residences/[id] Residence Detail Summary dashboard, task/contractor/document counts, users
/app/residences/[id]/edit Edit Residence Same form as create, pre-filled

API Endpoints Used

Method Endpoint Purpose
GET /api/residences/my/ User's residences with summaries
GET /api/residences/:id/ Residence detail
POST /api/residences/ Create residence
PUT /api/residences/:id/ Update residence
DELETE /api/residences/:id/ Delete residence

Components

  • ResidenceCard: Summary card showing name, address, type icon, task counts (overdue, due soon, total)
  • ResidenceForm: React Hook Form + Zod. Fields: name (required), address, residence type (dropdown from lookups), photo upload
  • ResidenceSummary: Dashboard showing task breakdown, contractor count, document count

TanStack Query Hooks

useResidences()                    // GET /residences/my/
useResidence(id)                   // GET /residences/:id/
useCreateResidence()               // POST /residences/
useUpdateResidence(id)             // PUT /residences/:id/
useDeleteResidence(id)             // DELETE /residences/:id/

Cache Invalidation

After create/update/delete → invalidate ['residences'] and ['residence', id] queries.


2. Tasks

Screens

Route Screen Description
/app/tasks Kanban Board All tasks across residences, grouped by column
/app/residences/[id]/tasks Residence Kanban Tasks for a specific residence
/app/tasks/new Create Task Full task form
/app/tasks/[id] Task Detail Full task info, completion history, actions
/app/tasks/[id]/edit Edit Task Same form as create, pre-filled
/app/tasks/[id]/complete Complete Task Completion form with photo upload

Kanban Board

Columns (from backend TaskColumnsResponse):

  1. Overdue (red indicator)
  2. Due Today (orange indicator)
  3. Due Soon (yellow indicator, within 30 days)
  4. Upcoming (blue indicator)
  5. In Progress (green indicator)
  6. Completed (gray indicator)

Drag-and-drop: Use @dnd-kit/core + @dnd-kit/sortable for dragging tasks between columns. Dropping a task triggers the appropriate API action:

  • Drop on "In Progress" → POST /tasks/:id/in-progress/
  • Drop on "Completed" → Opens completion form
  • Other moves may just reorder (no API call)

Board controls:

  • Filter by residence (dropdown)
  • Filter by category, priority
  • Search by title

Task Form Fields

Field Type Required Source
Title Text / Autocomplete Yes Free text or template
Residence Select Yes User's residences
Category Select No Lookups (categories)
Priority Select No Lookups (priorities)
Due Date Date picker No Calendar
Frequency Select No Lookups (frequencies) — for recurring tasks
Estimated Cost Number No Currency input
Notes Textarea No Free text
Assigned Contractor Select No User's contractors

Task Actions

Action Endpoint UI Trigger
Complete POST /tasks/:id/complete/ Button + completion form
Mark In Progress POST /tasks/:id/in-progress/ Button or drag
Cancel POST /tasks/:id/cancel/ Menu action
Archive POST /tasks/:id/archive/ Menu action
Uncancel POST /tasks/:id/uncancel/ Menu action
Unarchive POST /tasks/:id/unarchive/ Menu action

Task Completion Form

Field Type Required
Completed At DateTime Yes (default: now)
Actual Cost Number No
Notes Textarea No
Rating Star rating (1-5) No
Photos File upload (multiple) No

File upload uses FormData with multipart, same as mobile's submitFormWithBinaryData.

Task Templates

  • Autocomplete search: As user types in title field, search /api/task-templates/search/?q=...
  • Template browser: Modal with categorized templates, click to prefill form
  • Template data populates: title, category, priority, estimated cost, frequency

API Endpoints Used

Method Endpoint Purpose
GET /api/tasks/ All user's tasks (kanban columns)
GET /api/tasks/by-residence/:id/ Tasks for one residence
POST /api/tasks/ Create task
PUT /api/tasks/:id/ Update task
DELETE /api/tasks/:id/ Delete task
POST /api/tasks/:id/complete/ Complete task
POST /api/tasks/:id/in-progress/ Mark in progress
POST /api/tasks/:id/cancel/ Cancel task
POST /api/tasks/:id/archive/ Archive task
POST /api/task-completions/ Create completion (with images)
GET /api/task-completions/ List completions
GET /api/task-templates/search/ Search templates

TanStack Query Hooks

useTasks()                         // GET /tasks/
useTasksByResidence(residenceId)   // GET /tasks/by-residence/:id/
useTask(id)                        // GET /tasks/:id/
useCreateTask()                    // POST /tasks/
useUpdateTask(id)                  // PUT /tasks/:id/
useDeleteTask(id)                  // DELETE /tasks/:id/
useCompleteTask(id)                // POST /tasks/:id/complete/
useTaskTemplateSearch(query)       // GET /task-templates/search/?q=...
useCreateCompletion()              // POST /task-completions/

3. Contractors

Screens

Route Screen Description
/app/contractors Contractor List Filterable list with search
/app/contractors/new Create Contractor Form with contact details
/app/contractors/[id] Contractor Detail Full info, quick actions, linked tasks
/app/contractors/[id]/edit Edit Contractor Same form as create, pre-filled

List Features

  • Search: Filter by name, company
  • Filter: By specialty (from lookups), by favorite
  • Sort: By name, recently added
  • Quick actions: Call, email, favorite toggle

Contractor Form Fields

Field Type Required Source
Name Text Yes Free text
Company Text No Free text
Phone Phone No Tel input
Email Email No Email input
Specialty Select No Lookups (specialties)
Notes Textarea No Free text
Is Favorite Toggle No Boolean
Residence Select Yes User's residences

Contractor Detail

  • Contact info with click-to-call (tel:) and click-to-email (mailto:)
  • Favorite toggle
  • Linked tasks (tasks assigned to this contractor)
  • Edit / delete actions

API Endpoints Used

Method Endpoint Purpose
GET /api/contractors/ User's contractors
GET /api/contractors/:id/ Contractor detail
POST /api/contractors/ Create contractor
PUT /api/contractors/:id/ Update contractor
DELETE /api/contractors/:id/ Delete contractor
POST /api/contractors/:id/favorite/ Toggle favorite

TanStack Query Hooks

useContractors()                   // GET /contractors/
useContractor(id)                  // GET /contractors/:id/
useCreateContractor()              // POST /contractors/
useUpdateContractor(id)            // PUT /contractors/:id/
useDeleteContractor(id)            // DELETE /contractors/:id/
useToggleFavorite(id)              // POST /contractors/:id/favorite/

4. Documents

Screens

Route Screen Description
/app/documents Document List Tabbed view: Warranties / Documents
/app/documents/new Create Document Form with type-specific fields
/app/documents/[id] Document Detail Full info, file viewer, image gallery
/app/documents/[id]/edit Edit Document Same form as create, pre-filled

Tabbed View

  • Warranties tab: Documents with is_warranty = true. Shows expiry dates, status (active/expired/expiring soon)
  • Documents tab: All other documents. Shows type, date added, file preview

Document Form Fields

Field Type Required Source
Title Text Yes Free text
Residence Select Yes User's residences
Type Select No Document type options
Notes Textarea No Free text
File File upload No File picker
Is Warranty Toggle No Boolean
Purchase Date Date Conditional If warranty
Expiry Date Date Conditional If warranty
Purchase Price Number Conditional If warranty

Document Detail

  • File preview (PDF viewer, image gallery)
  • Download button
  • Warranty status indicator (if warranty)
  • Edit / delete actions

API Endpoints Used

Method Endpoint Purpose
GET /api/documents/ User's documents
GET /api/documents/:id/ Document detail
POST /api/documents/ Create document
PUT /api/documents/:id/ Update document
DELETE /api/documents/:id/ Delete document
POST /api/documents/:id/activate/ Activate document
POST /api/documents/:id/deactivate/ Deactivate document

TanStack Query Hooks

useDocuments()                     // GET /documents/
useDocument(id)                    // GET /documents/:id/
useCreateDocument()                // POST /documents/
useUpdateDocument(id)              // PUT /documents/:id/
useDeleteDocument(id)              // DELETE /documents/:id/

Shared Patterns

Form Pattern

Every CRUD form follows the same structure:

// src/components/forms/TaskForm.tsx
const taskSchema = z.object({
  title: z.string().min(1, 'Title is required'),
  residenceId: z.number().min(1, 'Residence is required'),
  categoryId: z.number().optional(),
  // ...
});

type TaskFormData = z.infer<typeof taskSchema>;

export function TaskForm({ task, onSubmit }: Props) {
  const form = useForm<TaskFormData>({
    resolver: zodResolver(taskSchema),
    defaultValues: task ? mapTaskToFormData(task) : defaults,
  });

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)}>
        {/* Fields */}
      </form>
    </Form>
  );
}

Mutation Pattern

export function useCreateTask() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (data: CreateTaskRequest) => api.tasks.createTask(data),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['tasks'] });
      queryClient.invalidateQueries({ queryKey: ['residences'] }); // Summary counts change
    },
  });
}

Loading / Error / Empty States

Every list and detail page handles three states:

  1. Loading: Skeleton loader (shadcn Skeleton component)
  2. Error: Error banner with retry button
  3. Empty: Empty state illustration with CTA to create first item

Deliverables

At the end of Phase 2, you should have:

  1. Full CRUD for all 4 domains
  2. Kanban board with drag-and-drop
  3. Task completion with photo upload
  4. Template search and autocomplete
  5. All forms validated with Zod
  6. Proper cache invalidation after mutations