@umituz/react-native-design-system 2.6.61 → 2.6.64
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/atoms/AtomicButton.tsx +6 -257
- package/src/atoms/AtomicChip.tsx +4 -224
- package/src/atoms/AtomicIcon.tsx +2 -6
- package/src/atoms/AtomicIcon.types.ts +5 -0
- package/src/atoms/AtomicInput.tsx +34 -154
- package/src/atoms/AtomicPicker.tsx +31 -123
- package/src/atoms/button/AtomicButton.tsx +108 -0
- package/src/atoms/button/configs/buttonSizeConfig.ts +37 -0
- package/src/atoms/button/index.ts +6 -0
- package/src/atoms/button/styles/buttonStyles.ts +36 -0
- package/src/atoms/button/styles/buttonVariantStyles.ts +88 -0
- package/src/atoms/button/types/index.ts +40 -0
- package/src/atoms/chip/AtomicChip.tsx +112 -0
- package/src/atoms/chip/configs/chipColorConfig.ts +47 -0
- package/src/atoms/chip/configs/chipSizeConfig.ts +34 -0
- package/src/atoms/chip/index.ts +6 -0
- package/src/atoms/chip/styles/chipStyles.ts +28 -0
- package/src/atoms/chip/types/index.ts +42 -0
- package/src/atoms/index.ts +6 -4
- package/src/atoms/input/components/InputHelper.tsx +49 -0
- package/src/atoms/input/components/InputIcon.tsx +44 -0
- package/src/atoms/input/components/InputLabel.tsx +20 -0
- package/src/atoms/input/styles/inputStylesHelper.ts +1 -1
- package/src/atoms/input/types.ts +72 -0
- package/src/atoms/picker/hooks/usePickerState.ts +139 -0
- package/src/exports/atoms.ts +69 -0
- package/src/exports/device.ts +58 -0
- package/src/exports/layouts.ts +19 -0
- package/src/exports/molecules.ts +166 -0
- package/src/exports/organisms.ts +9 -0
- package/src/exports/responsive.ts +36 -0
- package/src/exports/safe-area.ts +6 -0
- package/src/exports/theme.ts +47 -0
- package/src/exports/typography.ts +22 -0
- package/src/exports/utilities.ts +6 -0
- package/src/exports/variants.ts +22 -0
- package/src/index.ts +11 -417
- package/src/layouts/ScreenLayout/ScreenLayout.tsx +17 -181
- package/src/layouts/ScreenLayout/components/ContentWrapper.tsx +31 -0
- package/src/layouts/ScreenLayout/components/index.ts +6 -0
- package/src/layouts/ScreenLayout/styles/screenLayoutStyles.ts +47 -0
- package/src/layouts/ScreenLayout/types/index.ts +27 -0
- package/src/molecules/avatar/Avatar.constants.ts +103 -0
- package/src/molecules/avatar/Avatar.types.ts +64 -0
- package/src/molecules/avatar/Avatar.utils.ts +8 -160
- package/src/molecules/calendar/index.ts +4 -9
- package/src/molecules/calendar/infrastructure/storage/CalendarStore.ts +103 -302
- package/src/molecules/calendar/infrastructure/storage/CalendarStore.ts.bak +116 -0
- package/src/molecules/calendar/infrastructure/storage/CalendarStore.types.ts +64 -0
- package/src/molecules/calendar/infrastructure/storage/CalendarStore.utils.ts +56 -0
- package/src/molecules/calendar/infrastructure/storage/EventActions.ts +140 -0
- package/src/molecules/calendar/infrastructure/storage/NavigationActions.ts +118 -0
- package/src/molecules/calendar/infrastructure/stores/storageAdapter.ts +34 -0
- package/src/molecules/calendar/infrastructure/stores/useCalendarEvents.ts +168 -0
- package/src/molecules/calendar/infrastructure/stores/useCalendarNavigation.ts +47 -0
- package/src/molecules/calendar/infrastructure/stores/useCalendarView.ts +24 -0
- package/src/molecules/calendar/presentation/hooks/useCalendar.ts +7 -11
- package/src/responsive/compute/computeDeviceInfo.ts +22 -0
- package/src/responsive/compute/computeResponsivePositioning.ts +42 -0
- package/src/responsive/compute/computeResponsiveSizes.ts +48 -0
- package/src/responsive/padding/paddingUtils.ts +65 -0
- package/src/responsive/positioning/positioningUtils.ts +61 -0
- package/src/responsive/responsiveLayout.ts +11 -264
- package/src/responsive/screen/screenLayoutConfig.ts +38 -0
- package/src/responsive/tabbar/tabBarConfig.ts +88 -0
- package/src/responsive/types/responsiveTypes.ts +69 -0
- package/src/responsive/useResponsive.ts +69 -158
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calendar Event Actions
|
|
3
|
+
* Event CRUD operations for calendar store
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { storageRepository, unwrap } from '@umituz/react-native-storage';
|
|
7
|
+
import type { CalendarEvent, CreateCalendarEventRequest, UpdateCalendarEventRequest } from '../../domain/entities/CalendarEvent.entity';
|
|
8
|
+
import { generateId, STORAGE_KEY, hydrateEvents, handleStorageError, handleStorageSuccess } from './CalendarStore.utils';
|
|
9
|
+
import type { CalendarState } from './CalendarStore.types';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Load events from storage
|
|
13
|
+
*/
|
|
14
|
+
export const loadEvents = async (
|
|
15
|
+
set: (state: Partial<CalendarState>) => void
|
|
16
|
+
): Promise<void> => {
|
|
17
|
+
set({ isLoading: true, error: null });
|
|
18
|
+
try {
|
|
19
|
+
const result = await storageRepository.getItem<CalendarEvent[]>(STORAGE_KEY, []);
|
|
20
|
+
const events = unwrap(result, []);
|
|
21
|
+
|
|
22
|
+
if (events && events.length > 0) {
|
|
23
|
+
const hydratedEvents = hydrateEvents(events);
|
|
24
|
+
set({ events: hydratedEvents, isLoading: false });
|
|
25
|
+
} else {
|
|
26
|
+
set({ isLoading: false });
|
|
27
|
+
}
|
|
28
|
+
} catch {
|
|
29
|
+
handleStorageError(set, 'Failed to load events');
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Add a new event
|
|
35
|
+
*/
|
|
36
|
+
export const addEvent = async (
|
|
37
|
+
request: CreateCalendarEventRequest,
|
|
38
|
+
set: (state: Partial<CalendarState>) => void,
|
|
39
|
+
get: () => CalendarState
|
|
40
|
+
): Promise<void> => {
|
|
41
|
+
set({ isLoading: true, error: null });
|
|
42
|
+
try {
|
|
43
|
+
const newEvent: CalendarEvent = {
|
|
44
|
+
id: generateId(),
|
|
45
|
+
...request,
|
|
46
|
+
isCompleted: false,
|
|
47
|
+
createdAt: new Date(),
|
|
48
|
+
updatedAt: new Date(),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const events = [...get().events, newEvent];
|
|
52
|
+
await storageRepository.setItem(STORAGE_KEY, events);
|
|
53
|
+
handleStorageSuccess(set, { events });
|
|
54
|
+
} catch {
|
|
55
|
+
handleStorageError(set, 'Failed to add event');
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Update an existing event
|
|
61
|
+
*/
|
|
62
|
+
export const updateEvent = async (
|
|
63
|
+
request: UpdateCalendarEventRequest,
|
|
64
|
+
set: (state: Partial<CalendarState>) => void,
|
|
65
|
+
get: () => CalendarState
|
|
66
|
+
): Promise<void> => {
|
|
67
|
+
set({ isLoading: true, error: null });
|
|
68
|
+
try {
|
|
69
|
+
const events = get().events.map((event) => {
|
|
70
|
+
if (event.id === request.id) {
|
|
71
|
+
return {
|
|
72
|
+
...event,
|
|
73
|
+
...request,
|
|
74
|
+
updatedAt: new Date(),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return event;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
await storageRepository.setItem(STORAGE_KEY, events);
|
|
81
|
+
handleStorageSuccess(set, { events });
|
|
82
|
+
} catch {
|
|
83
|
+
handleStorageError(set, 'Failed to update event');
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Delete an event
|
|
89
|
+
*/
|
|
90
|
+
export const deleteEvent = async (
|
|
91
|
+
id: string,
|
|
92
|
+
set: (state: Partial<CalendarState>) => void,
|
|
93
|
+
get: () => CalendarState
|
|
94
|
+
): Promise<void> => {
|
|
95
|
+
set({ isLoading: true, error: null });
|
|
96
|
+
try {
|
|
97
|
+
const events = get().events.filter((event) => event.id !== id);
|
|
98
|
+
await storageRepository.setItem(STORAGE_KEY, events);
|
|
99
|
+
handleStorageSuccess(set, { events });
|
|
100
|
+
} catch {
|
|
101
|
+
handleStorageError(set, 'Failed to delete event');
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Mark event as completed
|
|
107
|
+
*/
|
|
108
|
+
export const completeEvent = async (
|
|
109
|
+
id: string,
|
|
110
|
+
get: () => CalendarState,
|
|
111
|
+
updateEvent: (request: UpdateCalendarEventRequest) => Promise<void>
|
|
112
|
+
): Promise<void> => {
|
|
113
|
+
await updateEvent({ id, isCompleted: true });
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Mark event as incomplete
|
|
118
|
+
*/
|
|
119
|
+
export const uncompleteEvent = async (
|
|
120
|
+
id: string,
|
|
121
|
+
get: () => CalendarState,
|
|
122
|
+
updateEvent: (request: UpdateCalendarEventRequest) => Promise<void>
|
|
123
|
+
): Promise<void> => {
|
|
124
|
+
await updateEvent({ id, isCompleted: false });
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Clear all events (for testing/reset)
|
|
129
|
+
*/
|
|
130
|
+
export const clearAllEvents = async (
|
|
131
|
+
set: (state: Partial<CalendarState>) => void
|
|
132
|
+
): Promise<void> => {
|
|
133
|
+
set({ isLoading: true, error: null });
|
|
134
|
+
try {
|
|
135
|
+
await storageRepository.removeItem(STORAGE_KEY);
|
|
136
|
+
handleStorageSuccess(set, { events: [] });
|
|
137
|
+
} catch {
|
|
138
|
+
handleStorageError(set, 'Failed to clear events');
|
|
139
|
+
}
|
|
140
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calendar Navigation Actions
|
|
3
|
+
* Navigation and view mode operations for calendar store
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { CalendarService } from '../services/CalendarService';
|
|
7
|
+
import type { CalendarState, CalendarViewMode } from './CalendarStore.types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Set selected date
|
|
11
|
+
*/
|
|
12
|
+
export const setSelectedDate = (
|
|
13
|
+
date: Date,
|
|
14
|
+
set: (state: Partial<CalendarState>) => void
|
|
15
|
+
): void => {
|
|
16
|
+
set({ selectedDate: date });
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Go to today's date
|
|
21
|
+
*/
|
|
22
|
+
export const goToToday = (
|
|
23
|
+
set: (state: Partial<CalendarState>) => void
|
|
24
|
+
): void => {
|
|
25
|
+
const today = new Date();
|
|
26
|
+
set({
|
|
27
|
+
selectedDate: today,
|
|
28
|
+
currentMonth: today,
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Navigate to previous/next month
|
|
34
|
+
*/
|
|
35
|
+
export const navigateMonth = (
|
|
36
|
+
direction: 'prev' | 'next',
|
|
37
|
+
set: (state: Partial<CalendarState>) => void,
|
|
38
|
+
get: () => CalendarState
|
|
39
|
+
): void => {
|
|
40
|
+
const currentMonth = get().currentMonth;
|
|
41
|
+
const newMonth =
|
|
42
|
+
direction === 'prev'
|
|
43
|
+
? CalendarService.getPreviousMonth(currentMonth)
|
|
44
|
+
: CalendarService.getNextMonth(currentMonth);
|
|
45
|
+
|
|
46
|
+
set({ currentMonth: newMonth });
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Navigate to previous/next week
|
|
51
|
+
*/
|
|
52
|
+
export const navigateWeek = (
|
|
53
|
+
direction: 'prev' | 'next',
|
|
54
|
+
set: (state: Partial<CalendarState>) => void,
|
|
55
|
+
get: () => CalendarState
|
|
56
|
+
): void => {
|
|
57
|
+
const selectedDate = get().selectedDate;
|
|
58
|
+
const newDate =
|
|
59
|
+
direction === 'prev'
|
|
60
|
+
? CalendarService.getPreviousWeek(selectedDate)
|
|
61
|
+
: CalendarService.getNextWeek(selectedDate);
|
|
62
|
+
|
|
63
|
+
set({ selectedDate: newDate });
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Set current month directly
|
|
68
|
+
*/
|
|
69
|
+
export const setCurrentMonth = (
|
|
70
|
+
date: Date,
|
|
71
|
+
set: (state: Partial<CalendarState>) => void
|
|
72
|
+
): void => {
|
|
73
|
+
set({ currentMonth: date });
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Set view mode
|
|
78
|
+
*/
|
|
79
|
+
export const setViewMode = (
|
|
80
|
+
mode: CalendarViewMode,
|
|
81
|
+
set: (state: Partial<CalendarState>) => void
|
|
82
|
+
): void => {
|
|
83
|
+
set({ viewMode: mode });
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get events for a specific date
|
|
88
|
+
*/
|
|
89
|
+
export const getEventsForDate = (
|
|
90
|
+
date: Date,
|
|
91
|
+
get: () => CalendarState
|
|
92
|
+
): ReturnType<typeof CalendarService.getEventsForDate> => {
|
|
93
|
+
const events = get().events;
|
|
94
|
+
return CalendarService.getEventsForDate(date, events);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get events for a specific month
|
|
99
|
+
*/
|
|
100
|
+
export const getEventsForMonth = (
|
|
101
|
+
year: number,
|
|
102
|
+
month: number,
|
|
103
|
+
get: () => CalendarState
|
|
104
|
+
): ReturnType<typeof CalendarService.getEventsInRange> => {
|
|
105
|
+
const events = get().events;
|
|
106
|
+
const firstDay = new Date(year, month, 1);
|
|
107
|
+
const lastDay = new Date(year, month + 1, 0);
|
|
108
|
+
return CalendarService.getEventsInRange(firstDay, lastDay, events);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Clear error state
|
|
113
|
+
*/
|
|
114
|
+
export const clearError = (
|
|
115
|
+
set: (state: Partial<CalendarState>) => void
|
|
116
|
+
): void => {
|
|
117
|
+
set({ error: null });
|
|
118
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AsyncStorage Adapter for Zustand Persist
|
|
3
|
+
* Converts AsyncStorageRepository to Zustand-compatible storage
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { storageRepository } from '@umituz/react-native-storage';
|
|
7
|
+
|
|
8
|
+
export const zustandStorage = {
|
|
9
|
+
getItem: async (name: string): Promise<string | null> => {
|
|
10
|
+
try {
|
|
11
|
+
const result = await storageRepository.getItem<string>(name);
|
|
12
|
+
if (result.success && result.data) {
|
|
13
|
+
return result.data;
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
setItem: async (name: string, value: string): Promise<void> => {
|
|
21
|
+
try {
|
|
22
|
+
await storageRepository.setItem(name, value);
|
|
23
|
+
} catch {
|
|
24
|
+
// Silent fail
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
removeItem: async (name: string): Promise<void> => {
|
|
28
|
+
try {
|
|
29
|
+
await storageRepository.removeItem(name);
|
|
30
|
+
} catch {
|
|
31
|
+
// Silent fail
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calendar Events Store
|
|
3
|
+
* Manages event CRUD operations only
|
|
4
|
+
* Uses manual persistence (no zustand persist middleware)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { create } from 'zustand';
|
|
8
|
+
import type { CalendarEvent, CreateCalendarEventRequest, UpdateCalendarEventRequest } from '../../domain/entities/CalendarEvent.entity';
|
|
9
|
+
import { zustandStorage } from './storageAdapter';
|
|
10
|
+
|
|
11
|
+
const STORAGE_KEY = 'calendar_events';
|
|
12
|
+
|
|
13
|
+
interface CalendarEventsState {
|
|
14
|
+
readonly events: CalendarEvent[];
|
|
15
|
+
readonly isLoading: boolean;
|
|
16
|
+
readonly error: string | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface CalendarEventsActions {
|
|
20
|
+
readonly loadEvents: () => Promise<void>;
|
|
21
|
+
readonly addEvent: (request: CreateCalendarEventRequest) => Promise<void>;
|
|
22
|
+
readonly updateEvent: (request: UpdateCalendarEventRequest) => Promise<void>;
|
|
23
|
+
readonly deleteEvent: (id: string) => Promise<void>;
|
|
24
|
+
readonly completeEvent: (id: string) => Promise<void>;
|
|
25
|
+
readonly uncompleteEvent: (id: string) => Promise<void>;
|
|
26
|
+
readonly clearError: () => void;
|
|
27
|
+
readonly clearAllEvents: () => Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type CalendarEventsStore = CalendarEventsState & CalendarEventsActions;
|
|
31
|
+
|
|
32
|
+
const generateId = (): string => `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
33
|
+
|
|
34
|
+
const persistEvents = async (events: CalendarEvent[]): Promise<void> => {
|
|
35
|
+
try {
|
|
36
|
+
await zustandStorage.setItem(STORAGE_KEY, JSON.stringify(events));
|
|
37
|
+
} catch {
|
|
38
|
+
// Silent fail
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const useCalendarEvents = create<CalendarEventsStore>()((set, get) => ({
|
|
43
|
+
events: [],
|
|
44
|
+
isLoading: false,
|
|
45
|
+
error: null,
|
|
46
|
+
|
|
47
|
+
loadEvents: async () => {
|
|
48
|
+
set({ isLoading: true, error: null });
|
|
49
|
+
try {
|
|
50
|
+
const json = await zustandStorage.getItem(STORAGE_KEY);
|
|
51
|
+
if (!json) {
|
|
52
|
+
set({ isLoading: false });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const events = JSON.parse(json) as CalendarEvent[];
|
|
57
|
+
|
|
58
|
+
if (events && events.length > 0) {
|
|
59
|
+
const hydratedEvents = events.map((event) => ({
|
|
60
|
+
...event,
|
|
61
|
+
createdAt: new Date(event.createdAt),
|
|
62
|
+
updatedAt: new Date(event.updatedAt),
|
|
63
|
+
}));
|
|
64
|
+
set({ events: hydratedEvents, isLoading: false });
|
|
65
|
+
} else {
|
|
66
|
+
set({ isLoading: false });
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
set({ error: 'Failed to load events', isLoading: false });
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
addEvent: async (request) => {
|
|
74
|
+
set({ isLoading: true, error: null });
|
|
75
|
+
try {
|
|
76
|
+
const newEvent: CalendarEvent = {
|
|
77
|
+
id: generateId(),
|
|
78
|
+
...request,
|
|
79
|
+
createdAt: new Date(),
|
|
80
|
+
updatedAt: new Date(),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const { events } = get();
|
|
84
|
+
const updatedEvents = [...events, newEvent];
|
|
85
|
+
|
|
86
|
+
await persistEvents(updatedEvents);
|
|
87
|
+
set({ events: updatedEvents, isLoading: false });
|
|
88
|
+
} catch {
|
|
89
|
+
set({ error: 'Failed to add event', isLoading: false });
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
updateEvent: async (request) => {
|
|
94
|
+
set({ isLoading: true, error: null });
|
|
95
|
+
try {
|
|
96
|
+
const { events } = get();
|
|
97
|
+
const updatedEvents = events.map((event) =>
|
|
98
|
+
event.id === request.id
|
|
99
|
+
? { ...event, ...request, updatedAt: new Date() }
|
|
100
|
+
: event
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
await persistEvents(updatedEvents);
|
|
104
|
+
set({ events: updatedEvents, isLoading: false });
|
|
105
|
+
} catch {
|
|
106
|
+
set({ error: 'Failed to update event', isLoading: false });
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
deleteEvent: async (id) => {
|
|
111
|
+
set({ isLoading: true, error: null });
|
|
112
|
+
try {
|
|
113
|
+
const { events } = get();
|
|
114
|
+
const updatedEvents = events.filter((event) => event.id !== id);
|
|
115
|
+
|
|
116
|
+
await persistEvents(updatedEvents);
|
|
117
|
+
set({ events: updatedEvents, isLoading: false });
|
|
118
|
+
} catch {
|
|
119
|
+
set({ error: 'Failed to delete event', isLoading: false });
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
completeEvent: async (id) => {
|
|
124
|
+
set({ isLoading: true, error: null });
|
|
125
|
+
try {
|
|
126
|
+
const { events } = get();
|
|
127
|
+
const updatedEvents = events.map((event) =>
|
|
128
|
+
event.id === id
|
|
129
|
+
? { ...event, completed: true, updatedAt: new Date() }
|
|
130
|
+
: event
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
await persistEvents(updatedEvents);
|
|
134
|
+
set({ events: updatedEvents, isLoading: false });
|
|
135
|
+
} catch {
|
|
136
|
+
set({ error: 'Failed to complete event', isLoading: false });
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
uncompleteEvent: async (id) => {
|
|
141
|
+
set({ isLoading: true, error: null });
|
|
142
|
+
try {
|
|
143
|
+
const { events } = get();
|
|
144
|
+
const updatedEvents = events.map((event) =>
|
|
145
|
+
event.id === id
|
|
146
|
+
? { ...event, completed: false, updatedAt: new Date() }
|
|
147
|
+
: event
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
await persistEvents(updatedEvents);
|
|
151
|
+
set({ events: updatedEvents, isLoading: false });
|
|
152
|
+
} catch {
|
|
153
|
+
set({ error: 'Failed to uncomplete event', isLoading: false });
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
clearError: () => set({ error: null }),
|
|
158
|
+
|
|
159
|
+
clearAllEvents: async () => {
|
|
160
|
+
set({ isLoading: true, error: null });
|
|
161
|
+
try {
|
|
162
|
+
await zustandStorage.removeItem(STORAGE_KEY);
|
|
163
|
+
set({ events: [], isLoading: false });
|
|
164
|
+
} catch {
|
|
165
|
+
set({ error: 'Failed to clear events', isLoading: false });
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
}));
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calendar Navigation Store
|
|
3
|
+
* Manages date navigation state (no persistence needed)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { create } from 'zustand';
|
|
7
|
+
|
|
8
|
+
interface CalendarNavigationState {
|
|
9
|
+
readonly selectedDate: Date;
|
|
10
|
+
readonly currentMonth: Date;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface CalendarNavigationActions {
|
|
14
|
+
readonly setSelectedDate: (date: Date) => void;
|
|
15
|
+
readonly goToToday: () => void;
|
|
16
|
+
readonly navigateMonth: (direction: 'prev' | 'next') => void;
|
|
17
|
+
readonly setCurrentMonth: (date: Date) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type CalendarNavigationStore = CalendarNavigationState & CalendarNavigationActions;
|
|
21
|
+
|
|
22
|
+
export const useCalendarNavigation = create<CalendarNavigationStore>()((set) => ({
|
|
23
|
+
selectedDate: new Date(),
|
|
24
|
+
currentMonth: new Date(new Date().getFullYear(), new Date().getMonth(), 1),
|
|
25
|
+
|
|
26
|
+
setSelectedDate: (date) => set({ selectedDate: date }),
|
|
27
|
+
|
|
28
|
+
goToToday: () => {
|
|
29
|
+
const today = new Date();
|
|
30
|
+
set({
|
|
31
|
+
selectedDate: today,
|
|
32
|
+
currentMonth: new Date(today.getFullYear(), today.getMonth(), 1),
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
navigateMonth: (direction) => {
|
|
37
|
+
set((state) => {
|
|
38
|
+
const { currentMonth } = state;
|
|
39
|
+
const newMonth = direction === 'next' ? currentMonth.getMonth() + 1 : currentMonth.getMonth() - 1;
|
|
40
|
+
return {
|
|
41
|
+
currentMonth: new Date(currentMonth.getFullYear(), newMonth, 1),
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
setCurrentMonth: (date) => set({ currentMonth: new Date(date.getFullYear(), date.getMonth(), 1) }),
|
|
47
|
+
}));
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calendar View Store
|
|
3
|
+
* Manages calendar view mode (no persistence needed)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { create } from 'zustand';
|
|
7
|
+
|
|
8
|
+
export type CalendarViewMode = 'month' | 'week' | 'day' | 'list';
|
|
9
|
+
|
|
10
|
+
interface CalendarViewState {
|
|
11
|
+
readonly viewMode: CalendarViewMode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface CalendarViewActions {
|
|
15
|
+
readonly setViewMode: (mode: CalendarViewMode) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type CalendarViewStore = CalendarViewState & CalendarViewActions;
|
|
19
|
+
|
|
20
|
+
export const useCalendarView = create<CalendarViewStore>()((set) => ({
|
|
21
|
+
viewMode: 'month',
|
|
22
|
+
|
|
23
|
+
setViewMode: (mode) => set({ viewMode: mode }),
|
|
24
|
+
}));
|
|
@@ -28,7 +28,6 @@
|
|
|
28
28
|
|
|
29
29
|
import { useMemo, useEffect } from 'react';
|
|
30
30
|
import { useCalendarStore, type CalendarViewMode } from '../../infrastructure/storage/CalendarStore';
|
|
31
|
-
import { CalendarService } from '../../infrastructure/services/CalendarService';
|
|
32
31
|
import type { CalendarDay } from '../../domain/entities/CalendarDay.entity';
|
|
33
32
|
import type { CalendarEvent } from '../../domain/entities/CalendarEvent.entity';
|
|
34
33
|
|
|
@@ -62,7 +61,6 @@ export interface UseCalendarReturn {
|
|
|
62
61
|
setSelectedDate: (date: Date) => void;
|
|
63
62
|
goToToday: () => void;
|
|
64
63
|
navigateMonth: (direction: 'prev' | 'next') => void;
|
|
65
|
-
navigateWeek: (direction: 'prev' | 'next') => void;
|
|
66
64
|
setCurrentMonth: (date: Date) => void;
|
|
67
65
|
setViewMode: (mode: CalendarViewMode) => void;
|
|
68
66
|
getEventsForDate: (date: Date) => CalendarEvent[];
|
|
@@ -75,7 +73,7 @@ export interface UseCalendarReturn {
|
|
|
75
73
|
/**
|
|
76
74
|
* Main calendar hook
|
|
77
75
|
*/
|
|
78
|
-
export const
|
|
76
|
+
export const useCalendarPresentation = (): UseCalendarReturn => {
|
|
79
77
|
const store = useCalendarStore();
|
|
80
78
|
const {
|
|
81
79
|
events,
|
|
@@ -92,13 +90,6 @@ export const useCalendar = (): UseCalendarReturn => {
|
|
|
92
90
|
actions.loadEvents();
|
|
93
91
|
}, []);
|
|
94
92
|
|
|
95
|
-
// Generate calendar days for current month
|
|
96
|
-
const days = useMemo(() => {
|
|
97
|
-
const year = currentMonth.getFullYear();
|
|
98
|
-
const month = currentMonth.getMonth();
|
|
99
|
-
return CalendarService.getMonthDays(year, month, events);
|
|
100
|
-
}, [currentMonth, events]);
|
|
101
|
-
|
|
102
93
|
// Get events for selected date
|
|
103
94
|
const selectedDateEvents = useMemo(() => {
|
|
104
95
|
return actions.getEventsForDate(selectedDate);
|
|
@@ -112,7 +103,7 @@ export const useCalendar = (): UseCalendarReturn => {
|
|
|
112
103
|
}, [currentMonth, events]);
|
|
113
104
|
|
|
114
105
|
return {
|
|
115
|
-
days,
|
|
106
|
+
days: store.days,
|
|
116
107
|
events,
|
|
117
108
|
selectedDate,
|
|
118
109
|
currentMonth,
|
|
@@ -181,3 +172,8 @@ export const useCalendarEvents = () => {
|
|
|
181
172
|
clearError,
|
|
182
173
|
};
|
|
183
174
|
};
|
|
175
|
+
|
|
176
|
+
// Re-export for convenience
|
|
177
|
+
export { useCalendar, useCalendarStore } from '../../infrastructure/storage/CalendarStore';
|
|
178
|
+
export type { CalendarViewMode } from '../../infrastructure/storage/CalendarStore';
|
|
179
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device Info Computation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { isSmallPhone, isTablet, isLandscape, getDeviceType, getSpacingMultiplier } from '../../device/detection';
|
|
6
|
+
import type { DeviceType } from '../../device/detection';
|
|
7
|
+
|
|
8
|
+
export interface ComputedDeviceInfo {
|
|
9
|
+
readonly isSmallDevice: boolean;
|
|
10
|
+
readonly isTabletDevice: boolean;
|
|
11
|
+
readonly isLandscapeDevice: boolean;
|
|
12
|
+
readonly deviceType: DeviceType;
|
|
13
|
+
readonly spacingMultiplier: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const computeDeviceInfo = (): ComputedDeviceInfo => ({
|
|
17
|
+
isSmallDevice: isSmallPhone(),
|
|
18
|
+
isTabletDevice: isTablet(),
|
|
19
|
+
isLandscapeDevice: isLandscape(),
|
|
20
|
+
deviceType: getDeviceType(),
|
|
21
|
+
spacingMultiplier: getSpacingMultiplier(),
|
|
22
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Responsive Positioning Computation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
getResponsiveHorizontalPadding,
|
|
7
|
+
getResponsiveVerticalPadding,
|
|
8
|
+
getResponsiveBottomPosition,
|
|
9
|
+
getResponsiveFABPosition,
|
|
10
|
+
getScreenLayoutConfig,
|
|
11
|
+
getResponsiveTabBarConfig,
|
|
12
|
+
getResponsiveModalLayout,
|
|
13
|
+
getResponsiveBottomSheetLayout,
|
|
14
|
+
getResponsiveDialogLayout,
|
|
15
|
+
} from '../responsive';
|
|
16
|
+
import type { ResponsiveModalLayout, ResponsiveBottomSheetLayout, ResponsiveDialogLayout, ResponsiveTabBarConfig, ScreenLayoutConfig } from '../responsive';
|
|
17
|
+
|
|
18
|
+
export interface ComputedResponsivePositioning {
|
|
19
|
+
readonly horizontalPadding: number;
|
|
20
|
+
readonly verticalPadding: number;
|
|
21
|
+
readonly bottomPosition: number;
|
|
22
|
+
readonly fabPosition: { readonly bottom: number; readonly right: number };
|
|
23
|
+
readonly screenLayoutConfig: ScreenLayoutConfig;
|
|
24
|
+
readonly tabBarConfig: ResponsiveTabBarConfig;
|
|
25
|
+
readonly modalLayout: ResponsiveModalLayout;
|
|
26
|
+
readonly bottomSheetLayout: ResponsiveBottomSheetLayout;
|
|
27
|
+
readonly dialogLayout: ResponsiveDialogLayout;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const computeResponsivePositioning = (
|
|
31
|
+
insets: { top: number; bottom: number; left: number; right: number }
|
|
32
|
+
): ComputedResponsivePositioning => ({
|
|
33
|
+
horizontalPadding: getResponsiveHorizontalPadding(undefined, insets),
|
|
34
|
+
verticalPadding: getResponsiveVerticalPadding(insets),
|
|
35
|
+
bottomPosition: getResponsiveBottomPosition(undefined, insets),
|
|
36
|
+
fabPosition: getResponsiveFABPosition(insets),
|
|
37
|
+
screenLayoutConfig: getScreenLayoutConfig(insets),
|
|
38
|
+
tabBarConfig: getResponsiveTabBarConfig(insets),
|
|
39
|
+
modalLayout: getResponsiveModalLayout(),
|
|
40
|
+
bottomSheetLayout: getResponsiveBottomSheetLayout(),
|
|
41
|
+
dialogLayout: getResponsiveDialogLayout(),
|
|
42
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Responsive Sizes Computation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
getResponsiveLogoSize,
|
|
7
|
+
getResponsiveInputHeight,
|
|
8
|
+
getResponsiveIconContainerSize,
|
|
9
|
+
getResponsiveMaxWidth,
|
|
10
|
+
getResponsiveModalMaxHeight,
|
|
11
|
+
getResponsiveMinModalHeight,
|
|
12
|
+
getResponsiveGridColumns,
|
|
13
|
+
} from '../responsive';
|
|
14
|
+
import { getMinTouchTarget } from '../platformConstants';
|
|
15
|
+
import type { ComputedDeviceInfo } from './computeDeviceInfo';
|
|
16
|
+
|
|
17
|
+
export interface ComputedResponsiveSizes {
|
|
18
|
+
readonly logoSize: number;
|
|
19
|
+
readonly inputHeight: number;
|
|
20
|
+
readonly iconContainerSize: number;
|
|
21
|
+
readonly maxContentWidth: number;
|
|
22
|
+
readonly minTouchTarget: number;
|
|
23
|
+
readonly modalMaxHeight: string;
|
|
24
|
+
readonly modalMinHeight: number;
|
|
25
|
+
readonly gridColumns: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const computeResponsiveSizes = (): ComputedResponsiveSizes => ({
|
|
29
|
+
logoSize: getResponsiveLogoSize(),
|
|
30
|
+
inputHeight: getResponsiveInputHeight(),
|
|
31
|
+
iconContainerSize: getResponsiveIconContainerSize(),
|
|
32
|
+
maxContentWidth: getResponsiveMaxWidth(),
|
|
33
|
+
minTouchTarget: getMinTouchTarget(),
|
|
34
|
+
modalMaxHeight: getResponsiveModalMaxHeight(),
|
|
35
|
+
modalMinHeight: getResponsiveMinModalHeight(),
|
|
36
|
+
gridColumns: getResponsiveGridColumns(),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export const computeOnboardingSizes = (
|
|
40
|
+
deviceInfo: ComputedDeviceInfo
|
|
41
|
+
) => ({
|
|
42
|
+
onboardingIconSize: getResponsiveIconContainerSize(64),
|
|
43
|
+
onboardingIconMarginTop: deviceInfo.spacingMultiplier * 24,
|
|
44
|
+
onboardingIconMarginBottom: deviceInfo.spacingMultiplier * 16,
|
|
45
|
+
onboardingTitleMarginBottom: deviceInfo.spacingMultiplier * 16,
|
|
46
|
+
onboardingDescriptionMarginTop: deviceInfo.spacingMultiplier * 12,
|
|
47
|
+
onboardingTextPadding: deviceInfo.spacingMultiplier * 20,
|
|
48
|
+
});
|