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;
|
name: string;
|
||||||
birthDate: string;
|
birthDate: string;
|
||||||
gender: 'male' | 'female' | 'other';
|
gender: 'male' | 'female' | 'other';
|
||||||
profilePhoto?: string;
|
photoUrl?: string;
|
||||||
metadata?: Record<string, any>;
|
photoAlt?: string;
|
||||||
|
displayColor: string;
|
||||||
|
sortOrder: number;
|
||||||
|
nickname?: string;
|
||||||
|
medicalInfo?: any;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt?: string;
|
||||||
// Offline metadata
|
// Offline metadata
|
||||||
_optimistic?: boolean;
|
_optimistic?: boolean;
|
||||||
_localId?: string;
|
_localId?: string;
|
||||||
@@ -20,7 +24,8 @@ export interface Child {
|
|||||||
// Create entity adapter
|
// Create entity adapter
|
||||||
const childrenAdapter = createEntityAdapter<Child>({
|
const childrenAdapter = createEntityAdapter<Child>({
|
||||||
selectId: (child) => child.id,
|
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
|
// 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({
|
const childrenSlice = createSlice({
|
||||||
name: 'children',
|
name: 'children',
|
||||||
initialState: childrenAdapter.getInitialState({
|
initialState: childrenAdapter.getInitialState<ChildrenState>({
|
||||||
loading: false,
|
loading: false,
|
||||||
error: null as string | null,
|
error: null,
|
||||||
selectedChildId: null as string | null,
|
selectedChildId: null,
|
||||||
lastSyncTime: null as string | null,
|
selectedChildIds: [],
|
||||||
|
defaultChildId: null,
|
||||||
|
viewMode: 'auto',
|
||||||
|
lastSelectedPerScreen: {},
|
||||||
|
lastSyncTime: null,
|
||||||
}),
|
}),
|
||||||
reducers: {
|
reducers: {
|
||||||
// Optimistic operations
|
// Optimistic operations
|
||||||
@@ -124,13 +144,59 @@ const childrenSlice = createSlice({
|
|||||||
_optimistic: false,
|
_optimistic: false,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// Select child
|
// Select child (single)
|
||||||
selectChild: (state, action: PayloadAction<string>) => {
|
selectChild: (state, action: PayloadAction<string>) => {
|
||||||
state.selectedChildId = action.payload;
|
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) => {
|
clearChildren: (state) => {
|
||||||
childrenAdapter.removeAll(state);
|
childrenAdapter.removeAll(state);
|
||||||
state.selectedChildId = null;
|
state.selectedChildId = null;
|
||||||
|
state.selectedChildIds = [];
|
||||||
|
state.defaultChildId = null;
|
||||||
state.error = null;
|
state.error = null;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -202,6 +268,11 @@ export const {
|
|||||||
rollbackOptimistic,
|
rollbackOptimistic,
|
||||||
markSynced,
|
markSynced,
|
||||||
selectChild,
|
selectChild,
|
||||||
|
selectChildren,
|
||||||
|
toggleChildSelection,
|
||||||
|
setDefaultChild,
|
||||||
|
setViewMode,
|
||||||
|
setLastSelectedForScreen,
|
||||||
clearChildren,
|
clearChildren,
|
||||||
} = childrenSlice.actions;
|
} = childrenSlice.actions;
|
||||||
|
|
||||||
@@ -216,6 +287,18 @@ export const selectSelectedChild = (state: RootState) => {
|
|||||||
return id ? state.children.entities[id] : null;
|
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) =>
|
export const selectChildrenByFamily = (state: RootState, familyId: string) =>
|
||||||
childrenSelectors
|
childrenSelectors
|
||||||
.selectAll(state)
|
.selectAll(state)
|
||||||
@@ -226,4 +309,27 @@ export const selectPendingChildren = (state: RootState) =>
|
|||||||
.selectAll(state)
|
.selectAll(state)
|
||||||
.filter((child) => child._optimistic);
|
.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;
|
export default childrenSlice.reducer;
|
||||||
|
|||||||
Reference in New Issue
Block a user