@ubkinfotech/tecaher-erp 0.1.0

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.
Files changed (146) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +413 -0
  3. package/lib/commonjs/core/api/apiClient.js +38 -0
  4. package/lib/commonjs/core/api/endpoints.js +139 -0
  5. package/lib/commonjs/core/api/interceptor.js +64 -0
  6. package/lib/commonjs/core/auth/authContext.js +30 -0
  7. package/lib/commonjs/core/auth/authService.js +12 -0
  8. package/lib/commonjs/core/hooks/useApiQuery.js +26 -0
  9. package/lib/commonjs/core/provider/ERPProvider.js +63 -0
  10. package/lib/commonjs/core/provider/types.js +5 -0
  11. package/lib/commonjs/core/provider/useERP.js +17 -0
  12. package/lib/commonjs/core/types/api.js +1 -0
  13. package/lib/commonjs/index.js +110 -0
  14. package/lib/commonjs/modules/assignment/hooks/useAssignmentList.js +16 -0
  15. package/lib/commonjs/modules/assignment/hooks/useHomeworkDetails.js +16 -0
  16. package/lib/commonjs/modules/assignment/hooks/useHomeworkSubmissions.js +16 -0
  17. package/lib/commonjs/modules/assignment/screens/AssignmentScreen.js +2615 -0
  18. package/lib/commonjs/modules/assignment/services/assignmentService.js +75 -0
  19. package/lib/commonjs/modules/attendance/hooks/useAttendance.js +16 -0
  20. package/lib/commonjs/modules/attendance/screens/AttendanceScreen.js +866 -0
  21. package/lib/commonjs/modules/attendance/services/attendanceService.js +31 -0
  22. package/lib/commonjs/modules/leaveRequest/hooks/useLeaveRequests.js +16 -0
  23. package/lib/commonjs/modules/leaveRequest/screens/LeaveRequestScreen.js +991 -0
  24. package/lib/commonjs/modules/leaveRequest/services/leaveRequestService.js +42 -0
  25. package/lib/commonjs/modules/marks/hooks/useMarks.js +14 -0
  26. package/lib/commonjs/modules/marks/screens/MarksScreen.js +1621 -0
  27. package/lib/commonjs/modules/marks/services/marksService.js +71 -0
  28. package/lib/commonjs/modules/myAttendance/hooks/useMyAttendance.js +16 -0
  29. package/lib/commonjs/modules/myAttendance/screens/MyAttendanceScreen.js +357 -0
  30. package/lib/commonjs/modules/myAttendance/services/myAttendanceService.js +11 -0
  31. package/lib/commonjs/modules/notes/hooks/useNotes.js +16 -0
  32. package/lib/commonjs/modules/notes/screens/NotesScreen.js +1287 -0
  33. package/lib/commonjs/modules/notes/services/notesService.js +65 -0
  34. package/lib/commonjs/modules/noticeboard/hooks/useNoticeboard.js +16 -0
  35. package/lib/commonjs/modules/noticeboard/screens/NoticeBoardScreen.js +381 -0
  36. package/lib/commonjs/modules/noticeboard/services/noticeboardService.js +16 -0
  37. package/lib/commonjs/modules/notification/hooks/useNotifications.js +16 -0
  38. package/lib/commonjs/modules/notification/screens/NotificationScreen.js +186 -0
  39. package/lib/commonjs/modules/notification/services/notificationService.js +16 -0
  40. package/lib/commonjs/modules/promoteStudent/hooks/usePromoteStudent.js +16 -0
  41. package/lib/commonjs/modules/promoteStudent/screens/PromoteStudentScreen.js +644 -0
  42. package/lib/commonjs/modules/promoteStudent/services/promoteStudentService.js +36 -0
  43. package/lib/commonjs/modules/timetable/hooks/useTimeTable.js +14 -0
  44. package/lib/commonjs/modules/timetable/screens/TimeTableScreen.js +258 -0
  45. package/lib/commonjs/modules/timetable/services/timetableService.js +16 -0
  46. package/lib/commonjs/package.json +1 -0
  47. package/lib/commonjs/shared/empty-states/EmptyState.js +45 -0
  48. package/lib/commonjs/shared/empty-states/ErrorState.js +45 -0
  49. package/lib/commonjs/shared/loaders/LoadingState.js +25 -0
  50. package/lib/commonjs/shared/theme/theme.js +22 -0
  51. package/lib/module/core/api/apiClient.js +32 -0
  52. package/lib/module/core/api/endpoints.js +135 -0
  53. package/lib/module/core/api/interceptor.js +60 -0
  54. package/lib/module/core/auth/authContext.js +23 -0
  55. package/lib/module/core/auth/authService.js +8 -0
  56. package/lib/module/core/hooks/useApiQuery.js +21 -0
  57. package/lib/module/core/provider/ERPProvider.js +56 -0
  58. package/lib/module/core/provider/types.js +3 -0
  59. package/lib/module/core/provider/useERP.js +12 -0
  60. package/lib/module/core/types/api.js +1 -0
  61. package/lib/module/index.js +17 -0
  62. package/lib/module/modules/assignment/hooks/useAssignmentList.js +12 -0
  63. package/lib/module/modules/assignment/hooks/useHomeworkDetails.js +12 -0
  64. package/lib/module/modules/assignment/hooks/useHomeworkSubmissions.js +12 -0
  65. package/lib/module/modules/assignment/screens/AssignmentScreen.js +2609 -0
  66. package/lib/module/modules/assignment/services/assignmentService.js +62 -0
  67. package/lib/module/modules/attendance/hooks/useAttendance.js +12 -0
  68. package/lib/module/modules/attendance/screens/AttendanceScreen.js +860 -0
  69. package/lib/module/modules/attendance/services/attendanceService.js +23 -0
  70. package/lib/module/modules/leaveRequest/hooks/useLeaveRequests.js +12 -0
  71. package/lib/module/modules/leaveRequest/screens/LeaveRequestScreen.js +985 -0
  72. package/lib/module/modules/leaveRequest/services/leaveRequestService.js +35 -0
  73. package/lib/module/modules/marks/hooks/useMarks.js +10 -0
  74. package/lib/module/modules/marks/screens/MarksScreen.js +1615 -0
  75. package/lib/module/modules/marks/services/marksService.js +55 -0
  76. package/lib/module/modules/myAttendance/hooks/useMyAttendance.js +12 -0
  77. package/lib/module/modules/myAttendance/screens/MyAttendanceScreen.js +351 -0
  78. package/lib/module/modules/myAttendance/services/myAttendanceService.js +7 -0
  79. package/lib/module/modules/notes/hooks/useNotes.js +12 -0
  80. package/lib/module/modules/notes/screens/NotesScreen.js +1281 -0
  81. package/lib/module/modules/notes/services/notesService.js +54 -0
  82. package/lib/module/modules/noticeboard/hooks/useNoticeboard.js +12 -0
  83. package/lib/module/modules/noticeboard/screens/NoticeBoardScreen.js +375 -0
  84. package/lib/module/modules/noticeboard/services/noticeboardService.js +12 -0
  85. package/lib/module/modules/notification/hooks/useNotifications.js +12 -0
  86. package/lib/module/modules/notification/screens/NotificationScreen.js +180 -0
  87. package/lib/module/modules/notification/services/notificationService.js +12 -0
  88. package/lib/module/modules/promoteStudent/hooks/usePromoteStudent.js +12 -0
  89. package/lib/module/modules/promoteStudent/screens/PromoteStudentScreen.js +638 -0
  90. package/lib/module/modules/promoteStudent/services/promoteStudentService.js +27 -0
  91. package/lib/module/modules/timetable/hooks/useTimeTable.js +10 -0
  92. package/lib/module/modules/timetable/screens/TimeTableScreen.js +252 -0
  93. package/lib/module/modules/timetable/services/timetableService.js +11 -0
  94. package/lib/module/package.json +1 -0
  95. package/lib/module/shared/empty-states/EmptyState.js +40 -0
  96. package/lib/module/shared/empty-states/ErrorState.js +40 -0
  97. package/lib/module/shared/loaders/LoadingState.js +20 -0
  98. package/lib/module/shared/theme/theme.js +18 -0
  99. package/lib/typescript/core/api/apiClient.d.ts +14 -0
  100. package/lib/typescript/core/api/endpoints.d.ts +164 -0
  101. package/lib/typescript/core/api/interceptor.d.ts +4 -0
  102. package/lib/typescript/core/auth/authContext.d.ts +11 -0
  103. package/lib/typescript/core/auth/authService.d.ts +9 -0
  104. package/lib/typescript/core/hooks/useApiQuery.d.ts +7 -0
  105. package/lib/typescript/core/provider/ERPProvider.d.ts +14 -0
  106. package/lib/typescript/core/provider/types.d.ts +34 -0
  107. package/lib/typescript/core/provider/useERP.d.ts +3 -0
  108. package/lib/typescript/core/types/api.d.ts +11 -0
  109. package/lib/typescript/index.d.ts +18 -0
  110. package/lib/typescript/modules/assignment/hooks/useAssignmentList.d.ts +3 -0
  111. package/lib/typescript/modules/assignment/hooks/useHomeworkDetails.d.ts +4 -0
  112. package/lib/typescript/modules/assignment/hooks/useHomeworkSubmissions.d.ts +4 -0
  113. package/lib/typescript/modules/assignment/screens/AssignmentScreen.d.ts +9 -0
  114. package/lib/typescript/modules/assignment/services/assignmentService.d.ts +89 -0
  115. package/lib/typescript/modules/attendance/hooks/useAttendance.d.ts +3 -0
  116. package/lib/typescript/modules/attendance/screens/AttendanceScreen.d.ts +5 -0
  117. package/lib/typescript/modules/attendance/services/attendanceService.d.ts +33 -0
  118. package/lib/typescript/modules/leaveRequest/hooks/useLeaveRequests.d.ts +3 -0
  119. package/lib/typescript/modules/leaveRequest/screens/LeaveRequestScreen.d.ts +5 -0
  120. package/lib/typescript/modules/leaveRequest/services/leaveRequestService.d.ts +39 -0
  121. package/lib/typescript/modules/marks/hooks/useMarks.d.ts +2 -0
  122. package/lib/typescript/modules/marks/screens/MarksScreen.d.ts +6 -0
  123. package/lib/typescript/modules/marks/services/marksService.d.ts +150 -0
  124. package/lib/typescript/modules/myAttendance/hooks/useMyAttendance.d.ts +3 -0
  125. package/lib/typescript/modules/myAttendance/screens/MyAttendanceScreen.d.ts +5 -0
  126. package/lib/typescript/modules/myAttendance/services/myAttendanceService.d.ts +10 -0
  127. package/lib/typescript/modules/notes/hooks/useNotes.d.ts +3 -0
  128. package/lib/typescript/modules/notes/screens/NotesScreen.d.ts +8 -0
  129. package/lib/typescript/modules/notes/services/notesService.d.ts +58 -0
  130. package/lib/typescript/modules/noticeboard/hooks/useNoticeboard.d.ts +3 -0
  131. package/lib/typescript/modules/noticeboard/screens/NoticeBoardScreen.d.ts +5 -0
  132. package/lib/typescript/modules/noticeboard/services/noticeboardService.d.ts +17 -0
  133. package/lib/typescript/modules/notification/hooks/useNotifications.d.ts +3 -0
  134. package/lib/typescript/modules/notification/screens/NotificationScreen.d.ts +5 -0
  135. package/lib/typescript/modules/notification/services/notificationService.d.ts +8 -0
  136. package/lib/typescript/modules/promoteStudent/hooks/usePromoteStudent.d.ts +3 -0
  137. package/lib/typescript/modules/promoteStudent/screens/PromoteStudentScreen.d.ts +5 -0
  138. package/lib/typescript/modules/promoteStudent/services/promoteStudentService.d.ts +40 -0
  139. package/lib/typescript/modules/timetable/hooks/useTimeTable.d.ts +3 -0
  140. package/lib/typescript/modules/timetable/screens/TimeTableScreen.d.ts +5 -0
  141. package/lib/typescript/modules/timetable/services/timetableService.d.ts +14 -0
  142. package/lib/typescript/shared/empty-states/EmptyState.d.ts +6 -0
  143. package/lib/typescript/shared/empty-states/ErrorState.d.ts +6 -0
  144. package/lib/typescript/shared/loaders/LoadingState.d.ts +3 -0
  145. package/lib/typescript/shared/theme/theme.d.ts +17 -0
  146. package/package.json +89 -0
