@ubkinfotech/tecaher-erp 0.1.0 → 0.1.2

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 (28) hide show
  1. package/README.md +143 -67
  2. package/lib/commonjs/core/api/endpoints.js +1 -1
  3. package/lib/commonjs/core/auth/authContext.js +1 -2
  4. package/lib/commonjs/core/provider/ERPProvider.js +1 -2
  5. package/lib/commonjs/modules/assignment/screens/AssignmentScreen.js +295 -80
  6. package/lib/commonjs/modules/assignment/services/assignmentService.js +2 -2
  7. package/lib/commonjs/modules/attendance/screens/AttendanceScreen.js +1 -2
  8. package/lib/commonjs/modules/leaveRequest/screens/LeaveRequestScreen.js +7 -7
  9. package/lib/commonjs/modules/leaveRequest/services/leaveRequestService.js +10 -2
  10. package/lib/commonjs/modules/marks/screens/MarksScreen.js +1 -2
  11. package/lib/commonjs/modules/myAttendance/screens/MyAttendanceScreen.js +1 -2
  12. package/lib/commonjs/modules/notes/screens/NotesScreen.js +410 -97
  13. package/lib/commonjs/modules/notes/services/notesService.js +10 -2
  14. package/lib/commonjs/modules/noticeboard/screens/NoticeBoardScreen.js +1 -2
  15. package/lib/commonjs/modules/notification/screens/NotificationScreen.js +1 -2
  16. package/lib/commonjs/modules/promoteStudent/screens/PromoteStudentScreen.js +1 -2
  17. package/lib/commonjs/modules/timetable/screens/TimeTableScreen.js +1 -2
  18. package/lib/module/core/api/endpoints.js +1 -1
  19. package/lib/module/modules/assignment/screens/AssignmentScreen.js +295 -79
  20. package/lib/module/modules/assignment/services/assignmentService.js +2 -2
  21. package/lib/module/modules/leaveRequest/screens/LeaveRequestScreen.js +6 -5
  22. package/lib/module/modules/leaveRequest/services/leaveRequestService.js +10 -2
  23. package/lib/module/modules/notes/screens/NotesScreen.js +410 -96
  24. package/lib/module/modules/notes/services/notesService.js +10 -2
  25. package/lib/typescript/modules/assignment/services/assignmentService.d.ts +1 -1
  26. package/lib/typescript/modules/leaveRequest/services/leaveRequestService.d.ts +1 -1
  27. package/lib/typescript/modules/notes/services/notesService.d.ts +1 -1
  28. package/package.json +3 -3
@@ -15,8 +15,7 @@ var _useHomeworkDetails = require("../hooks/useHomeworkDetails");
15
15
  var _useHomeworkSubmissions = require("../hooks/useHomeworkSubmissions");
16
16
  var _assignmentService = require("../services/assignmentService");
17
17
  var _jsxRuntime = require("react/jsx-runtime");
18
- function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
19
- function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
18
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
20
19
  function normalizeOptions(result) {
21
20
  if (!result) {
22
21
  return [];
@@ -132,6 +131,63 @@ function SelectModal({
132
131
  })]
133
132
  });
134
133
  }
