feat: Update Redux children slice for multi-child support
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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user