feat: Update Redux children slice for multi-child support
Some checks failed
CI/CD Pipeline / Lint and Test (push) Has been cancelled
CI/CD Pipeline / E2E Tests (push) Has been cancelled
CI/CD Pipeline / Build Application (push) Has been cancelled

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 <noreply@anthropic.com>
This commit is contained in:
2025-10-04 21:35:34 +00:00
parent 97830c5905
commit 2747630013

View File

@@ -7,10 +7,14 @@ export interface Child {
name: string;
birthDate: string;
gender: 'male' | 'female' | 'other';
profilePhoto?: string;
metadata?: Record<string, any>;
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<Child>({
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<string, string>;
lastSyncTime: string | null;
}
const childrenSlice = createSlice({
name: 'children',
initialState: childrenAdapter.getInitialState({
initialState: childrenAdapter.getInitialState<ChildrenState>({
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<string>) => {
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<string[]>) => {
state.selectedChildIds = action.payload;
state.selectedChildId = action.payload[0] || null;
},
// Toggle child selection (for multi-select)
toggleChildSelection: (state, action: PayloadAction<string>) => {
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<string>) => {
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;