Shiftly manages all shared application state through Redux Toolkit. The store is composed of two slices:Documentation Index
Fetch the complete documentation index at: https://mintlify.com/dmaman86/shiftly/llms.txt
Use this file to discover all available pages before exploring further.
globalSlice, which owns the user’s pay configuration, the per-day pay map cache, and the running monthly aggregate; and workDaysSlice, which holds the generated list of work-day metadata for the currently selected month. React components interact with the store exclusively through three custom hooks — useGlobalState, useWorkDays, and useDomain — keeping dispatch and selector logic out of component bodies.
Store Setup
The Redux store is created insrc/redux/store.ts using configureStore from Redux Toolkit:
RootState and AppDispatch types are derived directly from the store instance so that all selectors and dispatch calls remain fully type-safe without manual maintenance.
The store is provided to the React tree by the AppProviders component (src/app/providers/AppProviders.tsx), which wraps the entire application in a Redux <Provider> alongside the MUI theme, Emotion cache (with RTL support via stylis-plugin-rtl), snackbar, domain, and date-picker providers:
globalSlice
globalSlice is the primary slice. It tracks the user’s pay configuration, a dictionary of per-day pay maps keyed by date string, and the accumulated monthly breakdown.
State shape
Initial state
The slice initialises withstandardHours: 6.67 (the Israeli labor law default for a six-day work week), baseRate: 0 (the user must enter their rate), and the current calendar year and month derived from new Date() at module load time. The globalBreakdown is seeded with an empty MonthPayMap created by the domain’s monthPayMapCalculator.createEmpty().
Actions
| Action | Payload | Description |
|---|---|---|
setYear(year) | number | Updates config.year and clears all daily pay maps and the monthly aggregate via resetMonthData |
setMonth(month) | number | Updates config.month and clears all daily pay maps and the monthly aggregate via resetMonthData |
setStandardHours(hours) | number | Updates the daily standard-hour threshold used in overtime tier calculations |
setBaseRate(rate) | number | Updates the hourly base rate used to compute pay amounts |
addDayPayMap({ dateKey, dayPayMap }) | { dateKey: string; dayPayMap: WorkDayMap } | Subtracts the previous contribution for that date (if any) from globalBreakdown, then accumulates the new dayPayMap into globalBreakdown and stores it in dailyPayMaps |
removeDayPayMap(dateKey) | string | Subtracts the stored day’s contribution from globalBreakdown and removes it from dailyPayMaps |
resetGlobal() | — | Clears all entries in dailyPayMaps and resets globalBreakdown to an empty MonthPayMap |
Dispatching
setYear or setMonth automatically calls the internal resetMonthData helper, which clears dailyPayMaps and resets globalBreakdown to an empty structure. This prevents stale pay data from a previous month or year from persisting after the user changes the calendar period.The addDayPayMap subtract-then-accumulate pattern
When a user saves or updates the shifts for a given day, addDayPayMap is dispatched. The reducer first checks whether a WorkDayMap already exists for that date key. If one does, it subtracts the previous contribution from the running monthly total before accumulating the new one. This ensures globalBreakdown always reflects exactly the current set of saved days, with no double-counting when a day is edited:
subtract and accumulate methods are pure functions from the domain’s monthPayMapCalculator — Redux Toolkit’s Immer integration allows the reassignment of state.globalBreakdown to a new value returned by those functions without violating immutability constraints.
resetMonthData helper
The private resetMonthData function is used by both setYear and setMonth to keep the reset logic in one place:
workDaysSlice
workDaysSlice stores the list of WorkDayInfo objects generated for the selected month. Each WorkDayInfo carries a meta object (date string, day type, cross-day continuation flag) and a Hebrew day label used in the UI calendar.
setWorkDays, which accepts a year, month, and eventMap (a map of date strings to event labels for public holidays), then delegates to payMap.workDaysMonthBuilder.build(...) to produce the full array of work days for that month:
React Hooks
Three custom hooks encapsulate all Redux interactions. Components import these hooks and never calluseSelector or useDispatch directly.
useGlobalState()
useGlobalState selects every field from state.global and provides typed action dispatchers for all seven mutations. It is the primary hook for the salary configuration panel and the monthly summary view.
useWorkDays()
useWorkDays selects state.workDays.workDays and wraps the setWorkDays action in a generate(year, month, eventMap) function. It also exposes four derived read helpers built on top of the raw workDays array:
isSpecialFullDay returns true when the day’s typeDay is WorkDayType.SpecialFull (Shabbat or a full public holiday). isPartialHolidayDay returns true for WorkDayType.SpecialPartialStart (e.g., Friday evening or a holiday eve). isCrossDaySpecial returns true when crossDayContinuation is set, indicating the day continues a special-day classification that started the previous evening.
useDomain()
useDomain reads the domain pipeline instance from DomainContext, which is provided by DomainProvider inside AppProviders. It throws a clear error if called outside the provider tree:
PayMapPipeline instance used by the Redux slices, giving components direct access to domain builders, calculators, and resolvers when they need to perform calculations outside of a dispatch cycle (for example, previewing shift pay before saving).