@ubkinfotech/tecaher-erp 0.1.1 → 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.
@@ -40,14 +40,22 @@ async function fetchNoteDetails(api, args) {
40
40
  const res = await api.get(_endpoints.endpoints.notes.details(args));
41
41
  return res.data;
42
42
  }
43
- async function uploadNoteFile(api, file) {
43
+ function buildUploadFilePath(relativePath, schoolCode) {
44
+ const normalized = String(relativePath).replace(/\\/g, '/').replace(/^\/+/, '');
45
+ if (!schoolCode) {
46
+ return normalized;
47
+ }
48
+ const withoutUploads = normalized.replace(/^uploads\//i, '');
49
+ return `school_${schoolCode}/${withoutUploads}`;
50
+ }
51
+ async function uploadNoteFile(api, file, schoolCode) {
44
52
  const formData = new FormData();
45
53
  formData.append('file', {
46
54
  uri: file.uri,
47
55
  name: file.name,
48
56
  type: file.type
49
57
  });
50
- formData.append('filepath', 'uploads/student/note/');
58
+ formData.append('filepath', buildUploadFilePath('student/note/', schoolCode));
51
59
  const res = await api.post(_endpoints.endpoints.notes.upload, formData, {
52
60
  headers: {
53
61
  'Content-Type': 'multipart/form-data'
@@ -91,7 +91,7 @@ export const endpoints = {
91
91
  return `staff/teacherAPP/attendenceSection/getsectionDropdownforTeacher?class_id=${args.classId}`;
92
92
  },
93
93
  teacherSubjectsByClass: args => {
94
- return `staff/teacherAPP/StaffHomework/getsubjectDropdownByClass?class_id=${args.classId}&session_id=${args.sessionId}`;
94
+ return `staff/teacherAPP/teacherbasicDetails/getteacherSubjectsaccordingtoClass?session_id=${args.sessionId}&class_id=${args.classId}`;
95
95
  },
96
96
  details: args => {
97
97
  return `staff/teacherAPP/Notes/getNotedatabyID?id=${args.noteId}`;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
 
3
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';
4
+ import { Alert, ActivityIndicator, FlatList, Image, Modal, Pressable, SafeAreaView, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
5
5
  import { useERP } from "../../../core/provider/useERP.js";
6
6
  import { EmptyState } from "../../../shared/empty-states/EmptyState.js";
7
7
  import { ErrorState } from "../../../shared/empty-states/ErrorState.js";
@@ -126,6 +126,63 @@ function SelectModal({
126
126
  })]
127
127
  });
128
128
  }
129
+ function StatusBanner({
130
+ text,
131
+ busy = false,
132
+ tone = 'info'
133
+ }) {
134
+ const palette = tone === 'error' ? {
135
+ backgroundColor: '#FEF2F2',
136
+ borderColor: '#FECACA',
137
+ textColor: '#B91C1C',
138
+ spinnerColor: '#DC2626'
139
+ } : tone === 'success' ? {
140
+ backgroundColor: '#ECFDF5',
141
+ borderColor: '#A7F3D0',
142
+ textColor: '#047857',
143
+ spinnerColor: '#059669'
144
+ } : {
145
+ backgroundColor: '#EFF6FF',
146
+ borderColor: '#BFDBFE',
147
+ textColor: '#1D4ED8',
148
+ spinnerColor: '#2563EB'
149
+ };
150
+ return /*#__PURE__*/_jsxs(View, {
151
+ style: [styles.statusBanner, {
152
+ backgroundColor: palette.backgroundColor,
153
+ borderColor: palette.borderColor
154
+ }],
155
+ children: [busy ? /*#__PURE__*/_jsx(ActivityIndicator, {
156
+ size: "small",
157
+ color: palette.spinnerColor
158
+ }) : null, /*#__PURE__*/_jsx(Text, {
159
+ style: [styles.statusBannerText, {
160
+ color: palette.textColor
161
+ }],
162
+ children: text
163
+ })]
164
+ });
165
+ }
166
+ async function uploadAssignmentFilesBatch(args) {
167
+ const uploaded = [];
168
+ let failedCount = 0;
169
+ await Promise.all(args.files.map(async file => {
170
+ try {
171
+ const res = await uploadAssignmentFile(args.api, file, args.schoolCode);
172
+ if (res?.Status === 'Success' && res?.data) {
173
+ uploaded.push(String(res.data));
174
+ return;
175
+ }
176
+ } catch {
177
+ // Keep partial success behavior for multi-file uploads.
178
+ }
179
+ failedCount += 1;
180
+ }));
181
+ return {
182
+ uploaded,
183
+ failedCount
184
+ };
185
+ }
129
186
  function resolveFileBaseUrl(args) {
130
187
  if (args.fileBaseUrl) {
131
188
  return args.fileBaseUrl.endsWith('/') ? args.fileBaseUrl : `${args.fileBaseUrl}/`;
@@ -474,9 +531,14 @@ function resolveUploadUrl(downloadsBaseUrl, rawPath) {
474
531
  }
475
532
  const cleaned = raw.replace(/\\/g, '/').replace(/^\/+/, '');
476
533
  const uploadsRoot = downloadsBaseUrl.replace(/uploads\/?$/i, '');
534
+ const uploadsBaseIndex = downloadsBaseUrl.toLowerCase().indexOf('uploads/');
535
+ const uploadsBase = uploadsBaseIndex >= 0 ? downloadsBaseUrl.slice(0, uploadsBaseIndex + 'uploads/'.length) : downloadsBaseUrl;
477
536
  if (cleaned.startsWith('uploads/')) {
478
537
  return encodeURI(`${uploadsRoot}${cleaned}`);
479
538
  }
539
+ if (cleaned.startsWith('school_')) {
540
+ return encodeURI(`${uploadsBase}${cleaned}`);
541
+ }
480
542
  if (cleaned.startsWith('student/homework/')) {
481
543
  return encodeURI(`${downloadsBaseUrl}${cleaned}`);
482
544
  }
@@ -908,6 +970,7 @@ function CreateAssignmentView({
908
970
  } = useERP();
909
971
  const ImageView = useMemo(() => tryGetImageViewer(), []);
910
972
  const Pdf = useMemo(() => tryGetPdf(), []);
973
+ const Calendar = useMemo(() => tryGetCalendar(), []);
911
974
  const RNFS = useMemo(() => tryGetRNFS(), []);
912
975
  const FileViewer = useMemo(() => tryGetFileViewer(), []);
913
976
  const [viewerOpen, setViewerOpen] = useState(false);
@@ -918,7 +981,11 @@ function CreateAssignmentView({
918
981
  const [pdfHeaders, setPdfHeaders] = useState({});
919
982
  const [pdfLoading, setPdfLoading] = useState(false);
920
983
  const [isDownloading, setIsDownloading] = useState(false);
921
- const [busy, setBusy] = useState(false);
984
+ const [calendarOpen, setCalendarOpen] = useState(false);
985
+ const [dateMode, setDateMode] = useState('start');
986
+ const [isUploading, setIsUploading] = useState(false);
987
+ const [isSubmitting, setIsSubmitting] = useState(false);
988
+ const [uploadStatus, setUploadStatus] = useState('');
922
989
  const [title, setTitle] = useState('');
923
990
  const [description, setDescription] = useState('');
924
991
  const [classOpt, setClassOpt] = useState(null);
@@ -929,6 +996,7 @@ function CreateAssignmentView({
929
996
  const [homewDate, setHomewDate] = useState('');
930
997
  const [submissionDate, setSubmissionDate] = useState('');
931
998
  const [status, setStatus] = useState('publish');
999
+ const [markedDates, setMarkedDates] = useState({});
932
1000
  const [fileList, setFileList] = useState([]);
933
1001
  const [activeFile, setActiveFile] = useState('');
934
1002
  const [fileError, setFileError] = useState(null);
@@ -1071,20 +1139,28 @@ function CreateAssignmentView({
1071
1139
  type: asset.type ?? 'image/jpeg'
1072
1140
  };
1073
1141
  setFileError(null);
1074
- setBusy(true);
1142
+ setUploadStatus('Uploading image...');
1143
+ setIsUploading(true);
1075
1144
  try {
1076
- const uploaded = await uploadAssignmentFile(api, file);
1145
+ const uploaded = await uploadAssignmentFile(api, file, schoolCode);
1077
1146
  if (uploaded?.Status === 'Success' && uploaded.data) {
1078
1147
  const nextPath = String(uploaded.data);
1079
1148
  setFileList(prev => [...prev, nextPath]);
1080
1149
  setActiveFile(prev => prev || nextPath);
1150
+ setUploadStatus('Image uploaded successfully');
1081
1151
  } else {
1082
1152
  setFileError('File upload failed');
1153
+ setUploadStatus('Image upload failed');
1154
+ Alert.alert('Error', String(uploaded?.msg ?? 'File not uploaded'));
1083
1155
  }
1156
+ } catch (error) {
1157
+ setFileError('File upload failed');
1158
+ setUploadStatus('Image upload failed');
1159
+ Alert.alert('Error', String(error?.message ?? 'File not uploaded'));
1084
1160
  } finally {
1085
- setBusy(false);
1161
+ setIsUploading(false);
1086
1162
  }
1087
- }, [api]);
1163
+ }, [api, schoolCode]);
1088
1164
  const pickFiles = useCallback(async () => {
1089
1165
  let dp = null;
1090
1166
  try {
@@ -1114,54 +1190,86 @@ function CreateAssignmentView({
1114
1190
  return;
1115
1191
  }
1116
1192
  setFileError(null);
1117
- setBusy(true);
1193
+ setUploadStatus(`Uploading ${files.length} file(s)...`);
1194
+ setIsUploading(true);
1118
1195
  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
- }
1196
+ const {
1197
+ uploaded,
1198
+ failedCount
1199
+ } = await uploadAssignmentFilesBatch({
1200
+ api,
1201
+ files,
1202
+ schoolCode
1203
+ });
1126
1204
  if (!uploaded.length) {
1127
1205
  setFileError('File upload failed');
1206
+ setUploadStatus('File upload failed');
1207
+ Alert.alert('Error', 'File not uploaded');
1128
1208
  return;
1129
1209
  }
1130
1210
  setFileList(prev => [...prev, ...uploaded]);
1131
1211
  setActiveFile(prev => prev ? prev : uploaded[0] ?? '');
1212
+ if (failedCount > 0) {
1213
+ setFileError(`${failedCount} file(s) failed to upload`);
1214
+ setUploadStatus(`${uploaded.length} file(s) uploaded, ${failedCount} failed`);
1215
+ Alert.alert('Upload Incomplete', `${uploaded.length} file(s) uploaded and ${failedCount} failed.`);
1216
+ return;
1217
+ }
1218
+ setFileError(null);
1219
+ setUploadStatus(`${uploaded.length} file(s) uploaded successfully`);
1220
+ } catch (error) {
1221
+ setFileError('File upload failed');
1222
+ setUploadStatus('File upload failed');
1223
+ Alert.alert('Error', String(error?.message ?? 'File not uploaded'));
1132
1224
  } finally {
1133
- setBusy(false);
1225
+ setIsUploading(false);
1134
1226
  }
1135
- }, [api]);
1227
+ }, [api, schoolCode]);
1136
1228
  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
- }
1229
+ const missing = [];
1230
+ const selectedClassId = classOpt?.value;
1231
+ const selectedSectionId = sectionOpt?.value;
1232
+ const selectedSubjectId = subjectOpt?.value;
1233
+ if (!title) missing.push('title');
1234
+ if (!description) missing.push('description');
1235
+ if (!selectedClassId) missing.push('class');
1236
+ if (!selectedSectionId) missing.push('section');
1237
+ if (!selectedSubjectId) missing.push('subject');
1238
+ if (!homewDate) missing.push('homework date');
1239
+ if (!submissionDate) missing.push('submission date');
1240
+ if (missing.length) {
1241
+ Alert.alert('Missing Fields', `Please fill: ${missing.join(', ')}`);
1141
1242
  return;
1142
1243
  }
1143
1244
  const fileCsv = fileList.join(',');
1144
1245
  const payload = {
1145
1246
  title,
1146
1247
  description,
1147
- class_id: classOpt.value,
1148
- section_id: sectionOpt.value,
1149
- sub_id: subjectOpt.value,
1248
+ class_id: selectedClassId,
1249
+ section_id: selectedSectionId,
1250
+ sub_id: selectedSubjectId,
1150
1251
  homew_date: homewDate,
1151
1252
  submission_date: submissionDate,
1152
1253
  status,
1153
1254
  file: fileCsv
1154
1255
  };
1155
- setBusy(true);
1256
+ setIsSubmitting(true);
1156
1257
  try {
1157
- await createAssignment(api, payload);
1258
+ const response = await createAssignment(api, payload);
1259
+ if (response?.Status && response.Status !== 'Success') {
1260
+ Alert.alert('Failed', String(response?.msg ?? response?.Status ?? 'Could not create assignment'));
1261
+ return;
1262
+ }
1263
+ Alert.alert('Success', String(response?.msg ?? 'Assignment created Successfully'));
1158
1264
  onDone();
1265
+ } catch (error) {
1266
+ Alert.alert('Failed', String(error?.message ?? 'Could not create assignment'));
1159
1267
  } finally {
1160
- setBusy(false);
1268
+ setIsSubmitting(false);
1161
1269
  }
1162
1270
  }, [api, classOpt?.value, description, homewDate, onDone, sectionOpt?.value, status, subjectOpt?.value, submissionDate, title, fileList]);
1163
1271
  const canPickSubject = sessionId !== undefined;
1164
- const canSubmit = canPickSubject && fileList.length > 0 && !busy;
1272
+ const canSubmit = !isSubmitting && !isUploading;
1165
1273
  return /*#__PURE__*/_jsxs(SafeAreaView, {
1166
1274
  style: styles.root,
1167
1275
  children: [/*#__PURE__*/_jsx(View, {
@@ -1219,25 +1327,33 @@ function CreateAssignmentView({
1219
1327
  style: styles.field,
1220
1328
  children: [/*#__PURE__*/_jsx(Text, {
1221
1329
  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
1330
+ children: "Homework Date"
1331
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
1332
+ style: styles.secondaryBtn,
1333
+ onPress: () => {
1334
+ setDateMode('start');
1335
+ setCalendarOpen(true);
1336
+ },
1337
+ children: /*#__PURE__*/_jsx(Text, {
1338
+ style: styles.secondaryBtnText,
1339
+ children: homewDate || 'Select start date'
1340
+ })
1229
1341
  })]
1230
1342
  }), /*#__PURE__*/_jsxs(View, {
1231
1343
  style: styles.field,
1232
1344
  children: [/*#__PURE__*/_jsx(Text, {
1233
1345
  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
1346
+ children: "Submission Date"
1347
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
1348
+ style: styles.secondaryBtn,
1349
+ onPress: () => {
1350
+ setDateMode('end');
1351
+ setCalendarOpen(true);
1352
+ },
1353
+ children: /*#__PURE__*/_jsx(Text, {
1354
+ style: styles.secondaryBtnText,
1355
+ children: submissionDate || 'Select end date'
1356
+ })
1241
1357
  })]
1242
1358
  }), /*#__PURE__*/_jsxs(View, {
1243
1359
  style: styles.field,
@@ -1274,7 +1390,7 @@ function CreateAssignmentView({
1274
1390
  onPress: () => {
1275
1391
  pickFiles().catch(() => {});
1276
1392
  },
1277
- disabled: busy,
1393
+ disabled: isUploading || isSubmitting,
1278
1394
  children: /*#__PURE__*/_jsx(Text, {
1279
1395
  style: styles.secondaryBtnText,
1280
1396
  children: "Gallery / File"
@@ -1284,13 +1400,19 @@ function CreateAssignmentView({
1284
1400
  onPress: () => {
1285
1401
  pickFromCamera().catch(() => {});
1286
1402
  },
1287
- disabled: busy,
1403
+ disabled: isUploading || isSubmitting,
1288
1404
  children: /*#__PURE__*/_jsx(Text, {
1289
1405
  style: styles.secondaryBtnText,
1290
1406
  children: "Camera"
1291
1407
  })
1292
1408
  })]
1293
- }), fileList.length ? /*#__PURE__*/_jsxs(Text, {
1409
+ }), isUploading ? /*#__PURE__*/_jsx(StatusBanner, {
1410
+ text: uploadStatus || 'Uploading file...',
1411
+ busy: true
1412
+ }) : uploadStatus ? /*#__PURE__*/_jsx(StatusBanner, {
1413
+ text: uploadStatus,
1414
+ tone: fileError ? 'error' : 'success'
1415
+ }) : null, fileList.length ? /*#__PURE__*/_jsxs(Text, {
1294
1416
  style: styles.fileSelectedText,
1295
1417
  children: ["Selected: ", fileList.length, " files"]
1296
1418
  }) : null, fileList.length ? /*#__PURE__*/_jsx(View, {
@@ -1437,7 +1559,7 @@ function CreateAssignmentView({
1437
1559
  disabled: !canSubmit,
1438
1560
  children: /*#__PURE__*/_jsx(Text, {
1439
1561
  style: styles.primaryBtnText,
1440
- children: busy ? 'Please wait...' : 'Submit'
1562
+ children: isSubmitting ? 'Submitting...' : isUploading ? 'Uploading...' : 'Submit'
1441
1563
  })
1442
1564
  }), !canPickSubject ? /*#__PURE__*/_jsx(Text, {
1443
1565
  style: styles.helperText,
@@ -1484,6 +1606,55 @@ function CreateAssignmentView({
1484
1606
  title: "PDF Viewer Missing"
1485
1607
  })]
1486
1608
  })
1609
+ }), /*#__PURE__*/_jsx(Modal, {
1610
+ visible: calendarOpen,
1611
+ transparent: true,
1612
+ animationType: "fade",
1613
+ children: /*#__PURE__*/_jsx(Pressable, {
1614
+ style: styles.modalOverlay,
1615
+ onPress: () => setCalendarOpen(false),
1616
+ children: /*#__PURE__*/_jsxs(Pressable, {
1617
+ style: styles.sheetCard,
1618
+ onPress: () => {},
1619
+ children: [/*#__PURE__*/_jsxs(View, {
1620
+ style: styles.modalHeader,
1621
+ children: [/*#__PURE__*/_jsx(Text, {
1622
+ style: styles.modalHeaderTitle,
1623
+ children: dateMode === 'start' ? 'Select start date' : 'Select end date'
1624
+ }), /*#__PURE__*/_jsx(TouchableOpacity, {
1625
+ onPress: () => setCalendarOpen(false),
1626
+ style: styles.modalClose,
1627
+ children: /*#__PURE__*/_jsx(Text, {
1628
+ style: styles.modalCloseText,
1629
+ children: "Close"
1630
+ })
1631
+ })]
1632
+ }), /*#__PURE__*/_jsx(View, {
1633
+ style: {
1634
+ flex: 1
1635
+ },
1636
+ children: Calendar ? /*#__PURE__*/_jsx(Calendar, {
1637
+ markingType: "period",
1638
+ markedDates: markedDates,
1639
+ onDayPress: day => {
1640
+ const d = String(day?.dateString ?? '');
1641
+ if (!d) return;
1642
+ if (dateMode === 'start') {
1643
+ setHomewDate(d);
1644
+ setMarkedDates(rangeMarkedDates(d, submissionDate || d));
1645
+ } else {
1646
+ setSubmissionDate(d);
1647
+ setMarkedDates(rangeMarkedDates(homewDate || d, d));
1648
+ }
1649
+ setCalendarOpen(false);
1650
+ }
1651
+ }) : /*#__PURE__*/_jsx(EmptyState, {
1652
+ title: "Calendar missing",
1653
+ message: "Install react-native-calendars to enable date picker"
1654
+ })
1655
+ })]
1656
+ })
1657
+ })
1487
1658
  })]
1488
1659
  });
1489
1660
  }
@@ -1516,6 +1687,9 @@ function EditAssignmentView({
1516
1687
  const [calendarOpen, setCalendarOpen] = useState(false);
1517
1688
  const [dateMode, setDateMode] = useState('start');
1518
1689
  const [isDownloading, setIsDownloading] = useState(false);
1690
+ const [isUploading, setIsUploading] = useState(false);
1691
+ const [isSubmitting, setIsSubmitting] = useState(false);
1692
+ const [uploadStatus, setUploadStatus] = useState('');
1519
1693
  const homeworkId = detailValue(initial, ['id', 'homework_id', 'homeworkId']);
1520
1694
  const initialTitle = String(detailValue(initial, ['title', 'homework_title'], '') ?? '');
1521
1695
  const initialDescription = String(detailValue(initial, ['description', 'desc', 'homework_description'], '') ?? '');
@@ -1527,7 +1701,6 @@ function EditAssignmentView({
1527
1701
  const initialStatus = initial?.status === 'unpublish' ? 'unpublish' : 'publish';
1528
1702
  const existingFile = String(detailValue(initial, ['document', 'file', 'attachment', 'document_path'], '') ?? '');
1529
1703
  const initialFiles = splitFileCsv(existingFile);
1530
- const [busy, setBusy] = useState(false);
1531
1704
  const [fileError, setFileError] = useState(null);
1532
1705
  const [fileList, setFileList] = useState(() => initialFiles);
1533
1706
  const [activeFile, setActiveFile] = useState(() => initialFiles[0] ?? '');
@@ -1584,7 +1757,6 @@ function EditAssignmentView({
1584
1757
  }).catch(() => {});
1585
1758
  } else {
1586
1759
  setSubjects([]);
1587
- setSubjectOpt(null);
1588
1760
  }
1589
1761
  }, [api, classOpt?.value, initialSectionId, initialSubjectId, sessionId]);
1590
1762
  const viewAttachment = useCallback(async rawFile => {
@@ -1701,20 +1873,28 @@ function EditAssignmentView({
1701
1873
  type: asset.type ?? 'image/jpeg'
1702
1874
  };
1703
1875
  setFileError(null);
1704
- setBusy(true);
1876
+ setUploadStatus('Uploading image...');
1877
+ setIsUploading(true);
1705
1878
  try {
1706
- const uploaded = await uploadAssignmentFile(api, file);
1879
+ const uploaded = await uploadAssignmentFile(api, file, schoolCode);
1707
1880
  if (uploaded?.Status === 'Success' && uploaded.data) {
1708
1881
  const nextPath = String(uploaded.data);
1709
1882
  setFileList(prev => [...prev, nextPath]);
1710
1883
  setActiveFile(prev => prev || nextPath);
1884
+ setUploadStatus('Image uploaded successfully');
1711
1885
  } else {
1712
1886
  setFileError('File upload failed');
1887
+ setUploadStatus('Image upload failed');
1888
+ Alert.alert('Error', String(uploaded?.msg ?? 'File not uploaded'));
1713
1889
  }
1890
+ } catch (error) {
1891
+ setFileError('File upload failed');
1892
+ setUploadStatus('Image upload failed');
1893
+ Alert.alert('Error', String(error?.message ?? 'File not uploaded'));
1714
1894
  } finally {
1715
- setBusy(false);
1895
+ setIsUploading(false);
1716
1896
  }
1717
- }, [api]);
1897
+ }, [api, schoolCode]);
1718
1898
  const pickFiles = useCallback(async () => {
1719
1899
  let dp = null;
1720
1900
  try {
@@ -1744,53 +1924,68 @@ function EditAssignmentView({
1744
1924
  return;
1745
1925
  }
1746
1926
  setFileError(null);
1747
- setBusy(true);
1927
+ setUploadStatus(`Uploading ${files.length} file(s)...`);
1928
+ setIsUploading(true);
1748
1929
  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
- }
1930
+ const {
1931
+ uploaded,
1932
+ failedCount
1933
+ } = await uploadAssignmentFilesBatch({
1934
+ api,
1935
+ files,
1936
+ schoolCode
1937
+ });
1756
1938
  if (!uploaded.length) {
1757
1939
  setFileError('File upload failed');
1940
+ setUploadStatus('File upload failed');
1941
+ Alert.alert('Error', 'File not uploaded');
1758
1942
  return;
1759
1943
  }
1760
1944
  setFileList(prev => [...prev, ...uploaded]);
1761
1945
  setActiveFile(prev => prev ? prev : uploaded[0] ?? '');
1946
+ if (failedCount > 0) {
1947
+ setFileError(`${failedCount} file(s) failed to upload`);
1948
+ setUploadStatus(`${uploaded.length} file(s) uploaded, ${failedCount} failed`);
1949
+ Alert.alert('Upload Incomplete', `${uploaded.length} file(s) uploaded and ${failedCount} failed.`);
1950
+ return;
1951
+ }
1952
+ setFileError(null);
1953
+ setUploadStatus(`${uploaded.length} file(s) uploaded successfully`);
1954
+ } catch (error) {
1955
+ setFileError('File upload failed');
1956
+ setUploadStatus('File upload failed');
1957
+ Alert.alert('Error', String(error?.message ?? 'File not uploaded'));
1762
1958
  } finally {
1763
- setBusy(false);
1959
+ setIsUploading(false);
1764
1960
  }
1765
- }, [api]);
1961
+ }, [api, schoolCode]);
1766
1962
  const submit = useCallback(async () => {
1963
+ const selectedClassId = classOpt?.value ?? initialClassId;
1964
+ const selectedSectionId = sectionOpt?.value ?? initialSectionId;
1965
+ const selectedSubjectId = subjectOpt?.value ?? initialSubjectId;
1767
1966
  const fileToSend = fileList.length ? fileList.join(',') : undefined;
1768
1967
  const missing = [];
1769
1968
  if (!homeworkId) missing.push('homework id');
1770
1969
  if (!title) missing.push('title');
1771
1970
  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');
1971
+ if (!selectedClassId) missing.push('class');
1972
+ if (!selectedSectionId) missing.push('section');
1973
+ if (!selectedSubjectId) missing.push('subject');
1775
1974
  if (!homewDate) missing.push('homework date');
1776
1975
  if (!submissionDate) missing.push('submission date');
1777
- if (!fileToSend) missing.push('file');
1778
1976
  if (missing.length) {
1779
- if (!fileToSend) {
1780
- setFileError('File is required');
1781
- }
1782
1977
  Alert.alert('Missing Fields', `Please fill: ${missing.join(', ')}`);
1783
1978
  return;
1784
1979
  }
1785
- setBusy(true);
1980
+ setIsSubmitting(true);
1786
1981
  try {
1787
1982
  const res = await updateAssignment(api, {
1788
1983
  id: homeworkId,
1789
1984
  title,
1790
1985
  description,
1791
- class_id: classOpt.value,
1792
- section_id: sectionOpt.value,
1793
- sub_id: subjectOpt.value,
1986
+ class_id: selectedClassId,
1987
+ section_id: selectedSectionId,
1988
+ sub_id: selectedSubjectId,
1794
1989
  homew_date: homewDate,
1795
1990
  submission_date: submissionDate,
1796
1991
  status,
@@ -1806,11 +2001,11 @@ function EditAssignmentView({
1806
2001
  } catch (error) {
1807
2002
  Alert.alert('Update Failed', String(error?.message ?? 'Could not update assignment'));
1808
2003
  } finally {
1809
- setBusy(false);
2004
+ setIsSubmitting(false);
1810
2005
  }
1811
- }, [api, classOpt?.value, description, fileList, homewDate, homeworkId, onDone, sectionOpt?.value, status, subjectOpt?.value, submissionDate, title]);
2006
+ }, [api, description, fileList, homewDate, homeworkId, initialClassId, initialSectionId, initialSubjectId, onDone, status, submissionDate, title, classOpt?.value, sectionOpt?.value, subjectOpt?.value]);
1812
2007
  const canPickSubject = sessionId !== undefined;
1813
- const canSubmit = canPickSubject && fileList.length > 0 && !busy;
2008
+ const canSubmit = !isSubmitting && !isUploading;
1814
2009
  return /*#__PURE__*/_jsxs(SafeAreaView, {
1815
2010
  style: styles.root,
1816
2011
  children: [/*#__PURE__*/_jsxs(View, {
@@ -1938,7 +2133,7 @@ function EditAssignmentView({
1938
2133
  onPress: () => {
1939
2134
  pickFiles().catch(() => {});
1940
2135
  },
1941
- disabled: busy,
2136
+ disabled: isUploading || isSubmitting,
1942
2137
  children: /*#__PURE__*/_jsx(Text, {
1943
2138
  style: styles.secondaryBtnText,
1944
2139
  children: "Add More Files"
@@ -1948,13 +2143,19 @@ function EditAssignmentView({
1948
2143
  onPress: () => {
1949
2144
  pickFromCamera().catch(() => {});
1950
2145
  },
1951
- disabled: busy,
2146
+ disabled: isUploading || isSubmitting,
1952
2147
  children: /*#__PURE__*/_jsx(Text, {
1953
2148
  style: styles.secondaryBtnText,
1954
2149
  children: "Camera"
1955
2150
  })
1956
2151
  })]
1957
- }), fileList.length ? /*#__PURE__*/_jsxs(Text, {
2152
+ }), isUploading ? /*#__PURE__*/_jsx(StatusBanner, {
2153
+ text: uploadStatus || 'Uploading file...',
2154
+ busy: true
2155
+ }) : uploadStatus ? /*#__PURE__*/_jsx(StatusBanner, {
2156
+ text: uploadStatus,
2157
+ tone: fileError ? 'error' : 'success'
2158
+ }) : null, fileList.length ? /*#__PURE__*/_jsxs(Text, {
1958
2159
  style: styles.fileSelectedText,
1959
2160
  children: ["Selected: ", fileList.length, " files"]
1960
2161
  }) : null, fileList.length ? /*#__PURE__*/_jsx(View, {
@@ -2101,7 +2302,7 @@ function EditAssignmentView({
2101
2302
  disabled: !canSubmit,
2102
2303
  children: /*#__PURE__*/_jsx(Text, {
2103
2304
  style: styles.primaryBtnText,
2104
- children: busy ? 'Please wait...' : 'Update'
2305
+ children: isSubmitting ? 'Updating...' : isUploading ? 'Uploading...' : 'Update'
2105
2306
  })
2106
2307
  }), !canPickSubject ? /*#__PURE__*/_jsx(Text, {
2107
2308
  style: styles.helperText,
@@ -2605,5 +2806,20 @@ const styles = StyleSheet.create({
2605
2806
  marginTop: 6,
2606
2807
  fontSize: 12,
2607
2808
  color: '#6B7280'
2809
+ },
2810
+ statusBanner: {
2811
+ marginTop: 10,
2812
+ paddingHorizontal: 12,
2813
+ paddingVertical: 10,
2814
+ borderRadius: 12,
2815
+ borderWidth: 1,
2816
+ flexDirection: 'row',
2817
+ alignItems: 'center',
2818
+ gap: 10
2819
+ },
2820
+ statusBannerText: {
2821
+ flex: 1,
2822
+ fontSize: 12,
2823
+ fontWeight: '700'
2608
2824
  }
2609
2825
  });
@@ -29,14 +29,14 @@ export async function createAssignment(api, payload) {
29
29
  const res = await api.post(endpoints.assignment.add, payload);
30
30
  return res.data;
31
31
  }
32
- export async function uploadAssignmentFile(api, file) {
32
+ export async function uploadAssignmentFile(api, file, _schoolCode) {
33
33
  const formData = new FormData();
34
34
  formData.append('file', {
35
35
  uri: file.uri,
36
36
  name: file.name,
37
37
  type: file.type
38
38
  });
39
- formData.append('filepath', 'uploads/student/homework/');
39
+ formData.append('filepath', 'student/homework/');
40
40
  const res = await api.post(endpoints.assignment.uploadFile, formData, {
41
41
  headers: {
42
42
  'Content-Type': 'multipart/form-data'