134
+ function StatusBanner({
135
+ text,
136
+ busy = false,
137
+ tone = 'info'
138
+ }) {
139
+ const palette = tone === 'error' ? {
140
+ backgroundColor: '#FEF2F2',
141
+ borderColor: '#FECACA',
142
+ textColor: '#B91C1C',
143
+ spinnerColor: '#DC2626'
144
+ } : tone === 'success' ? {
145
+ backgroundColor: '#ECFDF5',
146
+ borderColor: '#A7F3D0',
147
+ textColor: '#047857',
148
+ spinnerColor: '#059669'
149
+ } : {
150
+ backgroundColor: '#EFF6FF',
151
+ borderColor: '#BFDBFE',
152
+ textColor: '#1D4ED8',
153
+ spinnerColor: '#2563EB'
154
+ };
155
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
156
+ style: [styles.statusBanner, {
157
+ backgroundColor: palette.backgroundColor,
158
+ borderColor: palette.borderColor
159
+ }],
160
+ children: [busy ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
161
+ size: "small",
162
+ color: palette.spinnerColor
163
+ }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
164
+ style: [styles.statusBannerText, {
165
+ color: palette.textColor
166
+ }],
167
+ children: text
168
+ })]
169
+ });
170
+ }
171
+ async function uploadAssignmentFilesBatch(args) {
172
+ const uploaded = [];
173
+ let failedCount = 0;
174
+ await Promise.all(args.files.map(async file => {
175
+ try {
176
+ const res = await (0, _assignmentService.uploadAssignmentFile)(args.api, file, args.schoolCode);
177
+ if (res?.Status === 'Success' && res?.data) {
178
+ uploaded.push(String(res.data));
179
+ return;
180
+ }
181
+ } catch {
182
+ // Keep partial success behavior for multi-file uploads.
183
+ }
184
+ failedCount += 1;
185
+ }));
186
+ return {
187
+ uploaded,
188
+ failedCount
189
+ };
190
+ }
135
191
  function resolveFileBaseUrl(args) {
136
192
  if (args.fileBaseUrl) {
137
193
  return args.fileBaseUrl.endsWith('/') ? args.fileBaseUrl : `${args.fileBaseUrl}/`;
@@ -480,9 +536,14 @@ function resolveUploadUrl(downloadsBaseUrl, rawPath) {
480
536
  }
481
537
  const cleaned = raw.replace(/\\/g, '/').replace(/^\/+/, '');
482
538
  const uploadsRoot = downloadsBaseUrl.replace(/uploads\/?$/i, '');
539
+ const uploadsBaseIndex = downloadsBaseUrl.toLowerCase().indexOf('uploads/');
540
+ const uploadsBase = uploadsBaseIndex >= 0 ? downloadsBaseUrl.slice(0, uploadsBaseIndex + 'uploads/'.length) : downloadsBaseUrl;
483
541
  if (cleaned.startsWith('uploads/')) {
484
542
  return encodeURI(`${uploadsRoot}${cleaned}`);
485
543
  }
544
+ if (cleaned.startsWith('school_')) {
545
+ return encodeURI(`${uploadsBase}${cleaned}`);
546
+ }
486
547
  if (cleaned.startsWith('student/homework/')) {
487
548
  return encodeURI(`${downloadsBaseUrl}${cleaned}`);
488
549
  }
@@ -914,6 +975,7 @@ function CreateAssignmentView({
914
975
  } = (0, _useERP.useERP)();
915
976
  const ImageView = (0, _react.useMemo)(() => tryGetImageViewer(), []);
916
977
  const Pdf = (0, _react.useMemo)(() => tryGetPdf(), []);
978
+ const Calendar = (0, _react.useMemo)(() => tryGetCalendar(), []);
917
979
  const RNFS = (0, _react.useMemo)(() => tryGetRNFS(), []);
918
980
  const FileViewer = (0, _react.useMemo)(() => tryGetFileViewer(), []);
919
981
  const [viewerOpen, setViewerOpen] = (0, _react.useState)(false);
@@ -924,7 +986,11 @@ function CreateAssignmentView({
924
986
  const [pdfHeaders, setPdfHeaders] = (0, _react.useState)({});
925
987
  const [pdfLoading, setPdfLoading] = (0, _react.useState)(false);
926
988
  const [isDownloading, setIsDownloading] = (0, _react.useState)(false);
927
- const [busy, setBusy] = (0, _react.useState)(false);
989
+ const [calendarOpen, setCalendarOpen] = (0, _react.useState)(false);
990
+ const [dateMode, setDateMode] = (0, _react.useState)('start');
991
+ const [isUploading, setIsUploading] = (0, _react.useState)(false);
992
+ const [isSubmitting, setIsSubmitting] = (0, _react.useState)(false);
993
+ const [uploadStatus, setUploadStatus] = (0, _react.useState)('');
928
994
  const [title, setTitle] = (0, _react.useState)('');
929
995
  const [description, setDescription] = (0, _react.useState)('');
930
996
  const [classOpt, setClassOpt] = (0, _react.useState)(null);
@@ -935,6 +1001,7 @@ function CreateAssignmentView({
935
1001
  const [homewDate, setHomewDate] = (0, _react.useState)('');
936
1002
  const [submissionDate, setSubmissionDate] = (0, _react.useState)('');
937
1003
  const [status, setStatus] = (0, _react.useState)('publish');
1004
+ const [markedDates, setMarkedDates] = (0, _react.useState)({});
938
1005
  const [fileList, setFileList] = (0, _react.useState)([]);
939
1006
  const [activeFile, setActiveFile] = (0, _react.useState)('');
940
1007
  const [fileError, setFileError] = (0, _react.useState)(null);
@@ -1077,20 +1144,28 @@ function CreateAssignmentView({
1077
1144
  type: asset.type ?? 'image/jpeg'
1078
1145
  };
1079
1146
  setFileError(null);
1080
- setBusy(true);
1147
+ setUploadStatus('Uploading image...');
1148
+ setIsUploading(true);
1081
1149
  try {
1082
- const uploaded = await (0, _assignmentService.uploadAssignmentFile)(api, file);
1150
+ const uploaded = await (0, _assignmentService.uploadAssignmentFile)(api, file, schoolCode);
1083
1151
  if (uploaded?.Status === 'Success' && uploaded.data) {
1084
1152
  const nextPath = String(uploaded.data);
1085
1153
  setFileList(prev => [...prev, nextPath]);
1086
1154
  setActiveFile(prev => prev || nextPath);
1155
+ setUploadStatus('Image uploaded successfully');
1087
1156
  } else {
1088
1157
  setFileError('File upload failed');
1158
+ setUploadStatus('Image upload failed');
1159
+ _reactNative.Alert.alert('Error', String(uploaded?.msg ?? 'File not uploaded'));
1089
1160
  }
1161
+ } catch (error) {
1162
+ setFileError('File upload failed');
1163
+ setUploadStatus('Image upload failed');
1164
+ _reactNative.Alert.alert('Error', String(error?.message ?? 'File not uploaded'));
1090
1165
  } finally {
1091
- setBusy(false);
1166
+ setIsUploading(false);
1092
1167
  }
1093
- }, [api]);
1168
+ }, [api, schoolCode]);
1094
1169
  const pickFiles = (0, _react.useCallback)(async () => {
1095
1170
  let dp = null;
1096
1171
  try {
@@ -1120,54 +1195,86 @@ function CreateAssignmentView({
1120
1195
  return;
1121
1196
  }
1122
1197
  setFileError(null);
1123
- setBusy(true);
1198
+ setUploadStatus(`Uploading ${files.length} file(s)...`);
1199
+ setIsUploading(true);
1124
1200
  try {
1125
- const uploaded = [];
1126
- for (const f of files) {
1127
- const res = await (0, _assignmentService.uploadAssignmentFile)(api, f);
1128
- if (res?.Status === 'Success' && res.data) {
1129
- uploaded.push(String(res.data));
1130
- }
1131
- }
1201
+ const {
1202
+ uploaded,
1203
+ failedCount
1204
+ } = await uploadAssignmentFilesBatch({
1205
+ api,
1206
+ files,
1207
+ schoolCode
1208
+ });
1132
1209
  if (!uploaded.length) {
1133
1210
  setFileError('File upload failed');
1211
+ setUploadStatus('File upload failed');
1212
+ _reactNative.Alert.alert('Error', 'File not uploaded');
1134
1213
  return;
1135
1214
  }
1136
1215
  setFileList(prev => [...prev, ...uploaded]);
1137
1216
  setActiveFile(prev => prev ? prev : uploaded[0] ?? '');
1217
+ if (failedCount > 0) {
1218
+ setFileError(`${failedCount} file(s) failed to upload`);
1219
+ setUploadStatus(`${uploaded.length} file(s) uploaded, ${failedCount} failed`);
1220
+ _reactNative.Alert.alert('Upload Incomplete', `${uploaded.length} file(s) uploaded and ${failedCount} failed.`);
1221
+ return;
1222
+ }
1223
+ setFileError(null);
1224
+ setUploadStatus(`${uploaded.length} file(s) uploaded successfully`);
1225
+ } catch (error) {
1226
+ setFileError('File upload failed');
1227
+ setUploadStatus('File upload failed');
1228
+ _reactNative.Alert.alert('Error', String(error?.message ?? 'File not uploaded'));
1138
1229
  } finally {
1139
- setBusy(false);
1230
+ setIsUploading(false);
1140
1231
  }
1141
- }, [api]);
1232
+ }, [api, schoolCode]);
1142
1233
  const submit = (0, _react.useCallback)(async () => {
1143
- if (!title || !description || !classOpt?.value || !sectionOpt?.value || !subjectOpt?.value || !homewDate || !submissionDate || !fileList.length) {
1144
- if (!fileList.length) {
1145
- setFileError('File is required');
1146
- }
1234
+ const missing = [];
1235
+ const selectedClassId = classOpt?.value;
1236
+ const selectedSectionId = sectionOpt?.value;
1237
+ const selectedSubjectId = subjectOpt?.value;
1238
+ if (!title) missing.push('title');
1239
+ if (!description) missing.push('description');
1240
+ if (!selectedClassId) missing.push('class');
1241
+ if (!selectedSectionId) missing.push('section');
1242
+ if (!selectedSubjectId) missing.push('subject');
1243
+ if (!homewDate) missing.push('homework date');
1244
+ if (!submissionDate) missing.push('submission date');
1245
+ if (missing.length) {
1246
+ _reactNative.Alert.alert('Missing Fields', `Please fill: ${missing.join(', ')}`);
1147
1247
  return;
1148
1248
  }
1149
1249
  const fileCsv = fileList.join(',');
1150
1250
  const payload = {
1151
1251
  title,
1152
1252
  description,
1153
- class_id: classOpt.value,
1154
- section_id: sectionOpt.value,
1155
- sub_id: subjectOpt.value,
1253
+ class_id: selectedClassId,
1254
+ section_id: selectedSectionId,
1255
+ sub_id: selectedSubjectId,
1156
1256
  homew_date: homewDate,
1157
1257
  submission_date: submissionDate,
1158
1258
  status,
1159
1259
  file: fileCsv
1160
1260
  };
1161
- setBusy(true);
1261
+ setIsSubmitting(true);
1162
1262
  try {
1163
- await (0, _assignmentService.createAssignment)(api, payload);
1263
+ const response = await (0, _assignmentService.createAssignment)(api, payload);
1264
+ if (response?.Status && response.Status !== 'Success') {
1265
+ _reactNative.Alert.alert('Failed', String(response?.msg ?? response?.Status ?? 'Could not create assignment'));
1266
+ return;
1267
+ }
1268
+ _reactNative.Alert.alert('Success', String(response?.msg ?? 'Assignment created Successfully'));
1164
1269
  onDone();
1270
+ } catch (error) {
1271
+ _reactNative.Alert.alert('Failed', String(error?.message ?? 'Could not create assignment'));
1165
1272
  } finally {
1166
- setBusy(false);
1273
+ setIsSubmitting(false);
1167
1274
  }
1168
1275
  }, [api, classOpt?.value, description, homewDate, onDone, sectionOpt?.value, status, subjectOpt?.value, submissionDate, title, fileList]);
1169
1276
  const canPickSubject = sessionId !== undefined;
1170
- const canSubmit = canPickSubject && fileList.length > 0 && !busy;
1277
+ const canSubmit = !isSubmitting && !isUploading;
1171
1278
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.SafeAreaView, {
1172
1279
  style: styles.root,
1173
1280
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
@@ -1225,25 +1332,33 @@ function CreateAssignmentView({
1225
1332
  style: styles.field,
1226
1333
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
1227
1334
  style: styles.label,
1228
- children: "Homework Date (YYYY-MM-DD)"
1229
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
1230
- value: homewDate,
1231
- onChangeText: setHomewDate,
1232
- placeholder: "2026-01-31",
1233
- placeholderTextColor: "#9CA3AF",
1234
- style: styles.input
1335
+ children: "Homework Date"
1336
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
1337
+ style: styles.secondaryBtn,
1338
+ onPress: () => {
1339
+ setDateMode('start');
1340
+ setCalendarOpen(true);
1341
+ },
1342
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
1343
+ style: styles.secondaryBtnText,
1344
+ children: homewDate || 'Select start date'
1345
+ })
1235
1346
  })]
1236
1347
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1237
1348
  style: styles.field,
1238
1349
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
1239
1350
  style: styles.label,
1240
- children: "Submission Date (YYYY-MM-DD)"
1241
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
1242
- value: submissionDate,
1243
- onChangeText: setSubmissionDate,
1244
- placeholder: "2026-02-05",
1245
- placeholderTextColor: "#9CA3AF",
1246
- style: styles.input
1351
+ children: "Submission Date"
1352
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
1353
+ style: styles.secondaryBtn,
1354
+ onPress: () => {
1355
+ setDateMode('end');
1356
+ setCalendarOpen(true);
1357
+ },
1358
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
1359
+ style: styles.secondaryBtnText,
1360
+ children: submissionDate || 'Select end date'
1361
+ })
1247
1362
  })]
