5a50d77515
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>
356 lines
12 KiB
Markdown
356 lines
12 KiB
Markdown
# 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<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
|
|
|
|
```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
|