@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,1281 @@
1
+ "use strict";
2
+
3
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
4
+ import { ActivityIndicator, Alert, FlatList, Image, Modal, Pressable, RefreshControl, 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 { createNote, fetchNoteDetails, fetchNotesClasses, fetchNotesList, fetchNotesSections, fetchNotesSubjects, updateNote, uploadNoteFile } from "../services/notesService.js";
10
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
+ function normalizeList(result) {
12
+ if (Array.isArray(result)) return result;
13
+ if (Array.isArray(result?.data)) return result.data;
14
+ if (Array.isArray(result?.data?.data)) return result.data.data;
15
+ return [];
16
+ }
17
+ function normalizeOptions(result) {
18
+ const list = normalizeList(result?.data ?? result);
19
+ return list.map((item, index) => ({
20
+ label: String(item?.label ?? item?.title ?? item?.name ?? `Option ${index + 1}`),
21
+ value: item?.value ?? item?.id ?? index + 1,
22
+ raw: item
23
+ }));
24
+ }
25
+ function resolveNoteUrl(baseUrl, file) {
26
+ if (!file) return '';
27
+ if (/^https?:\/\//i.test(file)) return file;
28
+ const base = String(baseUrl ?? '').replace(/\/?$/, '/');
29
+ const normalized = String(file).replace(/^\/+/, '');
30
+ return `${base}student/note/${normalized}`;
31
+ }
32
+ function isImageUrl(file) {
33
+ const lower = String(file ?? '').toLowerCase();
34
+ return lower.endsWith('.png') || lower.endsWith('.jpg') || lower.endsWith('.jpeg') || lower.endsWith('.webp') || lower.endsWith('.gif');
35
+ }
36
+ function fileBaseName(path) {
37
+ const raw = String(path ?? '');
38
+ const cleaned = raw.split('?')[0] ?? raw;
39
+ const parts = cleaned.split(/[\\/]/);
40
+ return parts[parts.length - 1] || raw;
41
+ }
42
+ function ensureFileUri(path) {
43
+ return path.startsWith('file://') ? path : `file://${path}`;
44
+ }
45
+ function tryGetImageViewer() {
46
+ try {
47
+ const mod = require('react-native-image-viewing');
48
+ return mod?.default ?? mod;
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+ function tryGetPdf() {
54
+ try {
55
+ const mod = require('react-native-pdf');
56
+ return mod?.default ?? mod;
57
+ } catch {
58
+ return null;
59
+ }
60
+ }
61
+ function tryGetRNFS() {
62
+ try {
63
+ return require('react-native-fs');
64
+ } catch {
65
+ return null;
66
+ }
67
+ }
68
+ function tryGetFileViewer() {
69
+ try {
70
+ return require('react-native-file-viewer');
71
+ } catch {
72
+ return null;
73
+ }
74
+ }
75
+ async function downloadPdfToLocal(args) {
76
+ const safeName = fileBaseName(args.fileName || `document-${Date.now()}.pdf`) || `document-${Date.now()}.pdf`;
77
+ const target = `${args.RNFS.CachesDirectoryPath}/${safeName}`;
78
+ const result = await args.RNFS.downloadFile({
79
+ fromUrl: args.url,
80
+ toFile: target,
81
+ headers: args.headers ?? {}
82
+ }).promise;
83
+ if (result?.statusCode && result.statusCode >= 400) {
84
+ throw new Error(`PDF download failed with status ${result.statusCode}`);
85
+ }
86
+ const exists = await args.RNFS.exists(target);
87
+ if (!exists) {
88
+ throw new Error('Downloaded PDF file is missing');
89
+ }
90
+ return ensureFileUri(target);
91
+ }
92
+ function SelectField({
93
+ label,
94
+ value,
95
+ options,
96
+ placeholder,
97
+ onChange
98
+ }) {
99
+ const [open, setOpen] = useState(false);
100
+ return /*#__PURE__*/_jsxs(View, {
101
+ style: styles.field,
102
+ children: [/*#__PURE__*/_jsx(Text, {
103
+ style: styles.label,
104
+ children: label
105
+ }), /*#__PURE__*/_jsx(Pressable, {
106
+ style: styles.select,
107
+ onPress: () => setOpen(true),
108
+ children: /*#__PURE__*/_jsx(Text, {
109
+ style: styles.selectText,
110
+ children: value?.label ?? placeholder
111
+ })
112
+ }), /*#__PURE__*/_jsx(Modal, {
113
+ visible: open,
114
+ transparent: true,
115
+ animationType: "fade",
116
+ children: /*#__PURE__*/_jsx(Pressable, {
117
+ style: styles.modalOverlay,
118
+ onPress: () => setOpen(false),
119
+ children: /*#__PURE__*/_jsxs(Pressable, {
120
+ style: styles.modalCard,
121
+ onPress: () => {},
122
+ children: [/*#__PURE__*/_jsx(Text, {
123
+ style: styles.modalTitle,
124
+ children: label
125
+ }), /*#__PURE__*/_jsx(FlatList, {
126
+ data: options,
127
+ keyExtractor: (item, index) => `${item.value}-${index}`,
128
+ renderItem: ({
129
+ item
130
+ }) => /*#__PURE__*/_jsx(TouchableOpacity, {
131
+ style: styles.optionRow,
132
+ onPress: () => {
133
+ onChange(item);
134
+ setOpen(false);
135
+ },
136
+ children: /*#__PURE__*/_jsx(Text, {
137
+ style: styles.optionText,
138
+ children: item.label
139
+ })
140
+ }),
141
+ ListEmptyComponent: /*#__PURE__*/_jsx(Text, {
142
+ style: styles.optionEmpty,
143
+ children: "No options"
144
+ })
145
+ })]
146
+ })
147
+ })
148
+ })]
149
+ });
150
+ }
151
+ function NoteEditor(props) {
152
+ const {
153
+ api,
154
+ fileBaseUrl,
155
+ authToken,
156
+ getAuthToken,
157
+ schoolCode
158
+ } = useERP();
159
+ const ImageView = useMemo(() => tryGetImageViewer(), []);
160
+ const Pdf = useMemo(() => tryGetPdf(), []);
161
+ const RNFS = useMemo(() => tryGetRNFS(), []);
162
+ const FileViewer = useMemo(() => tryGetFileViewer(), []);
163
+ const [loading, setLoading] = useState(props.mode === 'edit');
164
+ const [saving, setSaving] = useState(false);
165
+ const [classes, setClasses] = useState([]);
166
+ const [sections, setSections] = useState([]);
167
+ const [subjects, setSubjects] = useState([]);
168
+ const [title, setTitle] = useState('');
169
+ const [description, setDescription] = useState('');
170
+ const [status, setStatus] = useState('publish');
171
+ const [classOpt, setClassOpt] = useState(null);
172
+ const [sectionOpt, setSectionOpt] = useState(null);
173
+ const [subjectOpt, setSubjectOpt] = useState(null);
174
+ const [file, setFile] = useState('');
175
+ const [viewerOpen, setViewerOpen] = useState(false);
176
+ const [pdfVisible, setPdfVisible] = useState(false);
177
+ const [pdfUri, setPdfUri] = useState('');
178
+ const [pdfHeaders, setPdfHeaders] = useState({});
179
+ const [pdfLoading, setPdfLoading] = useState(false);
180
+ const [downloading, setDownloading] = useState(false);
181
+ const resolveHeaders = useCallback(async () => {
182
+ const nextToken = (await getAuthToken?.()) ?? authToken;
183
+ return {
184
+ ...(nextToken ? {
185
+ Authorization: nextToken
186
+ } : {}),
187
+ ...(schoolCode ? {
188
+ school_code: schoolCode
189
+ } : {})
190
+ };
191
+ }, [authToken, getAuthToken, schoolCode]);
192
+ useEffect(() => {
193
+ fetchNotesClasses(api).then(res => {
194
+ const classOptions = normalizeOptions(res);
195
+ setClasses(classOptions);
196
+ }).catch(() => {});
197
+ }, [api]);
198
+ useEffect(() => {
199
+ if (!classOpt?.value) {
200
+ setSections([]);
201
+ setSubjects([]);
202
+ return;
203
+ }
204
+ fetchNotesSections(api, {
205
+ classId: classOpt.value
206
+ }).then(res => setSections(normalizeOptions(res))).catch(() => {});
207
+ if (props.sessionId !== undefined) {
208
+ fetchNotesSubjects(api, {
209
+ classId: classOpt.value,
210
+ sessionId: props.sessionId
211
+ }).then(res => setSubjects(normalizeOptions(res))).catch(() => {});
212
+ }
213
+ }, [api, classOpt?.value, props.sessionId]);
214
+ useEffect(() => {
215
+ if (props.mode !== 'edit' || !props.noteId) {
216
+ return;
217
+ }
218
+ const load = async () => {
219
+ try {
220
+ setLoading(true);
221
+ const res = await fetchNoteDetails(api, {
222
+ noteId: props.noteId
223
+ });
224
+ const data = res?.data ?? res ?? {};
225
+ setTitle(String(data?.title ?? ''));
226
+ setDescription(String(data?.description ?? data?.remark ?? ''));
227
+ setStatus(String(data?.status ?? 'publish').toLowerCase() === 'unpublish' ? 'unpublish' : 'publish');
228
+ setFile(String(data?.file ?? ''));
229
+ const classId = data?.class_id ?? props.defaults.classId;
230
+ const sectionId = data?.section_id ?? props.defaults.sectionId;
231
+ const subjectId = data?.sub_id ?? data?.subject_id ?? props.defaults.subjectId;
232
+ if (classId !== undefined) {
233
+ const nextClass = normalizeOptions(await fetchNotesClasses(api)).find(it => String(it.value) === String(classId)) ?? null;
234
+ setClasses(normalizeOptions(await fetchNotesClasses(api)));
235
+ setClassOpt(nextClass);
236
+ if (nextClass) {
237
+ const sectionOptions = normalizeOptions(await fetchNotesSections(api, {
238
+ classId: nextClass.value
239
+ }));
240
+ setSections(sectionOptions);
241
+ setSectionOpt(sectionOptions.find(it => String(it.value) === String(sectionId)) ?? null);
242
+ if (props.sessionId !== undefined) {
243
+ const subjectOptions = normalizeOptions(await fetchNotesSubjects(api, {
244
+ classId: nextClass.value,
245
+ sessionId: props.sessionId
246
+ }));
247
+ setSubjects(subjectOptions);
248
+ setSubjectOpt(subjectOptions.find(it => String(it.value) === String(subjectId)) ?? null);
249
+ }
250
+ }
251
+ }
252
+ } catch (e) {
253
+ Alert.alert('Error', String(e?.message ?? 'Could not load note details'));
254
+ } finally {
255
+ setLoading(false);
256
+ }
257
+ };
258
+ load().catch(() => {});
259
+ }, [api, props.defaults.classId, props.defaults.sectionId, props.defaults.subjectId, props.mode, props.noteId, props.sessionId]);
260
+ useEffect(() => {
261
+ if (props.mode !== 'create' || !classes.length) return;
262
+ if (props.defaults.classId !== undefined && !classOpt) {
263
+ setClassOpt(classes.find(it => String(it.value) === String(props.defaults.classId)) ?? null);
264
+ }
265
+ }, [classOpt, classes, props.defaults.classId, props.mode]);
266
+ useEffect(() => {
267
+ if (!sections.length || props.mode !== 'create' || sectionOpt) return;
268
+ if (props.defaults.sectionId !== undefined) {
269
+ setSectionOpt(sections.find(it => String(it.value) === String(props.defaults.sectionId)) ?? null);
270
+ }
271
+ }, [props.defaults.sectionId, props.mode, sectionOpt, sections]);
272
+ const pickDocument = useCallback(async () => {
273
+ let dp = null;
274
+ try {
275
+ dp = require('react-native-document-picker');
276
+ } catch {}
277
+ if (!dp) return;
278
+ const selected = await dp.pickSingle({
279
+ presentationStyle: 'fullScreen',
280
+ type: [dp.types.pdf, dp.types.images]
281
+ });
282
+ const upload = {
283
+ uri: selected.uri,
284
+ name: selected.name ?? 'file',
285
+ type: selected.type ?? 'application/octet-stream'
286
+ };
287
+ setSaving(true);
288
+ try {
289
+ const res = await uploadNoteFile(api, upload);
290
+ if (res?.Status === 'Success' && res?.data) {
291
+ setFile(String(res.data));
292
+ } else {
293
+ Alert.alert('Error', 'File not uploaded');
294
+ }
295
+ } finally {
296
+ setSaving(false);
297
+ }
298
+ }, [api]);
299
+ const pickCamera = useCallback(async () => {
300
+ let picker = null;
301
+ try {
302
+ picker = require('react-native-image-picker');
303
+ } catch {}
304
+ if (!picker?.launchCamera) return;
305
+ const res = await new Promise(resolve => {
306
+ picker.launchCamera({
307
+ mediaType: 'photo',
308
+ saveToPhotos: true,
309
+ includeBase64: false
310
+ }, r => resolve(r));
311
+ });
312
+ const asset = res?.assets?.[0];
313
+ if (!asset?.uri) return;
314
+ const upload = {
315
+ uri: asset.uri,
316
+ name: asset.fileName ?? 'camera.jpg',
317
+ type: asset.type ?? 'image/jpeg'
318
+ };
319
+ setSaving(true);
320
+ try {
321
+ const response = await uploadNoteFile(api, upload);
322
+ if (response?.Status === 'Success' && response?.data) {
323
+ setFile(String(response.data));
324
+ } else {
325
+ Alert.alert('Error', 'File not uploaded');
326
+ }
327
+ } finally {
328
+ setSaving(false);
329
+ }
330
+ }, [api]);
331
+ const viewAttachment = useCallback(async () => {
332
+ if (!file) {
333
+ Alert.alert('No Attachment');
334
+ return;
335
+ }
336
+ const url = resolveNoteUrl(fileBaseUrl, file);
337
+ const headers = await resolveHeaders();
338
+ if (ImageView && isImageUrl(file)) {
339
+ setViewerOpen(true);
340
+ return;
341
+ }
342
+ const isPdf = String(file).toLowerCase().endsWith('.pdf') || url.toLowerCase().endsWith('.pdf');
343
+ if (Pdf && isPdf) {
344
+ if (!RNFS) {
345
+ setPdfHeaders(headers);
346
+ setPdfUri(url);
347
+ setPdfVisible(true);
348
+ return;
349
+ }
350
+ setDownloading(true);
351
+ setPdfLoading(true);
352
+ try {
353
+ const local = await downloadPdfToLocal({
354
+ RNFS,
355
+ url,
356
+ fileName: fileBaseName(file),
357
+ headers
358
+ });
359
+ setPdfHeaders({});
360
+ setPdfUri(local);
361
+ setPdfVisible(true);
362
+ } catch (e) {
363
+ Alert.alert('Error', String(e?.message ?? 'Could not open PDF'));
364
+ } finally {
365
+ setDownloading(false);
366
+ setPdfLoading(false);
367
+ }
368
+ return;
369
+ }
370
+ if (!RNFS || !FileViewer) {
371
+ Alert.alert('Viewer Missing', 'Install react-native-fs and react-native-file-viewer.');
372
+ return;
373
+ }
374
+ setDownloading(true);
375
+ try {
376
+ const localFile = `${RNFS.DocumentDirectoryPath}/${fileBaseName(file)}`;
377
+ await RNFS.downloadFile({
378
+ fromUrl: url,
379
+ toFile: localFile,
380
+ headers
381
+ }).promise;
382
+ await FileViewer.open(localFile, {
383
+ showOpenWithDialog: true
384
+ });
385
+ } catch {
386
+ Alert.alert('Error', 'Could not view the file.');
387
+ } finally {
388
+ setDownloading(false);
389
+ }
390
+ }, [FileViewer, ImageView, Pdf, RNFS, file, fileBaseUrl, resolveHeaders]);
391
+ const submit = useCallback(async () => {
392
+ if (!title || !description || !classOpt?.value || !sectionOpt?.value || !subjectOpt?.value || !file) {
393
+ Alert.alert('Error', 'Please enter all the details');
394
+ return;
395
+ }
396
+ const payload = {
397
+ id: props.noteId ?? undefined,
398
+ title,
399
+ description,
400
+ remark: description,
401
+ class_id: classOpt.value,
402
+ section_id: sectionOpt.value,
403
+ sub_id: subjectOpt.value,
404
+ file,
405
+ status
406
+ };
407
+ setSaving(true);
408
+ try {
409
+ const res = props.mode === 'create' ? await createNote(api, payload) : await updateNote(api, {
410
+ ...payload,
411
+ id: props.noteId ?? payload.id
412
+ });
413
+ const response = res;
414
+ if (response?.Status && response.Status !== 'Success') {
415
+ Alert.alert('Failed', String(response?.msg ?? 'Request failed'));
416
+ return;
417
+ }
418
+ Alert.alert('Success', props.mode === 'create' ? 'Note created successfully' : 'Note updated successfully');
419
+ props.onDone();
420
+ } catch (e) {
421
+ Alert.alert('Error', String(e?.message ?? 'Could not save note'));
422
+ } finally {
423
+ setSaving(false);
424
+ }
425
+ }, [api, classOpt?.value, description, file, props, sectionOpt?.value, status, subjectOpt?.value, title]);
426
+ if (loading) {
427
+ return /*#__PURE__*/_jsx(LoadingState, {});
428
+ }
429
+ const fileUrl = file ? resolveNoteUrl(fileBaseUrl, file) : '';
430
+ const headers = authToken || schoolCode ? {
431
+ ...(authToken ? {
432
+ Authorization: authToken
433
+ } : {}),
434
+ ...(schoolCode ? {
435
+ school_code: schoolCode
436
+ } : {})
437
+ } : undefined;
438
+ return /*#__PURE__*/_jsxs(SafeAreaView, {
439
+ style: styles.root,
440
+ children: [/*#__PURE__*/_jsxs(ScrollView, {
441
+ contentContainerStyle: styles.content,
442
+ children: [/*#__PURE__*/_jsxs(View, {
443
+ style: styles.headerRow,
444
+ children: [/*#__PURE__*/_jsx(Text, {
445
+ style: styles.title,
446
+ children: props.mode === 'create' ? 'Create Note' : 'Edit Note'
447
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
448
+ onPress: props.onCancel,
449
+ style: styles.secondaryBtn,
450
+ children: /*#__PURE__*/_jsx(Text, {
451
+ style: styles.secondaryBtnText,
452
+ children: "Back"
453
+ })
454
+ })]
455
+ }), /*#__PURE__*/_jsxs(View, {
456
+ style: styles.card,
457
+ children: [/*#__PURE__*/_jsxs(View, {
458
+ style: styles.field,
459
+ children: [/*#__PURE__*/_jsx(Text, {
460
+ style: styles.label,
461
+ children: "Title"
462
+ }), /*#__PURE__*/_jsx(TextInput, {
463
+ value: title,
464
+ onChangeText: setTitle,
465
+ style: styles.input,
466
+ placeholder: "Enter title",
467
+ placeholderTextColor: "#9CA3AF"
468
+ })]
469
+ }), /*#__PURE__*/_jsxs(View, {
470
+ style: styles.field,
471
+ children: [/*#__PURE__*/_jsx(Text, {
472
+ style: styles.label,
473
+ children: "Description"
474
+ }), /*#__PURE__*/_jsx(TextInput, {
475
+ value: description,
476
+ onChangeText: setDescription,
477
+ style: [styles.input, styles.textarea],
478
+ placeholder: "Enter description",
479
+ placeholderTextColor: "#9CA3AF",
480
+ multiline: true
481
+ })]
482
+ }), /*#__PURE__*/_jsx(SelectField, {
483
+ label: "Class",
484
+ value: classOpt,
485
+ options: classes,
486
+ placeholder: "Select class",
487
+ onChange: setClassOpt
488
+ }), /*#__PURE__*/_jsx(SelectField, {
489
+ label: "Section",
490
+ value: sectionOpt,
491
+ options: sections,
492
+ placeholder: "Select section",
493
+ onChange: setSectionOpt
494
+ }), /*#__PURE__*/_jsx(SelectField, {
495
+ label: "Subject",
496
+ value: subjectOpt,
497
+ options: subjects,
498
+ placeholder: props.sessionId === undefined ? 'Provide sessionId' : 'Select subject',
499
+ onChange: setSubjectOpt
500
+ }), /*#__PURE__*/_jsxs(View, {
501
+ style: styles.field,
502
+ children: [/*#__PURE__*/_jsx(Text, {
503
+ style: styles.label,
504
+ children: "Status"
505
+ }), /*#__PURE__*/_jsxs(View, {
506
+ style: styles.row,
507
+ children: [/*#__PURE__*/_jsx(TouchableOpacity, {
508
+ style: [styles.chip, status === 'publish' ? styles.chipActive : null],
509
+ onPress: () => setStatus('publish'),
510
+ children: /*#__PURE__*/_jsx(Text, {
511
+ style: [styles.chipText, status === 'publish' ? styles.chipTextActive : null],
512
+ children: "Publish"
513
+ })
514
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
515
+ style: [styles.chip, status === 'unpublish' ? styles.chipActive : null],
516
+ onPress: () => setStatus('unpublish'),
517
+ children: /*#__PURE__*/_jsx(Text, {
518
+ style: [styles.chipText, status === 'unpublish' ? styles.chipTextActive : null],
519
+ children: "Unpublish"
520
+ })
521
+ })]
522
+ })]
523
+ }), /*#__PURE__*/_jsxs(View, {
524
+ style: styles.field,
525
+ children: [/*#__PURE__*/_jsx(Text, {
526
+ style: styles.label,
527
+ children: "Attachment"
528
+ }), /*#__PURE__*/_jsxs(View, {
529
+ style: styles.row,
530
+ children: [/*#__PURE__*/_jsx(TouchableOpacity, {
531
+ style: styles.secondaryBtn,
532
+ onPress: () => pickDocument().catch(() => {}),
533
+ children: /*#__PURE__*/_jsx(Text, {
534
+ style: styles.secondaryBtnText,
535
+ children: file ? 'Change File' : 'Upload File'
536
+ })
537
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
538
+ style: styles.secondaryBtn,
539
+ onPress: () => pickCamera().catch(() => {}),
540
+ children: /*#__PURE__*/_jsx(Text, {
541
+ style: styles.secondaryBtnText,
542
+ children: "Camera"
543
+ })
544
+ })]
545
+ }), file ? /*#__PURE__*/_jsxs(Text, {
546
+ style: styles.fileText,
547
+ children: ["Selected: ", fileBaseName(file)]
548
+ }) : null, file ? isImageUrl(file) ? /*#__PURE__*/_jsx(TouchableOpacity, {
549
+ style: styles.previewWrap,
550
+ onPress: () => setViewerOpen(true),
551
+ activeOpacity: 0.9,
552
+ children: /*#__PURE__*/_jsx(Image, {
553
+ source: {
554
+ uri: fileUrl,
555
+ headers
556
+ },
557
+ style: styles.previewImage,
558
+ resizeMode: "contain"
559
+ })
560
+ }) : /*#__PURE__*/_jsx(TouchableOpacity, {
561
+ style: styles.documentBtn,
562
+ onPress: () => viewAttachment().catch(() => {}),
563
+ disabled: downloading,
564
+ children: /*#__PURE__*/_jsx(Text, {
565
+ style: styles.documentBtnText,
566
+ children: downloading ? 'Opening Document...' : 'View Document'
567
+ })
568
+ }) : null]
569
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
570
+ style: styles.primaryBtn,
571
+ onPress: () => submit().catch(() => {}),
572
+ disabled: saving,
573
+ children: saving ? /*#__PURE__*/_jsx(ActivityIndicator, {
574
+ color: "#FFFFFF"
575
+ }) : /*#__PURE__*/_jsx(Text, {
576
+ style: styles.primaryBtnText,
577
+ children: props.mode === 'create' ? 'Create Note' : 'Update Note'
578
+ })
579
+ })]
580
+ })]
581
+ }), ImageView && file && isImageUrl(file) ? /*#__PURE__*/_jsx(ImageView, {
582
+ images: [{
583
+ uri: fileUrl,
584
+ headers
585
+ }],
586
+ imageIndex: 0,
587
+ visible: viewerOpen,
588
+ onRequestClose: () => setViewerOpen(false)
589
+ }) : null, /*#__PURE__*/_jsx(Modal, {
590
+ visible: pdfVisible,
591
+ animationType: "slide",
592
+ children: /*#__PURE__*/_jsxs(SafeAreaView, {
593
+ style: styles.root,
594
+ children: [/*#__PURE__*/_jsxs(View, {
595
+ style: styles.headerRow,
596
+ children: [/*#__PURE__*/_jsx(Text, {
597
+ style: styles.title,
598
+ children: "PDF Viewer"
599
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
600
+ onPress: () => {
601
+ setPdfVisible(false);
602
+ setPdfUri('');
603
+ setPdfHeaders({});
604
+ },
605
+ style: styles.secondaryBtn,
606
+ children: /*#__PURE__*/_jsx(Text, {
607
+ style: styles.secondaryBtnText,
608
+ children: "Close"
609
+ })
610
+ })]
611
+ }), pdfLoading ? /*#__PURE__*/_jsx(LoadingState, {}) : Pdf && pdfUri ? /*#__PURE__*/_jsx(Pdf, {
612
+ source: {
613
+ uri: pdfUri,
614
+ cache: true,
615
+ headers: pdfHeaders
616
+ },
617
+ style: {
618
+ flex: 1
619
+ }
620
+ }) : /*#__PURE__*/_jsx(EmptyState, {
621
+ title: "PDF Viewer Missing"
622
+ })]
623
+ })
624
+ })]
625
+ });
626
+ }
627
+ export function NotesScreen(props) {
628
+ const {
629
+ api,
630
+ fileBaseUrl,
631
+ authToken,
632
+ getAuthToken,
633
+ schoolCode
634
+ } = useERP();
635
+ const ImageView = useMemo(() => tryGetImageViewer(), []);
636
+ const Pdf = useMemo(() => tryGetPdf(), []);
637
+ const RNFS = useMemo(() => tryGetRNFS(), []);
638
+ const FileViewer = useMemo(() => tryGetFileViewer(), []);
639
+ const [mode, setMode] = useState('list');
640
+ const [editNoteId, setEditNoteId] = useState(null);
641
+ const [classes, setClasses] = useState([]);
642
+ const [sections, setSections] = useState([]);
643
+ const [subjects, setSubjects] = useState([]);
644
+ const [classOpt, setClassOpt] = useState(null);
645
+ const [sectionOpt, setSectionOpt] = useState(null);
646
+ const [subjectOpt, setSubjectOpt] = useState(null);
647
+ const [items, setItems] = useState([]);
648
+ const [page, setPage] = useState(props.page ?? 1);
649
+ const [totalPages, setTotalPages] = useState(1);
650
+ const [loading, setLoading] = useState(true);
651
+ const [refreshing, setRefreshing] = useState(false);
652
+ const [loadingMore, setLoadingMore] = useState(false);
653
+ const [error, setError] = useState(null);
654
+ const [selectedNote, setSelectedNote] = useState(null);
655
+ const [viewerOpen, setViewerOpen] = useState(false);
656
+ const [pdfVisible, setPdfVisible] = useState(false);
657
+ const [pdfUri, setPdfUri] = useState('');
658
+ const [pdfHeaders, setPdfHeaders] = useState({});
659
+ const [pdfLoading, setPdfLoading] = useState(false);
660
+ const [downloading, setDownloading] = useState(false);
661
+ const resolveHeaders = useCallback(async () => {
662
+ const nextToken = (await getAuthToken?.()) ?? authToken;
663
+ return {
664
+ ...(nextToken ? {
665
+ Authorization: nextToken
666
+ } : {}),
667
+ ...(schoolCode ? {
668
+ school_code: schoolCode
669
+ } : {})
670
+ };
671
+ }, [authToken, getAuthToken, schoolCode]);
672
+ useEffect(() => {
673
+ fetchNotesClasses(api).then(res => {
674
+ const opts = normalizeOptions(res);
675
+ setClasses(opts);
676
+ if (props.classId !== undefined) {
677
+ setClassOpt(opts.find(it => String(it.value) === String(props.classId)) ?? null);
678
+ }
679
+ }).catch(e => setError(String(e?.message ?? 'Could not load classes')));
680
+ }, [api, props.classId]);
681
+ useEffect(() => {
682
+ if (!classOpt?.value) {
683
+ setSections([]);
684
+ setSubjects([]);
685
+ setSectionOpt(props.sectionId === undefined ? null : sectionOpt);
686
+ return;
687
+ }
688
+ fetchNotesSections(api, {
689
+ classId: classOpt.value
690
+ }).then(res => {
691
+ const opts = normalizeOptions(res);
692
+ setSections(opts);
693
+ if (props.sectionId !== undefined) {
694
+ setSectionOpt(opts.find(it => String(it.value) === String(props.sectionId)) ?? null);
695
+ }
696
+ }).catch(() => {});
697
+ if (props.sessionId !== undefined) {
698
+ fetchNotesSubjects(api, {
699
+ classId: classOpt.value,
700
+ sessionId: props.sessionId
701
+ }).then(res => setSubjects(normalizeOptions(res))).catch(() => {});
702
+ }
703
+ }, [api, classOpt?.value, props.sectionId, props.sessionId]);
704
+ const loadList = useCallback(async (nextPage, reset = false) => {
705
+ try {
706
+ if (reset) {
707
+ setRefreshing(true);
708
+ } else if (nextPage === (props.page ?? 1) && !items.length) {
709
+ setLoading(true);
710
+ } else {
711
+ setLoadingMore(true);
712
+ }
713
+ setError(null);
714
+ const res = await fetchNotesList(api, {
715
+ page: nextPage,
716
+ perPage: props.perPage,
717
+ classId: classOpt?.value,
718
+ sectionId: sectionOpt?.value,
719
+ subjectId: subjectOpt?.value
720
+ });
721
+ const list = normalizeList(res?.data ?? res);
722
+ setItems(prev => reset ? list : [...prev, ...list]);
723
+ setPage(nextPage);
724
+ setTotalPages(Number(res?.pagination?.total_pages ?? 1));
725
+ } catch (e) {
726
+ setError(String(e?.message ?? 'Could not load notes'));
727
+ } finally {
728
+ setLoading(false);
729
+ setRefreshing(false);
730
+ setLoadingMore(false);
731
+ }
732
+ }, [api, classOpt?.value, items.length, props.page, props.perPage, sectionOpt?.value, subjectOpt?.value]);
733
+ useEffect(() => {
734
+ if (mode !== 'list') return;
735
+ loadList(props.page ?? 1, true).catch(() => {});
736
+ }, [loadList, mode, props.page]);
737
+ const viewNoteAttachment = useCallback(async () => {
738
+ const file = String(selectedNote?.file ?? '');
739
+ if (!file) {
740
+ Alert.alert('No Attachment');
741
+ return;
742
+ }
743
+ const url = resolveNoteUrl(fileBaseUrl, file);
744
+ const headers = await resolveHeaders();
745
+ if (ImageView && isImageUrl(file)) {
746
+ setViewerOpen(true);
747
+ return;
748
+ }
749
+ const isPdf = String(file).toLowerCase().endsWith('.pdf') || url.toLowerCase().endsWith('.pdf');
750
+ if (Pdf && isPdf) {
751
+ if (!RNFS) {
752
+ setPdfHeaders(headers);
753
+ setPdfUri(url);
754
+ setPdfVisible(true);
755
+ return;
756
+ }
757
+ setDownloading(true);
758
+ setPdfLoading(true);
759
+ try {
760
+ const local = await downloadPdfToLocal({
761
+ RNFS,
762
+ url,
763
+ fileName: fileBaseName(file),
764
+ headers
765
+ });
766
+ setPdfHeaders({});
767
+ setPdfUri(local);
768
+ setPdfVisible(true);
769
+ } catch (e) {
770
+ Alert.alert('Error', String(e?.message ?? 'Could not open PDF'));
771
+ } finally {
772
+ setDownloading(false);
773
+ setPdfLoading(false);
774
+ }
775
+ return;
776
+ }
777
+ if (!RNFS || !FileViewer) {
778
+ Alert.alert('Viewer Missing', 'Install react-native-fs and react-native-file-viewer.');
779
+ return;
780
+ }
781
+ setDownloading(true);
782
+ try {
783
+ const localFile = `${RNFS.DocumentDirectoryPath}/${fileBaseName(file)}`;
784
+ await RNFS.downloadFile({
785
+ fromUrl: url,
786
+ toFile: localFile,
787
+ headers
788
+ }).promise;
789
+ await FileViewer.open(localFile, {
790
+ showOpenWithDialog: true
791
+ });
792
+ } catch {
793
+ Alert.alert('Error', 'Could not view the file.');
794
+ } finally {
795
+ setDownloading(false);
796
+ }
797
+ }, [FileViewer, ImageView, Pdf, RNFS, fileBaseUrl, resolveHeaders, selectedNote?.file]);
798
+ if (mode === 'create') {
799
+ return /*#__PURE__*/_jsx(NoteEditor, {
800
+ mode: "create",
801
+ sessionId: props.sessionId,
802
+ defaults: {
803
+ classId: classOpt?.value ?? props.classId,
804
+ sectionId: sectionOpt?.value ?? props.sectionId,
805
+ subjectId: subjectOpt?.value
806
+ },
807
+ onDone: () => {
808
+ setMode('list');
809
+ loadList(props.page ?? 1, true).catch(() => {});
810
+ },
811
+ onCancel: () => setMode('list')
812
+ });
813
+ }
814
+ if (mode === 'edit' && editNoteId !== null) {
815
+ return /*#__PURE__*/_jsx(NoteEditor, {
816
+ mode: "edit",
817
+ noteId: editNoteId,
818
+ sessionId: props.sessionId,
819
+ defaults: {
820
+ classId: classOpt?.value ?? props.classId,
821
+ sectionId: sectionOpt?.value ?? props.sectionId,
822
+ subjectId: subjectOpt?.value
823
+ },
824
+ onDone: () => {
825
+ setMode('list');
826
+ setEditNoteId(null);
827
+ loadList(props.page ?? 1, true).catch(() => {});
828
+ },
829
+ onCancel: () => {
830
+ setMode('list');
831
+ setEditNoteId(null);
832
+ }
833
+ });
834
+ }
835
+ if (loading && !items.length) {
836
+ return /*#__PURE__*/_jsx(LoadingState, {});
837
+ }
838
+ if (error && !items.length) {
839
+ return /*#__PURE__*/_jsx(ErrorState, {
840
+ message: error
841
+ });
842
+ }
843
+ return /*#__PURE__*/_jsxs(SafeAreaView, {
844
+ style: styles.root,
845
+ children: [/*#__PURE__*/_jsx(FlatList, {
846
+ data: items,
847
+ keyExtractor: (item, index) => String(item?.id ?? index),
848
+ contentContainerStyle: styles.content,
849
+ refreshControl: /*#__PURE__*/_jsx(RefreshControl, {
850
+ refreshing: refreshing,
851
+ onRefresh: () => {
852
+ loadList(props.page ?? 1, true).catch(() => {});
853
+ },
854
+ tintColor: "#1D4ED8"
855
+ }),
856
+ ListHeaderComponent: /*#__PURE__*/_jsxs(View, {
857
+ children: [/*#__PURE__*/_jsxs(View, {
858
+ style: styles.headerRow,
859
+ children: [/*#__PURE__*/_jsx(Text, {
860
+ style: styles.title,
861
+ children: "Notes"
862
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
863
+ style: styles.primaryBtnCompact,
864
+ onPress: () => setMode('create'),
865
+ children: /*#__PURE__*/_jsx(Text, {
866
+ style: styles.primaryBtnText,
867
+ children: "Create"
868
+ })
869
+ })]
870
+ }), /*#__PURE__*/_jsxs(View, {
871
+ style: styles.card,
872
+ children: [/*#__PURE__*/_jsx(SelectField, {
873
+ label: "Class",
874
+ value: classOpt,
875
+ options: classes,
876
+ placeholder: "All classes",
877
+ onChange: next => {
878
+ setClassOpt(next);
879
+ setSectionOpt(null);
880
+ setSubjectOpt(null);
881
+ }
882
+ }), /*#__PURE__*/_jsx(SelectField, {
883
+ label: "Section",
884
+ value: sectionOpt,
885
+ options: sections,
886
+ placeholder: "All sections",
887
+ onChange: setSectionOpt
888
+ }), /*#__PURE__*/_jsx(SelectField, {
889
+ label: "Subject",
890
+ value: subjectOpt,
891
+ options: subjects,
892
+ placeholder: props.sessionId === undefined ? 'Provide sessionId' : 'All subjects',
893
+ onChange: setSubjectOpt
894
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
895
+ style: styles.primaryBtn,
896
+ onPress: () => loadList(props.page ?? 1, true).catch(() => {}),
897
+ children: /*#__PURE__*/_jsx(Text, {
898
+ style: styles.primaryBtnText,
899
+ children: "Search"
900
+ })
901
+ })]
902
+ })]
903
+ }),
904
+ renderItem: ({
905
+ item
906
+ }) => /*#__PURE__*/_jsxs(View, {
907
+ style: styles.card,
908
+ children: [/*#__PURE__*/_jsx(Text, {
909
+ style: styles.noteTitle,
910
+ children: String(item?.title ?? 'Note')
911
+ }), /*#__PURE__*/_jsx(Text, {
912
+ style: styles.noteDesc,
913
+ numberOfLines: 3,
914
+ children: String(item?.description ?? item?.remark ?? '-')
915
+ }), /*#__PURE__*/_jsx(Text, {
916
+ style: styles.noteMeta,
917
+ children: String(item?.created_at ?? '')
918
+ }), /*#__PURE__*/_jsxs(View, {
919
+ style: styles.row,
920
+ children: [/*#__PURE__*/_jsx(TouchableOpacity, {
921
+ style: styles.secondaryBtn,
922
+ onPress: () => setSelectedNote(item),
923
+ children: /*#__PURE__*/_jsx(Text, {
924
+ style: styles.secondaryBtnText,
925
+ children: "View"
926
+ })
927
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
928
+ style: styles.secondaryBtn,
929
+ onPress: () => {
930
+ setEditNoteId(item?.id ?? null);
931
+ setMode('edit');
932
+ },
933
+ children: /*#__PURE__*/_jsx(Text, {
934
+ style: styles.secondaryBtnText,
935
+ children: "Edit"
936
+ })
937
+ })]
938
+ })]
939
+ }),
940
+ ListEmptyComponent: /*#__PURE__*/_jsx(EmptyState, {
941
+ title: "No Data Found"
942
+ }),
943
+ onEndReached: () => {
944
+ if (!loadingMore && page < totalPages) {
945
+ loadList(page + 1).catch(() => {});
946
+ }
947
+ },
948
+ onEndReachedThreshold: 0.4,
949
+ ListFooterComponent: loadingMore ? /*#__PURE__*/_jsx(ActivityIndicator, {
950
+ color: "#1D4ED8"
951
+ }) : null
952
+ }), /*#__PURE__*/_jsx(Modal, {
953
+ visible: selectedNote !== null,
954
+ transparent: true,
955
+ animationType: "slide",
956
+ children: /*#__PURE__*/_jsx(Pressable, {
957
+ style: styles.modalOverlay,
958
+ onPress: () => setSelectedNote(null),
959
+ children: /*#__PURE__*/_jsxs(Pressable, {
960
+ style: styles.detailCard,
961
+ onPress: () => {},
962
+ children: [/*#__PURE__*/_jsxs(View, {
963
+ style: styles.headerRow,
964
+ children: [/*#__PURE__*/_jsx(Text, {
965
+ style: styles.title,
966
+ children: "Note Details"
967
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
968
+ onPress: () => setSelectedNote(null),
969
+ style: styles.secondaryBtn,
970
+ children: /*#__PURE__*/_jsx(Text, {
971
+ style: styles.secondaryBtnText,
972
+ children: "Close"
973
+ })
974
+ })]
975
+ }), selectedNote ? /*#__PURE__*/_jsxs(ScrollView, {
976
+ children: [/*#__PURE__*/_jsx(Text, {
977
+ style: styles.noteTitle,
978
+ children: String(selectedNote?.title ?? '')
979
+ }), /*#__PURE__*/_jsx(Text, {
980
+ style: styles.noteMeta,
981
+ children: String(selectedNote?.created_at ?? '')
982
+ }), /*#__PURE__*/_jsx(Text, {
983
+ style: styles.noteDescFull,
984
+ children: String(selectedNote?.description ?? selectedNote?.remark ?? '-')
985
+ }), selectedNote?.file ? isImageUrl(String(selectedNote.file)) ? /*#__PURE__*/_jsx(TouchableOpacity, {
986
+ style: styles.previewWrap,
987
+ onPress: () => setViewerOpen(true),
988
+ activeOpacity: 0.9,
989
+ children: /*#__PURE__*/_jsx(Image, {
990
+ source: {
991
+ uri: resolveNoteUrl(fileBaseUrl, String(selectedNote.file)),
992
+ headers: authToken || schoolCode ? {
993
+ ...(authToken ? {
994
+ Authorization: authToken
995
+ } : {}),
996
+ ...(schoolCode ? {
997
+ school_code: schoolCode
998
+ } : {})
999
+ } : undefined
1000
+ },
1001
+ style: styles.previewImage,
1002
+ resizeMode: "contain"
1003
+ })
1004
+ }) : /*#__PURE__*/_jsx(TouchableOpacity, {
1005
+ style: styles.documentBtn,
1006
+ onPress: () => viewNoteAttachment().catch(() => {}),
1007
+ disabled: downloading,
1008
+ children: /*#__PURE__*/_jsx(Text, {
1009
+ style: styles.documentBtnText,
1010
+ children: downloading ? 'Opening Document...' : 'View Document'
1011
+ })
1012
+ }) : null]
1013
+ }) : null]
1014
+ })
1015
+ })
1016
+ }), ImageView && selectedNote?.file && isImageUrl(String(selectedNote.file)) ? /*#__PURE__*/_jsx(ImageView, {
1017
+ images: [{
1018
+ uri: resolveNoteUrl(fileBaseUrl, String(selectedNote.file))
1019
+ }],
1020
+ imageIndex: 0,
1021
+ visible: viewerOpen,
1022
+ onRequestClose: () => setViewerOpen(false)
1023
+ }) : null, /*#__PURE__*/_jsx(Modal, {
1024
+ visible: pdfVisible,
1025
+ animationType: "slide",
1026
+ children: /*#__PURE__*/_jsxs(SafeAreaView, {
1027
+ style: styles.root,
1028
+ children: [/*#__PURE__*/_jsxs(View, {
1029
+ style: styles.headerRow,
1030
+ children: [/*#__PURE__*/_jsx(Text, {
1031
+ style: styles.title,
1032
+ children: "PDF Viewer"
1033
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
1034
+ onPress: () => {
1035
+ setPdfVisible(false);
1036
+ setPdfUri('');
1037
+ setPdfHeaders({});
1038
+ },
1039
+ style: styles.secondaryBtn,
1040
+ children: /*#__PURE__*/_jsx(Text, {
1041
+ style: styles.secondaryBtnText,
1042
+ children: "Close"
1043
+ })
1044
+ })]
1045
+ }), pdfLoading ? /*#__PURE__*/_jsx(LoadingState, {}) : Pdf && pdfUri ? /*#__PURE__*/_jsx(Pdf, {
1046
+ source: {
1047
+ uri: pdfUri,
1048
+ cache: true,
1049
+ headers: pdfHeaders
1050
+ },
1051
+ style: {
1052
+ flex: 1
1053
+ }
1054
+ }) : /*#__PURE__*/_jsx(EmptyState, {
1055
+ title: "PDF Viewer Missing"
1056
+ })]
1057
+ })
1058
+ })]
1059
+ });
1060
+ }
1061
+ const styles = StyleSheet.create({
1062
+ root: {
1063
+ flex: 1,
1064
+ backgroundColor: '#F9FAFB'
1065
+ },
1066
+ content: {
1067
+ padding: 16,
1068
+ paddingBottom: 32
1069
+ },
1070
+ headerRow: {
1071
+ flexDirection: 'row',
1072
+ alignItems: 'center',
1073
+ justifyContent: 'space-between',
1074
+ gap: 12,
1075
+ marginBottom: 12
1076
+ },
1077
+ title: {
1078
+ fontSize: 20,
1079
+ fontWeight: '700',
1080
+ color: '#111827'
1081
+ },
1082
+ card: {
1083
+ backgroundColor: '#FFFFFF',
1084
+ borderWidth: 1,
1085
+ borderColor: '#E5E7EB',
1086
+ borderRadius: 14,
1087
+ padding: 14,
1088
+ marginBottom: 12
1089
+ },
1090
+ field: {
1091
+ marginBottom: 12
1092
+ },
1093
+ label: {
1094
+ fontSize: 13,
1095
+ fontWeight: '600',
1096
+ color: '#1F2937',
1097
+ marginBottom: 6
1098
+ },
1099
+ select: {
1100
+ borderWidth: 1,
1101
+ borderColor: '#D1D5DB',
1102
+ borderRadius: 10,
1103
+ backgroundColor: '#FFFFFF',
1104
+ paddingHorizontal: 12,
1105
+ paddingVertical: 12
1106
+ },
1107
+ selectText: {
1108
+ fontSize: 14,
1109
+ color: '#111827'
1110
+ },
1111
+ input: {
1112
+ borderWidth: 1,
1113
+ borderColor: '#D1D5DB',
1114
+ borderRadius: 10,
1115
+ backgroundColor: '#FFFFFF',
1116
+ paddingHorizontal: 12,
1117
+ paddingVertical: 12,
1118
+ fontSize: 14,
1119
+ color: '#111827'
1120
+ },
1121
+ textarea: {
1122
+ minHeight: 120,
1123
+ textAlignVertical: 'top'
1124
+ },
1125
+ row: {
1126
+ flexDirection: 'row',
1127
+ alignItems: 'center',
1128
+ gap: 10,
1129
+ flexWrap: 'wrap'
1130
+ },
1131
+ primaryBtn: {
1132
+ backgroundColor: '#1D4ED8',
1133
+ borderRadius: 12,
1134
+ paddingVertical: 14,
1135
+ alignItems: 'center',
1136
+ justifyContent: 'center',
1137
+ marginTop: 6
1138
+ },
1139
+ primaryBtnCompact: {
1140
+ backgroundColor: '#1D4ED8',
1141
+ borderRadius: 10,
1142
+ paddingHorizontal: 16,
1143
+ paddingVertical: 10,
1144
+ alignItems: 'center',
1145
+ justifyContent: 'center'
1146
+ },
1147
+ primaryBtnText: {
1148
+ color: '#FFFFFF',
1149
+ fontSize: 14,
1150
+ fontWeight: '700'
1151
+ },
1152
+ secondaryBtn: {
1153
+ borderWidth: 1,
1154
+ borderColor: '#BFDBFE',
1155
+ backgroundColor: '#EFF6FF',
1156
+ borderRadius: 10,
1157
+ paddingHorizontal: 14,
1158
+ paddingVertical: 10
1159
+ },
1160
+ secondaryBtnText: {
1161
+ fontSize: 14,
1162
+ fontWeight: '700',
1163
+ color: '#1D4ED8'
1164
+ },
1165
+ chip: {
1166
+ flex: 1,
1167
+ minWidth: 120,
1168
+ borderWidth: 1,
1169
+ borderColor: '#BFDBFE',
1170
+ backgroundColor: '#EFF6FF',
1171
+ paddingVertical: 10,
1172
+ borderRadius: 10,
1173
+ alignItems: 'center'
1174
+ },
1175
+ chipActive: {
1176
+ backgroundColor: '#1D4ED8',
1177
+ borderColor: '#1D4ED8'
1178
+ },
1179
+ chipText: {
1180
+ fontSize: 14,
1181
+ fontWeight: '700',
1182
+ color: '#1D4ED8'
1183
+ },
1184
+ chipTextActive: {
1185
+ color: '#FFFFFF'
1186
+ },
1187
+ fileText: {
1188
+ fontSize: 13,
1189
+ color: '#374151',
1190
+ marginTop: 8
1191
+ },
1192
+ previewWrap: {
1193
+ marginTop: 12,
1194
+ borderWidth: 1,
1195
+ borderColor: '#E5E7EB',
1196
+ borderRadius: 12,
1197
+ overflow: 'hidden',
1198
+ backgroundColor: '#F9FAFB',
1199
+ height: 220
1200
+ },
1201
+ previewImage: {
1202
+ width: '100%',
1203
+ height: '100%'
1204
+ },
1205
+ documentBtn: {
1206
+ marginTop: 12,
1207
+ borderWidth: 1,
1208
+ borderStyle: 'dashed',
1209
+ borderColor: '#1D4ED8',
1210
+ borderRadius: 12,
1211
+ paddingVertical: 16,
1212
+ alignItems: 'center'
1213
+ },
1214
+ documentBtnText: {
1215
+ fontSize: 15,
1216
+ fontWeight: '700',
1217
+ color: '#1D4ED8'
1218
+ },
1219
+ noteTitle: {
1220
+ fontSize: 16,
1221
+ fontWeight: '700',
1222
+ color: '#111827',
1223
+ marginBottom: 6
1224
+ },
1225
+ noteDesc: {
1226
+ fontSize: 14,
1227
+ lineHeight: 20,
1228
+ color: '#374151',
1229
+ marginBottom: 8
1230
+ },
1231
+ noteDescFull: {
1232
+ fontSize: 14,
1233
+ lineHeight: 22,
1234
+ color: '#374151',
1235
+ marginTop: 10
1236
+ },
1237
+ noteMeta: {
1238
+ fontSize: 12,
1239
+ color: '#6B7280',
1240
+ marginBottom: 10
1241
+ },
1242
+ modalOverlay: {
1243
+ flex: 1,
1244
+ backgroundColor: 'rgba(0,0,0,0.35)',
1245
+ justifyContent: 'center',
1246
+ padding: 16
1247
+ },
1248
+ modalCard: {
1249
+ backgroundColor: '#FFFFFF',
1250
+ borderRadius: 14,
1251
+ maxHeight: '75%',
1252
+ padding: 16
1253
+ },
1254
+ detailCard: {
1255
+ backgroundColor: '#FFFFFF',
1256
+ borderRadius: 14,
1257
+ maxHeight: '85%',
1258
+ padding: 16
1259
+ },
1260
+ modalTitle: {
1261
+ fontSize: 16,
1262
+ fontWeight: '700',
1263
+ color: '#111827',
1264
+ marginBottom: 12
1265
+ },
1266
+ optionRow: {
1267
+ paddingVertical: 12,
1268
+ borderBottomWidth: 1,
1269
+ borderBottomColor: '#F3F4F6'
1270
+ },
1271
+ optionText: {
1272
+ fontSize: 14,
1273
+ color: '#111827'
1274
+ },
1275
+ optionEmpty: {
1276
+ fontSize: 14,
1277
+ color: '#6B7280',
1278
+ textAlign: 'center',
1279
+ paddingVertical: 16
1280
+ }
1281
+ });