1248
1363
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1249
1364
  style: styles.field,
@@ -1280,7 +1395,7 @@ function CreateAssignmentView({
1280
1395
  onPress: () => {
1281
1396
  pickFiles().catch(() => {});
1282
1397
  },
1283
- disabled: busy,
1398
+ disabled: isUploading || isSubmitting,
1284
1399
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
1285
1400
  style: styles.secondaryBtnText,
1286
1401
  children: "Gallery / File"
@@ -1290,13 +1405,19 @@ function CreateAssignmentView({
1290
1405
  onPress: () => {
1291
1406
  pickFromCamera().catch(() => {});
1292
1407
  },
1293
- disabled: busy,
1408
+ disabled: isUploading || isSubmitting,
1294
1409
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
1295
1410
  style: styles.secondaryBtnText,
1296
1411
  children: "Camera"
1297
1412
  })
1298
1413
  })]
1299
- }), fileList.length ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
1414
+ }), isUploading ? /*#__PURE__*/(0, _jsxRuntime.jsx)(StatusBanner, {
1415
+ text: uploadStatus || 'Uploading file...',
1416
+ busy: true
1417
+ }) : uploadStatus ? /*#__PURE__*/(0, _jsxRuntime.jsx)(StatusBanner, {
1418
+ text: uploadStatus,
1419
+ tone: fileError ? 'error' : 'success'
1420
+ }) : null, fileList.length ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
1300
1421
  style: styles.fileSelectedText,
