@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.
- package/README.md +143 -67
- package/lib/commonjs/core/api/endpoints.js +1 -1
- package/lib/commonjs/core/auth/authContext.js +1 -2
- package/lib/commonjs/core/provider/ERPProvider.js +1 -2
- package/lib/commonjs/modules/assignment/screens/AssignmentScreen.js +295 -80
- package/lib/commonjs/modules/assignment/services/assignmentService.js +2 -2
- package/lib/commonjs/modules/attendance/screens/AttendanceScreen.js +1 -2
- package/lib/commonjs/modules/leaveRequest/screens/LeaveRequestScreen.js +7 -7
- package/lib/commonjs/modules/leaveRequest/services/leaveRequestService.js +10 -2
- package/lib/commonjs/modules/marks/screens/MarksScreen.js +1 -2
- package/lib/commonjs/modules/myAttendance/screens/MyAttendanceScreen.js +1 -2
- package/lib/commonjs/modules/notes/screens/NotesScreen.js +410 -97
- package/lib/commonjs/modules/notes/services/notesService.js +10 -2
- package/lib/commonjs/modules/noticeboard/screens/NoticeBoardScreen.js +1 -2
- package/lib/commonjs/modules/notification/screens/NotificationScreen.js +1 -2
- package/lib/commonjs/modules/promoteStudent/screens/PromoteStudentScreen.js +1 -2
- package/lib/commonjs/modules/timetable/screens/TimeTableScreen.js +1 -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 +3 -3
|
@@ -12,8 +12,7 @@ var _ErrorState = require("../../../shared/empty-states/ErrorState");
|
|
|
12
12
|
var _LoadingState = require("../../../shared/loaders/LoadingState");
|
|
13
13
|
var _notesService = require("../services/notesService");
|
|
14
14
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
15
|
-
function
|
|
16
|
-
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; }
|
|
15
|
+
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); }
|
|
17
16
|
function normalizeList(result) {
|
|
18
17
|
if (Array.isArray(result)) return result;
|
|
19
18
|
if (Array.isArray(result?.data)) return result.data;
|
|
@@ -32,7 +31,22 @@ function resolveNoteUrl(baseUrl, file) {
|
|
|
32
31
|
if (!file) return '';
|
|
33
32
|
if (/^https?:\/\//i.test(file)) return file;
|
|
34
33
|
const base = String(baseUrl ?? '').replace(/\/?$/, '/');
|
|
35
|
-
const normalized = String(file).replace(/^\/+/, '');
|
|
34
|
+
const normalized = String(file).replace(/\\/g, '/').replace(/^\/+/, '');
|
|
35
|
+
const uploadsIndex = base.toLowerCase().indexOf('uploads/');
|
|
36
|
+
const uploadsBase = uploadsIndex >= 0 ? base.slice(0, uploadsIndex + 'uploads/'.length) : base;
|
|
37
|
+
const uploadsRoot = uploadsIndex >= 0 ? base.slice(0, uploadsIndex) : base;
|
|
38
|
+
if (normalized.startsWith('uploads/')) {
|
|
39
|
+
return `${uploadsRoot}${normalized}`;
|
|
40
|
+
}
|
|
41
|
+
if (normalized.startsWith('school_')) {
|
|
42
|
+
return `${uploadsBase}${normalized}`;
|
|
43
|
+
}
|
|
44
|
+
if (normalized.startsWith('student/note/')) {
|
|
45
|
+
return `${base}${normalized}`;
|
|
46
|
+
}
|
|
47
|
+
if (normalized.includes('/')) {
|
|
48
|
+
return `${base}${normalized}`;
|
|
49
|
+
}
|
|
36
50
|
return `${base}student/note/${normalized}`;
|
|
37
51
|
}
|
|
38
52
|
function isImageUrl(file) {
|
|
@@ -48,6 +62,10 @@ function fileBaseName(path) {
|
|
|
48
62
|
function ensureFileUri(path) {
|
|
49
63
|
return path.startsWith('file://') ? path : `file://${path}`;
|
|
50
64
|
}
|
|
65
|
+
function splitFileCsv(raw) {
|
|
66
|
+
if (!raw) return [];
|
|
67
|
+
return String(raw).split(',').map(item => item.trim()).filter(Boolean);
|
|
68
|
+
}
|
|
51
69
|
function tryGetImageViewer() {
|
|
52
70
|
try {
|
|
53
71
|
const mod = require('react-native-image-viewing');
|
|
@@ -95,6 +113,68 @@ async function downloadPdfToLocal(args) {
|
|
|
95
113
|
}
|
|
96
114
|
return ensureFileUri(target);
|
|
97
115
|
}
|
|
116
|
+
function StatusBanner({
|
|
117
|
+
text,
|
|
118
|
+
busy = false,
|
|
119
|
+
tone = 'info'
|
|
120
|
+
}) {
|
|
121
|
+
const palette = tone === 'error' ? {
|
|
122
|
+
backgroundColor: '#FEF2F2',
|
|
123
|
+
borderColor: '#FECACA',
|
|
124
|
+
textColor: '#B91C1C',
|
|
125
|
+
spinnerColor: '#DC2626'
|
|
126
|
+
} : tone === 'success' ? {
|
|
127
|
+
backgroundColor: '#ECFDF5',
|
|
128
|
+
borderColor: '#A7F3D0',
|
|
129
|
+
textColor: '#047857',
|
|
130
|
+
spinnerColor: '#059669'
|
|
131
|
+
} : tone === 'warning' ? {
|
|
132
|
+
backgroundColor: '#FFF7ED',
|
|
133
|
+
borderColor: '#FED7AA',
|
|
134
|
+
textColor: '#C2410C',
|
|
135
|
+
spinnerColor: '#EA580C'
|
|
136
|
+
} : {
|
|
137
|
+
backgroundColor: '#EFF6FF',
|
|
138
|
+
borderColor: '#BFDBFE',
|
|
139
|
+
textColor: '#1D4ED8',
|
|
140
|
+
spinnerColor: '#2563EB'
|
|
141
|
+
};
|
|
142
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
143
|
+
style: [styles.statusBanner, {
|
|
144
|
+
backgroundColor: palette.backgroundColor,
|
|
145
|
+
borderColor: palette.borderColor
|
|
146
|
+
}],
|
|
147
|
+
children: [busy ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
|
|
148
|
+
size: "small",
|
|
149
|
+
color: palette.spinnerColor
|
|
150
|
+
}) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
151
|
+
style: [styles.statusBannerText, {
|
|
152
|
+
color: palette.textColor
|
|
153
|
+
}],
|
|
154
|
+
children: text
|
|
155
|
+
})]
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
async function uploadNoteFilesBatch(args) {
|
|
159
|
+
const uploaded = [];
|
|
160
|
+
let failedCount = 0;
|
|
161
|
+
await Promise.all(args.files.map(async file => {
|
|
162
|
+
try {
|
|
163
|
+
const response = await (0, _notesService.uploadNoteFile)(args.api, file, args.schoolCode);
|
|
164
|
+
if (response?.Status === 'Success' && response?.data) {
|
|
165
|
+
uploaded.push(String(response.data));
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
} catch {
|
|
169
|
+
// Keep partial success behavior for multi-file uploads.
|
|
170
|
+
}
|
|
171
|
+
failedCount += 1;
|
|
172
|
+
}));
|
|
173
|
+
return {
|
|
174
|
+
uploaded,
|
|
175
|
+
failedCount
|
|
176
|
+
};
|
|
177
|
+
}
|
|
98
178
|
function SelectField({
|
|
99
179
|
label,
|
|
100
180
|
value,
|
|
@@ -167,7 +247,10 @@ function NoteEditor(props) {
|
|
|
167
247
|
const RNFS = (0, _react.useMemo)(() => tryGetRNFS(), []);
|
|
168
248
|
const FileViewer = (0, _react.useMemo)(() => tryGetFileViewer(), []);
|
|
169
249
|
const [loading, setLoading] = (0, _react.useState)(props.mode === 'edit');
|
|
170
|
-
const [
|
|
250
|
+
const [isUploading, setIsUploading] = (0, _react.useState)(false);
|
|
251
|
+
const [isSubmitting, setIsSubmitting] = (0, _react.useState)(false);
|
|
252
|
+
const [uploadStatus, setUploadStatus] = (0, _react.useState)('');
|
|
253
|
+
const [uploadTone, setUploadTone] = (0, _react.useState)('info');
|
|
171
254
|
const [classes, setClasses] = (0, _react.useState)([]);
|
|
172
255
|
const [sections, setSections] = (0, _react.useState)([]);
|
|
173
256
|
const [subjects, setSubjects] = (0, _react.useState)([]);
|
|
@@ -177,8 +260,12 @@ function NoteEditor(props) {
|
|
|
177
260
|
const [classOpt, setClassOpt] = (0, _react.useState)(null);
|
|
178
261
|
const [sectionOpt, setSectionOpt] = (0, _react.useState)(null);
|
|
179
262
|
const [subjectOpt, setSubjectOpt] = (0, _react.useState)(null);
|
|
180
|
-
const [
|
|
263
|
+
const [fileList, setFileList] = (0, _react.useState)([]);
|
|
264
|
+
const [activeFile, setActiveFile] = (0, _react.useState)('');
|
|
265
|
+
const [fileError, setFileError] = (0, _react.useState)(null);
|
|
181
266
|
const [viewerOpen, setViewerOpen] = (0, _react.useState)(false);
|
|
267
|
+
const [viewerIndex, setViewerIndex] = (0, _react.useState)(0);
|
|
268
|
+
const [viewerImages, setViewerImages] = (0, _react.useState)([]);
|
|
182
269
|
const [pdfVisible, setPdfVisible] = (0, _react.useState)(false);
|
|
183
270
|
const [pdfUri, setPdfUri] = (0, _react.useState)('');
|
|
184
271
|
const [pdfHeaders, setPdfHeaders] = (0, _react.useState)({});
|
|
@@ -231,7 +318,9 @@ function NoteEditor(props) {
|
|
|
231
318
|
setTitle(String(data?.title ?? ''));
|
|
232
319
|
setDescription(String(data?.description ?? data?.remark ?? ''));
|
|
233
320
|
setStatus(String(data?.status ?? 'publish').toLowerCase() === 'unpublish' ? 'unpublish' : 'publish');
|
|
234
|
-
|
|
321
|
+
const noteFiles = splitFileCsv(data?.file ?? '');
|
|
322
|
+
setFileList(noteFiles);
|
|
323
|
+
setActiveFile(noteFiles[0] ?? '');
|
|
235
324
|
const classId = data?.class_id ?? props.defaults.classId;
|
|
236
325
|
const sectionId = data?.section_id ?? props.defaults.sectionId;
|
|
237
326
|
const subjectId = data?.sub_id ?? data?.subject_id ?? props.defaults.subjectId;
|
|
@@ -281,27 +370,51 @@ function NoteEditor(props) {
|
|
|
281
370
|
dp = require('react-native-document-picker');
|
|
282
371
|
} catch {}
|
|
283
372
|
if (!dp) return;
|
|
284
|
-
const selected = await dp.
|
|
373
|
+
const selected = await dp.pick({
|
|
285
374
|
presentationStyle: 'fullScreen',
|
|
375
|
+
allowMultiSelection: true,
|
|
286
376
|
type: [dp.types.pdf, dp.types.images]
|
|
287
377
|
});
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
378
|
+
const picks = Array.isArray(selected) ? selected : [selected];
|
|
379
|
+
const uploads = picks.map(item => ({
|
|
380
|
+
uri: item.uri,
|
|
381
|
+
name: item.name ?? 'file',
|
|
382
|
+
type: item.type ?? 'application/octet-stream'
|
|
383
|
+
})).filter(item => !!item.uri);
|
|
384
|
+
if (!uploads.length) return;
|
|
385
|
+
setFileError(null);
|
|
386
|
+
setUploadStatus(`Uploading ${uploads.length} file(s)...`);
|
|
387
|
+
setUploadTone('info');
|
|
388
|
+
setIsUploading(true);
|
|
294
389
|
try {
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
|
|
390
|
+
const {
|
|
391
|
+
uploaded,
|
|
392
|
+
failedCount
|
|
393
|
+
} = await uploadNoteFilesBatch({
|
|
394
|
+
api,
|
|
395
|
+
files: uploads,
|
|
396
|
+
schoolCode
|
|
397
|
+
});
|
|
398
|
+
if (!uploaded.length) {
|
|
399
|
+
setFileError('File upload failed');
|
|
400
|
+
setUploadStatus('File upload failed');
|
|
401
|
+
setUploadTone('error');
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
setFileList(prev => [...prev, ...uploaded]);
|
|
405
|
+
setActiveFile(prev => prev || uploaded[0] || '');
|
|
406
|
+
if (failedCount > 0) {
|
|
407
|
+
setFileError(`${failedCount} file(s) failed to upload`);
|
|
408
|
+
setUploadStatus(`${uploaded.length} file(s) uploaded and ${failedCount} failed.`);
|
|
409
|
+
setUploadTone('warning');
|
|
410
|
+
return;
|
|
300
411
|
}
|
|
412
|
+
setUploadStatus(`${uploaded.length} file(s) uploaded successfully.`);
|
|
413
|
+
setUploadTone('success');
|
|
301
414
|
} finally {
|
|
302
|
-
|
|
415
|
+
setIsUploading(false);
|
|
303
416
|
}
|
|
304
|
-
}, [api]);
|
|
417
|
+
}, [api, schoolCode]);
|
|
305
418
|
const pickCamera = (0, _react.useCallback)(async () => {
|
|
306
419
|
let picker = null;
|
|
307
420
|
try {
|
|
@@ -322,30 +435,51 @@ function NoteEditor(props) {
|
|
|
322
435
|
name: asset.fileName ?? 'camera.jpg',
|
|
323
436
|
type: asset.type ?? 'image/jpeg'
|
|
324
437
|
};
|
|
325
|
-
|
|
438
|
+
setFileError(null);
|
|
439
|
+
setUploadStatus('Uploading image...');
|
|
440
|
+
setUploadTone('info');
|
|
441
|
+
setIsUploading(true);
|
|
326
442
|
try {
|
|
327
|
-
const response = await (0, _notesService.uploadNoteFile)(api, upload);
|
|
443
|
+
const response = await (0, _notesService.uploadNoteFile)(api, upload, schoolCode);
|
|
328
444
|
if (response?.Status === 'Success' && response?.data) {
|
|
329
|
-
|
|
445
|
+
const nextFile = String(response.data);
|
|
446
|
+
setFileList(prev => [...prev, nextFile]);
|
|
447
|
+
setActiveFile(prev => prev || nextFile);
|
|
448
|
+
setUploadStatus('Image uploaded successfully.');
|
|
449
|
+
setUploadTone('success');
|
|
330
450
|
} else {
|
|
331
|
-
|
|
451
|
+
setFileError('Image upload failed');
|
|
452
|
+
setUploadStatus(String(response?.msg ?? 'Image upload failed'));
|
|
453
|
+
setUploadTone('error');
|
|
332
454
|
}
|
|
333
455
|
} finally {
|
|
334
|
-
|
|
456
|
+
setIsUploading(false);
|
|
335
457
|
}
|
|
336
|
-
}, [api]);
|
|
337
|
-
const viewAttachment = (0, _react.useCallback)(async
|
|
338
|
-
|
|
458
|
+
}, [api, schoolCode]);
|
|
459
|
+
const viewAttachment = (0, _react.useCallback)(async selectedFile => {
|
|
460
|
+
const targetFile = String(selectedFile ?? activeFile ?? fileList[0] ?? '');
|
|
461
|
+
if (!targetFile) {
|
|
339
462
|
_reactNative.Alert.alert('No Attachment');
|
|
340
463
|
return;
|
|
341
464
|
}
|
|
342
|
-
const url = resolveNoteUrl(fileBaseUrl,
|
|
465
|
+
const url = resolveNoteUrl(fileBaseUrl, targetFile);
|
|
343
466
|
const headers = await resolveHeaders();
|
|
344
|
-
|
|
467
|
+
const targetIsImage = isImageUrl(targetFile) || isImageUrl(url);
|
|
468
|
+
if (ImageView && targetIsImage) {
|
|
469
|
+
const imageFiles = fileList.filter(currentFile => {
|
|
470
|
+
const currentUrl = resolveNoteUrl(fileBaseUrl, currentFile);
|
|
471
|
+
return isImageUrl(currentFile) || isImageUrl(currentUrl);
|
|
472
|
+
});
|
|
473
|
+
const nextIndex = Math.max(0, imageFiles.findIndex(currentFile => String(currentFile) === String(targetFile)));
|
|
474
|
+
setViewerImages(imageFiles.map(currentFile => ({
|
|
475
|
+
uri: resolveNoteUrl(fileBaseUrl, currentFile),
|
|
476
|
+
headers: Object.keys(headers).length ? headers : undefined
|
|
477
|
+
})));
|
|
478
|
+
setViewerIndex(nextIndex);
|
|
345
479
|
setViewerOpen(true);
|
|
346
480
|
return;
|
|
347
481
|
}
|
|
348
|
-
const isPdf = String(
|
|
482
|
+
const isPdf = String(targetFile).toLowerCase().endsWith('.pdf') || url.toLowerCase().endsWith('.pdf');
|
|
349
483
|
if (Pdf && isPdf) {
|
|
350
484
|
if (!RNFS) {
|
|
351
485
|
setPdfHeaders(headers);
|
|
@@ -359,7 +493,7 @@ function NoteEditor(props) {
|
|
|
359
493
|
const local = await downloadPdfToLocal({
|
|
360
494
|
RNFS,
|
|
361
495
|
url,
|
|
362
|
-
fileName: fileBaseName(
|
|
496
|
+
fileName: fileBaseName(targetFile),
|
|
363
497
|
headers
|
|
364
498
|
});
|
|
365
499
|
setPdfHeaders({});
|
|
@@ -379,7 +513,7 @@ function NoteEditor(props) {
|
|
|
379
513
|
}
|
|
380
514
|
setDownloading(true);
|
|
381
515
|
try {
|
|
382
|
-
const localFile = `${RNFS.DocumentDirectoryPath}/${fileBaseName(
|
|
516
|
+
const localFile = `${RNFS.DocumentDirectoryPath}/${fileBaseName(targetFile)}`;
|
|
383
517
|
await RNFS.downloadFile({
|
|
384
518
|
fromUrl: url,
|
|
385
519
|
toFile: localFile,
|
|
@@ -393,10 +527,23 @@ function NoteEditor(props) {
|
|
|
393
527
|
} finally {
|
|
394
528
|
setDownloading(false);
|
|
395
529
|
}
|
|
396
|
-
}, [FileViewer, ImageView, Pdf, RNFS,
|
|
530
|
+
}, [FileViewer, ImageView, Pdf, RNFS, activeFile, fileBaseUrl, fileList, resolveHeaders]);
|
|
397
531
|
const submit = (0, _react.useCallback)(async () => {
|
|
398
|
-
|
|
399
|
-
|
|
532
|
+
const selectedClassId = classOpt?.value;
|
|
533
|
+
const selectedSectionId = sectionOpt?.value;
|
|
534
|
+
const selectedSubjectId = subjectOpt?.value;
|
|
535
|
+
const missing = [];
|
|
536
|
+
if (!title) missing.push('title');
|
|
537
|
+
if (!description) missing.push('description');
|
|
538
|
+
if (!selectedClassId) missing.push('class');
|
|
539
|
+
if (!selectedSectionId) missing.push('section');
|
|
540
|
+
if (!selectedSubjectId) missing.push('subject');
|
|
541
|
+
if (props.mode === 'edit' && !fileList.length) missing.push('file');
|
|
542
|
+
if (missing.length) {
|
|
543
|
+
if (props.mode === 'edit' && !fileList.length) {
|
|
544
|
+
setFileError('File is required');
|
|
545
|
+
}
|
|
546
|
+
_reactNative.Alert.alert('Error', `Please fill: ${missing.join(', ')}`);
|
|
400
547
|
return;
|
|
401
548
|
}
|
|
402
549
|
const payload = {
|
|
@@ -404,13 +551,13 @@ function NoteEditor(props) {
|
|
|
404
551
|
title,
|
|
405
552
|
description,
|
|
406
553
|
remark: description,
|
|
407
|
-
class_id:
|
|
408
|
-
section_id:
|
|
409
|
-
sub_id:
|
|
410
|
-
file,
|
|
554
|
+
class_id: selectedClassId,
|
|
555
|
+
section_id: selectedSectionId,
|
|
556
|
+
sub_id: selectedSubjectId,
|
|
557
|
+
file: fileList.join(','),
|
|
411
558
|
status
|
|
412
559
|
};
|
|
413
|
-
|
|
560
|
+
setIsSubmitting(true);
|
|
414
561
|
try {
|
|
415
562
|
const res = props.mode === 'create' ? await (0, _notesService.createNote)(api, payload) : await (0, _notesService.updateNote)(api, {
|
|
416
563
|
...payload,
|
|
@@ -426,13 +573,13 @@ function NoteEditor(props) {
|
|
|
426
573
|
} catch (e) {
|
|
427
574
|
_reactNative.Alert.alert('Error', String(e?.message ?? 'Could not save note'));
|
|
428
575
|
} finally {
|
|
429
|
-
|
|
576
|
+
setIsSubmitting(false);
|
|
430
577
|
}
|
|
431
|
-
}, [api, classOpt?.value, description,
|
|
578
|
+
}, [api, classOpt?.value, description, fileList, props, sectionOpt?.value, status, subjectOpt?.value, title]);
|
|
432
579
|
if (loading) {
|
|
433
580
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_LoadingState.LoadingState, {});
|
|
434
581
|
}
|
|
435
|
-
const fileUrl =
|
|
582
|
+
const fileUrl = activeFile ? resolveNoteUrl(fileBaseUrl, activeFile) : '';
|
|
436
583
|
const headers = authToken || schoolCode ? {
|
|
437
584
|
...(authToken ? {
|
|
438
585
|
Authorization: authToken
|
|
@@ -490,7 +637,11 @@ function NoteEditor(props) {
|
|
|
490
637
|
value: classOpt,
|
|
491
638
|
options: classes,
|
|
492
639
|
placeholder: "Select class",
|
|
493
|
-
onChange:
|
|
640
|
+
onChange: next => {
|
|
641
|
+
setClassOpt(next);
|
|
642
|
+
setSectionOpt(null);
|
|
643
|
+
setSubjectOpt(null);
|
|
644
|
+
}
|
|
494
645
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(SelectField, {
|
|
495
646
|
label: "Section",
|
|
496
647
|
value: sectionOpt,
|
|
@@ -536,24 +687,69 @@ function NoteEditor(props) {
|
|
|
536
687
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
537
688
|
style: styles.secondaryBtn,
|
|
538
689
|
onPress: () => pickDocument().catch(() => {}),
|
|
690
|
+
disabled: isUploading || isSubmitting,
|
|
539
691
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
540
692
|
style: styles.secondaryBtnText,
|
|
541
|
-
children:
|
|
693
|
+
children: fileList.length ? 'Add More Files' : 'Upload File'
|
|
542
694
|
})
|
|
543
695
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
544
696
|
style: styles.secondaryBtn,
|
|
545
697
|
onPress: () => pickCamera().catch(() => {}),
|
|
698
|
+
disabled: isUploading || isSubmitting,
|
|
546
699
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
547
700
|
style: styles.secondaryBtnText,
|
|
548
701
|
children: "Camera"
|
|
549
702
|
})
|
|
550
703
|
})]
|
|
551
|
-
}),
|
|
704
|
+
}), isUploading ? /*#__PURE__*/(0, _jsxRuntime.jsx)(StatusBanner, {
|
|
705
|
+
text: uploadStatus || 'Uploading file...',
|
|
706
|
+
busy: true,
|
|
707
|
+
tone: uploadTone
|
|
708
|
+
}) : uploadStatus ? /*#__PURE__*/(0, _jsxRuntime.jsx)(StatusBanner, {
|
|
709
|
+
text: uploadStatus,
|
|
710
|
+
tone: uploadTone
|
|
711
|
+
}) : null, fileList.length ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
|
|
552
712
|
style: styles.fileText,
|
|
553
|
-
children: ["Selected: ",
|
|
554
|
-
}) : null,
|
|
713
|
+
children: ["Selected: ", fileList.length, " file", fileList.length > 1 ? 's' : '']
|
|
714
|
+
}) : null, fileList.length ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
715
|
+
style: styles.attachmentList,
|
|
716
|
+
children: fileList.map((currentFile, index) => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
717
|
+
style: styles.attachmentRow,
|
|
718
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
719
|
+
style: styles.attachmentName,
|
|
720
|
+
numberOfLines: 1,
|
|
721
|
+
children: fileBaseName(currentFile)
|
|
722
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
723
|
+
style: styles.attachmentActions,
|
|
724
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
725
|
+
style: styles.inlineActionBtn,
|
|
726
|
+
onPress: () => {
|
|
727
|
+
setActiveFile(currentFile);
|
|
728
|
+
viewAttachment(currentFile).catch(() => {});
|
|
729
|
+
},
|
|
730
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
731
|
+
style: styles.inlineActionText,
|
|
732
|
+
children: "View"
|
|
733
|
+
})
|
|
734
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
735
|
+
style: styles.inlineRemoveBtn,
|
|
736
|
+
onPress: () => {
|
|
737
|
+
setFileList(prev => {
|
|
738
|
+
const next = prev.filter((_, itemIndex) => itemIndex !== index);
|
|
739
|
+
setActiveFile(current => current === currentFile ? next[0] ?? '' : current);
|
|
740
|
+
return next;
|
|
741
|
+
});
|
|
742
|
+
},
|
|
743
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
744
|
+
style: styles.inlineRemoveText,
|
|
745
|
+
children: "Remove"
|
|
746
|
+
})
|
|
747
|
+
})]
|
|
748
|
+
})]
|
|
749
|
+
}, `${currentFile}-${index}`))
|
|
750
|
+
}) : null, activeFile ? isImageUrl(activeFile) || isImageUrl(fileUrl) ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
555
751
|
style: styles.previewWrap,
|
|
556
|
-
onPress: () =>
|
|
752
|
+
onPress: () => viewAttachment(activeFile).catch(() => {}),
|
|
557
753
|
activeOpacity: 0.9,
|
|
558
754
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
|
|
559
755
|
source: {
|
|
@@ -565,31 +761,37 @@ function NoteEditor(props) {
|
|
|
565
761
|
})
|
|
566
762
|
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
567
763
|
style: styles.documentBtn,
|
|
568
|
-
onPress: () => viewAttachment().catch(() => {}),
|
|
764
|
+
onPress: () => viewAttachment(activeFile).catch(() => {}),
|
|
569
765
|
disabled: downloading,
|
|
570
766
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
571
767
|
style: styles.documentBtnText,
|
|
572
768
|
children: downloading ? 'Opening Document...' : 'View Document'
|
|
573
769
|
})
|
|
770
|
+
}) : null, fileError ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
771
|
+
style: styles.errorText,
|
|
772
|
+
children: fileError
|
|
574
773
|
}) : null]
|
|
575
774
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
576
775
|
style: styles.primaryBtn,
|
|
577
776
|
onPress: () => submit().catch(() => {}),
|
|
578
|
-
disabled:
|
|
579
|
-
children:
|
|
580
|
-
|
|
777
|
+
disabled: isUploading || isSubmitting,
|
|
778
|
+
children: isSubmitting ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
779
|
+
style: styles.loadingRow,
|
|
780
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
|
|
781
|
+
color: "#FFFFFF"
|
|
782
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
783
|
+
style: styles.primaryBtnText,
|
|
784
|
+
children: props.mode === 'create' ? 'Creating...' : 'Updating...'
|
|
785
|
+
})]
|
|
581
786
|
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
582
787
|
style: styles.primaryBtnText,
|
|
583
788
|
children: props.mode === 'create' ? 'Create Note' : 'Update Note'
|
|
584
789
|
})
|
|
585
790
|
})]
|
|
586
791
|
})]
|
|
587
|
-
}), ImageView &&
|
|
588
|
-
images:
|
|
589
|
-
|
|
590
|
-
headers
|
|
591
|
-
}],
|
|
592
|
-
imageIndex: 0,
|
|
792
|
+
}), ImageView && viewerImages.length ? /*#__PURE__*/(0, _jsxRuntime.jsx)(ImageView, {
|
|
793
|
+
images: viewerImages,
|
|
794
|
+
imageIndex: viewerIndex,
|
|
593
795
|
visible: viewerOpen,
|
|
594
796
|
onRequestClose: () => setViewerOpen(false)
|
|
595
797
|
}) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Modal, {
|
|
@@ -658,7 +860,10 @@ function NotesScreen(props) {
|
|
|
658
860
|
const [loadingMore, setLoadingMore] = (0, _react.useState)(false);
|
|
659
861
|
const [error, setError] = (0, _react.useState)(null);
|
|
660
862
|
const [selectedNote, setSelectedNote] = (0, _react.useState)(null);
|
|
863
|
+
const [selectedActiveFile, setSelectedActiveFile] = (0, _react.useState)('');
|
|
661
864
|
const [viewerOpen, setViewerOpen] = (0, _react.useState)(false);
|
|
865
|
+
const [viewerIndex, setViewerIndex] = (0, _react.useState)(0);
|
|
866
|
+
const [viewerImages, setViewerImages] = (0, _react.useState)([]);
|
|
662
867
|
const [pdfVisible, setPdfVisible] = (0, _react.useState)(false);
|
|
663
868
|
const [pdfUri, setPdfUri] = (0, _react.useState)('');
|
|
664
869
|
const [pdfHeaders, setPdfHeaders] = (0, _react.useState)({});
|
|
@@ -740,15 +945,26 @@ function NotesScreen(props) {
|
|
|
740
945
|
if (mode !== 'list') return;
|
|
741
946
|
loadList(props.page ?? 1, true).catch(() => {});
|
|
742
947
|
}, [loadList, mode, props.page]);
|
|
743
|
-
const
|
|
744
|
-
|
|
948
|
+
const selectedNoteFiles = (0, _react.useMemo)(() => splitFileCsv(selectedNote?.file ?? ''), [selectedNote?.file]);
|
|
949
|
+
const viewNoteAttachment = (0, _react.useCallback)(async selectedFile => {
|
|
950
|
+
const file = String(selectedFile ?? selectedActiveFile ?? selectedNoteFiles[0] ?? '');
|
|
745
951
|
if (!file) {
|
|
746
952
|
_reactNative.Alert.alert('No Attachment');
|
|
747
953
|
return;
|
|
748
954
|
}
|
|
749
955
|
const url = resolveNoteUrl(fileBaseUrl, file);
|
|
750
956
|
const headers = await resolveHeaders();
|
|
751
|
-
if (ImageView && isImageUrl(file)) {
|
|
957
|
+
if (ImageView && (isImageUrl(file) || isImageUrl(url))) {
|
|
958
|
+
const imageFiles = selectedNoteFiles.filter(currentFile => {
|
|
959
|
+
const currentUrl = resolveNoteUrl(fileBaseUrl, currentFile);
|
|
960
|
+
return isImageUrl(currentFile) || isImageUrl(currentUrl);
|
|
961
|
+
});
|
|
962
|
+
const nextIndex = Math.max(0, imageFiles.findIndex(currentFile => String(currentFile) === String(file)));
|
|
963
|
+
setViewerImages(imageFiles.map(currentFile => ({
|
|
964
|
+
uri: resolveNoteUrl(fileBaseUrl, currentFile),
|
|
965
|
+
headers: Object.keys(headers).length ? headers : undefined
|
|
966
|
+
})));
|
|
967
|
+
setViewerIndex(nextIndex);
|
|
752
968
|
setViewerOpen(true);
|
|
753
969
|
return;
|
|
754
970
|
}
|
|
@@ -800,7 +1016,7 @@ function NotesScreen(props) {
|
|
|
800
1016
|
} finally {
|
|
801
1017
|
setDownloading(false);
|
|
802
1018
|
}
|
|
803
|
-
}, [FileViewer, ImageView, Pdf, RNFS, fileBaseUrl, resolveHeaders,
|
|
1019
|
+
}, [FileViewer, ImageView, Pdf, RNFS, fileBaseUrl, resolveHeaders, selectedActiveFile, selectedNoteFiles]);
|
|
804
1020
|
if (mode === 'create') {
|
|
805
1021
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(NoteEditor, {
|
|
806
1022
|
mode: "create",
|
|
@@ -925,7 +1141,10 @@ function NotesScreen(props) {
|
|
|
925
1141
|
style: styles.row,
|
|
926
1142
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
927
1143
|
style: styles.secondaryBtn,
|
|
928
|
-
onPress: () =>
|
|
1144
|
+
onPress: () => {
|
|
1145
|
+
setSelectedNote(item);
|
|
1146
|
+
setSelectedActiveFile(splitFileCsv(item?.file ?? '')[0] ?? '');
|
|
1147
|
+
},
|
|
929
1148
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
930
1149
|
style: styles.secondaryBtnText,
|
|
931
1150
|
children: "View"
|
|
@@ -988,42 +1207,65 @@ function NotesScreen(props) {
|
|
|
988
1207
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
989
1208
|
style: styles.noteDescFull,
|
|
990
1209
|
children: String(selectedNote?.description ?? selectedNote?.remark ?? '-')
|
|
991
|
-
}),
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1210
|
+
}), selectedNoteFiles.length ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
1211
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
|
|
1212
|
+
style: styles.fileText,
|
|
1213
|
+
children: ["Selected: ", selectedNoteFiles.length, " file", selectedNoteFiles.length > 1 ? 's' : '']
|
|
1214
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
1215
|
+
style: styles.attachmentList,
|
|
1216
|
+
children: selectedNoteFiles.map((currentFile, index) => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
1217
|
+
style: styles.attachmentRow,
|
|
1218
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
1219
|
+
style: styles.attachmentName,
|
|
1220
|
+
numberOfLines: 1,
|
|
1221
|
+
children: fileBaseName(currentFile)
|
|
1222
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
1223
|
+
style: styles.inlineActionBtn,
|
|
1224
|
+
onPress: () => {
|
|
1225
|
+
setSelectedActiveFile(currentFile);
|
|
1226
|
+
viewNoteAttachment(currentFile).catch(() => {});
|
|
1227
|
+
},
|
|
1228
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
1229
|
+
style: styles.inlineActionText,
|
|
1230
|
+
children: "View"
|
|
1231
|
+
})
|
|
1232
|
+
})]
|
|
1233
|
+
}, `${currentFile}-${index}`))
|
|
1234
|
+
}), selectedActiveFile ? isImageUrl(selectedActiveFile) || isImageUrl(resolveNoteUrl(fileBaseUrl, selectedActiveFile)) ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
1235
|
+
style: styles.previewWrap,
|
|
1236
|
+
onPress: () => viewNoteAttachment(selectedActiveFile).catch(() => {}),
|
|
1237
|
+
activeOpacity: 0.9,
|
|
1238
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
|
|
1239
|
+
source: {
|
|
1240
|
+
uri: resolveNoteUrl(fileBaseUrl, selectedActiveFile),
|
|
1241
|
+
headers: authToken || schoolCode ? {
|
|
1242
|
+
...(authToken ? {
|
|
1243
|
+
Authorization: authToken
|
|
1244
|
+
} : {}),
|
|
1245
|
+
...(schoolCode ? {
|
|
1246
|
+
school_code: schoolCode
|
|
1247
|
+
} : {})
|
|
1248
|
+
} : undefined
|
|
1249
|
+
},
|
|
1250
|
+
style: styles.previewImage,
|
|
1251
|
+
resizeMode: "contain"
|
|
1252
|
+
})
|
|
1253
|
+
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
1254
|
+
style: styles.documentBtn,
|
|
1255
|
+
onPress: () => viewNoteAttachment(selectedActiveFile).catch(() => {}),
|
|
1256
|
+
disabled: downloading,
|
|
1257
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
1258
|
+
style: styles.documentBtnText,
|
|
1259
|
+
children: downloading ? 'Opening Document...' : 'View Document'
|
|
1260
|
+
})
|
|
1261
|
+
}) : null]
|
|
1018
1262
|
}) : null]
|
|
1019
1263
|
}) : null]
|
|
1020
1264
|
})
|
|
1021
1265
|
})
|
|
1022
|
-
}), ImageView &&
|
|
1023
|
-
images:
|
|
1024
|
-
|
|
1025
|
-
}],
|
|
1026
|
-
imageIndex: 0,
|
|
1266
|
+
}), ImageView && viewerImages.length ? /*#__PURE__*/(0, _jsxRuntime.jsx)(ImageView, {
|
|
1267
|
+
images: viewerImages,
|
|
1268
|
+
imageIndex: viewerIndex,
|
|
1027
1269
|
visible: viewerOpen,
|
|
1028
1270
|
onRequestClose: () => setViewerOpen(false)
|
|
1029
1271
|
}) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Modal, {
|
|
@@ -1195,6 +1437,51 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
1195
1437
|
color: '#374151',
|
|
1196
1438
|
marginTop: 8
|
|
1197
1439
|
},
|
|
1440
|
+
attachmentList: {
|
|
1441
|
+
width: '100%',
|
|
1442
|
+
marginTop: 8
|
|
1443
|
+
},
|
|
1444
|
+
attachmentRow: {
|
|
1445
|
+
flexDirection: 'row',
|
|
1446
|
+
alignItems: 'center',
|
|
1447
|
+
justifyContent: 'space-between',
|
|
1448
|
+
paddingVertical: 8,
|
|
1449
|
+
borderBottomWidth: 1,
|
|
1450
|
+
borderBottomColor: '#E5E7EB',
|
|
1451
|
+
gap: 10
|
|
1452
|
+
},
|
|
1453
|
+
attachmentName: {
|
|
1454
|
+
flex: 1,
|
|
1455
|
+
color: '#111827',
|
|
1456
|
+
fontSize: 13
|
|
1457
|
+
},
|
|
1458
|
+
attachmentActions: {
|
|
1459
|
+
flexDirection: 'row',
|
|
1460
|
+
alignItems: 'center',
|
|
1461
|
+
gap: 8
|
|
1462
|
+
},
|
|
1463
|
+
inlineActionBtn: {
|
|
1464
|
+
paddingHorizontal: 10,
|
|
1465
|
+
paddingVertical: 6,
|
|
1466
|
+
borderRadius: 8,
|
|
1467
|
+
backgroundColor: '#DBEAFE'
|
|
1468
|
+
},
|
|
1469
|
+
inlineActionText: {
|
|
1470
|
+
color: '#1D4ED8',
|
|
1471
|
+
fontWeight: '700',
|
|
1472
|
+
fontSize: 12
|
|
1473
|
+
},
|
|
1474
|
+
inlineRemoveBtn: {
|
|
1475
|
+
paddingHorizontal: 10,
|
|
1476
|
+
paddingVertical: 6,
|
|
1477
|
+
borderRadius: 8,
|
|
1478
|
+
backgroundColor: '#FEE2E2'
|
|
1479
|
+
},
|
|
1480
|
+
inlineRemoveText: {
|
|
1481
|
+
color: '#B91C1C',
|
|
1482
|
+
fontWeight: '700',
|
|
1483
|
+
fontSize: 12
|
|
1484
|
+
},
|
|
1198
1485
|
previewWrap: {
|
|
1199
1486
|
marginTop: 12,
|
|
1200
1487
|
borderWidth: 1,
|
|
@@ -1283,5 +1570,31 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
1283
1570
|
color: '#6B7280',
|
|
1284
1571
|
textAlign: 'center',
|
|
1285
1572
|
paddingVertical: 16
|
|
1573
|
+
},
|
|
1574
|
+
statusBanner: {
|
|
1575
|
+
marginTop: 10,
|
|
1576
|
+
paddingHorizontal: 12,
|
|
1577
|
+
paddingVertical: 10,
|
|
1578
|
+
borderRadius: 12,
|
|
1579
|
+
borderWidth: 1,
|
|
1580
|
+
flexDirection: 'row',
|
|
1581
|
+
alignItems: 'center',
|
|
1582
|
+
gap: 10
|
|
1583
|
+
},
|
|
1584
|
+
statusBannerText: {
|
|
1585
|
+
flex: 1,
|
|
1586
|
+
fontSize: 12,
|
|
1587
|
+
fontWeight: '700'
|
|
1588
|
+
},
|
|
1589
|
+
errorText: {
|
|
1590
|
+
marginTop: 6,
|
|
1591
|
+
fontSize: 12,
|
|
1592
|
+
color: '#DC2626',
|
|
1593
|
+
fontWeight: '600'
|
|
1594
|
+
},
|
|
1595
|
+
loadingRow: {
|
|
1596
|
+
flexDirection: 'row',
|
|
1597
|
+
alignItems: 'center',
|
|
1598
|
+
gap: 8
|
|
1286
1599
|
}
|
|
1287
1600
|
});
|