From 27476300139f2b8e07e43c948905549cffa5944d Mon Sep 17 00:00:00 2001 From: Andrei Date: Sat, 4 Oct 2025 21:35:34 +0000 Subject: [PATCH] feat: Update Redux children slice for multi-child support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit State Updates: - Added selectedChildIds array for multi-select - Added defaultChildId for quick actions - Added viewMode (auto/tabs/cards) with automatic detection - Added lastSelectedPerScreen for per-route child memory - Updated Child interface with displayColor, sortOrder, nickname fields - Changed sort comparator to use sortOrder (birth order) instead of createdAt New Actions: - selectChildren(ids[]) - Select multiple children - toggleChildSelection(id) - Toggle single child in multi-select - setDefaultChild(id) - Set default child for quick actions - setViewMode(mode) - Manual view mode override - setLastSelectedForScreen({screen, childId}) - Remember per-screen selection localStorage Integration: - Persists selectedChildId - Persists defaultChildId - Persists viewMode preference - Persists lastSelectedPerScreen map New Selectors: - selectSelectedChildren() - Get all selected children as array - selectDefaultChild() - Get default child entity - selectChildrenCount() - Total number of children - selectViewMode() - Computed view mode (tabs/cards based on count) - selectChildColor(childId) - Get child's display color - selectLastSelectedForScreen(screen) - Get last child for specific screen View Mode Logic: - auto + <=3 children = tabs - auto + >3 children = cards - manual override = use set value Use Cases: - Dashboard child switching with tabs/cards - Multi-child activity logging - Child-specific routing memory - Default child for quick actions - Color-coded UI elements 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- maternal-web/store/slices/childrenSlice.ts | 124 +++++++++++++++++++-- 1 file changed, 115 insertions(+), 9 deletions(-) diff --git a/maternal-web/store/slices/childrenSlice.ts b/maternal-web/store/slices/childrenSlice.ts index dde4484..5740ccb 100644 --- a/maternal-web/store/slices/childrenSlice.ts +++ b/maternal-web/store/slices/childrenSlice.ts @@ -7,10 +7,14 @@ export interface Child { name: string; birthDate: string; gender: 'male' | 'female' | 'other'; - profilePhoto?: string; - metadata?: Record; + photoUrl?: string; + photoAlt?: string; + displayColor: string; + sortOrder: number; + nickname?: string; + medicalInfo?: any; createdAt: string; - updatedAt: string; + updatedAt?: string; // Offline metadata _optimistic?: boolean; _localId?: string; @@ -20,7 +24,8 @@ export interface Child { // Create entity adapter const childrenAdapter = createEntityAdapter({ selectId: (child) => child.id, - sortComparer: (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), + // Sort by sortOrder (birth order) instead of createdAt + sortComparer: (a, b) => a.sortOrder - b.sortOrder, }); // Async thunks @@ -78,13 +83,28 @@ export const updateChild = createAsyncThunk( } ); +interface ChildrenState { + loading: boolean; + error: string | null; + selectedChildId: string | null; + selectedChildIds: string[]; + defaultChildId: string | null; + viewMode: 'auto' | 'tabs' | 'cards'; + lastSelectedPerScreen: Record; + lastSyncTime: string | null; +} + const childrenSlice = createSlice({ name: 'children', - initialState: childrenAdapter.getInitialState({ + initialState: childrenAdapter.getInitialState({ loading: false, - error: null as string | null, - selectedChildId: null as string | null, - lastSyncTime: null as string | null, + error: null, + selectedChildId: null, + selectedChildIds: [], + defaultChildId: null, + viewMode: 'auto', + lastSelectedPerScreen: {}, + lastSyncTime: null, }), reducers: { // Optimistic operations @@ -124,13 +144,59 @@ const childrenSlice = createSlice({ _optimistic: false, }); }, - // Select child + // Select child (single) selectChild: (state, action: PayloadAction) => { state.selectedChildId = action.payload; + state.selectedChildIds = [action.payload]; + // Save to localStorage + if (typeof window !== 'undefined') { + localStorage.setItem('selectedChildId', action.payload); + } + }, + // Select multiple children + selectChildren: (state, action: PayloadAction) => { + state.selectedChildIds = action.payload; + state.selectedChildId = action.payload[0] || null; + }, + // Toggle child selection (for multi-select) + toggleChildSelection: (state, action: PayloadAction) => { + const childId = action.payload; + const index = state.selectedChildIds.indexOf(childId); + + if (index >= 0) { + state.selectedChildIds.splice(index, 1); + } else { + state.selectedChildIds.push(childId); + } + + state.selectedChildId = state.selectedChildIds[0] || null; + }, + // Set default child for quick actions + setDefaultChild: (state, action: PayloadAction) => { + state.defaultChildId = action.payload; + if (typeof window !== 'undefined') { + localStorage.setItem('defaultChildId', action.payload); + } + }, + // Set view mode + setViewMode: (state, action: PayloadAction<'auto' | 'tabs' | 'cards'>) => { + state.viewMode = action.payload; + if (typeof window !== 'undefined') { + localStorage.setItem('childViewMode', action.payload); + } + }, + // Remember last selected child per screen + setLastSelectedForScreen: (state, action: PayloadAction<{ screen: string; childId: string }>) => { + state.lastSelectedPerScreen[action.payload.screen] = action.payload.childId; + if (typeof window !== 'undefined') { + localStorage.setItem('lastSelectedPerScreen', JSON.stringify(state.lastSelectedPerScreen)); + } }, clearChildren: (state) => { childrenAdapter.removeAll(state); state.selectedChildId = null; + state.selectedChildIds = []; + state.defaultChildId = null; state.error = null; }, }, @@ -202,6 +268,11 @@ export const { rollbackOptimistic, markSynced, selectChild, + selectChildren, + toggleChildSelection, + setDefaultChild, + setViewMode, + setLastSelectedForScreen, clearChildren, } = childrenSlice.actions; @@ -216,6 +287,18 @@ export const selectSelectedChild = (state: RootState) => { return id ? state.children.entities[id] : null; }; +export const selectSelectedChildren = (state: RootState) => { + const ids = state.children.selectedChildIds; + return ids + .map(id => state.children.entities[id]) + .filter((child): child is Child => child !== undefined); +}; + +export const selectDefaultChild = (state: RootState) => { + const id = state.children.defaultChildId; + return id ? state.children.entities[id] : null; +}; + export const selectChildrenByFamily = (state: RootState, familyId: string) => childrenSelectors .selectAll(state) @@ -226,4 +309,27 @@ export const selectPendingChildren = (state: RootState) => .selectAll(state) .filter((child) => child._optimistic); +export const selectChildrenCount = (state: RootState) => + childrenSelectors.selectTotal(state); + +export const selectViewMode = (state: RootState) => { + const { viewMode } = state.children; + const childrenCount = childrenSelectors.selectTotal(state); + + if (viewMode === 'auto') { + return childrenCount <= 3 ? 'tabs' : 'cards'; + } + + return viewMode; +}; + +export const selectChildColor = (childId: string) => (state: RootState) => { + const child = state.children.entities[childId]; + return child?.displayColor || '#FF6B9D'; +}; + +export const selectLastSelectedForScreen = (screen: string) => (state: RootState) => { + return state.children.lastSelectedPerScreen[screen]; +}; + export default childrenSlice.reducer;