1301
1422
  children: ["Selected: ", fileList.length, " files"]
1302
1423
  }) : null, fileList.length ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
@@ -1443,7 +1564,7 @@ function CreateAssignmentView({
1443
1564
  disabled: !canSubmit,
1444
1565
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
1445
1566
  style: styles.primaryBtnText,
1446
- children: busy ? 'Please wait...' : 'Submit'
1567
+ children: isSubmitting ? 'Submitting...' : isUploading ? 'Uploading...' : 'Submit'
1447
1568
  })
1448
1569
  }), !canPickSubject ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
1449
1570
  style: styles.helperText,
@@ -1490,6 +1611,55 @@ function CreateAssignmentView({
1490
1611
  title: "PDF Viewer Missing"
1491
1612
  })]
1492
1613
  })
1614
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Modal, {
1615
+ visible: calendarOpen,
1616
+ transparent: true,
1617
+ animationType: "fade",
1618
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
1619
+ style: styles.modalOverlay,
1620
+ onPress: () => setCalendarOpen(false),
1621
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
1622
+ style: styles.sheetCard,
1623
+ onPress: () => {},
1624
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
1625
+ style: styles.modalHeader,
1626
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
1627
+ style: styles.modalHeaderTitle,
1628
+ children: dateMode === 'start' ? 'Select start date' : 'Select end date'
1629
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
1630
+ onPress: () => setCalendarOpen(false),
1631
+ style: styles.modalClose,
1632
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
1633
+ style: styles.modalCloseText,
1634
+ children: "Close"
1635
+ })
1636
+ })]
1637
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
1638
+ style: {
1639
+ flex: 1
1640
+ },
1641
+ children: Calendar ? /*#__PURE__*/(0, _jsxRuntime.jsx)(Calendar, {
1642
+ markingType: "period",
1643
+ markedDates: markedDates,
1644
+ onDayPress: day => {
1645
+ const d = String(day?.dateString ?? '');
1646
+ if (!d) return;
1647
+ if (dateMode === 'start') {
1648
+ setHomewDate(d);
1649
+ setMarkedDates(rangeMarkedDates(d, submissionDate || d));
1650
+ } else {
1651
+ setSubmissionDate(d);
1652
+ setMarkedDates(rangeMarkedDates(homewDate || d, d));
1653
+ }
1654
+ setCalendarOpen(false);
1655
+ }
1656
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_EmptyState.EmptyState, {
1657
+ title: "Calendar missing",
1658
+ message: "Install react-native-calendars to enable date picker"
1659
+ })
1660
+ })]
1661
+ })
1662
+ })
1493
1663
  })]
