# 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 ```typescript 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 ```typescript 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 ```typescript 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 ```typescript 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: ```typescript // 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; export function TaskForm({ task, onSubmit }: Props) { const form = useForm({ resolver: zodResolver(taskSchema), defaultValues: task ? mapTaskToFormData(task) : defaults, }); return (
{/* Fields */}
); } ``` ### Mutation Pattern ```typescript 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