feat: add chore, kindness, and penalty management components
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 2m34s

- Implemented ChoreAssignView for assigning chores to children.
- Created ChoreConfirmDialog for confirming chore completion.
- Developed KindnessAssignView for assigning kindness acts.
- Added PenaltyAssignView for assigning penalties.
- Introduced ChoreEditView and ChoreView for editing and viewing chores.
- Created KindnessEditView and KindnessView for managing kindness acts.
- Developed PenaltyEditView and PenaltyView for managing penalties.
- Added TaskSubNav for navigation between chores, kindness acts, and penalties.
This commit is contained in:
2026-02-28 11:25:56 -05:00
parent 65e987ceb6
commit d7316bb00a
61 changed files with 7364 additions and 647 deletions

View File

@@ -61,8 +61,16 @@ export function isPasswordStrong(password: string): boolean {
*/
export async function getTrackingEventsForChild(params: {
childId: string
entityType?: 'task' | 'reward' | 'penalty'
action?: 'activated' | 'requested' | 'redeemed' | 'cancelled'
entityType?: 'task' | 'reward' | 'penalty' | 'chore' | 'kindness'
action?:
| 'activated'
| 'requested'
| 'redeemed'
| 'cancelled'
| 'confirmed'
| 'approved'
| 'rejected'
| 'reset'
limit?: number
offset?: number
}): Promise<Response> {
@@ -160,3 +168,67 @@ export async function extendChoreTime(
body: JSON.stringify({ date: localDate }),
})
}
// ── Chore Confirmation API ──────────────────────────────────────────────────
/**
* Child confirms they completed a chore.
*/
export async function confirmChore(childId: string, taskId: string): Promise<Response> {
return fetch(`/api/child/${childId}/confirm-chore`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ task_id: taskId }),
})
}
/**
* Child cancels a pending chore confirmation.
*/
export async function cancelConfirmChore(childId: string, taskId: string): Promise<Response> {
return fetch(`/api/child/${childId}/cancel-confirm-chore`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ task_id: taskId }),
})
}
/**
* Parent approves a pending chore confirmation (awards points).
*/
export async function approveChore(childId: string, taskId: string): Promise<Response> {
return fetch(`/api/child/${childId}/approve-chore`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ task_id: taskId }),
})
}
/**
* Parent rejects a pending chore confirmation (no points, resets to available).
*/
export async function rejectChore(childId: string, taskId: string): Promise<Response> {
return fetch(`/api/child/${childId}/reject-chore`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ task_id: taskId }),
})
}
/**
* Parent resets a completed chore (points kept, chore can be confirmed again).
*/
export async function resetChore(childId: string, taskId: string): Promise<Response> {
return fetch(`/api/child/${childId}/reset-chore`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ task_id: taskId }),
})
}
/**
* Fetch all pending confirmations (both chores and rewards) for the current user.
*/
export async function fetchPendingConfirmations(): Promise<Response> {
return fetch('/api/pending-confirmations')
}

View File

@@ -1,12 +1,14 @@
export type TaskType = 'chore' | 'kindness' | 'penalty'
export interface Task {
id: string
name: string
points: number
is_good: boolean
type: TaskType
image_id: string | null
image_url?: string | null // optional, for resolved URLs
}
export const TASK_FIELDS = ['id', 'name', 'points', 'is_good', 'image_id'] as const
export const TASK_FIELDS = ['id', 'name', 'points', 'type', 'image_id'] as const
export interface DayConfig {
day: number // 0=Sun, 1=Mon, ..., 6=Sat
@@ -37,13 +39,15 @@ export interface ChoreSchedule {
export interface ChildTask {
id: string
name: string
is_good: boolean
type: TaskType
points: number
image_id: string | null
image_url?: string | null
custom_value?: number | null
schedule?: ChoreSchedule | null
extension_date?: string | null // ISO date of today's extension, if any
pending_status?: 'pending' | 'approved' | null
approved_at?: string | null
}
export interface User {
@@ -100,25 +104,31 @@ export const REWARD_STATUS_FIELDS = [
'image_id',
] as const
export interface PendingReward {
export interface PendingConfirmation {
id: string
child_id: string
child_name: string
child_image_id: string | null
child_image_url?: string | null // optional, for resolved URLs
reward_id: string
reward_name: string
reward_image_id: string | null
reward_image_url?: string | null // optional, for resolved URLs
child_image_url?: string | null
entity_id: string
entity_type: 'chore' | 'reward'
entity_name: string
entity_image_id: string | null
entity_image_url?: string | null
status: 'pending' | 'approved' | 'rejected'
approved_at: string | null
}
export const PENDING_REWARD_FIELDS = [
export const PENDING_CONFIRMATION_FIELDS = [
'id',
'child_id',
'child_name',
'child_image_id',
'reward_id',
'reward_name',
'reward_image_id',
'entity_id',
'entity_type',
'entity_name',
'entity_image_id',
'status',
'approved_at',
] as const
export interface Event {
@@ -137,6 +147,7 @@ export interface Event {
| ChildOverrideDeletedPayload
| ChoreScheduleModifiedPayload
| ChoreTimeExtendedPayload
| ChildChoreConfirmationPayload
}
export interface ChildModifiedEventPayload {
@@ -197,8 +208,16 @@ export interface ChildOverrideDeletedPayload {
entity_type: string
}
export type EntityType = 'task' | 'reward' | 'penalty'
export type ActionType = 'activated' | 'requested' | 'redeemed' | 'cancelled'
export type EntityType = 'task' | 'reward' | 'penalty' | 'chore' | 'kindness'
export type ActionType =
| 'activated'
| 'requested'
| 'redeemed'
| 'cancelled'
| 'confirmed'
| 'approved'
| 'rejected'
| 'reset'
export interface TrackingEvent {
id: string
@@ -232,7 +251,7 @@ export const TRACKING_EVENT_FIELDS = [
'metadata',
] as const
export type OverrideEntityType = 'task' | 'reward'
export type OverrideEntityType = 'task' | 'reward' | 'chore' | 'kindness' | 'penalty'
export interface ChildOverride {
id: string
@@ -264,3 +283,9 @@ export interface ChoreTimeExtendedPayload {
child_id: string
task_id: string
}
export interface ChildChoreConfirmationPayload {
child_id: string
task_id: string
operation: 'CONFIRMED' | 'APPROVED' | 'REJECTED' | 'CANCELLED' | 'RESET'
}