1494
1664
  });
1495
1665
  }
@@ -1522,6 +1692,9 @@ function EditAssignmentView({
1522
1692
  const [calendarOpen, setCalendarOpen] = (0, _react.useState)(false);
1523
1693
  const [dateMode, setDateMode] = (0, _react.useState)('start');
1524
1694
  const [isDownloading, setIsDownloading] = (0, _react.useState)(false);
1695
+ const [isUploading, setIsUploading] = (0, _react.useState)(false);
1696
+ const [isSubmitting, setIsSubmitting] = (0, _react.useState)(false);
1697
+ const [uploadStatus, setUploadStatus] = (0, _react.useState)('');
1525
1698
  const homeworkId = detailValue(initial, ['id', 'homework_id', 'homeworkId']);
1526
1699
  const initialTitle = String(detailValue(initial, ['title', 'homework_title'], '') ?? '');
1527
1700
  const initialDescription = String(detailValue(initial, ['description', 'desc', 'homework_description'], '') ?? '');
@@ -1533,7 +1706,6 @@ function EditAssignmentView({
1533
1706
  const initialStatus = initial?.status === 'unpublish' ? 'unpublish' : 'publish';
1534
1707
  const existingFile = String(detailValue(initial, ['document', 'file', 'attachment', 'document_path'], '') ?? '');
1535
1708
  const initialFiles = splitFileCsv(existingFile);
1536
- const [busy, setBusy] = (0, _react.useState)(false);
1537
1709
  const [fileError, setFileError] = (0, _react.useState)(null);
1538
1710
  const [fileList, setFileList] = (0, _react.useState)(() => initialFiles);
1539
1711
  const [activeFile, setActiveFile] = (0, _react.useState)(() => initialFiles[0] ?? '');
@@ -1590,7 +1762,6 @@ function EditAssignmentView({
1590
1762
  }).catch(() => {});
1591
1763
  } else {
1592
1764
  setSubjects([]);
1593
- setSubjectOpt(null);
1594
1765
  }
1595
1766
  }, [api, classOpt?.value, initialSectionId, initialSubjectId, sessionId]);
1596
1767
  const viewAttachment = (0, _react.useCallback)(async rawFile => {
@@ -1707,20 +1878,28 @@ function EditAssignmentView({
1707
1878
  type: asset.type ?? 'image/jpeg'
1708
1879
  };
1709
1880
  setFileError(null);
1710
- setBusy(true);
1881
+ setUploadStatus('Uploading image...');
1882
+ setIsUploading(true);
1711
1883
  try {
1712
- const uploaded = await (0, _assignmentService.uploadAssignmentFile)(api, file);
1884
+ const uploaded = await (0, _assignmentService.uploadAssignmentFile)(api, file, schoolCode);
1713
1885
  if (uploaded?.Status === 'Success' && uploaded.data) {
1714
1886
  const nextPath = String(uploaded.data);
1715
1887
  setFileList(prev => [...prev, nextPath]);
1716
1888
  setActiveFile(prev => prev || nextPath);
1889
+ setUploadStatus('Image uploaded successfully');
1717
1890
  } else {
1718
1891
  setFileError('File upload failed');
1892
+ setUploadStatus('Image upload failed');
1893
+ _reactNative.Alert.alert('Error', String(uploaded?.msg ?? 'File not uploaded'));
1719
1894
  }
1895
+ } catch (error) {
1896
+ setFileError('File upload failed');
1897
+ setUploadStatus('Image upload failed');
1898
+ _reactNative.Alert.alert('Error', String(error?.message ?? 'File not uploaded'));
1720
1899
  } finally {
1721
- setBusy(false);
1900
+ setIsUploading(false);
1722
1901
  }
1723
- }, [api]);
1902
+ }, [api, schoolCode]);
1724
1903
  const pickFiles = (0, _react.useCallback)(async () => {
1725
1904
  let dp = null;
1726
1905
  try {
@@ -1750,53 +1929,68 @@ function EditAssignmentView({
1750
1929
  return;
1751
1930
  }
1752
1931
  setFileError(null);
1753
- setBusy(true);
1932
+ setUploadStatus(`Uploading ${files.length} file(s)...`);
1933
+ setIsUploading(true);
1754
1934
  try {
1755
- const uploaded = [];
1756
- for (const f of files) {
1757
- const res = await (0, _assignmentService.uploadAssignmentFile)(api, f);
1758
- if (res?.Status === 'Success' && res.data) {
1759
- uploaded.push(String(res.data));
1760
- }
1761
- }
1935
+ const {
1936
+ uploaded,
1937
+ failedCount
1938
+ } = await uploadAssignmentFilesBatch({
1939
+ api,
1940
+ files,
1941
+ schoolCode
1942
+ });
1762
1943
  if (!uploaded.length) {
1763
1944
  setFileError('File upload failed');
1945
+ setUploadStatus('File upload failed');
1946
+ _reactNative.Alert.alert('Error', 'File not uploaded');
1764
1947
  return;
1765
1948
  }
1766
1949
  setFileList(prev => [...prev, ...uploaded]);
1767
1950
  setActiveFile(prev => prev ? prev : uploaded[0] ?? '');
1951
+ if (failedCount > 0) {
1952
+ setFileError(`${failedCount} file(s) failed to upload`);
1953
+ setUploadStatus(`${uploaded.length} file(s) uploaded, ${failedCount} failed`);
1954
+ _reactNative.Alert.alert('Upload Incomplete', `${uploaded.length} file(s) uploaded and ${failedCount} failed.`);
1955
+ return;
1956
+ }
1957
+ setFileError(null);
1958
+ setUploadStatus(`${uploaded.length} file(s) uploaded successfully`);
1959
+ } catch (error) {
1960
+ setFileError('File upload failed');
1961
+ setUploadStatus('File upload failed');
1962
+ _reactNative.Alert.alert('Error', String(error?.message ?? 'File not uploaded'));
1768
1963
  } finally {
1769
- setBusy(false);
1964
+ setIsUploading(false);
1770
1965
  }
1771
- }, [api]);
1966
+ }, [api, schoolCode]);
1772
1967
  const submit = (0, _react.useCallback)(async () => {
1968
+ const selectedClassId = classOpt?.value ?? initialClassId;
1969
+ const selectedSectionId = sectionOpt?.value ?? initialSectionId;
1970
+ const selectedSubjectId = subjectOpt?.value ?? initialSubjectId;
1773
1971
  const fileToSend = fileList.length ? fileList.join(',') : undefined;
1774
1972
  const missing = [];
1775
1973
  if (!homeworkId) missing.push('homework id');
1776
1974
  if (!title) missing.push('title');
1777
1975
  if (!description) missing.push('description');
1778
- if (!classOpt?.value) missing.push('class');
1779
- if (!sectionOpt?.value) missing.push('section');
1780
- if (!subjectOpt?.value) missing.push('subject');
1976
+ if (!selectedClassId) missing.push('class');
1977
+ if (!selectedSectionId) missing.push('section');
1978
+ if (!selectedSubjectId) missing.push('subject');
1781
1979
  if (!homewDate) missing.push('homework date');
1782
1980
  if (!submissionDate) missing.push('submission date');
1783
- if (!fileToSend) missing.push('file');
1784
1981
  if (missing.length) {
1785
- if (!fileToSend) {
1786
- setFileError('File is required');
1787
- }
1788
1982
  _reactNative.Alert.alert('Missing Fields', `Please fill: ${missing.join(', ')}`);
1789
1983
  return;
1790
1984
  }
1791
- setBusy(true);
1985
+ setIsSubmitting(true);
1792
1986
  try {
1793
1987
  const res = await (0, _assignmentService.updateAssignment)(api, {
1794
1988
  id: homeworkId,
1795
1989
  title,
1796
1990
  description,
1797
- class_id: classOpt.value,
1798
- section_id: sectionOpt.value,
1799
- sub_id: subjectOpt.value,
1991
+ class_id: selectedClassId,
1992
+ section_id: selectedSectionId,
1993
+ sub_id: selectedSubjectId,
1800
1994
  homew_date: homewDate,
1801
1995
  submission_date: submissionDate,
1802
1996
  status,
@@ -1812,11 +2006,11 @@ function EditAssignmentView({
1812
2006
  } catch (error) {
1813
2007
  _reactNative.Alert.alert('Update Failed', String(error?.message ?? 'Could not update assignment'));
1814
2008
  } finally {
1815
- setBusy(false);
2009
+ setIsSubmitting(false);
1816
2010
  }
1817
- }, [api, classOpt?.value, description, fileList, homewDate, homeworkId, onDone, sectionOpt?.value, status, subjectOpt?.value, submissionDate, title]);
2011
+ }, [api, description, fileList, homewDate, homeworkId, initialClassId, initialSectionId, initialSubjectId, onDone, status, submissionDate, title, classOpt?.value, sectionOpt?.value, subjectOpt?.value]);
1818
2012
  const canPickSubject = sessionId !== undefined;
1819
- const canSubmit = canPickSubject && fileList.length > 0 && !busy;
2013
+ const canSubmit = !isSubmitting && !isUploading;
1820
2014
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.SafeAreaView, {
1821
2015
  style: styles.root,
1822
2016
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
@@ -1944,7 +2138,7 @@ function EditAssignmentView({
1944
2138
  onPress: () => {
1945
2139
  pickFiles().catch(() => {});
1946
2140
  },
1947
- disabled: busy,
2141
+ disabled: isUploading || isSubmitting,
1948
2142
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
1949
2143
  style: styles.secondaryBtnText,
1950
2144
  children: "Add More Files"
@@ -1954,13 +2148,19 @@ function EditAssignmentView({
1954
2148
  onPress: () => {
1955
2149
  pickFromCamera().catch(() => {});
1956
2150
  },
1957
- disabled: busy,
2151
+ disabled: isUploading || isSubmitting,
1958
2152
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
1959
2153
  style: styles.secondaryBtnText,
1960
2154
  children: "Camera"
1961
2155
  })
1962
2156
  })]
1963
- }), fileList.length ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
2157
+ }), isUploading ? /*#__PURE__*/(0, _jsxRuntime.jsx)(StatusBanner, {
2158
+ text: uploadStatus || 'Uploading file...',
2159
+ busy: true
2160
+ }) : uploadStatus ? /*#__PURE__*/(0, _jsxRuntime.jsx)(StatusBanner, {
2161
+ text: uploadStatus,
2162
+ tone: fileError ? 'error' : 'success'
2163
+ }) : null, fileList.length ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
1964
2164
  style: styles.fileSelectedText,
