@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
|
@@ -31,7 +31,22 @@ function resolveNoteUrl(baseUrl, file) {
|
|
|
31
31
|
if (!file) return '';
|
|
32
32
|
if (/^https?:\/\//i.test(file)) return file;
|
|
33
33
|
const base = String(baseUrl ?? '').replace(/\/?$/, '/');
|
|
34
|
-
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
|
+
}
|
|
35
50
|
return `${base}student/note/${normalized}`;
|
|
36
51
|
}
|
|
37
52
|
function isImageUrl(file) {
|
|
@@ -47,6 +62,10 @@ function fileBaseName(path) {
|
|
|
47
62
|
function ensureFileUri(path) {
|
|
48
63
|
return path.startsWith('file://') ? path : `file://${path}`;
|
|
49
64
|
}
|
|
65
|
+
function splitFileCsv(raw) {
|
|
66
|
+
if (!raw) return [];
|
|
67
|
+
return String(raw).split(',').map(item => item.trim()).filter(Boolean);
|
|
68
|
+
}
|
|
50
69
|
function tryGetImageViewer() {
|
|
51
70
|
try {
|
|
52
71
|
const mod = require('react-native-image-viewing');
|
|
@@ -94,6 +113,68 @@ async function downloadPdfToLocal(args) {
|
|
|
94
113
|
}
|
|
95
114
|
return ensureFileUri(target);
|
|
96
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
|
+
}
|
|
97
178
|
function SelectField({
|
|
98
179
|
label,
|
|
99
180
|
value,
|
|
@@ -166,7 +247,10 @@ function NoteEditor(props) {
|
|
|
166
247
|
const RNFS = (0, _react.useMemo)(() => tryGetRNFS(), []);
|
|
167
248
|
const FileViewer = (0, _react.useMemo)(() => tryGetFileViewer(), []);
|
|
168
249
|
const [loading, setLoading] = (0, _react.useState)(props.mode === 'edit');
|
|
169
|
-
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');
|
|
170
254
|
const [classes, setClasses] = (0, _react.useState)([]);
|
|
171
255
|
const [sections, setSections] = (0, _react.useState)([]);
|
|
172
256
|
const [subjects, setSubjects] = (0, _react.useState)([]);
|
|
@@ -176,8 +260,12 @@ function NoteEditor(props) {
|
|
|
176
260
|
const [classOpt, setClassOpt] = (0, _react.useState)(null);
|
|
177
261
|
const [sectionOpt, setSectionOpt] = (0, _react.useState)(null);
|
|
178
262
|
const [subjectOpt, setSubjectOpt] = (0, _react.useState)(null);
|
|
179
|
-
const [
|
|
263
|
+
const [fileList, setFileList] = (0, _react.useState)([]);
|
|
264
|
+
const [activeFile, setActiveFile] = (0, _react.useState)('');
|
|
265
|
+
const [fileError, setFileError] = (0, _react.useState)(null);
|
|
180
266
|
const [viewerOpen, setViewerOpen] = (0, _react.useState)(false);
|
|
267
|
+
const [viewerIndex, setViewerIndex] = (0, _react.useState)(0);
|
|
268
|
+
const [viewerImages, setViewerImages] = (0, _react.useState)([]);
|
|
181
269
|
const [pdfVisible, setPdfVisible] = (0, _react.useState)(false);
|
|
182
270
|
const [pdfUri, setPdfUri] = (0, _react.useState)('');
|
|
183
271
|
const [pdfHeaders, setPdfHeaders] = (0, _react.useState)({});
|
|
@@ -230,7 +318,9 @@ function NoteEditor(props) {
|
|
|
230
318
|
setTitle(String(data?.title ?? ''));
|
|
231
319
|
setDescription(String(data?.description ?? data?.remark ?? ''));
|
|
232
320
|
setStatus(String(data?.status ?? 'publish').toLowerCase() === 'unpublish' ? 'unpublish' : 'publish');
|
|
233
|
-
|
|
321
|
+
const noteFiles = splitFileCsv(data?.file ?? '');
|
|
322
|
+
setFileList(noteFiles);
|
|
323
|
+
setActiveFile(noteFiles[0] ?? '');
|
|
234
324
|
const classId = data?.class_id ?? props.defaults.classId;
|
|
235
325
|
const sectionId = data?.section_id ?? props.defaults.sectionId;
|
|
236
326
|
const subjectId = data?.sub_id ?? data?.subject_id ?? props.defaults.subjectId;
|
|
@@ -280,27 +370,51 @@ function NoteEditor(props) {
|
|
|
280
370
|
dp = require('react-native-document-picker');
|
|
281
371
|
} catch {}
|
|
282
372
|
if (!dp) return;
|
|
283
|
-
const selected = await dp.
|
|
373
|
+
const selected = await dp.pick({
|
|
284
374
|
presentationStyle: 'fullScreen',
|
|
375
|
+
allowMultiSelection: true,
|
|
285
376
|
type: [dp.types.pdf, dp.types.images]
|
|
286
377
|
});
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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);
|
|
293
389
|
try {
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
298
|
-
|
|
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;
|
|
299
411
|
}
|
|
412
|
+
setUploadStatus(`${uploaded.length} file(s) uploaded successfully.`);
|
|
413
|
+
setUploadTone('success');
|
|
300
414
|
} finally {
|
|
301
|
-
|
|
415
|
+
setIsUploading(false);
|
|
302
416
|
}
|
|
303
|
-
}, [api]);
|
|
417
|
+
}, [api, schoolCode]);
|
|
304
418
|
const pickCamera = (0, _react.useCallback)(async () => {
|
|
305
419
|
let picker = null;
|
|
306
420
|
try {
|
|
@@ -321,30 +435,51 @@ function NoteEditor(props) {
|
|
|
321
435
|
name: asset.fileName ?? 'camera.jpg',
|
|
322
436
|
type: asset.type ?? 'image/jpeg'
|
|
323
437
|
};
|
|
324
|
-
|
|
438
|
+
setFileError(null);
|
|
439
|
+
setUploadStatus('Uploading image...');
|
|
440
|
+
setUploadTone('info');
|
|
441
|
+
setIsUploading(true);
|
|
325
442
|
try {
|
|
326
|
-
const response = await (0, _notesService.uploadNoteFile)(api, upload);
|
|
443
|
+
const response = await (0, _notesService.uploadNoteFile)(api, upload, schoolCode);
|
|
327
444
|
if (response?.Status === 'Success' && response?.data) {
|
|
328
|
-
|
|
445
|
+
const nextFile = String(response.data);
|
|
446
|
+
setFileList(prev => [...prev, nextFile]);
|
|
447
|
+
setActiveFile(prev => prev || nextFile);
|
|
448
|
+
setUploadStatus('Image uploaded successfully.');
|
|
449
|
+
setUploadTone('success');
|
|
329
450
|
} else {
|
|
330
|
-
|
|
451
|
+
setFileError('Image upload failed');
|
|
452
|
+
setUploadStatus(String(response?.msg ?? 'Image upload failed'));
|
|
453
|
+
setUploadTone('error');
|
|
331
454
|
}
|
|
332
455
|
} finally {
|
|
333
|
-
|
|
456
|
+
setIsUploading(false);
|
|
334
457
|
}
|
|
335
|
-
}, [api]);
|
|
336
|
-
const viewAttachment = (0, _react.useCallback)(async
|
|
337
|
-
|
|
458
|
+
}, [api, schoolCode]);
|
|
459
|
+
const viewAttachment = (0, _react.useCallback)(async selectedFile => {
|
|
460
|
+
const targetFile = String(selectedFile ?? activeFile ?? fileList[0] ?? '');
|
|
461
|
+
if (!targetFile) {
|
|
338
462
|
_reactNative.Alert.alert('No Attachment');
|
|
339
463
|
return;
|
|
340
464
|
}
|
|
341
|
-
const url = resolveNoteUrl(fileBaseUrl,
|
|
465
|
+
const url = resolveNoteUrl(fileBaseUrl, targetFile);
|
|
342
466
|
const headers = await resolveHeaders();
|
|
343
|
-
|
|
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);
|
|
344
479
|
setViewerOpen(true);
|
|
345
480
|
return;
|
|
346
481
|
}
|
|
347
|
-
const isPdf = String(
|
|
482
|
+
const isPdf = String(targetFile).toLowerCase().endsWith('.pdf') || url.toLowerCase().endsWith('.pdf');
|
|
348
483
|
if (Pdf && isPdf) {
|
|
349
484
|
if (!RNFS) {
|
|
350
485
|
setPdfHeaders(headers);
|
|
@@ -358,7 +493,7 @@ function NoteEditor(props) {
|
|
|
358
493
|
const local = await downloadPdfToLocal({
|
|
359
494
|
RNFS,
|
|
360
495
|
url,
|
|
361
|
-
fileName: fileBaseName(
|
|
496
|
+
fileName: fileBaseName(targetFile),
|
|
362
497
|
headers
|
|
363
498
|
});
|
|
364
499
|
setPdfHeaders({});
|
|
@@ -378,7 +513,7 @@ function NoteEditor(props) {
|
|
|
378
513
|
}
|
|
379
514
|
setDownloading(true);
|
|
380
515
|
try {
|
|
381
|
-
const localFile = `${RNFS.DocumentDirectoryPath}/${fileBaseName(
|
|
516
|
+
const localFile = `${RNFS.DocumentDirectoryPath}/${fileBaseName(targetFile)}`;
|
|
382
517
|
await RNFS.downloadFile({
|
|
383
518
|
fromUrl: url,
|
|
384
519
|
toFile: localFile,
|
|
@@ -392,10 +527,23 @@ function NoteEditor(props) {
|
|
|
392
527
|
} finally {
|
|
393
528
|
setDownloading(false);
|
|
394
529
|
}
|
|
395
|
-
}, [FileViewer, ImageView, Pdf, RNFS,
|
|
530
|
+
}, [FileViewer, ImageView, Pdf, RNFS, activeFile, fileBaseUrl, fileList, resolveHeaders]);
|
|
396
531
|
const submit = (0, _react.useCallback)(async () => {
|
|
397
|
-
|
|
398
|
-
|
|
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(', ')}`);
|
|
399
547
|
return;
|
|
400
548
|
}
|
|
401
549
|
const payload = {
|
|
@@ -403,13 +551,13 @@ function NoteEditor(props) {
|
|
|
403
551
|
title,
|
|
404
552
|
description,
|
|
405
553
|
remark: description,
|
|
406
|
-
class_id:
|
|
407
|
-
section_id:
|
|
408
|
-
sub_id:
|
|
409
|
-
file,
|
|
554
|
+
class_id: selectedClassId,
|
|
555
|
+
section_id: selectedSectionId,
|
|
556
|
+
sub_id: selectedSubjectId,
|
|
557
|
+
file: fileList.join(','),
|
|
410
558
|
status
|
|
411
559
|
};
|
|
412
|
-
|
|
560
|
+
setIsSubmitting(true);
|
|
413
561
|
try {
|
|
414
562
|
const res = props.mode === 'create' ? await (0, _notesService.createNote)(api, payload) : await (0, _notesService.updateNote)(api, {
|
|
415
563
|
...payload,
|
|
@@ -425,13 +573,13 @@ function NoteEditor(props) {
|
|
|
425
573
|
} catch (e) {
|
|
426
574
|
_reactNative.Alert.alert('Error', String(e?.message ?? 'Could not save note'));
|
|
427
575
|
} finally {
|
|
428
|
-
|
|
576
|
+
setIsSubmitting(false);
|
|
429
577
|
}
|
|
430
|
-
}, [api, classOpt?.value, description,
|
|
578
|
+
}, [api, classOpt?.value, description, fileList, props, sectionOpt?.value, status, subjectOpt?.value, title]);
|
|
431
579
|
if (loading) {
|
|
432
580
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_LoadingState.LoadingState, {});
|
|
433
581
|
}
|
|
434
|
-
const fileUrl =
|
|
582
|
+
const fileUrl = activeFile ? resolveNoteUrl(fileBaseUrl, activeFile) : '';
|
|
435
583
|
const headers = authToken || schoolCode ? {
|
|
436
584
|
...(authToken ? {
|
|
437
585
|
Authorization: authToken
|
|
@@ -489,7 +637,11 @@ function NoteEditor(props) {
|
|
|
489
637
|
value: classOpt,
|
|
490
638
|
options: classes,
|
|
491
639
|
placeholder: "Select class",
|
|
492
|
-
onChange:
|
|
640
|
+
onChange: next => {
|
|
641
|
+
setClassOpt(next);
|
|
642
|
+
setSectionOpt(null);
|
|
643
|
+
setSubjectOpt(null);
|
|
644
|
+
}
|
|
493
645
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(SelectField, {
|
|
494
646
|
label: "Section",
|
|
495
647
|
value: sectionOpt,
|
|
@@ -535,24 +687,69 @@ function NoteEditor(props) {
|
|
|
535
687
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
536
688
|
style: styles.secondaryBtn,
|
|
537
689
|
onPress: () => pickDocument().catch(() => {}),
|
|
690
|
+
disabled: isUploading || isSubmitting,
|
|
538
691
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
539
692
|
style: styles.secondaryBtnText,
|
|
540
|
-
children:
|
|
693
|
+
children: fileList.length ? 'Add More Files' : 'Upload File'
|
|
541
694
|
})
|
|
542
695
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
543
696
|
style: styles.secondaryBtn,
|
|
544
697
|
onPress: () => pickCamera().catch(() => {}),
|
|
698
|
+
disabled: isUploading || isSubmitting,
|
|
545
699
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
546
700
|
style: styles.secondaryBtnText,
|
|
547
701
|
children: "Camera"
|
|
548
702
|
})
|
|
549
703
|
})]
|
|
550
|
-
}),
|
|
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, {
|
|
551
712
|
style: styles.fileText,
|
|
552
|
-
children: ["Selected: ",
|
|
553
|
-
}) : 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, {
|
|
554
751
|
style: styles.previewWrap,
|
|
555
|
-
onPress: () =>
|
|
752
|
+
onPress: () => viewAttachment(activeFile).catch(() => {}),
|
|
556
753
|
activeOpacity: 0.9,
|
|
557
754
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
|
|
558
755
|
source: {
|
|
@@ -564,31 +761,37 @@ function NoteEditor(props) {
|
|
|
564
761
|
})
|
|
565
762
|
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
566
763
|
style: styles.documentBtn,
|
|
567
|
-
onPress: () => viewAttachment().catch(() => {}),
|
|
764
|
+
onPress: () => viewAttachment(activeFile).catch(() => {}),
|
|
568
765
|
disabled: downloading,
|
|
569
766
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
570
767
|
style: styles.documentBtnText,
|
|
571
768
|
children: downloading ? 'Opening Document...' : 'View Document'
|
|
572
769
|
})
|
|
770
|
+
}) : null, fileError ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
771
|
+
style: styles.errorText,
|
|
772
|
+
children: fileError
|
|
573
773
|
}) : null]
|
|
574
774
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
575
775
|
style: styles.primaryBtn,
|
|
576
776
|
onPress: () => submit().catch(() => {}),
|
|
577
|
-
disabled:
|
|
578
|
-
children:
|
|
579
|
-
|
|
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
|
+
})]
|
|
580
786
|
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
581
787
|
style: styles.primaryBtnText,
|
|
582
788
|
children: props.mode === 'create' ? 'Create Note' : 'Update Note'
|
|
583
789
|
})
|
|
584
790
|
})]
|
|
585
791
|
})]
|
|
586
|
-
}), ImageView &&
|
|
587
|
-
images:
|
|
588
|
-
|
|
589
|
-
headers
|
|
590
|
-
}],
|
|
591
|
-
imageIndex: 0,
|
|
792
|
+
}), ImageView && viewerImages.length ? /*#__PURE__*/(0, _jsxRuntime.jsx)(ImageView, {
|
|
793
|
+
images: viewerImages,
|
|
794
|
+
imageIndex: viewerIndex,
|
|
592
795
|
visible: viewerOpen,
|
|
593
796
|
onRequestClose: () => setViewerOpen(false)
|
|
594
797
|
}) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Modal, {
|
|
@@ -657,7 +860,10 @@ function NotesScreen(props) {
|
|
|
657
860
|
const [loadingMore, setLoadingMore] = (0, _react.useState)(false);
|
|
658
861
|
const [error, setError] = (0, _react.useState)(null);
|
|
659
862
|
const [selectedNote, setSelectedNote] = (0, _react.useState)(null);
|
|
863
|
+
const [selectedActiveFile, setSelectedActiveFile] = (0, _react.useState)('');
|
|
660
864
|
const [viewerOpen, setViewerOpen] = (0, _react.useState)(false);
|
|
865
|
+
const [viewerIndex, setViewerIndex] = (0, _react.useState)(0);
|
|
866
|
+
const [viewerImages, setViewerImages] = (0, _react.useState)([]);
|
|
661
867
|
const [pdfVisible, setPdfVisible] = (0, _react.useState)(false);
|
|
662
868
|
const [pdfUri, setPdfUri] = (0, _react.useState)('');
|
|
663
869
|
const [pdfHeaders, setPdfHeaders] = (0, _react.useState)({});
|
|
@@ -739,15 +945,26 @@ function NotesScreen(props) {
|
|
|
739
945
|
if (mode !== 'list') return;
|
|
740
946
|
loadList(props.page ?? 1, true).catch(() => {});
|
|
741
947
|
}, [loadList, mode, props.page]);
|
|
742
|
-
const
|
|
743
|
-
|
|
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] ?? '');
|
|
744
951
|
if (!file) {
|
|
745
952
|
_reactNative.Alert.alert('No Attachment');
|
|
746
953
|
return;
|
|
747
954
|
}
|
|
748
955
|
const url = resolveNoteUrl(fileBaseUrl, file);
|
|
749
956
|
const headers = await resolveHeaders();
|
|
750
|
-
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);
|
|
751
968
|
setViewerOpen(true);
|
|
752
969
|
return;
|
|
753
970
|
}
|
|
@@ -799,7 +1016,7 @@ function NotesScreen(props) {
|
|
|
799
1016
|
} finally {
|
|
800
1017
|
setDownloading(false);
|
|
801
1018
|
}
|
|
802
|
-
}, [FileViewer, ImageView, Pdf, RNFS, fileBaseUrl, resolveHeaders,
|
|
1019
|
+
}, [FileViewer, ImageView, Pdf, RNFS, fileBaseUrl, resolveHeaders, selectedActiveFile, selectedNoteFiles]);
|
|
803
1020
|
if (mode === 'create') {
|
|
804
1021
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(NoteEditor, {
|
|
805
1022
|
mode: "create",
|
|
@@ -924,7 +1141,10 @@ function NotesScreen(props) {
|
|
|
924
1141
|
style: styles.row,
|
|
925
1142
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
926
1143
|
style: styles.secondaryBtn,
|
|
927
|
-
onPress: () =>
|
|
1144
|
+
onPress: () => {
|
|
1145
|
+
setSelectedNote(item);
|
|
1146
|
+
setSelectedActiveFile(splitFileCsv(item?.file ?? '')[0] ?? '');
|
|
1147
|
+
},
|
|
928
1148
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
929
1149
|
style: styles.secondaryBtnText,
|
|
930
1150
|
children: "View"
|
|
@@ -987,42 +1207,65 @@ function NotesScreen(props) {
|
|
|
987
1207
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
988
1208
|
style: styles.noteDescFull,
|
|
989
1209
|
children: String(selectedNote?.description ?? selectedNote?.remark ?? '-')
|
|
990
|
-
}),
|
|
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
|
-
|
|
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]
|
|
1017
1262
|
}) : null]
|
|
1018
1263
|
}) : null]
|
|
1019
1264
|
})
|
|
1020
1265
|
})
|
|
1021
|
-
}), ImageView &&
|
|
1022
|
-
images:
|
|
1023
|
-
|
|
1024
|
-
}],
|
|
1025
|
-
imageIndex: 0,
|
|
1266
|
+
}), ImageView && viewerImages.length ? /*#__PURE__*/(0, _jsxRuntime.jsx)(ImageView, {
|
|
1267
|
+
images: viewerImages,
|
|
1268
|
+
imageIndex: viewerIndex,
|
|
1026
1269
|
visible: viewerOpen,
|
|
1027
1270
|
onRequestClose: () => setViewerOpen(false)
|
|
1028
1271
|
}) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Modal, {
|
|
@@ -1194,6 +1437,51 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
1194
1437
|
color: '#374151',
|
|
1195
1438
|
marginTop: 8
|
|
1196
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
|
+
},
|
|
1197
1485
|
previewWrap: {
|
|
1198
1486
|
marginTop: 12,
|
|
1199
1487
|
borderWidth: 1,
|
|
@@ -1282,5 +1570,31 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
1282
1570
|
color: '#6B7280',
|
|
1283
1571
|
textAlign: 'center',
|
|
1284
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
|
|
1285
1599
|
}
|
|
1286
1600
|
});
|