@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
|
@@ -7,7 +7,7 @@ import { EmptyState } from "../../../shared/empty-states/EmptyState.js";
|
|
|
7
7
|
import { ErrorState } from "../../../shared/empty-states/ErrorState.js";
|
|
8
8
|
import { LoadingState } from "../../../shared/loaders/LoadingState.js";
|
|
9
9
|
import { createNote, fetchNoteDetails, fetchNotesClasses, fetchNotesList, fetchNotesSections, fetchNotesSubjects, updateNote, uploadNoteFile } from "../services/notesService.js";
|
|
10
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
10
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
11
11
|
function normalizeList(result) {
|
|
12
12
|
if (Array.isArray(result)) return result;
|
|
13
13
|
if (Array.isArray(result?.data)) return result.data;
|
|
@@ -26,7 +26,22 @@ function resolveNoteUrl(baseUrl, file) {
|
|
|
26
26
|
if (!file) return '';
|
|
27
27
|
if (/^https?:\/\//i.test(file)) return file;
|
|
28
28
|
const base = String(baseUrl ?? '').replace(/\/?$/, '/');
|
|
29
|
-
const normalized = String(file).replace(/^\/+/, '');
|
|
29
|
+
const normalized = String(file).replace(/\\/g, '/').replace(/^\/+/, '');
|
|
30
|
+
const uploadsIndex = base.toLowerCase().indexOf('uploads/');
|
|
31
|
+
const uploadsBase = uploadsIndex >= 0 ? base.slice(0, uploadsIndex + 'uploads/'.length) : base;
|
|
32
|
+
const uploadsRoot = uploadsIndex >= 0 ? base.slice(0, uploadsIndex) : base;
|
|
33
|
+
if (normalized.startsWith('uploads/')) {
|
|
34
|
+
return `${uploadsRoot}${normalized}`;
|
|
35
|
+
}
|
|
36
|
+
if (normalized.startsWith('school_')) {
|
|
37
|
+
return `${uploadsBase}${normalized}`;
|
|
38
|
+
}
|
|
39
|
+
if (normalized.startsWith('student/note/')) {
|
|
40
|
+
return `${base}${normalized}`;
|
|
41
|
+
}
|
|
42
|
+
if (normalized.includes('/')) {
|
|
43
|
+
return `${base}${normalized}`;
|
|
44
|
+
}
|
|
30
45
|
return `${base}student/note/${normalized}`;
|
|
31
46
|
}
|
|
32
47
|
function isImageUrl(file) {
|
|
@@ -42,6 +57,10 @@ function fileBaseName(path) {
|
|
|
42
57
|
function ensureFileUri(path) {
|
|
43
58
|
return path.startsWith('file://') ? path : `file://${path}`;
|
|
44
59
|
}
|
|
60
|
+
function splitFileCsv(raw) {
|
|
61
|
+
if (!raw) return [];
|
|
62
|
+
return String(raw).split(',').map(item => item.trim()).filter(Boolean);
|
|
63
|
+
}
|
|
45
64
|
function tryGetImageViewer() {
|
|
46
65
|
try {
|
|
47
66
|
const mod = require('react-native-image-viewing');
|
|
@@ -89,6 +108,68 @@ async function downloadPdfToLocal(args) {
|
|
|
89
108
|
}
|
|
90
109
|
return ensureFileUri(target);
|
|
91
110
|
}
|
|
111
|
+
function StatusBanner({
|
|
112
|
+
text,
|
|
113
|
+
busy = false,
|
|
114
|
+
tone = 'info'
|
|
115
|
+
}) {
|
|
116
|
+
const palette = tone === 'error' ? {
|
|
117
|
+
backgroundColor: '#FEF2F2',
|
|
118
|
+
borderColor: '#FECACA',
|
|
119
|
+
textColor: '#B91C1C',
|
|
120
|
+
spinnerColor: '#DC2626'
|
|
121
|
+
} : tone === 'success' ? {
|
|
122
|
+
backgroundColor: '#ECFDF5',
|
|
123
|
+
borderColor: '#A7F3D0',
|
|
124
|
+
textColor: '#047857',
|
|
125
|
+
spinnerColor: '#059669'
|
|
126
|
+
} : tone === 'warning' ? {
|
|
127
|
+
backgroundColor: '#FFF7ED',
|
|
128
|
+
borderColor: '#FED7AA',
|
|
129
|
+
textColor: '#C2410C',
|
|
130
|
+
spinnerColor: '#EA580C'
|
|
131
|
+
} : {
|
|
132
|
+
backgroundColor: '#EFF6FF',
|
|
133
|
+
borderColor: '#BFDBFE',
|
|
134
|
+
textColor: '#1D4ED8',
|
|
135
|
+
spinnerColor: '#2563EB'
|
|
136
|
+
};
|
|
137
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
138
|
+
style: [styles.statusBanner, {
|
|
139
|
+
backgroundColor: palette.backgroundColor,
|
|
140
|
+
borderColor: palette.borderColor
|
|
141
|
+
}],
|
|
142
|
+
children: [busy ? /*#__PURE__*/_jsx(ActivityIndicator, {
|
|
143
|
+
size: "small",
|
|
144
|
+
color: palette.spinnerColor
|
|
145
|
+
}) : null, /*#__PURE__*/_jsx(Text, {
|
|
146
|
+
style: [styles.statusBannerText, {
|
|
147
|
+
color: palette.textColor
|
|
148
|
+
}],
|
|
149
|
+
children: text
|
|
150
|
+
})]
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
async function uploadNoteFilesBatch(args) {
|
|
154
|
+
const uploaded = [];
|
|
155
|
+
let failedCount = 0;
|
|
156
|
+
await Promise.all(args.files.map(async file => {
|
|
157
|
+
try {
|
|
158
|
+
const response = await uploadNoteFile(args.api, file, args.schoolCode);
|
|
159
|
+
if (response?.Status === 'Success' && response?.data) {
|
|
160
|
+
uploaded.push(String(response.data));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
} catch {
|
|
164
|
+
// Keep partial success behavior for multi-file uploads.
|
|
165
|
+
}
|
|
166
|
+
failedCount += 1;
|
|
167
|
+
}));
|
|
168
|
+
return {
|
|
169
|
+
uploaded,
|
|
170
|
+
failedCount
|
|
171
|
+
};
|
|
172
|
+
}
|
|
92
173
|
function SelectField({
|
|
93
174
|
label,
|
|
94
175
|
value,
|
|
@@ -161,7 +242,10 @@ function NoteEditor(props) {
|
|
|
161
242
|
const RNFS = useMemo(() => tryGetRNFS(), []);
|
|
162
243
|
const FileViewer = useMemo(() => tryGetFileViewer(), []);
|
|
163
244
|
const [loading, setLoading] = useState(props.mode === 'edit');
|
|
164
|
-
const [
|
|
245
|
+
const [isUploading, setIsUploading] = useState(false);
|
|
246
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
247
|
+
const [uploadStatus, setUploadStatus] = useState('');
|
|
248
|
+
const [uploadTone, setUploadTone] = useState('info');
|
|
165
249
|
const [classes, setClasses] = useState([]);
|
|
166
250
|
const [sections, setSections] = useState([]);
|
|
167
251
|
const [subjects, setSubjects] = useState([]);
|
|
@@ -171,8 +255,12 @@ function NoteEditor(props) {
|
|
|
171
255
|
const [classOpt, setClassOpt] = useState(null);
|
|
172
256
|
const [sectionOpt, setSectionOpt] = useState(null);
|
|
173
257
|
const [subjectOpt, setSubjectOpt] = useState(null);
|
|
174
|
-
const [
|
|
258
|
+
const [fileList, setFileList] = useState([]);
|
|
259
|
+
const [activeFile, setActiveFile] = useState('');
|
|
260
|
+
const [fileError, setFileError] = useState(null);
|
|
175
261
|
const [viewerOpen, setViewerOpen] = useState(false);
|
|
262
|
+
const [viewerIndex, setViewerIndex] = useState(0);
|
|
263
|
+
const [viewerImages, setViewerImages] = useState([]);
|
|
176
264
|
const [pdfVisible, setPdfVisible] = useState(false);
|
|
177
265
|
const [pdfUri, setPdfUri] = useState('');
|
|
178
266
|
const [pdfHeaders, setPdfHeaders] = useState({});
|
|
@@ -225,7 +313,9 @@ function NoteEditor(props) {
|
|
|
225
313
|
setTitle(String(data?.title ?? ''));
|
|
226
314
|
setDescription(String(data?.description ?? data?.remark ?? ''));
|
|
227
315
|
setStatus(String(data?.status ?? 'publish').toLowerCase() === 'unpublish' ? 'unpublish' : 'publish');
|
|
228
|
-
|
|
316
|
+
const noteFiles = splitFileCsv(data?.file ?? '');
|
|
317
|
+
setFileList(noteFiles);
|
|
318
|
+
setActiveFile(noteFiles[0] ?? '');
|
|
229
319
|
const classId = data?.class_id ?? props.defaults.classId;
|
|
230
320
|
const sectionId = data?.section_id ?? props.defaults.sectionId;
|
|
231
321
|
const subjectId = data?.sub_id ?? data?.subject_id ?? props.defaults.subjectId;
|
|
@@ -275,27 +365,51 @@ function NoteEditor(props) {
|
|
|
275
365
|
dp = require('react-native-document-picker');
|
|
276
366
|
} catch {}
|
|
277
367
|
if (!dp) return;
|
|
278
|
-
const selected = await dp.
|
|
368
|
+
const selected = await dp.pick({
|
|
279
369
|
presentationStyle: 'fullScreen',
|
|
370
|
+
allowMultiSelection: true,
|
|
280
371
|
type: [dp.types.pdf, dp.types.images]
|
|
281
372
|
});
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
373
|
+
const picks = Array.isArray(selected) ? selected : [selected];
|
|
374
|
+
const uploads = picks.map(item => ({
|
|
375
|
+
uri: item.uri,
|
|
376
|
+
name: item.name ?? 'file',
|
|
377
|
+
type: item.type ?? 'application/octet-stream'
|
|
378
|
+
})).filter(item => !!item.uri);
|
|
379
|
+
if (!uploads.length) return;
|
|
380
|
+
setFileError(null);
|
|
381
|
+
setUploadStatus(`Uploading ${uploads.length} file(s)...`);
|
|
382
|
+
setUploadTone('info');
|
|
383
|
+
setIsUploading(true);
|
|
288
384
|
try {
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}
|
|
293
|
-
|
|
385
|
+
const {
|
|
386
|
+
uploaded,
|
|
387
|
+
failedCount
|
|
388
|
+
} = await uploadNoteFilesBatch({
|
|
389
|
+
api,
|
|
390
|
+
files: uploads,
|
|
391
|
+
schoolCode
|
|
392
|
+
});
|
|
393
|
+
if (!uploaded.length) {
|
|
394
|
+
setFileError('File upload failed');
|
|
395
|
+
setUploadStatus('File upload failed');
|
|
396
|
+
setUploadTone('error');
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
setFileList(prev => [...prev, ...uploaded]);
|
|
400
|
+
setActiveFile(prev => prev || uploaded[0] || '');
|
|
401
|
+
if (failedCount > 0) {
|
|
402
|
+
setFileError(`${failedCount} file(s) failed to upload`);
|
|
403
|
+
setUploadStatus(`${uploaded.length} file(s) uploaded and ${failedCount} failed.`);
|
|
404
|
+
setUploadTone('warning');
|
|
405
|
+
return;
|
|
294
406
|
}
|
|
407
|
+
setUploadStatus(`${uploaded.length} file(s) uploaded successfully.`);
|
|
408
|
+
setUploadTone('success');
|
|
295
409
|
} finally {
|
|
296
|
-
|
|
410
|
+
setIsUploading(false);
|
|
297
411
|
}
|
|
298
|
-
}, [api]);
|
|
412
|
+
}, [api, schoolCode]);
|
|
299
413
|
const pickCamera = useCallback(async () => {
|
|
300
414
|
let picker = null;
|
|
301
415
|
try {
|
|
@@ -316,30 +430,51 @@ function NoteEditor(props) {
|
|
|
316
430
|
name: asset.fileName ?? 'camera.jpg',
|
|
317
431
|
type: asset.type ?? 'image/jpeg'
|
|
318
432
|
};
|
|
319
|
-
|
|
433
|
+
setFileError(null);
|
|
434
|
+
setUploadStatus('Uploading image...');
|
|
435
|
+
setUploadTone('info');
|
|
436
|
+
setIsUploading(true);
|
|
320
437
|
try {
|
|
321
|
-
const response = await uploadNoteFile(api, upload);
|
|
438
|
+
const response = await uploadNoteFile(api, upload, schoolCode);
|
|
322
439
|
if (response?.Status === 'Success' && response?.data) {
|
|
323
|
-
|
|
440
|
+
const nextFile = String(response.data);
|
|
441
|
+
setFileList(prev => [...prev, nextFile]);
|
|
442
|
+
setActiveFile(prev => prev || nextFile);
|
|
443
|
+
setUploadStatus('Image uploaded successfully.');
|
|
444
|
+
setUploadTone('success');
|
|
324
445
|
} else {
|
|
325
|
-
|
|
446
|
+
setFileError('Image upload failed');
|
|
447
|
+
setUploadStatus(String(response?.msg ?? 'Image upload failed'));
|
|
448
|
+
setUploadTone('error');
|
|
326
449
|
}
|
|
327
450
|
} finally {
|
|
328
|
-
|
|
451
|
+
setIsUploading(false);
|
|
329
452
|
}
|
|
330
|
-
}, [api]);
|
|
331
|
-
const viewAttachment = useCallback(async
|
|
332
|
-
|
|
453
|
+
}, [api, schoolCode]);
|
|
454
|
+
const viewAttachment = useCallback(async selectedFile => {
|
|
455
|
+
const targetFile = String(selectedFile ?? activeFile ?? fileList[0] ?? '');
|
|
456
|
+
if (!targetFile) {
|
|
333
457
|
Alert.alert('No Attachment');
|
|
334
458
|
return;
|
|
335
459
|
}
|
|
336
|
-
const url = resolveNoteUrl(fileBaseUrl,
|
|
460
|
+
const url = resolveNoteUrl(fileBaseUrl, targetFile);
|
|
337
461
|
const headers = await resolveHeaders();
|
|
338
|
-
|
|
462
|
+
const targetIsImage = isImageUrl(targetFile) || isImageUrl(url);
|
|
463
|
+
if (ImageView && targetIsImage) {
|
|
464
|
+
const imageFiles = fileList.filter(currentFile => {
|
|
465
|
+
const currentUrl = resolveNoteUrl(fileBaseUrl, currentFile);
|
|
466
|
+
return isImageUrl(currentFile) || isImageUrl(currentUrl);
|
|
467
|
+
});
|
|
468
|
+
const nextIndex = Math.max(0, imageFiles.findIndex(currentFile => String(currentFile) === String(targetFile)));
|
|
469
|
+
setViewerImages(imageFiles.map(currentFile => ({
|
|
470
|
+
uri: resolveNoteUrl(fileBaseUrl, currentFile),
|
|
471
|
+
headers: Object.keys(headers).length ? headers : undefined
|
|
472
|
+
})));
|
|
473
|
+
setViewerIndex(nextIndex);
|
|
339
474
|
setViewerOpen(true);
|
|
340
475
|
return;
|
|
341
476
|
}
|
|
342
|
-
const isPdf = String(
|
|
477
|
+
const isPdf = String(targetFile).toLowerCase().endsWith('.pdf') || url.toLowerCase().endsWith('.pdf');
|
|
343
478
|
if (Pdf && isPdf) {
|
|
344
479
|
if (!RNFS) {
|
|
345
480
|
setPdfHeaders(headers);
|
|
@@ -353,7 +488,7 @@ function NoteEditor(props) {
|
|
|
353
488
|
const local = await downloadPdfToLocal({
|
|
354
489
|
RNFS,
|
|
355
490
|
url,
|
|
356
|
-
fileName: fileBaseName(
|
|
491
|
+
fileName: fileBaseName(targetFile),
|
|
357
492
|
headers
|
|
358
493
|
});
|
|
359
494
|
setPdfHeaders({});
|
|
@@ -373,7 +508,7 @@ function NoteEditor(props) {
|
|
|
373
508
|
}
|
|
374
509
|
setDownloading(true);
|
|
375
510
|
try {
|
|
376
|
-
const localFile = `${RNFS.DocumentDirectoryPath}/${fileBaseName(
|
|
511
|
+
const localFile = `${RNFS.DocumentDirectoryPath}/${fileBaseName(targetFile)}`;
|
|
377
512
|
await RNFS.downloadFile({
|
|
378
513
|
fromUrl: url,
|
|
379
514
|
toFile: localFile,
|
|
@@ -387,10 +522,23 @@ function NoteEditor(props) {
|
|
|
387
522
|
} finally {
|
|
388
523
|
setDownloading(false);
|
|
389
524
|
}
|
|
390
|
-
}, [FileViewer, ImageView, Pdf, RNFS,
|
|
525
|
+
}, [FileViewer, ImageView, Pdf, RNFS, activeFile, fileBaseUrl, fileList, resolveHeaders]);
|
|
391
526
|
const submit = useCallback(async () => {
|
|
392
|
-
|
|
393
|
-
|
|
527
|
+
const selectedClassId = classOpt?.value;
|
|
528
|
+
const selectedSectionId = sectionOpt?.value;
|
|
529
|
+
const selectedSubjectId = subjectOpt?.value;
|
|
530
|
+
const missing = [];
|
|
531
|
+
if (!title) missing.push('title');
|
|
532
|
+
if (!description) missing.push('description');
|
|
533
|
+
if (!selectedClassId) missing.push('class');
|
|
534
|
+
if (!selectedSectionId) missing.push('section');
|
|
535
|
+
if (!selectedSubjectId) missing.push('subject');
|
|
536
|
+
if (props.mode === 'edit' && !fileList.length) missing.push('file');
|
|
537
|
+
if (missing.length) {
|
|
538
|
+
if (props.mode === 'edit' && !fileList.length) {
|
|
539
|
+
setFileError('File is required');
|
|
540
|
+
}
|
|
541
|
+
Alert.alert('Error', `Please fill: ${missing.join(', ')}`);
|
|
394
542
|
return;
|
|
395
543
|
}
|
|
396
544
|
const payload = {
|
|
@@ -398,13 +546,13 @@ function NoteEditor(props) {
|
|
|
398
546
|
title,
|
|
399
547
|
description,
|
|
400
548
|
remark: description,
|
|
401
|
-
class_id:
|
|
402
|
-
section_id:
|
|
403
|
-
sub_id:
|
|
404
|
-
file,
|
|
549
|
+
class_id: selectedClassId,
|
|
550
|
+
section_id: selectedSectionId,
|
|
551
|
+
sub_id: selectedSubjectId,
|
|
552
|
+
file: fileList.join(','),
|
|
405
553
|
status
|
|
406
554
|
};
|
|
407
|
-
|
|
555
|
+
setIsSubmitting(true);
|
|
408
556
|
try {
|
|
409
557
|
const res = props.mode === 'create' ? await createNote(api, payload) : await updateNote(api, {
|
|
410
558
|
...payload,
|
|
@@ -420,13 +568,13 @@ function NoteEditor(props) {
|
|
|
420
568
|
} catch (e) {
|
|
421
569
|
Alert.alert('Error', String(e?.message ?? 'Could not save note'));
|
|
422
570
|
} finally {
|
|
423
|
-
|
|
571
|
+
setIsSubmitting(false);
|
|
424
572
|
}
|
|
425
|
-
}, [api, classOpt?.value, description,
|
|
573
|
+
}, [api, classOpt?.value, description, fileList, props, sectionOpt?.value, status, subjectOpt?.value, title]);
|
|
426
574
|
if (loading) {
|
|
427
575
|
return /*#__PURE__*/_jsx(LoadingState, {});
|
|
428
576
|
}
|
|
429
|
-
const fileUrl =
|
|
577
|
+
const fileUrl = activeFile ? resolveNoteUrl(fileBaseUrl, activeFile) : '';
|
|
430
578
|
const headers = authToken || schoolCode ? {
|
|
431
579
|
...(authToken ? {
|
|
432
580
|
Authorization: authToken
|
|
@@ -484,7 +632,11 @@ function NoteEditor(props) {
|
|
|
484
632
|
value: classOpt,
|
|
485
633
|
options: classes,
|
|
486
634
|
placeholder: "Select class",
|
|
487
|
-
onChange:
|
|
635
|
+
onChange: next => {
|
|
636
|
+
setClassOpt(next);
|
|
637
|
+
setSectionOpt(null);
|
|
638
|
+
setSubjectOpt(null);
|
|
639
|
+
}
|
|
488
640
|
}), /*#__PURE__*/_jsx(SelectField, {
|
|
489
641
|
label: "Section",
|
|
490
642
|
value: sectionOpt,
|
|
@@ -530,24 +682,69 @@ function NoteEditor(props) {
|
|
|
530
682
|
children: [/*#__PURE__*/_jsx(TouchableOpacity, {
|
|
531
683
|
style: styles.secondaryBtn,
|
|
532
684
|
onPress: () => pickDocument().catch(() => {}),
|
|
685
|
+
disabled: isUploading || isSubmitting,
|
|
533
686
|
children: /*#__PURE__*/_jsx(Text, {
|
|
534
687
|
style: styles.secondaryBtnText,
|
|
535
|
-
children:
|
|
688
|
+
children: fileList.length ? 'Add More Files' : 'Upload File'
|
|
536
689
|
})
|
|
537
690
|
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
538
691
|
style: styles.secondaryBtn,
|
|
539
692
|
onPress: () => pickCamera().catch(() => {}),
|
|
693
|
+
disabled: isUploading || isSubmitting,
|
|
540
694
|
children: /*#__PURE__*/_jsx(Text, {
|
|
541
695
|
style: styles.secondaryBtnText,
|
|
542
696
|
children: "Camera"
|
|
543
697
|
})
|
|
544
698
|
})]
|
|
545
|
-
}),
|
|
699
|
+
}), isUploading ? /*#__PURE__*/_jsx(StatusBanner, {
|
|
700
|
+
text: uploadStatus || 'Uploading file...',
|
|
701
|
+
busy: true,
|
|
702
|
+
tone: uploadTone
|
|
703
|
+
}) : uploadStatus ? /*#__PURE__*/_jsx(StatusBanner, {
|
|
704
|
+
text: uploadStatus,
|
|
705
|
+
tone: uploadTone
|
|
706
|
+
}) : null, fileList.length ? /*#__PURE__*/_jsxs(Text, {
|
|
546
707
|
style: styles.fileText,
|
|
547
|
-
children: ["Selected: ",
|
|
548
|
-
}) : null,
|
|
708
|
+
children: ["Selected: ", fileList.length, " file", fileList.length > 1 ? 's' : '']
|
|
709
|
+
}) : null, fileList.length ? /*#__PURE__*/_jsx(View, {
|
|
710
|
+
style: styles.attachmentList,
|
|
711
|
+
children: fileList.map((currentFile, index) => /*#__PURE__*/_jsxs(View, {
|
|
712
|
+
style: styles.attachmentRow,
|
|
713
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
714
|
+
style: styles.attachmentName,
|
|
715
|
+
numberOfLines: 1,
|
|
716
|
+
children: fileBaseName(currentFile)
|
|
717
|
+
}), /*#__PURE__*/_jsxs(View, {
|
|
718
|
+
style: styles.attachmentActions,
|
|
719
|
+
children: [/*#__PURE__*/_jsx(TouchableOpacity, {
|
|
720
|
+
style: styles.inlineActionBtn,
|
|
721
|
+
onPress: () => {
|
|
722
|
+
setActiveFile(currentFile);
|
|
723
|
+
viewAttachment(currentFile).catch(() => {});
|
|
724
|
+
},
|
|
725
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
726
|
+
style: styles.inlineActionText,
|
|
727
|
+
children: "View"
|
|
728
|
+
})
|
|
729
|
+
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
730
|
+
style: styles.inlineRemoveBtn,
|
|
731
|
+
onPress: () => {
|
|
732
|
+
setFileList(prev => {
|
|
733
|
+
const next = prev.filter((_, itemIndex) => itemIndex !== index);
|
|
734
|
+
setActiveFile(current => current === currentFile ? next[0] ?? '' : current);
|
|
735
|
+
return next;
|
|
736
|
+
});
|
|
737
|
+
},
|
|
738
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
739
|
+
style: styles.inlineRemoveText,
|
|
740
|
+
children: "Remove"
|
|
741
|
+
})
|
|
742
|
+
})]
|
|
743
|
+
})]
|
|
744
|
+
}, `${currentFile}-${index}`))
|
|
745
|
+
}) : null, activeFile ? isImageUrl(activeFile) || isImageUrl(fileUrl) ? /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
549
746
|
style: styles.previewWrap,
|
|
550
|
-
onPress: () =>
|
|
747
|
+
onPress: () => viewAttachment(activeFile).catch(() => {}),
|
|
551
748
|
activeOpacity: 0.9,
|
|
552
749
|
children: /*#__PURE__*/_jsx(Image, {
|
|
553
750
|
source: {
|
|
@@ -559,31 +756,37 @@ function NoteEditor(props) {
|
|
|
559
756
|
})
|
|
560
757
|
}) : /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
561
758
|
style: styles.documentBtn,
|
|
562
|
-
onPress: () => viewAttachment().catch(() => {}),
|
|
759
|
+
onPress: () => viewAttachment(activeFile).catch(() => {}),
|
|
563
760
|
disabled: downloading,
|
|
564
761
|
children: /*#__PURE__*/_jsx(Text, {
|
|
565
762
|
style: styles.documentBtnText,
|
|
566
763
|
children: downloading ? 'Opening Document...' : 'View Document'
|
|
567
764
|
})
|
|
765
|
+
}) : null, fileError ? /*#__PURE__*/_jsx(Text, {
|
|
766
|
+
style: styles.errorText,
|
|
767
|
+
children: fileError
|
|
568
768
|
}) : null]
|
|
569
769
|
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
570
770
|
style: styles.primaryBtn,
|
|
571
771
|
onPress: () => submit().catch(() => {}),
|
|
572
|
-
disabled:
|
|
573
|
-
children:
|
|
574
|
-
|
|
772
|
+
disabled: isUploading || isSubmitting,
|
|
773
|
+
children: isSubmitting ? /*#__PURE__*/_jsxs(View, {
|
|
774
|
+
style: styles.loadingRow,
|
|
775
|
+
children: [/*#__PURE__*/_jsx(ActivityIndicator, {
|
|
776
|
+
color: "#FFFFFF"
|
|
777
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
778
|
+
style: styles.primaryBtnText,
|
|
779
|
+
children: props.mode === 'create' ? 'Creating...' : 'Updating...'
|
|
780
|
+
})]
|
|
575
781
|
}) : /*#__PURE__*/_jsx(Text, {
|
|
576
782
|
style: styles.primaryBtnText,
|
|
577
783
|
children: props.mode === 'create' ? 'Create Note' : 'Update Note'
|
|
578
784
|
})
|
|
579
785
|
})]
|
|
580
786
|
})]
|
|
581
|
-
}), ImageView &&
|
|
582
|
-
images:
|
|
583
|
-
|
|
584
|
-
headers
|
|
585
|
-
}],
|
|
586
|
-
imageIndex: 0,
|
|
787
|
+
}), ImageView && viewerImages.length ? /*#__PURE__*/_jsx(ImageView, {
|
|
788
|
+
images: viewerImages,
|
|
789
|
+
imageIndex: viewerIndex,
|
|
587
790
|
visible: viewerOpen,
|
|
588
791
|
onRequestClose: () => setViewerOpen(false)
|
|
589
792
|
}) : null, /*#__PURE__*/_jsx(Modal, {
|
|
@@ -652,7 +855,10 @@ export function NotesScreen(props) {
|
|
|
652
855
|
const [loadingMore, setLoadingMore] = useState(false);
|
|
653
856
|
const [error, setError] = useState(null);
|
|
654
857
|
const [selectedNote, setSelectedNote] = useState(null);
|
|
858
|
+
const [selectedActiveFile, setSelectedActiveFile] = useState('');
|
|
655
859
|
const [viewerOpen, setViewerOpen] = useState(false);
|
|
860
|
+
const [viewerIndex, setViewerIndex] = useState(0);
|
|
861
|
+
const [viewerImages, setViewerImages] = useState([]);
|
|
656
862
|
const [pdfVisible, setPdfVisible] = useState(false);
|
|
657
863
|
const [pdfUri, setPdfUri] = useState('');
|
|
658
864
|
const [pdfHeaders, setPdfHeaders] = useState({});
|
|
@@ -734,15 +940,26 @@ export function NotesScreen(props) {
|
|
|
734
940
|
if (mode !== 'list') return;
|
|
735
941
|
loadList(props.page ?? 1, true).catch(() => {});
|
|
736
942
|
}, [loadList, mode, props.page]);
|
|
737
|
-
const
|
|
738
|
-
|
|
943
|
+
const selectedNoteFiles = useMemo(() => splitFileCsv(selectedNote?.file ?? ''), [selectedNote?.file]);
|
|
944
|
+
const viewNoteAttachment = useCallback(async selectedFile => {
|
|
945
|
+
const file = String(selectedFile ?? selectedActiveFile ?? selectedNoteFiles[0] ?? '');
|
|
739
946
|
if (!file) {
|
|
740
947
|
Alert.alert('No Attachment');
|
|
741
948
|
return;
|
|
742
949
|
}
|
|
743
950
|
const url = resolveNoteUrl(fileBaseUrl, file);
|
|
744
951
|
const headers = await resolveHeaders();
|
|
745
|
-
if (ImageView && isImageUrl(file)) {
|
|
952
|
+
if (ImageView && (isImageUrl(file) || isImageUrl(url))) {
|
|
953
|
+
const imageFiles = selectedNoteFiles.filter(currentFile => {
|
|
954
|
+
const currentUrl = resolveNoteUrl(fileBaseUrl, currentFile);
|
|
955
|
+
return isImageUrl(currentFile) || isImageUrl(currentUrl);
|
|
956
|
+
});
|
|
957
|
+
const nextIndex = Math.max(0, imageFiles.findIndex(currentFile => String(currentFile) === String(file)));
|
|
958
|
+
setViewerImages(imageFiles.map(currentFile => ({
|
|
959
|
+
uri: resolveNoteUrl(fileBaseUrl, currentFile),
|
|
960
|
+
headers: Object.keys(headers).length ? headers : undefined
|
|
961
|
+
})));
|
|
962
|
+
setViewerIndex(nextIndex);
|
|
746
963
|
setViewerOpen(true);
|
|
747
964
|
return;
|
|
748
965
|
}
|
|
@@ -794,7 +1011,7 @@ export function NotesScreen(props) {
|
|
|
794
1011
|
} finally {
|
|
795
1012
|
setDownloading(false);
|
|
796
1013
|
}
|
|
797
|
-
}, [FileViewer, ImageView, Pdf, RNFS, fileBaseUrl, resolveHeaders,
|
|
1014
|
+
}, [FileViewer, ImageView, Pdf, RNFS, fileBaseUrl, resolveHeaders, selectedActiveFile, selectedNoteFiles]);
|
|
798
1015
|
if (mode === 'create') {
|
|
799
1016
|
return /*#__PURE__*/_jsx(NoteEditor, {
|
|
800
1017
|
mode: "create",
|
|
@@ -919,7 +1136,10 @@ export function NotesScreen(props) {
|
|
|
919
1136
|
style: styles.row,
|
|
920
1137
|
children: [/*#__PURE__*/_jsx(TouchableOpacity, {
|
|
921
1138
|
style: styles.secondaryBtn,
|
|
922
|
-
onPress: () =>
|
|
1139
|
+
onPress: () => {
|
|
1140
|
+
setSelectedNote(item);
|
|
1141
|
+
setSelectedActiveFile(splitFileCsv(item?.file ?? '')[0] ?? '');
|
|
1142
|
+
},
|
|
923
1143
|
children: /*#__PURE__*/_jsx(Text, {
|
|
924
1144
|
style: styles.secondaryBtnText,
|
|
925
1145
|
children: "View"
|
|
@@ -982,42 +1202,65 @@ export function NotesScreen(props) {
|
|
|
982
1202
|
}), /*#__PURE__*/_jsx(Text, {
|
|
983
1203
|
style: styles.noteDescFull,
|
|
984
1204
|
children: String(selectedNote?.description ?? selectedNote?.remark ?? '-')
|
|
985
|
-
}),
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1205
|
+
}), selectedNoteFiles.length ? /*#__PURE__*/_jsxs(_Fragment, {
|
|
1206
|
+
children: [/*#__PURE__*/_jsxs(Text, {
|
|
1207
|
+
style: styles.fileText,
|
|
1208
|
+
children: ["Selected: ", selectedNoteFiles.length, " file", selectedNoteFiles.length > 1 ? 's' : '']
|
|
1209
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
1210
|
+
style: styles.attachmentList,
|
|
1211
|
+
children: selectedNoteFiles.map((currentFile, index) => /*#__PURE__*/_jsxs(View, {
|
|
1212
|
+
style: styles.attachmentRow,
|
|
1213
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
1214
|
+
style: styles.attachmentName,
|
|
1215
|
+
numberOfLines: 1,
|
|
1216
|
+
children: fileBaseName(currentFile)
|
|
1217
|
+
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
1218
|
+
style: styles.inlineActionBtn,
|
|
1219
|
+
onPress: () => {
|
|
1220
|
+
setSelectedActiveFile(currentFile);
|
|
1221
|
+
viewNoteAttachment(currentFile).catch(() => {});
|
|
1222
|
+
},
|
|
1223
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
1224
|
+
style: styles.inlineActionText,
|
|
1225
|
+
children: "View"
|
|
1226
|
+
})
|
|
1227
|
+
})]
|
|
1228
|
+
}, `${currentFile}-${index}`))
|
|
1229
|
+
}), selectedActiveFile ? isImageUrl(selectedActiveFile) || isImageUrl(resolveNoteUrl(fileBaseUrl, selectedActiveFile)) ? /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
1230
|
+
style: styles.previewWrap,
|
|
1231
|
+
onPress: () => viewNoteAttachment(selectedActiveFile).catch(() => {}),
|
|
1232
|
+
activeOpacity: 0.9,
|
|
1233
|
+
children: /*#__PURE__*/_jsx(Image, {
|
|
1234
|
+
source: {
|
|
1235
|
+
uri: resolveNoteUrl(fileBaseUrl, selectedActiveFile),
|
|
1236
|
+
headers: authToken || schoolCode ? {
|
|
1237
|
+
...(authToken ? {
|
|
1238
|
+
Authorization: authToken
|
|
1239
|
+
} : {}),
|
|
1240
|
+
...(schoolCode ? {
|
|
1241
|
+
school_code: schoolCode
|
|
1242
|
+
} : {})
|
|
1243
|
+
} : undefined
|
|
1244
|
+
},
|
|
1245
|
+
style: styles.previewImage,
|
|
1246
|
+
resizeMode: "contain"
|
|
1247
|
+
})
|
|
1248
|
+
}) : /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
1249
|
+
style: styles.documentBtn,
|
|
1250
|
+
onPress: () => viewNoteAttachment(selectedActiveFile).catch(() => {}),
|
|
1251
|
+
disabled: downloading,
|
|
1252
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
1253
|
+
style: styles.documentBtnText,
|
|
1254
|
+
children: downloading ? 'Opening Document...' : 'View Document'
|
|
1255
|
+
})
|
|
1256
|
+
}) : null]
|
|
1012
1257
|
}) : null]
|
|
1013
1258
|
}) : null]
|
|
1014
1259
|
})
|
|
1015
1260
|
})
|
|
1016
|
-
}), ImageView &&
|
|
1017
|
-
images:
|
|
1018
|
-
|
|
1019
|
-
}],
|
|
1020
|
-
imageIndex: 0,
|
|
1261
|
+
}), ImageView && viewerImages.length ? /*#__PURE__*/_jsx(ImageView, {
|
|
1262
|
+
images: viewerImages,
|
|
1263
|
+
imageIndex: viewerIndex,
|
|
1021
1264
|
visible: viewerOpen,
|
|
1022
1265
|
onRequestClose: () => setViewerOpen(false)
|
|
1023
1266
|
}) : null, /*#__PURE__*/_jsx(Modal, {
|
|
@@ -1189,6 +1432,51 @@ const styles = StyleSheet.create({
|
|
|
1189
1432
|
color: '#374151',
|
|
1190
1433
|
marginTop: 8
|
|
1191
1434
|
},
|
|
1435
|
+
attachmentList: {
|
|
1436
|
+
width: '100%',
|
|
1437
|
+
marginTop: 8
|
|
1438
|
+
},
|
|
1439
|
+
attachmentRow: {
|
|
1440
|
+
flexDirection: 'row',
|
|
1441
|
+
alignItems: 'center',
|
|
1442
|
+
justifyContent: 'space-between',
|
|
1443
|
+
paddingVertical: 8,
|
|
1444
|
+
borderBottomWidth: 1,
|
|
1445
|
+
borderBottomColor: '#E5E7EB',
|
|
1446
|
+
gap: 10
|
|
1447
|
+
},
|
|
1448
|
+
attachmentName: {
|
|
1449
|
+
flex: 1,
|
|
1450
|
+
color: '#111827',
|
|
1451
|
+
fontSize: 13
|
|
1452
|
+
},
|
|
1453
|
+
attachmentActions: {
|
|
1454
|
+
flexDirection: 'row',
|
|
1455
|
+
alignItems: 'center',
|
|
1456
|
+
gap: 8
|
|
1457
|
+
},
|
|
1458
|
+
inlineActionBtn: {
|
|
1459
|
+
paddingHorizontal: 10,
|
|
1460
|
+
paddingVertical: 6,
|
|
1461
|
+
borderRadius: 8,
|
|
1462
|
+
backgroundColor: '#DBEAFE'
|
|
1463
|
+
},
|
|
1464
|
+
inlineActionText: {
|
|
1465
|
+
color: '#1D4ED8',
|
|
1466
|
+
fontWeight: '700',
|
|
1467
|
+
fontSize: 12
|
|
1468
|
+
},
|
|
1469
|
+
inlineRemoveBtn: {
|
|
1470
|
+
paddingHorizontal: 10,
|
|
1471
|
+
paddingVertical: 6,
|
|
1472
|
+
borderRadius: 8,
|
|
1473
|
+
backgroundColor: '#FEE2E2'
|
|
1474
|
+
},
|
|
1475
|
+
inlineRemoveText: {
|
|
1476
|
+
color: '#B91C1C',
|
|
1477
|
+
fontWeight: '700',
|
|
1478
|
+
fontSize: 12
|
|
1479
|
+
},
|
|
1192
1480
|
previewWrap: {
|
|
1193
1481
|
marginTop: 12,
|
|
1194
1482
|
borderWidth: 1,
|
|
@@ -1277,5 +1565,31 @@ const styles = StyleSheet.create({
|
|
|
1277
1565
|
color: '#6B7280',
|
|
1278
1566
|
textAlign: 'center',
|
|
1279
1567
|
paddingVertical: 16
|
|
1568
|
+
},
|
|
1569
|
+
statusBanner: {
|
|
1570
|
+
marginTop: 10,
|
|
1571
|
+
paddingHorizontal: 12,
|
|
1572
|
+
paddingVertical: 10,
|
|
1573
|
+
borderRadius: 12,
|
|
1574
|
+
borderWidth: 1,
|
|
1575
|
+
flexDirection: 'row',
|
|
1576
|
+
alignItems: 'center',
|
|
1577
|
+
gap: 10
|
|
1578
|
+
},
|
|
1579
|
+
statusBannerText: {
|
|
1580
|
+
flex: 1,
|
|
1581
|
+
fontSize: 12,
|
|
1582
|
+
fontWeight: '700'
|
|
1583
|
+
},
|
|
1584
|
+
errorText: {
|
|
1585
|
+
marginTop: 6,
|
|
1586
|
+
fontSize: 12,
|
|
1587
|
+
color: '#DC2626',
|
|
1588
|
+
fontWeight: '600'
|
|
1589
|
+
},
|
|
1590
|
+
loadingRow: {
|
|
1591
|
+
flexDirection: 'row',
|
|
1592
|
+
alignItems: 'center',
|
|
1593
|
+
gap: 8
|
|
1280
1594
|
}
|
|
1281
1595
|
});
|