1965
2165
  children: ["Selected: ", fileList.length, " files"]
1966
2166
  }) : null, fileList.length ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
@@ -2107,7 +2307,7 @@ function EditAssignmentView({
2107
2307
  disabled: !canSubmit,
2108
2308
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
2109
2309
  style: styles.primaryBtnText,
2110
- children: busy ? 'Please wait...' : 'Update'
2310
+ children: isSubmitting ? 'Updating...' : isUploading ? 'Uploading...' : 'Update'
2111
2311
  })
2112
2312
  }), !canPickSubject ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
2113
2313
  style: styles.helperText,
@@ -2611,5 +2811,20 @@ const styles = _reactNative.StyleSheet.create({
2611
2811
  marginTop: 6,
2612
2812
  fontSize: 12,
2613
2813
  color: '#6B7280'
2814
+ },
2815
+ statusBanner: {
2816
+ marginTop: 10,
2817
+ paddingHorizontal: 12,
2818
+ paddingVertical: 10,
2819
+ borderRadius: 12,
2820
+ borderWidth: 1,
2821
+ flexDirection: 'row',
2822
+ alignItems: 'center',
2823
+ gap: 10
2824
+ },
2825
+ statusBannerText: {
2826
+ flex: 1,
2827
+ fontSize: 12,
2828
+ fontWeight: '700'
2614
2829
  }
2615
2830
  });
@@ -42,14 +42,14 @@ async function createAssignment(api, payload) {
42
42
  const res = await api.post(_endpoints.endpoints.assignment.add, payload);
43
43
  return res.data;
44
44
  }
45
- async function uploadAssignmentFile(api, file) {
45
+ async function uploadAssignmentFile(api, file, _schoolCode) {
46
46
  const formData = new FormData();
47
47
  formData.append('file', {
48
48
  uri: file.uri,
49
49
  name: file.name,
50
50
  type: file.type
51
51
  });
52
- formData.append('filepath', 'uploads/student/homework/');
52
+ formData.append('filepath', 'student/homework/');
53
53
  const res = await api.post(_endpoints.endpoints.assignment.uploadFile, formData, {
54
54
  headers: {
55
55
  'Content-Type': 'multipart/form-data'
@@ -11,8 +11,7 @@ var _EmptyState = require("../../../shared/empty-states/EmptyState");
11
11
  var _LoadingState = require("../../../shared/loaders/LoadingState");
12
12
  var _attendanceService = require("../services/attendanceService");
13
13
  var _jsxRuntime = require("react/jsx-runtime");
14
- function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
15
- function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
14
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
16
15
  function normalizeOptions(result) {
17
16
  if (!result) {
18
17
  return [];