@@ -0,0 +1,2609 @@
1
+ "use strict";
2
+
3
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
4
+ import { Alert, FlatList, Image, Modal, Pressable, SafeAreaView, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
5
+ import { useERP } from "../../../core/provider/useERP.js";
6
+ import { EmptyState } from "../../../shared/empty-states/EmptyState.js";
7
+ import { ErrorState } from "../../../shared/empty-states/ErrorState.js";
8
+ import { LoadingState } from "../../../shared/loaders/LoadingState.js";
9
+ import { useAssignmentList } from "../hooks/useAssignmentList.js";
10
+ import { useHomeworkDetails } from "../hooks/useHomeworkDetails.js";
11
+ import { useHomeworkSubmissions } from "../hooks/useHomeworkSubmissions.js";
12
+ import { createAssignment, fetchTeacherClasses, fetchTeacherSections, fetchTeacherSubjectsByClass, giveMarksToHomework, updateAssignment, uploadAssignmentFile } from "../services/assignmentService.js";
13
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
14
+ function normalizeOptions(result) {
15
+ if (!result) {
16
+ return [];
17
+ }
18
+ if (Array.isArray(result)) {
19
+ return result;
20
+ }
21
+ if (Array.isArray(result?.data)) {
22
+ return result.data;
23
+ }
24
+ if (Array.isArray(result?.data?.data)) {
25
+ return result.data.data;
26
+ }
27
+ return [];
28
+ }
29
+ function normalizeAssignments(result) {
30
+ const raw = result?.data ?? result;
31
+ if (Array.isArray(raw)) {
32
+ return raw;
33
+ }
34
+ if (Array.isArray(raw?.data)) {
35
+ return raw.data;
36
+ }
37
+ if (Array.isArray(raw?.data?.data)) {
38
+ return raw.data.data;
39
+ }
40
+ return [];
41
+ }
42
+ function normalizeHomeworkDetail(result) {
43
+ if (!result) return null;
44
+ const raw = result?.data ?? result;
45
+ if (Array.isArray(raw)) {
46
+ return raw[0] ?? null;
47
+ }
48
+ if (Array.isArray(raw?.data)) {
49
+ return raw.data[0] ?? null;
50
+ }
51
+ if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
52
+ if (raw?.data && typeof raw.data === 'object') {
53
+ return raw.data;
54
+ }
55
+ return raw;
56
+ }
57
+ return null;
58
+ }
59
+ function detailValue(source, keys, fallback) {
60
+ for (const key of keys) {
61
+ const value = source?.[key];
62
+ if (value !== undefined && value !== null && value !== '') {
63
+ return value;
64
+ }
65
+ }
66
+ return fallback;
67
+ }
68
+ function SelectModal({
69
+ label,
70
+ value,
71
+ options,
72
+ placeholder,
73
+ onChange
74
+ }) {
75
+ const [open, setOpen] = useState(false);
76
+ return /*#__PURE__*/_jsxs(View, {
77
+ style: styles.field,
78
+ children: [/*#__PURE__*/_jsx(Text, {
79
+ style: styles.label,
80
+ children: label
81
+ }), /*#__PURE__*/_jsx(Pressable, {
82
+ onPress: () => setOpen(true),
83
+ style: styles.select,
84
+ children: /*#__PURE__*/_jsx(Text, {
85
+ style: styles.selectText,
86
+ children: value?.label ?? placeholder
87
+ })
88
+ }), /*#__PURE__*/_jsx(Modal, {
89
+ visible: open,
90
+ transparent: true,
91
+ animationType: "fade",
92
+ children: /*#__PURE__*/_jsx(Pressable, {
93
+ style: styles.modalBackdrop,
94
+ onPress: () => setOpen(false),
95
+ children: /*#__PURE__*/_jsxs(Pressable, {
96
+ style: styles.modalCard,
97
+ onPress: () => {},
98
+ children: [/*#__PURE__*/_jsx(Text, {
99
+ style: styles.modalTitle,
100
+ children: label
101
+ }), /*#__PURE__*/_jsx(FlatList, {
102
+ data: options,
103
+ keyExtractor: (it, idx) => `${it.value}-${idx}`,
104
+ renderItem: ({
105
+ item
106
+ }) => {
107
+ return /*#__PURE__*/_jsx(Pressable, {
108
+ style: styles.optionRow,
109
+ onPress: () => {
110
+ onChange(item);
111
+ setOpen(false);
112
+ },
113
+ children: /*#__PURE__*/_jsx(Text, {
114
+ style: styles.optionText,
115
+ children: item.label
116
+ })
117
+ });
118
+ },
119
+ ListEmptyComponent: /*#__PURE__*/_jsx(Text, {
120
+ style: styles.optionEmpty,
121
+ children: "No options"
122
+ })
123
+ })]
124
+ })
125
+ })
126
+ })]
127
+ });
128
+ }
129
+ function resolveFileBaseUrl(args) {
130
+ if (args.fileBaseUrl) {
131
+ return args.fileBaseUrl.endsWith('/') ? args.fileBaseUrl : `${args.fileBaseUrl}/`;
132
+ }
133
+ const base = args.baseUrl;
134
+ if (base.includes('/api/')) {
135
+ return base.replace('/api/', '/uploads/');
136
+ }
137
+ if (base.endsWith('/api')) {
138
+ return `${base.slice(0, -3)}/uploads/`;
139
+ }
140
+ return base.endsWith('/') ? `${base}uploads/` : `${base}/uploads/`;
141
+ }
142
+ export function AssignmentScreen(props) {
143
+ const {
144
+ api,
145
+ baseUrl,
146
+ fileBaseUrl
147
+ } = useERP();
148
+ const downloadsBaseUrl = useMemo(() => {
149
+ return resolveFileBaseUrl({
150
+ baseUrl,
151
+ fileBaseUrl
152
+ });
153
+ }, [baseUrl, fileBaseUrl]);
154
+ const [mode, setMode] = useState('list');
155
+ const [selectedHomework, setSelectedHomework] = useState(null);
156
+ const [submissionsOpen, setSubmissionsOpen] = useState(false);
157
+ const [editHomeworkId, setEditHomeworkId] = useState(null);
158
+ const [filterClass, setFilterClass] = useState(null);
159
+ const [filterSection, setFilterSection] = useState(null);
160
+ const [filterSubject, setFilterSubject] = useState(null);
161
+ const [classes, setClasses] = useState([]);
162
+ const [sections, setSections] = useState([]);
163
+ const [subjects, setSubjects] = useState([]);
164
+ const listParams = useMemo(() => {
165
+ return {
166
+ page: props.page ?? 1,
167
+ perPage: props.perPage ?? 10,
168
+ classId: props.classId ?? filterClass?.value,
169
+ sectionId: props.sectionId ?? filterSection?.value,
170
+ subjectId: props.subjectId ?? filterSubject?.value
171
+ };
172
+ }, [filterClass?.value, filterSection?.value, filterSubject?.value, props.classId, props.page, props.perPage, props.sectionId, props.subjectId]);
173
+ const {
174
+ data,
175
+ isLoading,
176
+ error,
177
+ refetch,
178
+ isFetching
179
+ } = useAssignmentList(listParams);
180
+ const assignments = useMemo(() => normalizeAssignments(data), [data]);
181
+ const submissionsQuery = useHomeworkSubmissions(selectedHomework?.id !== undefined && selectedHomework?.id !== null ? {
182
+ homeworkId: selectedHomework.id
183
+ } : undefined);
184
+ const homeworkDetailsQuery = useHomeworkDetails(editHomeworkId !== null ? {
185
+ homeworkId: editHomeworkId
186
+ } : undefined);
187
+ const loadClasses = useCallback(async () => {
188
+ const res = await fetchTeacherClasses(api);
189
+ setClasses(normalizeOptions(res));
190
+ }, [api]);
191
+ useEffect(() => {
192
+ loadClasses().catch(() => {});
193
+ }, [loadClasses]);
194
+ useEffect(() => {
195
+ if (!filterClass?.value) {
196
+ setSections([]);
197
+ setSubjects([]);
198
+ setFilterSection(null);
199
+ setFilterSubject(null);
200
+ return;
201
+ }
202
+ fetchTeacherSections(api, {
203
+ classId: filterClass.value
204
+ }).then(res => {
205
+ setSections(normalizeOptions(res));
206
+ }).catch(() => {});
207
+ if (props.sessionId !== undefined) {
208
+ fetchTeacherSubjectsByClass(api, {
209
+ classId: filterClass.value,
210
+ sessionId: props.sessionId
211
+ }).then(res => {
212
+ setSubjects(normalizeOptions(res));
213
+ }).catch(() => {});
214
+ } else {
215
+ setSubjects([]);
216
+ setFilterSubject(null);
217
+ }
218
+ }, [api, filterClass?.value, props.sessionId]);
219
+ const headerRight = useMemo(() => {
220
+ if (mode === 'list') {
221
+ return /*#__PURE__*/_jsx(TouchableOpacity, {
222
+ onPress: () => setMode('create'),
223
+ style: styles.primaryBtn,
224
+ children: /*#__PURE__*/_jsx(Text, {
225
+ style: styles.primaryBtnText,
226
+ children: "Create"
227
+ })
228
+ });
229
+ }
230
+ return /*#__PURE__*/_jsx(TouchableOpacity, {
231
+ onPress: () => setMode('list'),
232
+ style: styles.secondaryBtn,
233
+ children: /*#__PURE__*/_jsx(Text, {
234
+ style: styles.secondaryBtnText,
235
+ children: "Back"
236
+ })
237
+ });
238
+ }, [mode]);
239
+ if (mode === 'create') {
240
+ return /*#__PURE__*/_jsx(CreateAssignmentView, {
241
+ sessionId: props.sessionId,
242
+ classes: classes,
243
+ api: api,
244
+ downloadsBaseUrl: downloadsBaseUrl,
245
+ onDone: () => {
246
+ setMode('list');
247
+ refetch();
248
+ }
249
+ });
250
+ }
251
+ if (mode === 'edit') {
252
+ if (editHomeworkId === null) {
253
+ return /*#__PURE__*/_jsx(ErrorState, {
254
+ message: "Missing homework id"
255
+ });
256
+ }
257
+ if (homeworkDetailsQuery.isLoading) {
258
+ return /*#__PURE__*/_jsx(LoadingState, {});
259
+ }
260
+ if (homeworkDetailsQuery.error) {
261
+ return /*#__PURE__*/_jsx(ErrorState, {
262
+ message: homeworkDetailsQuery.error.message
263
+ });
264
+ }
265
+ const detail = normalizeHomeworkDetail(homeworkDetailsQuery.data);
266
+ if (!detail) {
267
+ return /*#__PURE__*/_jsx(ErrorState, {
268
+ message: "Unable to load homework details"
269
+ });
270
+ }
271
+ return /*#__PURE__*/_jsx(EditAssignmentView, {
272
+ api: api,
273
+ sessionId: props.sessionId,
274
+ classes: classes,
275
+ downloadsBaseUrl: downloadsBaseUrl,
276
+ initial: detail,
277
+ onDone: () => {
278
+ setMode('list');
279
+ setEditHomeworkId(null);
280
+ refetch();
281
+ },
282
+ onCancel: () => {
283
+ setMode('list');
284
+ setEditHomeworkId(null);
285
+ }
286
+ });
287
+ }
288
+ if (isLoading) {
289
+ return /*#__PURE__*/_jsx(LoadingState, {});
290
+ }
291
+ if (error) {
292
+ return /*#__PURE__*/_jsx(ErrorState, {
293
+ message: error.message
294
+ });
295
+ }
296
+ const closeSubmissions = () => {
297
+ setSubmissionsOpen(false);
298
+ };
299
+ return /*#__PURE__*/_jsxs(SafeAreaView, {
300
+ style: styles.root,
301
+ children: [/*#__PURE__*/_jsxs(View, {
302
+ style: styles.header,
303
+ children: [/*#__PURE__*/_jsx(Text, {
304
+ style: styles.headerTitle,
305
+ children: "Assignments"
306
+ }), headerRight]
307
+ }), /*#__PURE__*/_jsxs(View, {
308
+ style: styles.filters,
309
+ children: [/*#__PURE__*/_jsx(SelectModal, {
310
+ label: "Class",
311
+ value: filterClass,
312
+ options: classes,
313
+ placeholder: "Select class",
314
+ onChange: next => setFilterClass(next)
315
+ }), /*#__PURE__*/_jsx(SelectModal, {
316
+ label: "Section",
317
+ value: filterSection,
318
+ options: sections,
319
+ placeholder: "Select section",
320
+ onChange: next => setFilterSection(next)
321
+ }), /*#__PURE__*/_jsx(SelectModal, {
322
+ label: "Subject",
323
+ value: filterSubject,
324
+ options: subjects,
325
+ placeholder: props.sessionId === undefined ? 'Provide sessionId' : 'Select subject',
326
+ onChange: next => setFilterSubject(next)
327
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
328
+ style: styles.secondaryBtn,
329
+ onPress: () => {
330
+ refetch();
331
+ },
332
+ children: /*#__PURE__*/_jsx(Text, {
333
+ style: styles.secondaryBtnText,
334
+ children: isFetching ? 'Refreshing...' : 'Apply'
335
+ })
336
+ })]
337
+ }), assignments.length === 0 ? /*#__PURE__*/_jsx(EmptyState, {}) : /*#__PURE__*/_jsx(FlatList, {
338
+ data: assignments,
339
+ keyExtractor: (it, idx) => `${it?.id ?? idx}`,
340
+ contentContainerStyle: styles.list,
341
+ renderItem: ({
342
+ item
343
+ }) => {
344
+ const total = item?.total_submission !== undefined && item?.total_submission !== null ? Number(item.total_submission) : 0;
345
+ const canView = Number.isFinite(total) && total > 0;
346
+ return /*#__PURE__*/_jsxs(View, {
347
+ style: styles.card,
348
+ children: [/*#__PURE__*/_jsxs(View, {
349
+ style: styles.cardTop,
350
+ children: [/*#__PURE__*/_jsx(Text, {
351
+ style: styles.cardTitle,
352
+ numberOfLines: 1,
353
+ children: item?.title ?? item?.homework_title ?? 'Assignment'
354
+ }), /*#__PURE__*/_jsxs(View, {
355
+ style: styles.cardActions,
356
+ children: [/*#__PURE__*/_jsx(TouchableOpacity, {
357
+ onPress: () => {
358
+ if (item.id === undefined || item.id === null) return;
359
+ setEditHomeworkId(item.id);
360
+ setMode('edit');
361
+ },
362
+ style: styles.editBtn,
363
+ children: /*#__PURE__*/_jsx(Text, {
364
+ style: styles.editBtnText,
365
+ children: "Edit"
366
+ })
367
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
368
+ onPress: () => {
369
+ if (!canView) return;
370
+ setSelectedHomework(item);
371
+ setSubmissionsOpen(true);
372
+ },
373
+ activeOpacity: canView ? 0.8 : 1,
374
+ style: [styles.viewBtn, canView ? styles.viewBtnActive : styles.viewBtnDisabled],
375
+ children: /*#__PURE__*/_jsxs(Text, {
376
+ style: [styles.viewBtnText, canView ? styles.viewBtnTextActive : styles.viewBtnTextDisabled],
377
+ children: ["View ", Number.isFinite(total) ? String(total) : '0']
378
+ })
379
+ })]
380
+ })]
381
+ }), item?.description ? /*#__PURE__*/_jsx(Text, {
382
+ style: styles.cardSub,
383
+ children: item.description
384
+ }) : null, /*#__PURE__*/_jsx(Text, {
385
+ style: styles.cardMeta,
386
+ children: [item?.class_name ? `Class: ${item.class_name}` : null, item?.section_name ? `Section: ${item.section_name}` : null, item?.subject_name ? `Subject: ${item.subject_name}` : null].filter(Boolean).join(' • ')
387
+ }), /*#__PURE__*/_jsx(Text, {
388
+ style: styles.cardMeta,
389
+ children: [item?.homew_date ? `From: ${item.homew_date}` : null, item?.submission_date ? `To: ${item.submission_date}` : null, item?.status ? `Status: ${item.status}` : null].filter(Boolean).join(' • ')
390
+ })]
391
+ });
392
+ }
393
+ }), /*#__PURE__*/_jsx(Modal, {
394
+ animationType: "fade",
395
+ transparent: true,
396
+ visible: submissionsOpen,
397
+ onRequestClose: closeSubmissions,
398
+ children: /*#__PURE__*/_jsx(Pressable, {
399
+ style: styles.modalOverlay,
400
+ onPress: closeSubmissions,
401
+ children: /*#__PURE__*/_jsxs(Pressable, {
402
+ style: styles.sheetCard,
403
+ onPress: () => {},
404
+ children: [/*#__PURE__*/_jsxs(View, {
405
+ style: styles.modalHeader,
406
+ children: [/*#__PURE__*/_jsx(Text, {
407
+ style: styles.modalHeaderTitle,
408
+ numberOfLines: 1,
409
+ children: "Submissions"
410
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
411
+ onPress: closeSubmissions,
412
+ style: styles.modalClose,
413
+ children: /*#__PURE__*/_jsx(Text, {
414
+ style: styles.modalCloseText,
415
+ children: "Close"
416
+ })
417
+ })]
418
+ }), submissionsQuery.isLoading ? /*#__PURE__*/_jsx(LoadingState, {}) : submissionsQuery.error ? /*#__PURE__*/_jsx(ErrorState, {
419
+ message: submissionsQuery.error.message
420
+ }) : /*#__PURE__*/_jsx(HomeworkSubmissionsList, {
421
+ data: submissionsQuery.data,
422
+ api: api,
423
+ downloadsBaseUrl: downloadsBaseUrl
424
+ })]
425
+ })
426
+ })
427
+ })]
428
+ });
429
+ }
430
+ function normalizeSubmissions(result) {
431
+ if (!result) return [];
432
+ const raw = result?.data ?? result;
433
+ if (Array.isArray(raw)) return raw;
434
+ if (Array.isArray(raw?.data)) return raw.data;
435
+ if (Array.isArray(raw?.data?.data)) return raw.data.data;
436
+ return [];
437
+ }
438
+ function isImageUrl(url) {
439
+ const lower = url.toLowerCase();
440
+ return lower.endsWith('.png') || lower.endsWith('.jpg') || lower.endsWith('.jpeg') || lower.endsWith('.webp') || lower.endsWith('.gif');
441
+ }
442
+ function fileBaseName(path) {
443
+ const raw = String(path ?? '');
444
+ const cleaned = raw.split('?')[0] ?? raw;
445
+ const parts = cleaned.split(/[\\/]/);
446
+ return parts[parts.length - 1] || raw;
447
+ }
448
+ function splitFileCsv(raw) {
449
+ if (!raw) return [];
450
+ return String(raw).split(',').map(s => s.trim()).filter(Boolean);
451
+ }
452
+ function ensureFileUri(path) {
453
+ if (!path) {
454
+ return '';
455
+ }
456
+ return path.startsWith('file://') ? path : `file://${path}`;
457
+ }
458
+ function getUploadPath(value) {
459
+ const candidates = [value?.file, value?.document, value?.image, value?.path, value?.file_path, value?.filePath, value?.document_path, value?.documentPath, value?.image_path, value?.imagePath, value?.url, value?.file_url, value?.fileUrl];
460
+ for (const candidate of candidates) {
461
+ if (typeof candidate === 'string' && candidate.trim()) {
462
+ return candidate.trim();
463
+ }
464
+ }
465
+ return '';
466
+ }
467
+ function resolveUploadUrl(downloadsBaseUrl, rawPath) {
468
+ const raw = String(rawPath ?? '').trim();
469
+ if (!raw) {
470
+ return '';
471
+ }
472
+ if (/^https?:\/\//i.test(raw)) {
473
+ return encodeURI(raw);
474
+ }
475
+ const cleaned = raw.replace(/\\/g, '/').replace(/^\/+/, '');
476
+ const uploadsRoot = downloadsBaseUrl.replace(/uploads\/?$/i, '');
477
+ if (cleaned.startsWith('uploads/')) {
478
+ return encodeURI(`${uploadsRoot}${cleaned}`);
479
+ }
480
+ if (cleaned.startsWith('student/homework/')) {
481
+ return encodeURI(`${downloadsBaseUrl}${cleaned}`);
482
+ }
483
+ if (cleaned.startsWith('homework/')) {
484
+ return encodeURI(`${downloadsBaseUrl}student/${cleaned}`);
485
+ }
486
+ return encodeURI(`${downloadsBaseUrl}student/homework/${cleaned}`);
487
+ }
488
+ async function downloadPdfToLocal(args) {
489
+ const fallbackName = args.fileName || `document-${Date.now()}.pdf`;
490
+ const safeName = fallbackName.toLowerCase().endsWith('.pdf') ? fallbackName : `${fallbackName}.pdf`;
491
+ const targetPath = `${args.RNFS.CachesDirectoryPath}/${safeName}`;
492
+ const result = await args.RNFS.downloadFile({
493
+ fromUrl: args.url,
494
+ toFile: targetPath,
495
+ headers: args.headers,
496
+ background: true,
497
+ discretionary: true
498
+ }).promise;
499
+ if (result?.statusCode && result.statusCode >= 400) {
500
+ throw new Error(`PDF download failed with status ${result.statusCode}`);
501
+ }
502
+ const exists = await args.RNFS.exists(targetPath);
503
+ if (!exists) {
504
+ throw new Error('Downloaded PDF file is missing');
505
+ }
506
+ return ensureFileUri(targetPath);
507
+ }
508
+ async function resolveViewerHeaders(args) {
509
+ const token = args.authToken ?? (args.getAuthToken ? await args.getAuthToken() : null);
510
+ const headers = {};
511
+ if (token) {
512
+ headers.Authorization = token;
513
+ }
514
+ if (args.schoolCode) {
515
+ headers.school_code = args.schoolCode;
516
+ }
517
+ return headers;
518
+ }
519
+ function tryGetImageViewer() {
520
+ try {
521
+ const mod = require('react-native-image-viewing');
522
+ return mod?.default ?? mod;
523
+ } catch {
524
+ return null;
525
+ }
526
+ }
527
+ function tryGetCalendar() {
528
+ try {
529
+ const mod = require('react-native-calendars');
530
+ return mod?.Calendar ?? mod?.default ?? mod;
531
+ } catch {
532
+ return null;
533
+ }
534
+ }
535
+ function tryGetRNFS() {
536
+ try {
537
+ return require('react-native-fs');
538
+ } catch {
539
+ return null;
540
+ }
541
+ }
542
+ function tryGetFileViewer() {
543
+ try {
544
+ const mod = require('react-native-file-viewer');
545
+ return mod?.default ?? mod;
546
+ } catch {
547
+ return null;
548
+ }
549
+ }
550
+ function tryGetPdf() {
551
+ try {
552
+ const mod = require('react-native-pdf');
553
+ return mod?.default ?? mod;
554
+ } catch {
555
+ return null;
556
+ }
557
+ }
558
+ function toYmd(d) {
559
+ const y = d.getFullYear();
560
+ const m = String(d.getMonth() + 1).padStart(2, '0');
561
+ const day = String(d.getDate()).padStart(2, '0');
562
+ return `${y}-${m}-${day}`;
563
+ }
564
+ function addDays(d, days) {
565
+ const next = new Date(d);
566
+ next.setDate(next.getDate() + days);
567
+ return next;
568
+ }
569
+ function rangeMarkedDates(start, end) {
570
+ if (!start || !end) return {};
571
+ const s = new Date(start);
572
+ const e = new Date(end);
573
+ if (Number.isNaN(s.getTime()) || Number.isNaN(e.getTime())) return {};
574
+ const from = s <= e ? s : e;
575
+ const to = s <= e ? e : s;
576
+ const out = {};
577
+ let cur = new Date(from);
578
+ while (cur <= to) {
579
+ const key = toYmd(cur);
580
+ out[key] = {
581
+ color: '#111827',
582
+ textColor: 'white'
583
+ };
584
+ cur = addDays(cur, 1);
585
+ }
586
+ const startKey = toYmd(from);
587
+ const endKey = toYmd(to);
588
+ out[startKey] = {
589
+ startingDay: true,
590
+ color: '#111827',
591
+ textColor: 'white'
592
+ };
593
+ out[endKey] = {
594
+ endingDay: true,
595
+ color: '#111827',
596
+ textColor: 'white'
597
+ };
598
+ return out;
599
+ }
600
+ function HomeworkSubmissionsList({
601
+ data,
602
+ api,
603
+ downloadsBaseUrl
604
+ }) {
605
+ const {
606
+ authToken,
607
+ getAuthToken,
608
+ schoolCode
609
+ } = useERP();
610
+ const items = useMemo(() => normalizeSubmissions(data), [data]);
611
+ const ImageView = useMemo(() => tryGetImageViewer(), []);
612
+ const Pdf = useMemo(() => tryGetPdf(), []);
613
+ const RNFS = useMemo(() => tryGetRNFS(), []);
614
+ const FileViewer = useMemo(() => tryGetFileViewer(), []);
615
+ const [viewerOpen, setViewerOpen] = useState(false);
616
+ const [viewerIndex, setViewerIndex] = useState(0);
617
+ const [viewerImages, setViewerImages] = useState([]);
618
+ const [pdfVisible, setPdfVisible] = useState(false);
619
+ const [pdfUri, setPdfUri] = useState('');
620
+ const [pdfHeaders, setPdfHeaders] = useState({});
621
+ const [pdfLoading, setPdfLoading] = useState(false);
622
+ const [drafts, setDrafts] = useState({});
623
+ const [saving, setSaving] = useState(false);
624
+ const [downloadingId, setDownloadingId] = useState(null);
625
+ if (!items.length) {
626
+ return /*#__PURE__*/_jsx(EmptyState, {
627
+ title: "No Submission"
628
+ });
629
+ }
630
+ const openUploads = async item => {
631
+ const list = Array.isArray(item?.submitted_homework) ? item.submitted_homework : [];
632
+ const uploads = list.map(el => {
633
+ const rawPath = getUploadPath(el);
634
+ return {
635
+ rawPath,
636
+ url: resolveUploadUrl(downloadsBaseUrl, rawPath)
637
+ };
638
+ }).filter(entry => entry.rawPath && entry.url);
639
+ const urls = uploads.map(entry => entry.url);
640
+ if (!urls.length) {
641
+ Alert.alert('No Uploads');
642
+ return;
643
+ }
644
+ const headers = await resolveViewerHeaders({
645
+ authToken,
646
+ getAuthToken,
647
+ schoolCode
648
+ });
649
+ const imageUrls = uploads.filter(entry => {
650
+ return isImageUrl(entry.url) || isImageUrl(entry.rawPath);
651
+ }).map(entry => entry.url);
652
+ if (ImageView && imageUrls.length) {
653
+ setViewerImages(imageUrls.map(uri => ({
654
+ uri,
655
+ headers: Object.keys(headers).length ? headers : undefined
656
+ })));
657
+ setViewerIndex(0);
658
+ setViewerOpen(true);
659
+ return;
660
+ }
661
+ const firstUpload = uploads[0];
662
+ const firstUrl = firstUpload?.url ?? '';
663
+ const firstFile = firstUpload?.rawPath ?? '';
664
+ const isPdf = firstUrl.toLowerCase().endsWith('.pdf') || firstFile.toLowerCase().endsWith('.pdf');
665
+ if (Pdf && isPdf) {
666
+ if (!RNFS) {
667
+ setPdfUri(firstUrl);
668
+ setPdfHeaders(headers);
669
+ setPdfVisible(true);
670
+ return;
671
+ }
672
+ setDownloadingId(String(item?.id ?? 'download'));
673
+ setPdfLoading(true);
674
+ try {
675
+ const localPdf = await downloadPdfToLocal({
676
+ RNFS,
677
+ url: firstUrl,
678
+ fileName: fileBaseName(firstFile || 'document.pdf'),
679
+ headers
680
+ });
681
+ setPdfHeaders({});
682
+ setPdfUri(localPdf);
683
+ setPdfVisible(true);
684
+ } catch (error) {
685
+ Alert.alert('Error', error?.message ?? 'Could not open PDF.');
686
+ } finally {
687
+ setPdfLoading(false);
688
+ setDownloadingId(null);
689
+ }
690
+ return;
691
+ }
692
+ if (!RNFS || !FileViewer) {
693
+ Alert.alert('Viewer Missing', 'Install react-native-fs and react-native-file-viewer to open documents inside the app.');
694
+ return;
695
+ }
696
+ setDownloadingId(String(item?.id ?? 'download'));
697
+ try {
698
+ let fileName = fileBaseName(firstFile || 'document');
699
+ const hasExtension = fileName.includes('.') && (fileName.split('.').pop()?.length ?? 0) > 0;
700
+ if (!hasExtension && firstUrl.startsWith('http')) {
701
+ try {
702
+ const response = await fetch(firstUrl, {
703
+ method: 'HEAD'
704
+ });
705
+ const contentType = response.headers.get('content-type') ?? '';
706
+ if (contentType.includes('pdf')) fileName += '.pdf';else if (contentType.includes('word') || contentType.includes('doc')) fileName += '.docx';else if (contentType.includes('sheet') || contentType.includes('excel')) fileName += '.xlsx';else fileName += '.pdf';
707
+ } catch {
708
+ fileName += '.pdf';
709
+ }
710
+ }
711
+ const localFile = `${RNFS.DocumentDirectoryPath}/${fileName}`;
712
+ await RNFS.downloadFile({
713
+ fromUrl: firstUrl,
714
+ toFile: localFile,
715
+ headers
716
+ }).promise;
717
+ await FileViewer.open(localFile, {
718
+ showOpenWithDialog: true
719
+ });
720
+ } catch {
721
+ Alert.alert('Error', 'Could not view the file.');
722
+ } finally {
723
+ setDownloadingId(null);
724
+ }
725
+ };
726
+ const setDraft = (id, patch) => {
727
+ setDrafts(prev => {
728
+ const key = String(id);
729
+ const next = {
730
+ ...(prev[key] ?? {}),
731
+ ...patch
732
+ };
733
+ return {
734
+ ...prev,
735
+ [key]: next
736
+ };
737
+ });
738
+ };
739
+ const giveMarks = async () => {
740
+ const payload = Object.entries(drafts).map(([id, v]) => {
741
+ return {
742
+ id,
743
+ number: v.number,
744
+ remark: v.remark
745
+ };
746
+ }).filter(it => it.number !== undefined || it.remark !== undefined);
747
+ if (!payload.length) {
748
+ Alert.alert('Error', 'Please fill any field');
749
+ return;
750
+ }
751
+ setSaving(true);
752
+ try {
753
+ const res = await giveMarksToHomework(api, payload);
754
+ Alert.alert(res?.Status ?? 'Success');
755
+ } catch (e) {
756
+ Alert.alert('Error', e?.message ?? 'Failed');
757
+ } finally {
758
+ setSaving(false);
759
+ }
760
+ };
761
+ return /*#__PURE__*/_jsxs(View, {
762
+ style: {
763
+ flex: 1
764
+ },
765
+ children: [/*#__PURE__*/_jsx(FlatList, {
766
+ data: items,
767
+ keyExtractor: (it, idx) => String(it?.id ?? idx),
768
+ contentContainerStyle: styles.submissionList,
769
+ renderItem: ({
770
+ item
771
+ }) => {
772
+ const roll = item?.roll_no ?? item?.roll ?? '-';
773
+ const studentName = item?.full_name ?? item?.student_name ?? 'Student';
774
+ const submittedAt = item?.submission_date ?? item?.date ?? '-';
775
+ const marksValue = drafts[String(item?.id)]?.number ?? item?.number ?? '';
776
+ const remarkValue = drafts[String(item?.id)]?.remark ?? item?.remark ?? '';
777
+ return /*#__PURE__*/_jsxs(View, {
778
+ style: styles.submissionCard,
779
+ children: [/*#__PURE__*/_jsxs(View, {
780
+ style: styles.submissionHeaderRow,
781
+ children: [/*#__PURE__*/_jsx(Text, {
782
+ style: styles.submissionName,
783
+ numberOfLines: 1,
784
+ children: studentName
785
+ }), /*#__PURE__*/_jsxs(Text, {
786
+ style: styles.submissionMeta,
787
+ numberOfLines: 1,
788
+ children: ["Roll: ", String(roll)]
789
+ })]
790
+ }), /*#__PURE__*/_jsx(Text, {
791
+ style: styles.submissionMeta,
792
+ numberOfLines: 1,
793
+ children: submittedAt
794
+ }), /*#__PURE__*/_jsxs(View, {
795
+ style: styles.submissionInputRow,
796
+ children: [/*#__PURE__*/_jsxs(View, {
797
+ style: {
798
+ flex: 1
799
+ },
800
+ children: [/*#__PURE__*/_jsx(Text, {
801
+ style: styles.label,
802
+ children: "Marks"
803
+ }), /*#__PURE__*/_jsx(TextInput, {
804
+ keyboardType: "numeric",
805
+ value: String(marksValue),
806
+ onChangeText: val => setDraft(item.id, {
807
+ number: val
808
+ }),
809
+ style: styles.smallInput,
810
+ placeholderTextColor: "#9CA3AF"
811
+ })]
812
+ }), /*#__PURE__*/_jsxs(View, {
813
+ style: {
814
+ flex: 2
815
+ },
816
+ children: [/*#__PURE__*/_jsx(Text, {
817
+ style: styles.label,
818
+ children: "Remark"
819
+ }), /*#__PURE__*/_jsx(TextInput, {
820
+ value: String(remarkValue),
821
+ onChangeText: val => setDraft(item.id, {
822
+ remark: val
823
+ }),
824
+ style: styles.smallInput,
825
+ placeholderTextColor: "#9CA3AF"
826
+ })]
827
+ })]
828
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
829
+ onPress: () => {
830
+ openUploads(item).catch(() => {});
831
+ },
832
+ style: styles.uploadBtn,
833
+ children: /*#__PURE__*/_jsx(Text, {
834
+ style: styles.uploadBtnText,
835
+ children: downloadingId === String(item?.id ?? '') ? 'Opening...' : 'View Uploads'
836
+ })
837
+ })]
838
+ });
839
+ }
840
+ }), /*#__PURE__*/_jsx(View, {
841
+ style: styles.submissionFooter,
842
+ children: /*#__PURE__*/_jsx(TouchableOpacity, {
843
+ onPress: () => {
844
+ giveMarks().catch(() => {});
845
+ },
846
+ style: styles.giveMarksBtn,
847
+ disabled: saving,
848
+ children: /*#__PURE__*/_jsx(Text, {
849
+ style: styles.giveMarksBtnText,
850
+ children: saving ? 'Saving...' : 'Give Marks'
851
+ })
852
+ })
853
+ }), ImageView ? /*#__PURE__*/_jsx(ImageView, {
854
+ images: viewerImages,
855
+ imageIndex: viewerIndex,
856
+ visible: viewerOpen,
857
+ onRequestClose: () => setViewerOpen(false)
858
+ }) : null, /*#__PURE__*/_jsx(Modal, {
859
+ visible: pdfVisible,
860
+ animationType: "slide",
861
+ children: /*#__PURE__*/_jsxs(SafeAreaView, {
862
+ style: styles.pdfRoot,
863
+ children: [/*#__PURE__*/_jsxs(View, {
864
+ style: styles.pdfHeader,
865
+ children: [/*#__PURE__*/_jsx(Text, {
866
+ style: styles.pdfTitle,
867
+ children: "Document Viewer"
868
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
869
+ onPress: () => {
870
+ setPdfVisible(false);
871
+ setPdfUri('');
872
+ setPdfHeaders({});
873
+ },
874
+ style: styles.modalClose,
875
+ children: /*#__PURE__*/_jsx(Text, {
876
+ style: styles.modalCloseText,
877
+ children: "Close"
878
+ })
879
+ })]
880
+ }), pdfLoading ? /*#__PURE__*/_jsx(LoadingState, {}) : Pdf && pdfUri ? /*#__PURE__*/_jsx(Pdf, {
881
+ source: {
882
+ uri: pdfUri,
883
+ cache: true,
884
+ headers: pdfHeaders
885
+ },
886
+ style: styles.pdfViewer,
887
+ onError: error => {
888
+ Alert.alert('Error', error?.message ?? 'Could not open PDF.');
889
+ }
890
+ }) : /*#__PURE__*/_jsx(EmptyState, {
891
+ title: "PDF Viewer Missing"
892
+ })]
893
+ })
894
+ })]
895
+ });
896
+ }
897
+ function CreateAssignmentView({
898
+ api,
899
+ sessionId,
900
+ classes,
901
+ downloadsBaseUrl,
902
+ onDone
903
+ }) {
904
+ const {
905
+ authToken,
906
+ getAuthToken,
907
+ schoolCode
908
+ } = useERP();
909
+ const ImageView = useMemo(() => tryGetImageViewer(), []);
910
+ const Pdf = useMemo(() => tryGetPdf(), []);
911
+ const RNFS = useMemo(() => tryGetRNFS(), []);
912
+ const FileViewer = useMemo(() => tryGetFileViewer(), []);
913
+ const [viewerOpen, setViewerOpen] = useState(false);
914
+ const [viewerIndex, setViewerIndex] = useState(0);
915
+ const [viewerImages, setViewerImages] = useState([]);
916
+ const [pdfVisible, setPdfVisible] = useState(false);
917
+ const [pdfUri, setPdfUri] = useState('');
918
+ const [pdfHeaders, setPdfHeaders] = useState({});
919
+ const [pdfLoading, setPdfLoading] = useState(false);
920
+ const [isDownloading, setIsDownloading] = useState(false);
921
+ const [busy, setBusy] = useState(false);
922
+ const [title, setTitle] = useState('');
923
+ const [description, setDescription] = useState('');
924
+ const [classOpt, setClassOpt] = useState(null);
925
+ const [sectionOpt, setSectionOpt] = useState(null);
926
+ const [subjectOpt, setSubjectOpt] = useState(null);
927
+ const [sections, setSections] = useState([]);
928
+ const [subjects, setSubjects] = useState([]);
929
+ const [homewDate, setHomewDate] = useState('');
930
+ const [submissionDate, setSubmissionDate] = useState('');
931
+ const [status, setStatus] = useState('publish');
932
+ const [fileList, setFileList] = useState([]);
933
+ const [activeFile, setActiveFile] = useState('');
934
+ const [fileError, setFileError] = useState(null);
935
+ useEffect(() => {
936
+ if (!classOpt?.value) {
937
+ setSections([]);
938
+ setSubjects([]);
939
+ setSectionOpt(null);
940
+ setSubjectOpt(null);
941
+ return;
942
+ }
943
+ fetchTeacherSections(api, {
944
+ classId: classOpt.value
945
+ }).then(res => {
946
+ setSections(normalizeOptions(res));
947
+ }).catch(() => {});
948
+ if (sessionId !== undefined) {
949
+ fetchTeacherSubjectsByClass(api, {
950
+ classId: classOpt.value,
951
+ sessionId
952
+ }).then(res => {
953
+ setSubjects(normalizeOptions(res));
954
+ }).catch(() => {});
955
+ } else {
956
+ setSubjects([]);
957
+ setSubjectOpt(null);
958
+ }
959
+ }, [api, classOpt?.value, sessionId]);
960
+ const viewAttachment = useCallback(async rawFile => {
961
+ const file = String(rawFile ?? '');
962
+ if (!file) {
963
+ Alert.alert('No Attachment');
964
+ return;
965
+ }
966
+ const url = resolveUploadUrl(downloadsBaseUrl, file);
967
+ const headers = await resolveViewerHeaders({
968
+ authToken,
969
+ getAuthToken,
970
+ schoolCode
971
+ });
972
+ const isImage = isImageUrl(url) || isImageUrl(file);
973
+ if (ImageView && isImage) {
974
+ const imageFiles = fileList.filter(p => {
975
+ const u = resolveUploadUrl(downloadsBaseUrl, p);
976
+ return isImageUrl(u) || isImageUrl(p);
977
+ });
978
+ const imageUrls = imageFiles.map(p => resolveUploadUrl(downloadsBaseUrl, p));
979
+ const idx = imageFiles.findIndex(p => String(p) === String(file));
980
+ setViewerImages(imageUrls.map(uri => ({
981
+ uri,
982
+ headers: Object.keys(headers).length ? headers : undefined
983
+ })));
984
+ setViewerIndex(Math.max(0, idx));
985
+ setViewerOpen(true);
986
+ return;
987
+ }
988
+ const isPdf = url.toLowerCase().endsWith('.pdf') || String(file).toLowerCase().endsWith('.pdf');
989
+ if (Pdf && isPdf) {
990
+ if (!RNFS) {
991
+ setPdfUri(url);
992
+ setPdfHeaders(headers);
993
+ setPdfVisible(true);
994
+ return;
995
+ }
996
+ setIsDownloading(true);
997
+ setPdfLoading(true);
998
+ try {
999
+ const localPdf = await downloadPdfToLocal({
1000
+ RNFS,
1001
+ url,
1002
+ fileName: fileBaseName(file || 'document.pdf'),
1003
+ headers
1004
+ });
1005
+ setPdfHeaders({});
1006
+ setPdfUri(localPdf);
1007
+ setPdfVisible(true);
1008
+ } catch (error) {
1009
+ Alert.alert('Error', error?.message ?? 'Could not open PDF.');
1010
+ } finally {
1011
+ setPdfLoading(false);
1012
+ setIsDownloading(false);
1013
+ }
1014
+ return;
1015
+ }
1016
+ if (!RNFS || !FileViewer) {
1017
+ Alert.alert('Viewer Missing', 'Install react-native-fs and react-native-file-viewer to open documents inside the app.');
1018
+ return;
1019
+ }
1020
+ setIsDownloading(true);
1021
+ try {
1022
+ let name = fileBaseName(file) || 'document';
1023
+ const hasExtension = name.includes('.') && (name.split('.').pop()?.length ?? 0) > 0;
1024
+ if (!hasExtension && url.startsWith('http')) {
1025
+ try {
1026
+ const response = await fetch(url, {
1027
+ method: 'HEAD'
1028
+ });
1029
+ const contentType = response.headers.get('content-type') ?? '';
1030
+ if (contentType.includes('pdf')) name += '.pdf';else if (contentType.includes('word') || contentType.includes('doc')) name += '.docx';else if (contentType.includes('sheet') || contentType.includes('excel')) name += '.xlsx';else if (contentType.includes('jpeg') || contentType.includes('jpg')) name += '.jpg';else if (contentType.includes('png')) name += '.png';else name += '.pdf';
1031
+ } catch {
1032
+ name += '.pdf';
1033
+ }
1034
+ }
1035
+ const localFile = `${RNFS.DocumentDirectoryPath}/${name}`;
1036
+ await RNFS.downloadFile({
1037
+ fromUrl: url,
1038
+ toFile: localFile,
1039
+ headers
1040
+ }).promise;
1041
+ await FileViewer.open(localFile, {
1042
+ showOpenWithDialog: true
1043
+ });
1044
+ } catch {
1045
+ Alert.alert('Error', 'Could not view the file.');
1046
+ } finally {
1047
+ setIsDownloading(false);
1048
+ }
1049
+ }, [FileViewer, ImageView, Pdf, RNFS, authToken, downloadsBaseUrl, fileList, getAuthToken, schoolCode]);
1050
+ const pickFromCamera = useCallback(async () => {
1051
+ let picker = null;
1052
+ try {
1053
+ picker = require('react-native-image-picker');
1054
+ } catch {}
1055
+ if (!picker?.launchCamera) {
1056
+ Alert.alert('Error', 'Camera picker is not available');
1057
+ return;
1058
+ }
1059
+ const res = await new Promise(resolve => {
1060
+ picker.launchCamera({
1061
+ mediaType: 'photo',
1062
+ saveToPhotos: true,
1063
+ includeBase64: false
1064
+ }, r => resolve(r));
1065
+ });
1066
+ const asset = res?.assets?.[0];
1067
+ if (!asset?.uri) return;
1068
+ const file = {
1069
+ uri: asset.uri,
1070
+ name: asset.fileName ?? 'camera.jpg',
1071
+ type: asset.type ?? 'image/jpeg'
1072
+ };
1073
+ setFileError(null);
1074
+ setBusy(true);
1075
+ try {
1076
+ const uploaded = await uploadAssignmentFile(api, file);
1077
+ if (uploaded?.Status === 'Success' && uploaded.data) {
1078
+ const nextPath = String(uploaded.data);
1079
+ setFileList(prev => [...prev, nextPath]);
1080
+ setActiveFile(prev => prev || nextPath);
1081
+ } else {
1082
+ setFileError('File upload failed');
1083
+ }
1084
+ } finally {
1085
+ setBusy(false);
1086
+ }
1087
+ }, [api]);
1088
+ const pickFiles = useCallback(async () => {
1089
+ let dp = null;
1090
+ try {
1091
+ dp = require('react-native-document-picker');
1092
+ } catch {}
1093
+ if (!dp) {
1094
+ return;
1095
+ }
1096
+ const selected = await dp.pick({
1097
+ presentationStyle: 'fullScreen',
1098
+ allowMultiSelection: true,
1099
+ type: [dp.types.pdf, dp.types.images]
1100
+ });
1101
+ const picks = Array.isArray(selected) ? selected : [selected];
1102
+ const allowed = ['pdf', 'png', 'jpg', 'jpeg', 'webp'];
1103
+ const files = picks.map(it => ({
1104
+ uri: it.uri,
1105
+ name: it.name ?? 'file',
1106
+ type: it.type ?? 'application/octet-stream'
1107
+ })).filter(it => !!it.uri);
1108
+ const invalid = files.find(it => {
1109
+ const ext = String(it.name).split('.').pop()?.toLowerCase();
1110
+ return ext ? !allowed.includes(ext) : false;
1111
+ });
1112
+ if (invalid) {
1113
+ setFileError('Only PDF or image files are allowed');
1114
+ return;
1115
+ }
1116
+ setFileError(null);
1117
+ setBusy(true);
1118
+ try {
1119
+ const uploaded = [];
1120
+ for (const f of files) {
1121
+ const res = await uploadAssignmentFile(api, f);
1122
+ if (res?.Status === 'Success' && res.data) {
1123
+ uploaded.push(String(res.data));
1124
+ }
1125
+ }
1126
+ if (!uploaded.length) {
1127
+ setFileError('File upload failed');
1128
+ return;
1129
+ }
1130
+ setFileList(prev => [...prev, ...uploaded]);
1131
+ setActiveFile(prev => prev ? prev : uploaded[0] ?? '');
1132
+ } finally {
1133
+ setBusy(false);
1134
+ }
1135
+ }, [api]);
1136
+ const submit = useCallback(async () => {
1137
+ if (!title || !description || !classOpt?.value || !sectionOpt?.value || !subjectOpt?.value || !homewDate || !submissionDate || !fileList.length) {
1138
+ if (!fileList.length) {
1139
+ setFileError('File is required');
1140
+ }
1141
+ return;
1142
+ }
1143
+ const fileCsv = fileList.join(',');
1144
+ const payload = {
1145
+ title,
1146
+ description,
1147
+ class_id: classOpt.value,
1148
+ section_id: sectionOpt.value,
1149
+ sub_id: subjectOpt.value,
1150
+ homew_date: homewDate,
1151
+ submission_date: submissionDate,
1152
+ status,
1153
+ file: fileCsv
1154
+ };
1155
+ setBusy(true);
1156
+ try {
1157
+ await createAssignment(api, payload);
1158
+ onDone();
1159
+ } finally {
1160
+ setBusy(false);
1161
+ }
1162
+ }, [api, classOpt?.value, description, homewDate, onDone, sectionOpt?.value, status, subjectOpt?.value, submissionDate, title, fileList]);
1163
+ const canPickSubject = sessionId !== undefined;
1164
+ const canSubmit = canPickSubject && fileList.length > 0 && !busy;
1165
+ return /*#__PURE__*/_jsxs(SafeAreaView, {
1166
+ style: styles.root,
1167
+ children: [/*#__PURE__*/_jsx(View, {
1168
+ style: styles.header,
1169
+ children: /*#__PURE__*/_jsx(Text, {
1170
+ style: styles.headerTitle,
1171
+ children: "Create Assignment"
1172
+ })
1173
+ }), /*#__PURE__*/_jsxs(ScrollView, {
1174
+ contentContainerStyle: styles.form,
1175
+ children: [/*#__PURE__*/_jsxs(View, {
1176
+ style: styles.field,
1177
+ children: [/*#__PURE__*/_jsx(Text, {
1178
+ style: styles.label,
1179
+ children: "Title"
1180
+ }), /*#__PURE__*/_jsx(TextInput, {
1181
+ value: title,
1182
+ onChangeText: setTitle,
1183
+ placeholder: "Enter title",
1184
+ placeholderTextColor: "#9CA3AF",
1185
+ style: styles.input
1186
+ })]
1187
+ }), /*#__PURE__*/_jsxs(View, {
1188
+ style: styles.field,
1189
+ children: [/*#__PURE__*/_jsx(Text, {
1190
+ style: styles.label,
1191
+ children: "Description"
1192
+ }), /*#__PURE__*/_jsx(TextInput, {
1193
+ value: description,
1194
+ onChangeText: setDescription,
1195
+ placeholder: "Enter description",
1196
+ placeholderTextColor: "#9CA3AF",
1197
+ style: [styles.input, styles.textarea],
1198
+ multiline: true
1199
+ })]
1200
+ }), /*#__PURE__*/_jsx(SelectModal, {
1201
+ label: "Class",
1202
+ value: classOpt,
1203
+ options: classes,
1204
+ placeholder: "Select class",
1205
+ onChange: setClassOpt
1206
+ }), /*#__PURE__*/_jsx(SelectModal, {
1207
+ label: "Section",
1208
+ value: sectionOpt,
1209
+ options: sections,
1210
+ placeholder: "Select section",
1211
+ onChange: setSectionOpt
1212
+ }), /*#__PURE__*/_jsx(SelectModal, {
1213
+ label: "Subject",
1214
+ value: subjectOpt,
1215
+ options: subjects,
1216
+ placeholder: canPickSubject ? 'Select subject' : 'Provide sessionId',
1217
+ onChange: setSubjectOpt
1218
+ }), /*#__PURE__*/_jsxs(View, {
1219
+ style: styles.field,
1220
+ children: [/*#__PURE__*/_jsx(Text, {
1221
+ style: styles.label,
1222
+ children: "Homework Date (YYYY-MM-DD)"
1223
+ }), /*#__PURE__*/_jsx(TextInput, {
1224
+ value: homewDate,
1225
+ onChangeText: setHomewDate,
1226
+ placeholder: "2026-01-31",
1227
+ placeholderTextColor: "#9CA3AF",
1228
+ style: styles.input
1229
+ })]
1230
+ }), /*#__PURE__*/_jsxs(View, {
1231
+ style: styles.field,
1232
+ children: [/*#__PURE__*/_jsx(Text, {
1233
+ style: styles.label,
1234
+ children: "Submission Date (YYYY-MM-DD)"
1235
+ }), /*#__PURE__*/_jsx(TextInput, {
1236
+ value: submissionDate,
1237
+ onChangeText: setSubmissionDate,
1238
+ placeholder: "2026-02-05",
1239
+ placeholderTextColor: "#9CA3AF",
1240
+ style: styles.input
1241
+ })]
1242
+ }), /*#__PURE__*/_jsxs(View, {
1243
+ style: styles.field,
1244
+ children: [/*#__PURE__*/_jsx(Text, {
1245
+ style: styles.label,
1246
+ children: "Status"
1247
+ }), /*#__PURE__*/_jsxs(View, {
1248
+ style: styles.row,
1249
+ children: [/*#__PURE__*/_jsx(TouchableOpacity, {
1250
+ style: [styles.chip, status === 'publish' ? styles.chipActive : null],
1251
+ onPress: () => setStatus('publish'),
1252
+ children: /*#__PURE__*/_jsx(Text, {
1253
+ style: [styles.chipText, status === 'publish' ? styles.chipTextActive : null],
1254
+ children: "Publish"
1255
+ })
1256
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
1257
+ style: [styles.chip, status === 'unpublish' ? styles.chipActive : null],
1258
+ onPress: () => setStatus('unpublish'),
1259
+ children: /*#__PURE__*/_jsx(Text, {
1260
+ style: [styles.chipText, status === 'unpublish' ? styles.chipTextActive : null],
1261
+ children: "Unpublish"
1262
+ })
1263
+ })]
1264
+ })]
1265
+ }), /*#__PURE__*/_jsxs(View, {
1266
+ style: styles.field,
1267
+ children: [/*#__PURE__*/_jsx(Text, {
1268
+ style: styles.label,
1269
+ children: "Attachment"
1270
+ }), /*#__PURE__*/_jsxs(View, {
1271
+ style: styles.row,
1272
+ children: [/*#__PURE__*/_jsx(TouchableOpacity, {
1273
+ style: styles.secondaryBtn,
1274
+ onPress: () => {
1275
+ pickFiles().catch(() => {});
1276
+ },
1277
+ disabled: busy,
1278
+ children: /*#__PURE__*/_jsx(Text, {
1279
+ style: styles.secondaryBtnText,
1280
+ children: "Gallery / File"
1281
+ })
1282
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
1283
+ style: styles.secondaryBtn,
1284
+ onPress: () => {
1285
+ pickFromCamera().catch(() => {});
1286
+ },
1287
+ disabled: busy,
1288
+ children: /*#__PURE__*/_jsx(Text, {
1289
+ style: styles.secondaryBtnText,
1290
+ children: "Camera"
1291
+ })
1292
+ })]
1293
+ }), fileList.length ? /*#__PURE__*/_jsxs(Text, {
1294
+ style: styles.fileSelectedText,
1295
+ children: ["Selected: ", fileList.length, " files"]
1296
+ }) : null, fileList.length ? /*#__PURE__*/_jsx(View, {
1297
+ style: {
1298
+ gap: 8
1299
+ },
1300
+ children: fileList.map((p, idx) => {
1301
+ const name = fileBaseName(p);
1302
+ return /*#__PURE__*/_jsxs(View, {
1303
+ style: {
1304
+ flexDirection: 'row',
1305
+ alignItems: 'center',
1306
+ justifyContent: 'space-between',
1307
+ paddingVertical: 6,
1308
+ borderBottomWidth: 1,
1309
+ borderBottomColor: '#E5E7EB',
1310
+ gap: 10
1311
+ },
1312
+ children: [/*#__PURE__*/_jsx(Text, {
1313
+ style: {
1314
+ flex: 1,
1315
+ color: '#111827'
1316
+ },
1317
+ numberOfLines: 1,
1318
+ children: name
1319
+ }), /*#__PURE__*/_jsxs(View, {
1320
+ style: {
1321
+ flexDirection: 'row',
1322
+ alignItems: 'center',
1323
+ gap: 10
1324
+ },
1325
+ children: [/*#__PURE__*/_jsxs(TouchableOpacity, {
1326
+ onPress: () => viewAttachment(p).catch(() => {}),
1327
+ style: {
1328
+ flexDirection: 'row',
1329
+ alignItems: 'center',
1330
+ gap: 6
1331
+ },
1332
+ children: [/*#__PURE__*/_jsx(View, {
1333
+ style: {
1334
+ width: 22,
1335
+ height: 22,
1336
+ borderRadius: 11,
1337
+ backgroundColor: '#DBEAFE',
1338
+ alignItems: 'center',
1339
+ justifyContent: 'center'
1340
+ },
1341
+ children: /*#__PURE__*/_jsx(Text, {
1342
+ style: {
1343
+ color: '#1D4ED8',
1344
+ fontWeight: '800'
1345
+ },
1346
+ children: "V"
1347
+ })
1348
+ }), /*#__PURE__*/_jsx(Text, {
1349
+ style: {
1350
+ color: '#1D4ED8',
1351
+ fontWeight: '700'
1352
+ },
1353
+ children: "View"
1354
+ })]
1355
+ }), /*#__PURE__*/_jsxs(TouchableOpacity, {
1356
+ onPress: () => {
1357
+ setFileList(prev => {
1358
+ const next = prev.filter((_, i) => i !== idx);
1359
+ setActiveFile(cur => String(cur) === String(p) ? next[0] ?? '' : cur);
1360
+ return next;
1361
+ });
1362
+ },
1363
+ style: {
1364
+ flexDirection: 'row',
1365
+ alignItems: 'center',
1366
+ gap: 6
1367
+ },
1368
+ children: [/*#__PURE__*/_jsx(View, {
1369
+ style: {
1370
+ width: 22,
1371
+ height: 22,
1372
+ borderRadius: 11,
1373
+ backgroundColor: '#FEE2E2',
1374
+ alignItems: 'center',
1375
+ justifyContent: 'center'
1376
+ },
1377
+ children: /*#__PURE__*/_jsx(Text, {
1378
+ style: {
1379
+ color: '#B91C1C',
1380
+ fontWeight: '800'
1381
+ },
1382
+ children: "\xD7"
1383
+ })
1384
+ }), /*#__PURE__*/_jsx(Text, {
1385
+ style: {
1386
+ color: '#B91C1C',
1387
+ fontWeight: '700'
1388
+ },
1389
+ children: "Remove"
1390
+ })]
1391
+ })]
1392
+ })]
1393
+ }, `${p}-${idx}`);
1394
+ })
1395
+ }) : null, (() => {
1396
+ const currentFile = activeFile || fileList[0] || '';
1397
+ const currentUrl = currentFile ? resolveUploadUrl(downloadsBaseUrl, currentFile) : '';
1398
+ const isImage = currentUrl ? isImageUrl(currentUrl) || isImageUrl(currentFile) : false;
1399
+ return currentUrl ? isImage ? /*#__PURE__*/_jsx(TouchableOpacity, {
1400
+ style: styles.attachmentPreview,
1401
+ onPress: () => viewAttachment(currentFile).catch(() => {}),
1402
+ activeOpacity: 0.9,
1403
+ children: /*#__PURE__*/_jsx(Image, {
1404
+ source: {
1405
+ uri: currentUrl,
1406
+ headers: authToken || schoolCode ? {
1407
+ ...(authToken ? {
1408
+ Authorization: authToken
1409
+ } : {}),
1410
+ ...(schoolCode ? {
1411
+ school_code: schoolCode
1412
+ } : {})
1413
+ } : undefined
1414
+ },
1415
+ style: styles.attachmentPreviewImage,
1416
+ resizeMode: "contain"
1417
+ })
1418
+ }) : /*#__PURE__*/_jsx(TouchableOpacity, {
1419
+ style: styles.viewDocumentBtn,
1420
+ onPress: () => viewAttachment(currentFile).catch(() => {}),
1421
+ disabled: isDownloading,
1422
+ activeOpacity: 0.9,
1423
+ children: /*#__PURE__*/_jsx(Text, {
1424
+ style: styles.viewDocumentBtnText,
1425
+ children: isDownloading ? 'Opening Document...' : 'View Document'
1426
+ })
1427
+ }) : null;
1428
+ })(), fileError ? /*#__PURE__*/_jsx(Text, {
1429
+ style: styles.errorText,
1430
+ children: fileError
1431
+ }) : null]
1432
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
1433
+ onPress: () => {
1434
+ submit().catch(() => {});
1435
+ },
1436
+ style: styles.primaryBtn,
1437
+ disabled: !canSubmit,
1438
+ children: /*#__PURE__*/_jsx(Text, {
1439
+ style: styles.primaryBtnText,
1440
+ children: busy ? 'Please wait...' : 'Submit'
1441
+ })
1442
+ }), !canPickSubject ? /*#__PURE__*/_jsx(Text, {
1443
+ style: styles.helperText,
1444
+ children: "Pass sessionId to AssignmentScreen to load subjects."
1445
+ }) : null]
1446
+ }), ImageView ? /*#__PURE__*/_jsx(ImageView, {
1447
+ images: viewerImages,
1448
+ imageIndex: viewerIndex,
1449
+ visible: viewerOpen,
1450
+ onRequestClose: () => setViewerOpen(false)
1451
+ }) : null, /*#__PURE__*/_jsx(Modal, {
1452
+ visible: pdfVisible,
1453
+ animationType: "slide",
1454
+ children: /*#__PURE__*/_jsxs(SafeAreaView, {
1455
+ style: styles.pdfRoot,
1456
+ children: [/*#__PURE__*/_jsxs(View, {
1457
+ style: styles.pdfHeader,
1458
+ children: [/*#__PURE__*/_jsx(Text, {
1459
+ style: styles.pdfTitle,
1460
+ children: "PDF Viewer"
1461
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
1462
+ onPress: () => {
1463
+ setPdfVisible(false);
1464
+ setPdfUri('');
1465
+ setPdfHeaders({});
1466
+ },
1467
+ style: styles.modalClose,
1468
+ children: /*#__PURE__*/_jsx(Text, {
1469
+ style: styles.modalCloseText,
1470
+ children: "Close"
1471
+ })
1472
+ })]
1473
+ }), pdfLoading ? /*#__PURE__*/_jsx(LoadingState, {}) : Pdf && pdfUri ? /*#__PURE__*/_jsx(Pdf, {
1474
+ source: {
1475
+ uri: pdfUri,
1476
+ cache: true,
1477
+ headers: pdfHeaders
1478
+ },
1479
+ style: styles.pdfViewer,
1480
+ onError: error => {
1481
+ Alert.alert('Error', error?.message ?? 'Could not open PDF.');
1482
+ }
1483
+ }) : /*#__PURE__*/_jsx(EmptyState, {
1484
+ title: "PDF Viewer Missing"
1485
+ })]
1486
+ })
1487
+ })]
1488
+ });
1489
+ }
1490
+ function EditAssignmentView({
1491
+ api,
1492
+ sessionId,
1493
+ classes,
1494
+ downloadsBaseUrl,
1495
+ initial,
1496
+ onDone,
1497
+ onCancel
1498
+ }) {
1499
+ const {
1500
+ authToken,
1501
+ getAuthToken,
1502
+ schoolCode
1503
+ } = useERP();
1504
+ const ImageView = useMemo(() => tryGetImageViewer(), []);
1505
+ const Pdf = useMemo(() => tryGetPdf(), []);
1506
+ const Calendar = useMemo(() => tryGetCalendar(), []);
1507
+ const RNFS = useMemo(() => tryGetRNFS(), []);
1508
+ const FileViewer = useMemo(() => tryGetFileViewer(), []);
1509
+ const [viewerOpen, setViewerOpen] = useState(false);
1510
+ const [viewerIndex, setViewerIndex] = useState(0);
1511
+ const [viewerImages, setViewerImages] = useState([]);
1512
+ const [pdfVisible, setPdfVisible] = useState(false);
1513
+ const [pdfUri, setPdfUri] = useState('');
1514
+ const [pdfHeaders, setPdfHeaders] = useState({});
1515
+ const [pdfLoading, setPdfLoading] = useState(false);
1516
+ const [calendarOpen, setCalendarOpen] = useState(false);
1517
+ const [dateMode, setDateMode] = useState('start');
1518
+ const [isDownloading, setIsDownloading] = useState(false);
1519
+ const homeworkId = detailValue(initial, ['id', 'homework_id', 'homeworkId']);
1520
+ const initialTitle = String(detailValue(initial, ['title', 'homework_title'], '') ?? '');
1521
+ const initialDescription = String(detailValue(initial, ['description', 'desc', 'homework_description'], '') ?? '');
1522
+ const initialClassId = detailValue(initial, ['class_id', 'classId', 'class']);
1523
+ const initialSectionId = detailValue(initial, ['section_id', 'sectionId', 'section']);
1524
+ const initialSubjectId = detailValue(initial, ['sub_id', 'subject_id', 'subjectId', 'subject']);
1525
+ const initialHomewDate = String(detailValue(initial, ['homew_date', 'homework_date', 'homeworkDate'], '') ?? '');
1526
+ const initialSubmissionDate = String(detailValue(initial, ['submission_date', 'submit_date', 'submissionDate'], '') ?? '');
1527
+ const initialStatus = initial?.status === 'unpublish' ? 'unpublish' : 'publish';
1528
+ const existingFile = String(detailValue(initial, ['document', 'file', 'attachment', 'document_path'], '') ?? '');
1529
+ const initialFiles = splitFileCsv(existingFile);
1530
+ const [busy, setBusy] = useState(false);
1531
+ const [fileError, setFileError] = useState(null);
1532
+ const [fileList, setFileList] = useState(() => initialFiles);
1533
+ const [activeFile, setActiveFile] = useState(() => initialFiles[0] ?? '');
1534
+ const [title, setTitle] = useState(initialTitle);
1535
+ const [description, setDescription] = useState(initialDescription);
1536
+ const [classOpt, setClassOpt] = useState(null);
1537
+ const [sectionOpt, setSectionOpt] = useState(null);
1538
+ const [subjectOpt, setSubjectOpt] = useState(null);
1539
+ const [sections, setSections] = useState([]);
1540
+ const [subjects, setSubjects] = useState([]);
1541
+ const [homewDate, setHomewDate] = useState(initialHomewDate);
1542
+ const [submissionDate, setSubmissionDate] = useState(initialSubmissionDate);
1543
+ const [status, setStatus] = useState(initialStatus);
1544
+ const [markedDates, setMarkedDates] = useState(() => {
1545
+ return rangeMarkedDates(initialHomewDate, initialSubmissionDate);
1546
+ });
1547
+ useEffect(() => {
1548
+ if (!classes.length) return;
1549
+ if (classOpt) return;
1550
+ const found = classes.find(it => String(it.value) === String(initialClassId));
1551
+ if (found) {
1552
+ setClassOpt(found);
1553
+ }
1554
+ }, [classOpt, classes, initialClassId]);
1555
+ useEffect(() => {
1556
+ if (!classOpt?.value) {
1557
+ setSections([]);
1558
+ setSubjects([]);
1559
+ setSectionOpt(null);
1560
+ setSubjectOpt(null);
1561
+ return;
1562
+ }
1563
+ fetchTeacherSections(api, {
1564
+ classId: classOpt.value
1565
+ }).then(res => {
1566
+ const opts = normalizeOptions(res);
1567
+ setSections(opts);
1568
+ const found = opts.find(it => String(it.value) === String(initialSectionId));
1569
+ if (found) {
1570
+ setSectionOpt(found);
1571
+ }
1572
+ }).catch(() => {});
1573
+ if (sessionId !== undefined) {
1574
+ fetchTeacherSubjectsByClass(api, {
1575
+ classId: classOpt.value,
1576
+ sessionId
1577
+ }).then(res => {
1578
+ const opts = normalizeOptions(res);
1579
+ setSubjects(opts);
1580
+ const found = opts.find(it => String(it.value) === String(initialSubjectId));
1581
+ if (found) {
1582
+ setSubjectOpt(found);
1583
+ }
1584
+ }).catch(() => {});
1585
+ } else {
1586
+ setSubjects([]);
1587
+ setSubjectOpt(null);
1588
+ }
1589
+ }, [api, classOpt?.value, initialSectionId, initialSubjectId, sessionId]);
1590
+ const viewAttachment = useCallback(async rawFile => {
1591
+ const file = String(rawFile ?? activeFile ?? fileList[0] ?? '');
1592
+ if (!file) {
1593
+ Alert.alert('No Attachment');
1594
+ return;
1595
+ }
1596
+ const url = resolveUploadUrl(downloadsBaseUrl, file);
1597
+ const headers = await resolveViewerHeaders({
1598
+ authToken,
1599
+ getAuthToken,
1600
+ schoolCode
1601
+ });
1602
+ const isImage = isImageUrl(url) || isImageUrl(file);
1603
+ if (ImageView && isImage) {
1604
+ const imageFiles = fileList.filter(p => {
1605
+ const u = resolveUploadUrl(downloadsBaseUrl, p);
1606
+ return isImageUrl(u) || isImageUrl(p);
1607
+ });
1608
+ const imageUrls = imageFiles.map(p => resolveUploadUrl(downloadsBaseUrl, p));
1609
+ const idx = imageFiles.findIndex(p => String(p) === String(file));
1610
+ setViewerImages(imageUrls.map(uri => ({
1611
+ uri,
1612
+ headers: Object.keys(headers).length ? headers : undefined
1613
+ })));
1614
+ setViewerIndex(Math.max(0, idx));
1615
+ setViewerOpen(true);
1616
+ return;
1617
+ }
1618
+ const isPdf = url.toLowerCase().endsWith('.pdf') || String(file).toLowerCase().endsWith('.pdf');
1619
+ if (Pdf && isPdf) {
1620
+ if (!RNFS) {
1621
+ setPdfUri(url);
1622
+ setPdfHeaders(headers);
1623
+ setPdfVisible(true);
1624
+ return;
1625
+ }
1626
+ setIsDownloading(true);
1627
+ setPdfLoading(true);
1628
+ try {
1629
+ const localPdf = await downloadPdfToLocal({
1630
+ RNFS,
1631
+ url,
1632
+ fileName: fileBaseName(file || 'document.pdf'),
1633
+ headers
1634
+ });
1635
+ setPdfHeaders({});
1636
+ setPdfUri(localPdf);
1637
+ setPdfVisible(true);
1638
+ } catch (error) {
1639
+ Alert.alert('Error', error?.message ?? 'Could not open PDF.');
1640
+ } finally {
1641
+ setPdfLoading(false);
1642
+ setIsDownloading(false);
1643
+ }
1644
+ return;
1645
+ }
1646
+ if (!RNFS || !FileViewer) {
1647
+ Alert.alert('Viewer Missing', 'Install react-native-fs and react-native-file-viewer to open documents inside the app.');
1648
+ return;
1649
+ }
1650
+ setIsDownloading(true);
1651
+ try {
1652
+ let fileName = fileBaseName(file) || 'document';
1653
+ const hasExtension = fileName.includes('.') && (fileName.split('.').pop()?.length ?? 0) > 0;
1654
+ if (!hasExtension && url.startsWith('http')) {
1655
+ try {
1656
+ const response = await fetch(url, {
1657
+ method: 'HEAD'
1658
+ });
1659
+ const contentType = response.headers.get('content-type') ?? '';
1660
+ if (contentType.includes('pdf')) fileName += '.pdf';else if (contentType.includes('word') || contentType.includes('doc')) fileName += '.docx';else if (contentType.includes('sheet') || contentType.includes('excel')) fileName += '.xlsx';else if (contentType.includes('jpeg') || contentType.includes('jpg')) fileName += '.jpg';else if (contentType.includes('png')) fileName += '.png';else fileName += '.pdf';
1661
+ } catch {
1662
+ fileName += '.pdf';
1663
+ }
1664
+ }
1665
+ const localFile = `${RNFS.DocumentDirectoryPath}/${fileName}`;
1666
+ await RNFS.downloadFile({
1667
+ fromUrl: url,
1668
+ toFile: localFile,
1669
+ headers
1670
+ }).promise;
1671
+ await FileViewer.open(localFile, {
1672
+ showOpenWithDialog: true
1673
+ });
1674
+ } catch {
1675
+ Alert.alert('Error', 'Could not view the file.');
1676
+ } finally {
1677
+ setIsDownloading(false);
1678
+ }
1679
+ }, [FileViewer, ImageView, Pdf, RNFS, activeFile, authToken, downloadsBaseUrl, fileList, getAuthToken, schoolCode]);
1680
+ const pickFromCamera = useCallback(async () => {
1681
+ let picker = null;
1682
+ try {
1683
+ picker = require('react-native-image-picker');
1684
+ } catch {}
1685
+ if (!picker?.launchCamera) {
1686
+ Alert.alert('Error', 'Camera picker is not available');
1687
+ return;
1688
+ }
1689
+ const res = await new Promise(resolve => {
1690
+ picker.launchCamera({
1691
+ mediaType: 'photo',
1692
+ saveToPhotos: true,
1693
+ includeBase64: false
1694
+ }, r => resolve(r));
1695
+ });
1696
+ const asset = res?.assets?.[0];
1697
+ if (!asset?.uri) return;
1698
+ const file = {
1699
+ uri: asset.uri,
1700
+ name: asset.fileName ?? 'camera.jpg',
1701
+ type: asset.type ?? 'image/jpeg'
1702
+ };
1703
+ setFileError(null);
1704
+ setBusy(true);
1705
+ try {
1706
+ const uploaded = await uploadAssignmentFile(api, file);
1707
+ if (uploaded?.Status === 'Success' && uploaded.data) {
1708
+ const nextPath = String(uploaded.data);
1709
+ setFileList(prev => [...prev, nextPath]);
1710
+ setActiveFile(prev => prev || nextPath);
1711
+ } else {
1712
+ setFileError('File upload failed');
1713
+ }
1714
+ } finally {
1715
+ setBusy(false);
1716
+ }
1717
+ }, [api]);
1718
+ const pickFiles = useCallback(async () => {
1719
+ let dp = null;
1720
+ try {
1721
+ dp = require('react-native-document-picker');
1722
+ } catch {}
1723
+ if (!dp) {
1724
+ return;
1725
+ }
1726
+ const allowed = ['pdf', 'png', 'jpg', 'jpeg', 'webp'];
1727
+ const selected = await dp.pick({
1728
+ presentationStyle: 'fullScreen',
1729
+ allowMultiSelection: true,
1730
+ type: [dp.types.pdf, dp.types.images]
1731
+ });
1732
+ const picks = Array.isArray(selected) ? selected : [selected];
1733
+ const files = picks.map(it => ({
1734
+ uri: it.uri,
1735
+ name: it.name ?? 'file',
1736
+ type: it.type ?? 'application/octet-stream'
1737
+ })).filter(it => !!it.uri);
1738
+ const invalid = files.find(it => {
1739
+ const ext = String(it.name).split('.').pop()?.toLowerCase();
1740
+ return ext ? !allowed.includes(ext) : false;
1741
+ });
1742
+ if (invalid) {
1743
+ setFileError('Only PDF or image files are allowed');
1744
+ return;
1745
+ }
1746
+ setFileError(null);
1747
+ setBusy(true);
1748
+ try {
1749
+ const uploaded = [];
1750
+ for (const f of files) {
1751
+ const res = await uploadAssignmentFile(api, f);
1752
+ if (res?.Status === 'Success' && res.data) {
1753
+ uploaded.push(String(res.data));
1754
+ }
1755
+ }
1756
+ if (!uploaded.length) {
1757
+ setFileError('File upload failed');
1758
+ return;
1759
+ }
1760
+ setFileList(prev => [...prev, ...uploaded]);
1761
+ setActiveFile(prev => prev ? prev : uploaded[0] ?? '');
1762
+ } finally {
1763
+ setBusy(false);
1764
+ }
1765
+ }, [api]);
1766
+ const submit = useCallback(async () => {
1767
+ const fileToSend = fileList.length ? fileList.join(',') : undefined;
1768
+ const missing = [];
1769
+ if (!homeworkId) missing.push('homework id');
1770
+ if (!title) missing.push('title');
1771
+ if (!description) missing.push('description');
1772
+ if (!classOpt?.value) missing.push('class');
1773
+ if (!sectionOpt?.value) missing.push('section');
1774
+ if (!subjectOpt?.value) missing.push('subject');
1775
+ if (!homewDate) missing.push('homework date');
1776
+ if (!submissionDate) missing.push('submission date');
1777
+ if (!fileToSend) missing.push('file');
1778
+ if (missing.length) {
1779
+ if (!fileToSend) {
1780
+ setFileError('File is required');
1781
+ }
1782
+ Alert.alert('Missing Fields', `Please fill: ${missing.join(', ')}`);
1783
+ return;
1784
+ }
1785
+ setBusy(true);
1786
+ try {
1787
+ const res = await updateAssignment(api, {
1788
+ id: homeworkId,
1789
+ title,
1790
+ description,
1791
+ class_id: classOpt.value,
1792
+ section_id: sectionOpt.value,
1793
+ sub_id: subjectOpt.value,
1794
+ homew_date: homewDate,
1795
+ submission_date: submissionDate,
1796
+ status,
1797
+ file: fileToSend
1798
+ });
1799
+ const response = res;
1800
+ if (response?.Status && response.Status !== 'Success') {
1801
+ Alert.alert('Update Failed', String(response?.msg ?? response?.Status ?? 'Request failed'));
1802
+ return;
1803
+ }
1804
+ Alert.alert('Success', String(response?.msg ?? 'Assignment updated successfully'));
1805
+ onDone();
1806
+ } catch (error) {
1807
+ Alert.alert('Update Failed', String(error?.message ?? 'Could not update assignment'));
1808
+ } finally {
1809
+ setBusy(false);
1810
+ }
1811
+ }, [api, classOpt?.value, description, fileList, homewDate, homeworkId, onDone, sectionOpt?.value, status, subjectOpt?.value, submissionDate, title]);
1812
+ const canPickSubject = sessionId !== undefined;
1813
+ const canSubmit = canPickSubject && fileList.length > 0 && !busy;
1814
+ return /*#__PURE__*/_jsxs(SafeAreaView, {
1815
+ style: styles.root,
1816
+ children: [/*#__PURE__*/_jsxs(View, {
1817
+ style: styles.header,
1818
+ children: [/*#__PURE__*/_jsx(Text, {
1819
+ style: styles.headerTitle,
1820
+ children: "Edit Assignment"
1821
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
1822
+ onPress: onCancel,
1823
+ style: styles.secondaryBtn,
1824
+ children: /*#__PURE__*/_jsx(Text, {
1825
+ style: styles.secondaryBtnText,
1826
+ children: "Back"
1827
+ })
1828
+ })]
1829
+ }), /*#__PURE__*/_jsxs(ScrollView, {
1830
+ contentContainerStyle: styles.form,
1831
+ children: [/*#__PURE__*/_jsxs(View, {
1832
+ style: styles.field,
1833
+ children: [/*#__PURE__*/_jsx(Text, {
1834
+ style: styles.label,
1835
+ children: "Title"
1836
+ }), /*#__PURE__*/_jsx(TextInput, {
1837
+ value: title,
1838
+ onChangeText: setTitle,
1839
+ placeholder: "Enter title",
1840
+ placeholderTextColor: "#9CA3AF",
1841
+ style: styles.input
1842
+ })]
1843
+ }), /*#__PURE__*/_jsxs(View, {
1844
+ style: styles.field,
1845
+ children: [/*#__PURE__*/_jsx(Text, {
1846
+ style: styles.label,
1847
+ children: "Description"
1848
+ }), /*#__PURE__*/_jsx(TextInput, {
1849
+ value: description,
1850
+ onChangeText: setDescription,
1851
+ placeholder: "Enter description",
1852
+ placeholderTextColor: "#9CA3AF",
1853
+ style: [styles.input, styles.textarea],
1854
+ multiline: true
1855
+ })]
1856
+ }), /*#__PURE__*/_jsx(SelectModal, {
1857
+ label: "Class",
1858
+ value: classOpt,
1859
+ options: classes,
1860
+ placeholder: "Select class",
1861
+ onChange: setClassOpt
1862
+ }), /*#__PURE__*/_jsx(SelectModal, {
1863
+ label: "Section",
1864
+ value: sectionOpt,
1865
+ options: sections,
1866
+ placeholder: "Select section",
1867
+ onChange: setSectionOpt
1868
+ }), /*#__PURE__*/_jsx(SelectModal, {
1869
+ label: "Subject",
1870
+ value: subjectOpt,
1871
+ options: subjects,
1872
+ placeholder: canPickSubject ? 'Select subject' : 'Provide sessionId',
1873
+ onChange: setSubjectOpt
1874
+ }), /*#__PURE__*/_jsxs(View, {
1875
+ style: styles.field,
1876
+ children: [/*#__PURE__*/_jsx(Text, {
1877
+ style: styles.label,
1878
+ children: "Homework Date"
1879
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
1880
+ style: styles.secondaryBtn,
1881
+ onPress: () => {
1882
+ setDateMode('start');
1883
+ setCalendarOpen(true);
1884
+ },
1885
+ children: /*#__PURE__*/_jsx(Text, {
1886
+ style: styles.secondaryBtnText,
1887
+ children: homewDate || 'Select start date'
1888
+ })
1889
+ })]
1890
+ }), /*#__PURE__*/_jsxs(View, {
1891
+ style: styles.field,
1892
+ children: [/*#__PURE__*/_jsx(Text, {
1893
+ style: styles.label,
1894
+ children: "Submission Date"
1895
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
1896
+ style: styles.secondaryBtn,
1897
+ onPress: () => {
1898
+ setDateMode('end');
1899
+ setCalendarOpen(true);
1900
+ },
1901
+ children: /*#__PURE__*/_jsx(Text, {
1902
+ style: styles.secondaryBtnText,
1903
+ children: submissionDate || 'Select end date'
1904
+ })
1905
+ })]
1906
+ }), /*#__PURE__*/_jsxs(View, {
1907
+ style: styles.field,
1908
+ children: [/*#__PURE__*/_jsx(Text, {
1909
+ style: styles.label,
1910
+ children: "Status"
1911
+ }), /*#__PURE__*/_jsxs(View, {
1912
+ style: styles.row,
1913
+ children: [/*#__PURE__*/_jsx(TouchableOpacity, {
1914
+ style: [styles.chip, status === 'publish' ? styles.chipActive : null],
1915
+ onPress: () => setStatus('publish'),
1916
+ children: /*#__PURE__*/_jsx(Text, {
1917
+ style: [styles.chipText, status === 'publish' ? styles.chipTextActive : null],
1918
+ children: "Publish"
1919
+ })
1920
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
1921
+ style: [styles.chip, status === 'unpublish' ? styles.chipActive : null],
1922
+ onPress: () => setStatus('unpublish'),
1923
+ children: /*#__PURE__*/_jsx(Text, {
1924
+ style: [styles.chipText, status === 'unpublish' ? styles.chipTextActive : null],
1925
+ children: "Unpublish"
1926
+ })
1927
+ })]
1928
+ })]
1929
+ }), /*#__PURE__*/_jsxs(View, {
1930
+ style: styles.field,
1931
+ children: [/*#__PURE__*/_jsx(Text, {
1932
+ style: styles.label,
1933
+ children: "Attachment"
1934
+ }), /*#__PURE__*/_jsxs(View, {
1935
+ style: styles.row,
1936
+ children: [/*#__PURE__*/_jsx(TouchableOpacity, {
1937
+ style: styles.secondaryBtn,
1938
+ onPress: () => {
1939
+ pickFiles().catch(() => {});
1940
+ },
1941
+ disabled: busy,
1942
+ children: /*#__PURE__*/_jsx(Text, {
1943
+ style: styles.secondaryBtnText,
1944
+ children: "Add More Files"
1945
+ })
1946
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
1947
+ style: styles.secondaryBtn,
1948
+ onPress: () => {
1949
+ pickFromCamera().catch(() => {});
1950
+ },
1951
+ disabled: busy,
1952
+ children: /*#__PURE__*/_jsx(Text, {
1953
+ style: styles.secondaryBtnText,
1954
+ children: "Camera"
1955
+ })
1956
+ })]
1957
+ }), fileList.length ? /*#__PURE__*/_jsxs(Text, {
1958
+ style: styles.fileSelectedText,
1959
+ children: ["Selected: ", fileList.length, " files"]
1960
+ }) : null, fileList.length ? /*#__PURE__*/_jsx(View, {
1961
+ style: {
1962
+ gap: 8
1963
+ },
1964
+ children: fileList.map((p, idx) => {
1965
+ const name = fileBaseName(p);
1966
+ return /*#__PURE__*/_jsxs(View, {
1967
+ style: {
1968
+ flexDirection: 'row',
1969
+ alignItems: 'center',
1970
+ justifyContent: 'space-between',
1971
+ paddingVertical: 6,
1972
+ borderBottomWidth: 1,
1973
+ borderBottomColor: '#E5E7EB',
1974
+ gap: 10
1975
+ },
1976
+ children: [/*#__PURE__*/_jsx(Text, {
1977
+ style: {
1978
+ flex: 1,
1979
+ color: '#111827'
1980
+ },
1981
+ numberOfLines: 1,
1982
+ children: name
1983
+ }), /*#__PURE__*/_jsxs(View, {
1984
+ style: {
1985
+ flexDirection: 'row',
1986
+ alignItems: 'center',
1987
+ gap: 10
1988
+ },
1989
+ children: [/*#__PURE__*/_jsxs(TouchableOpacity, {
1990
+ onPress: () => viewAttachment(p).catch(() => {}),
1991
+ style: {
1992
+ flexDirection: 'row',
1993
+ alignItems: 'center',
1994
+ gap: 6
1995
+ },
1996
+ children: [/*#__PURE__*/_jsx(View, {
1997
+ style: {
1998
+ width: 22,
1999
+ height: 22,
2000
+ borderRadius: 11,
2001
+ backgroundColor: '#DBEAFE',
2002
+ alignItems: 'center',
2003
+ justifyContent: 'center'
2004
+ },
2005
+ children: /*#__PURE__*/_jsx(Text, {
2006
+ style: {
2007
+ color: '#1D4ED8',
2008
+ fontWeight: '800'
2009
+ },
2010
+ children: "V"
2011
+ })
2012
+ }), /*#__PURE__*/_jsx(Text, {
2013
+ style: {
2014
+ color: '#1D4ED8',
2015
+ fontWeight: '700'
2016
+ },
2017
+ children: "View"
2018
+ })]
2019
+ }), /*#__PURE__*/_jsxs(TouchableOpacity, {
2020
+ onPress: () => {
2021
+ setFileList(prev => {
2022
+ const next = prev.filter((_, i) => i !== idx);
2023
+ setActiveFile(cur => String(cur) === String(p) ? next[0] ?? '' : cur);
2024
+ return next;
2025
+ });
2026
+ },
2027
+ style: {
2028
+ flexDirection: 'row',
2029
+ alignItems: 'center',
2030
+ gap: 6
2031
+ },
2032
+ children: [/*#__PURE__*/_jsx(View, {
2033
+ style: {
2034
+ width: 22,
2035
+ height: 22,
2036
+ borderRadius: 11,
2037
+ backgroundColor: '#FEE2E2',
2038
+ alignItems: 'center',
2039
+ justifyContent: 'center'
2040
+ },
2041
+ children: /*#__PURE__*/_jsx(Text, {
2042
+ style: {
2043
+ color: '#B91C1C',
2044
+ fontWeight: '800'
2045
+ },
2046
+ children: "\xD7"
2047
+ })
2048
+ }), /*#__PURE__*/_jsx(Text, {
2049
+ style: {
2050
+ color: '#B91C1C',
2051
+ fontWeight: '700'
2052
+ },
2053
+ children: "Remove"
2054
+ })]
2055
+ })]
2056
+ })]
2057
+ }, `${p}-${idx}`);
2058
+ })
2059
+ }) : null, (() => {
2060
+ const currentFile = activeFile || fileList[0] || '';
2061
+ const currentUrl = currentFile ? resolveUploadUrl(downloadsBaseUrl, currentFile) : '';
2062
+ const isImage = currentUrl ? isImageUrl(currentUrl) || isImageUrl(currentFile) : false;
2063
+ return currentUrl ? isImage ? /*#__PURE__*/_jsx(TouchableOpacity, {
2064
+ style: styles.attachmentPreview,
2065
+ onPress: () => viewAttachment(currentFile).catch(() => {}),
2066
+ activeOpacity: 0.9,
2067
+ children: /*#__PURE__*/_jsx(Image, {
2068
+ source: {
2069
+ uri: currentUrl,
2070
+ headers: authToken || schoolCode ? {
2071
+ ...(authToken ? {
2072
+ Authorization: authToken
2073
+ } : {}),
2074
+ ...(schoolCode ? {
2075
+ school_code: schoolCode
2076
+ } : {})
2077
+ } : undefined
2078
+ },
2079
+ style: styles.attachmentPreviewImage,
2080
+ resizeMode: "contain"
2081
+ })
2082
+ }) : /*#__PURE__*/_jsx(TouchableOpacity, {
2083
+ style: styles.viewDocumentBtn,
2084
+ onPress: () => viewAttachment(currentFile).catch(() => {}),
2085
+ disabled: isDownloading,
2086
+ activeOpacity: 0.9,
2087
+ children: /*#__PURE__*/_jsx(Text, {
2088
+ style: styles.viewDocumentBtnText,
2089
+ children: isDownloading ? 'Opening Document...' : 'View Document'
2090
+ })
2091
+ }) : null;
2092
+ })(), fileError ? /*#__PURE__*/_jsx(Text, {
2093
+ style: styles.errorText,
2094
+ children: fileError
2095
+ }) : null]
2096
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
2097
+ onPress: () => {
2098
+ submit().catch(() => {});
2099
+ },
2100
+ style: styles.primaryBtn,
2101
+ disabled: !canSubmit,
2102
+ children: /*#__PURE__*/_jsx(Text, {
2103
+ style: styles.primaryBtnText,
2104
+ children: busy ? 'Please wait...' : 'Update'
2105
+ })
2106
+ }), !canPickSubject ? /*#__PURE__*/_jsx(Text, {
2107
+ style: styles.helperText,
2108
+ children: "Pass sessionId to AssignmentScreen to load subjects."
2109
+ }) : null]
2110
+ }), /*#__PURE__*/_jsx(Modal, {
2111
+ visible: calendarOpen,
2112
+ transparent: true,
2113
+ animationType: "fade",
2114
+ children: /*#__PURE__*/_jsx(Pressable, {
2115
+ style: styles.modalOverlay,
2116
+ onPress: () => setCalendarOpen(false),
2117
+ children: /*#__PURE__*/_jsxs(Pressable, {
2118
+ style: styles.sheetCard,
2119
+ onPress: () => {},
2120
+ children: [/*#__PURE__*/_jsxs(View, {
2121
+ style: styles.modalHeader,
2122
+ children: [/*#__PURE__*/_jsx(Text, {
2123
+ style: styles.modalHeaderTitle,
2124
+ children: dateMode === 'start' ? 'Select start date' : 'Select end date'
2125
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
2126
+ onPress: () => setCalendarOpen(false),
2127
+ style: styles.modalClose,
2128
+ children: /*#__PURE__*/_jsx(Text, {
2129
+ style: styles.modalCloseText,
2130
+ children: "Close"
2131
+ })
2132
+ })]
2133
+ }), /*#__PURE__*/_jsx(View, {
2134
+ style: {
2135
+ flex: 1
2136
+ },
2137
+ children: Calendar ? /*#__PURE__*/_jsx(Calendar, {
2138
+ markingType: "period",
2139
+ markedDates: markedDates,
2140
+ onDayPress: day => {
2141
+ const d = String(day?.dateString ?? '');
2142
+ if (!d) return;
2143
+ if (dateMode === 'start') {
2144
+ setHomewDate(d);
2145
+ const next = rangeMarkedDates(d, submissionDate || d);
2146
+ setMarkedDates(next);
2147
+ } else {
2148
+ setSubmissionDate(d);
2149
+ const next = rangeMarkedDates(homewDate || d, d);
2150
+ setMarkedDates(next);
2151
+ }
2152
+ setCalendarOpen(false);
2153
+ }
2154
+ }) : /*#__PURE__*/_jsx(EmptyState, {
2155
+ title: "Calendar missing",
2156
+ message: "Install react-native-calendars to enable date picker"
2157
+ })
2158
+ })]
2159
+ })
2160
+ })
2161
+ }), ImageView ? /*#__PURE__*/_jsx(ImageView, {
2162
+ images: viewerImages,
2163
+ imageIndex: viewerIndex,
2164
+ visible: viewerOpen,
2165
+ onRequestClose: () => setViewerOpen(false)
2166
+ }) : null, /*#__PURE__*/_jsx(Modal, {
2167
+ visible: pdfVisible,
2168
+ animationType: "slide",
2169
+ children: /*#__PURE__*/_jsxs(SafeAreaView, {
2170
+ style: styles.pdfRoot,
2171
+ children: [/*#__PURE__*/_jsxs(View, {
2172
+ style: styles.pdfHeader,
2173
+ children: [/*#__PURE__*/_jsx(Text, {
2174
+ style: styles.pdfTitle,
2175
+ children: "PDF Viewer"
2176
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
2177
+ onPress: () => {
2178
+ setPdfVisible(false);
2179
+ setPdfUri('');
2180
+ setPdfHeaders({});
2181
+ },
2182
+ style: styles.modalClose,
2183
+ children: /*#__PURE__*/_jsx(Text, {
2184
+ style: styles.modalCloseText,
2185
+ children: "Close"
2186
+ })
2187
+ })]
2188
+ }), pdfLoading ? /*#__PURE__*/_jsx(LoadingState, {}) : Pdf && pdfUri ? /*#__PURE__*/_jsx(Pdf, {
2189
+ source: {
2190
+ uri: pdfUri,
2191
+ cache: true,
2192
+ headers: pdfHeaders
2193
+ },
2194
+ style: styles.pdfViewer,
2195
+ onError: error => {
2196
+ Alert.alert('Error', error?.message ?? 'Could not open PDF.');
2197
+ }
2198
+ }) : /*#__PURE__*/_jsx(EmptyState, {
2199
+ title: "PDF Viewer Missing"
2200
+ })]
2201
+ })
2202
+ })]
2203
+ });
2204
+ }
2205
+ const styles = StyleSheet.create({
2206
+ root: {
2207
+ flex: 1,
2208
+ backgroundColor: '#FFFFFF'
2209
+ },
2210
+ header: {
2211
+ paddingHorizontal: 16,
2212
+ paddingVertical: 12,
2213
+ borderBottomWidth: 1,
2214
+ borderBottomColor: '#E5E7EB',
2215
+ flexDirection: 'row',
2216
+ justifyContent: 'space-between',
2217
+ alignItems: 'center'
2218
+ },
2219
+ headerTitle: {
2220
+ fontSize: 18,
2221
+ fontWeight: '600',
2222
+ color: '#111827'
2223
+ },
2224
+ filters: {
2225
+ paddingHorizontal: 16,
2226
+ paddingTop: 12,
2227
+ paddingBottom: 6,
2228
+ gap: 10
2229
+ },
2230
+ list: {
2231
+ padding: 16,
2232
+ gap: 12
2233
+ },
2234
+ card: {
2235
+ borderWidth: 1,
2236
+ borderColor: '#E5E7EB',
2237
+ borderRadius: 12,
2238
+ padding: 12,
2239
+ backgroundColor: '#FFFFFF'
2240
+ },
2241
+ cardTop: {
2242
+ flexDirection: 'row',
2243
+ alignItems: 'center',
2244
+ justifyContent: 'space-between',
2245
+ gap: 10
2246
+ },
2247
+ cardActions: {
2248
+ flexDirection: 'row',
2249
+ alignItems: 'center',
2250
+ gap: 10
2251
+ },
2252
+ editBtn: {
2253
+ paddingHorizontal: 10,
2254
+ paddingVertical: 6,
2255
+ borderRadius: 999,
2256
+ borderWidth: 1,
2257
+ borderColor: '#E5E7EB',
2258
+ backgroundColor: '#F3F4F6'
2259
+ },
2260
+ editBtnText: {
2261
+ fontSize: 12,
2262
+ fontWeight: '700',
2263
+ color: '#111827'
2264
+ },
2265
+ cardTitle: {
2266
+ fontSize: 16,
2267
+ fontWeight: '600',
2268
+ color: '#111827'
2269
+ },
2270
+ cardSub: {
2271
+ marginTop: 6,
2272
+ fontSize: 13,
2273
+ color: '#374151'
2274
+ },
2275
+ cardMeta: {
2276
+ marginTop: 6,
2277
+ fontSize: 12,
2278
+ color: '#6B7280'
2279
+ },
2280
+ viewBtn: {
2281
+ paddingHorizontal: 10,
2282
+ paddingVertical: 6,
2283
+ borderRadius: 999,
2284
+ borderWidth: 1,
2285
+ alignItems: 'center',
2286
+ justifyContent: 'center'
2287
+ },
2288
+ viewBtnActive: {
2289
+ backgroundColor: '#E8F5E9',
2290
+ borderColor: '#4CAF50'
2291
+ },
2292
+ viewBtnDisabled: {
2293
+ backgroundColor: '#F5F5F5',
2294
+ borderColor: '#E0E0E0'
2295
+ },
2296
+ viewBtnText: {
2297
+ fontSize: 12,
2298
+ fontWeight: '700'
2299
+ },
2300
+ viewBtnTextActive: {
2301
+ color: '#2E7D32'
2302
+ },
2303
+ viewBtnTextDisabled: {
2304
+ color: '#757575'
2305
+ },
2306
+ primaryBtn: {
2307
+ paddingHorizontal: 14,
2308
+ paddingVertical: 10,
2309
+ backgroundColor: '#111827',
2310
+ borderRadius: 10,
2311
+ alignItems: 'center',
2312
+ justifyContent: 'center'
2313
+ },
2314
+ primaryBtnText: {
2315
+ color: '#FFFFFF',
2316
+ fontWeight: '600'
2317
+ },
2318
+ secondaryBtn: {
2319
+ paddingHorizontal: 14,
2320
+ paddingVertical: 10,
2321
+ backgroundColor: '#F3F4F6',
2322
+ borderRadius: 10,
2323
+ alignItems: 'center',
2324
+ justifyContent: 'center'
2325
+ },
2326
+ secondaryBtnTextSmall: {
2327
+ color: '#111827',
2328
+ fontWeight: '600',
2329
+ fontSize: 12
2330
+ },
2331
+ secondaryBtnText: {
2332
+ color: '#111827',
2333
+ fontWeight: '600'
2334
+ },
2335
+ form: {
2336
+ padding: 16,
2337
+ gap: 12
2338
+ },
2339
+ field: {
2340
+ gap: 6
2341
+ },
2342
+ label: {
2343
+ fontSize: 12,
2344
+ color: '#6B7280'
2345
+ },
2346
+ input: {
2347
+ borderWidth: 1,
2348
+ borderColor: '#E5E7EB',
2349
+ borderRadius: 10,
2350
+ paddingHorizontal: 12,
2351
+ paddingVertical: 10,
2352
+ color: '#111827'
2353
+ },
2354
+ textarea: {
2355
+ minHeight: 90,
2356
+ textAlignVertical: 'top'
2357
+ },
2358
+ select: {
2359
+ borderWidth: 1,
2360
+ borderColor: '#E5E7EB',
2361
+ borderRadius: 10,
2362
+ paddingHorizontal: 12,
2363
+ paddingVertical: 12,
2364
+ backgroundColor: '#FFFFFF'
2365
+ },
2366
+ selectText: {
2367
+ color: '#111827'
2368
+ },
2369
+ modalBackdrop: {
2370
+ flex: 1,
2371
+ backgroundColor: 'rgba(0,0,0,0.35)',
2372
+ justifyContent: 'center',
2373
+ padding: 16
2374
+ },
2375
+ modalCard: {
2376
+ backgroundColor: '#FFFFFF',
2377
+ borderRadius: 12,
2378
+ maxHeight: '70%',
2379
+ paddingVertical: 12
2380
+ },
2381
+ sheetCard: {
2382
+ width: '92%',
2383
+ height: '85%',
2384
+ borderRadius: 18,
2385
+ backgroundColor: '#FFFFFF',
2386
+ overflow: 'hidden'
2387
+ },
2388
+ pdfRoot: {
2389
+ flex: 1,
2390
+ backgroundColor: '#FFFFFF'
2391
+ },
2392
+ pdfHeader: {
2393
+ paddingHorizontal: 16,
2394
+ paddingVertical: 12,
2395
+ borderBottomWidth: 1,
2396
+ borderBottomColor: '#E5E7EB',
2397
+ flexDirection: 'row',
2398
+ justifyContent: 'space-between',
2399
+ alignItems: 'center'
2400
+ },
2401
+ pdfTitle: {
2402
+ fontSize: 16,
2403
+ fontWeight: '700',
2404
+ color: '#111827'
2405
+ },
2406
+ pdfViewer: {
2407
+ flex: 1,
2408
+ width: '100%',
2409
+ backgroundColor: '#F3F4F6'
2410
+ },
2411
+ modalTitle: {
2412
+ fontSize: 16,
2413
+ fontWeight: '600',
2414
+ color: '#111827',
2415
+ paddingHorizontal: 16,
2416
+ paddingBottom: 12
2417
+ },
2418
+ optionRow: {
2419
+ paddingHorizontal: 16,
2420
+ paddingVertical: 12
2421
+ },
2422
+ optionText: {
2423
+ fontSize: 14,
2424
+ color: '#111827'
2425
+ },
2426
+ optionEmpty: {
2427
+ paddingHorizontal: 16,
2428
+ paddingVertical: 12,
2429
+ color: '#6B7280'
2430
+ },
2431
+ modalOverlay: {
2432
+ flex: 1,
2433
+ backgroundColor: 'rgba(0,0,0,0.45)',
2434
+ alignItems: 'center',
2435
+ justifyContent: 'center',
2436
+ padding: 16
2437
+ },
2438
+ modalHeader: {
2439
+ paddingHorizontal: 16,
2440
+ paddingVertical: 12,
2441
+ borderBottomWidth: 1,
2442
+ borderBottomColor: '#E5E7EB',
2443
+ flexDirection: 'row',
2444
+ justifyContent: 'space-between',
2445
+ alignItems: 'center'
2446
+ },
2447
+ modalHeaderTitle: {
2448
+ fontSize: 16,
2449
+ fontWeight: '700',
2450
+ color: '#111827'
2451
+ },
2452
+ modalClose: {
2453
+ paddingHorizontal: 10,
2454
+ paddingVertical: 6,
2455
+ borderRadius: 10,
2456
+ backgroundColor: '#F3F4F6'
2457
+ },
2458
+ modalCloseText: {
2459
+ fontSize: 12,
2460
+ fontWeight: '700',
2461
+ color: '#111827'
2462
+ },
2463
+ submissionList: {
2464
+ padding: 16,
2465
+ gap: 12
2466
+ },
2467
+ submissionCard: {
2468
+ borderWidth: 1,
2469
+ borderColor: '#E5E7EB',
2470
+ borderRadius: 12,
2471
+ padding: 12,
2472
+ backgroundColor: '#FFFFFF'
2473
+ },
2474
+ submissionHeaderRow: {
2475
+ flexDirection: 'row',
2476
+ alignItems: 'center',
2477
+ justifyContent: 'space-between',
2478
+ gap: 10
2479
+ },
2480
+ submissionInputRow: {
2481
+ marginTop: 10,
2482
+ flexDirection: 'row',
2483
+ alignItems: 'flex-start',
2484
+ gap: 10
2485
+ },
2486
+ smallInput: {
2487
+ borderWidth: 1,
2488
+ borderColor: '#E5E7EB',
2489
+ borderRadius: 10,
2490
+ paddingHorizontal: 10,
2491
+ paddingVertical: 8,
2492
+ color: '#111827'
2493
+ },
2494
+ uploadBtn: {
2495
+ marginTop: 10,
2496
+ alignSelf: 'stretch',
2497
+ paddingHorizontal: 12,
2498
+ paddingVertical: 10,
2499
+ borderRadius: 10,
2500
+ backgroundColor: '#111827',
2501
+ alignItems: 'center'
2502
+ },
2503
+ uploadBtnText: {
2504
+ color: '#FFFFFF',
2505
+ fontWeight: '700',
2506
+ fontSize: 12
2507
+ },
2508
+ submissionFooter: {
2509
+ padding: 12,
2510
+ borderTopWidth: 1,
2511
+ borderTopColor: '#E5E7EB',
2512
+ backgroundColor: '#FFFFFF'
2513
+ },
2514
+ giveMarksBtn: {
2515
+ paddingHorizontal: 14,
2516
+ paddingVertical: 12,
2517
+ borderRadius: 12,
2518
+ backgroundColor: '#DC2626',
2519
+ alignItems: 'center',
2520
+ justifyContent: 'center'
2521
+ },
2522
+ giveMarksBtnText: {
2523
+ color: '#FFFFFF',
2524
+ fontWeight: '800'
2525
+ },
2526
+ submissionName: {
2527
+ fontSize: 14,
2528
+ fontWeight: '800',
2529
+ color: '#111827'
2530
+ },
2531
+ submissionMeta: {
2532
+ marginTop: 6,
2533
+ fontSize: 12,
2534
+ color: '#6B7280'
2535
+ },
2536
+ row: {
2537
+ flexDirection: 'row',
2538
+ alignItems: 'center',
2539
+ gap: 10
2540
+ },
2541
+ chip: {
2542
+ borderWidth: 1,
2543
+ borderColor: '#E5E7EB',
2544
+ borderRadius: 999,
2545
+ paddingHorizontal: 12,
2546
+ paddingVertical: 8
2547
+ },
2548
+ chipActive: {
2549
+ backgroundColor: '#111827',
2550
+ borderColor: '#111827'
2551
+ },
2552
+ chipText: {
2553
+ fontSize: 12,
2554
+ color: '#111827',
2555
+ fontWeight: '600'
2556
+ },
2557
+ chipTextActive: {
2558
+ color: '#FFFFFF'
2559
+ },
2560
+ fileText: {
2561
+ flex: 1,
2562
+ fontSize: 12,
2563
+ color: '#6B7280'
2564
+ },
2565
+ fileSelectedText: {
2566
+ marginTop: 10,
2567
+ fontSize: 12,
2568
+ color: '#6B7280'
2569
+ },
2570
+ attachmentPreview: {
2571
+ marginTop: 10,
2572
+ borderWidth: 1,
2573
+ borderColor: '#E5E7EB',
2574
+ borderRadius: 12,
2575
+ backgroundColor: '#F9FAFB',
2576
+ overflow: 'hidden',
2577
+ height: 160
2578
+ },
2579
+ attachmentPreviewImage: {
2580
+ width: '100%',
2581
+ height: '100%'
2582
+ },
2583
+ viewDocumentBtn: {
2584
+ marginTop: 10,
2585
+ paddingVertical: 12,
2586
+ borderRadius: 12,
2587
+ borderWidth: 1,
2588
+ borderStyle: 'dashed',
2589
+ borderColor: '#93C5FD',
2590
+ backgroundColor: '#EFF6FF',
2591
+ alignItems: 'center',
2592
+ justifyContent: 'center'
2593
+ },
2594
+ viewDocumentBtnText: {
2595
+ color: '#1D4ED8',
2596
+ fontWeight: '800'
2597
+ },
2598
+ errorText: {
2599
+ marginTop: 6,
2600
+ fontSize: 12,
2601
+ color: '#DC2626',
2602
+ fontWeight: '600'
2603
+ },
2604
+ helperText: {
2605
+ marginTop: 6,
2606
+ fontSize: 12,
2607
+ color: '#6B7280'
2608
+ }
2609
+ });