@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.
- package/lib/commonjs/core/api/endpoints.js +1 -1
- package/lib/commonjs/modules/assignment/screens/AssignmentScreen.js +294 -78
- package/lib/commonjs/modules/assignment/services/assignmentService.js +2 -2
- package/lib/commonjs/modules/leaveRequest/screens/LeaveRequestScreen.js +6 -5
- package/lib/commonjs/modules/leaveRequest/services/leaveRequestService.js +10 -2
- package/lib/commonjs/modules/notes/screens/NotesScreen.js +409 -95
- package/lib/commonjs/modules/notes/services/notesService.js +10 -2
- package/lib/module/core/api/endpoints.js +1 -1
- package/lib/module/modules/assignment/screens/AssignmentScreen.js +295 -79
- package/lib/module/modules/assignment/services/assignmentService.js +2 -2
- package/lib/module/modules/leaveRequest/screens/LeaveRequestScreen.js +6 -5
- package/lib/module/modules/leaveRequest/services/leaveRequestService.js +10 -2
- package/lib/module/modules/notes/screens/NotesScreen.js +410 -96
- package/lib/module/modules/notes/services/notesService.js +10 -2
- package/lib/typescript/modules/assignment/services/assignmentService.d.ts +1 -1
- package/lib/typescript/modules/leaveRequest/services/leaveRequestService.d.ts +1 -1
- package/lib/typescript/modules/notes/services/notesService.d.ts +1 -1
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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', '
|
|
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/
|
|
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 [
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1193
|
+
setUploadStatus(`Uploading ${files.length} file(s)...`);
|
|
1194
|
+
setIsUploading(true);
|
|
1118
1195
|
try {
|
|
1119
|
-
const
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
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
|
-
|
|
1225
|
+
setIsUploading(false);
|
|
1134
1226
|
}
|
|
1135
|
-
}, [api]);
|
|
1227
|
+
}, [api, schoolCode]);
|
|
1136
1228
|
const submit = useCallback(async () => {
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
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:
|
|
1148
|
-
section_id:
|
|
1149
|
-
sub_id:
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
1223
|
-
}), /*#__PURE__*/_jsx(
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
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
|
|
1235
|
-
}), /*#__PURE__*/_jsx(
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
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:
|
|
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:
|
|
1403
|
+
disabled: isUploading || isSubmitting,
|
|
1288
1404
|
children: /*#__PURE__*/_jsx(Text, {
|
|
1289
1405
|
style: styles.secondaryBtnText,
|
|
1290
1406
|
children: "Camera"
|
|
1291
1407
|
})
|
|
1292
1408
|
})]
|
|
1293
|
-
}),
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1927
|
+
setUploadStatus(`Uploading ${files.length} file(s)...`);
|
|
1928
|
+
setIsUploading(true);
|
|
1748
1929
|
try {
|
|
1749
|
-
const
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
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
|
-
|
|
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 (!
|
|
1773
|
-
if (!
|
|
1774
|
-
if (!
|
|
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
|
-
|
|
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:
|
|
1792
|
-
section_id:
|
|
1793
|
-
sub_id:
|
|
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
|
-
|
|
2004
|
+
setIsSubmitting(false);
|
|
1810
2005
|
}
|
|
1811
|
-
}, [api,
|
|
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 =
|
|
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:
|
|
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:
|
|
2146
|
+
disabled: isUploading || isSubmitting,
|
|
1952
2147
|
children: /*#__PURE__*/_jsx(Text, {
|
|
1953
2148
|
style: styles.secondaryBtnText,
|
|
1954
2149
|
children: "Camera"
|
|
1955
2150
|
})
|
|
1956
2151
|
})]
|
|
1957
|
-
}),
|
|
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:
|
|
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', '
|
|
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'
|