@sasindi/samplecollection 1.0.1 → 1.0.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/dist/30.js +1 -1
- package/dist/30.js.map +1 -1
- package/dist/425.js +1 -1
- package/dist/425.js.map +1 -1
- package/dist/529.js +1 -1
- package/dist/577.js +1 -1
- package/dist/577.js.map +1 -1
- package/dist/main.js +3 -3
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-samplecollection.js +3 -3
- package/dist/openmrs-esm-samplecollection.js.buildmanifest.json +48 -48
- package/dist/openmrs-esm-samplecollection.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/routes.json +1 -1
- package/src/samplecollection.component.tsx +162 -148
- package/src/samplecollection.scss +235 -101
- package/src/samplecollectionroom.component.tsx +228 -215
|
@@ -1,26 +1,9 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
|
-
import {
|
|
4
|
-
Tile,
|
|
5
|
-
TextInput,
|
|
6
|
-
Select,
|
|
7
|
-
SelectItem,
|
|
8
|
-
TextArea,
|
|
9
|
-
Button,
|
|
10
|
-
Table,
|
|
11
|
-
TableHead,
|
|
12
|
-
TableRow,
|
|
13
|
-
TableHeader,
|
|
14
|
-
TableBody,
|
|
15
|
-
TableCell,
|
|
16
|
-
Checkbox,
|
|
17
|
-
Loading,
|
|
18
|
-
InlineNotification
|
|
19
|
-
} from '@carbon/react';
|
|
20
3
|
import styles from './samplecollection.scss';
|
|
21
4
|
|
|
22
5
|
const UserAvatar = () => (
|
|
23
|
-
<svg width="
|
|
6
|
+
<svg width="60" height="60" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ borderRadius: '16px', border: '3px solid #20e5bf' }}>
|
|
24
7
|
<circle cx="50" cy="50" r="50" fill="#E0F2F1"/>
|
|
25
8
|
<circle cx="50" cy="40" r="18" fill="#009384"/>
|
|
26
9
|
<path d="M18 78C18 62.536 32.3269 50 50 50C67.6731 50 82 62.536 82 78" stroke="#009384" strokeWidth="8" strokeLinecap="round"/>
|
|
@@ -419,223 +402,253 @@ const SamplecollectionRoom: React.FC = () => {
|
|
|
419
402
|
}
|
|
420
403
|
};
|
|
421
404
|
|
|
422
|
-
if (loading)
|
|
405
|
+
if (loading) {
|
|
406
|
+
return (
|
|
407
|
+
<div className={styles.container} style={{ textAlign: 'center', padding: '50px' }}>
|
|
408
|
+
Loading patient details...
|
|
409
|
+
</div>
|
|
410
|
+
);
|
|
411
|
+
}
|
|
423
412
|
|
|
424
413
|
return (
|
|
425
414
|
<div className={styles.container}>
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
<div className={styles.
|
|
429
|
-
<
|
|
430
|
-
|
|
431
|
-
</h1>
|
|
432
|
-
<div className={styles.locationBadge}>
|
|
433
|
-
<span className="icon-hospital" />
|
|
434
|
-
<span>{locationName}</span>
|
|
435
|
-
</div>
|
|
415
|
+
{/* Clinic/Hospital Header */}
|
|
416
|
+
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: '10px' }}>
|
|
417
|
+
<div className={styles.locationBadge} style={{ background: '#e9ecef', color: '#495057' }}>
|
|
418
|
+
<span className="icon-hospital" />
|
|
419
|
+
<span id="lblInstituteName" style={{ fontWeight: 'bold' }}>{locationName}</span>
|
|
436
420
|
</div>
|
|
421
|
+
</div>
|
|
437
422
|
|
|
438
|
-
|
|
439
|
-
{
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
subtitle={successMsg}
|
|
453
|
-
onClose={() => setSuccessMsg('')}
|
|
454
|
-
style={{ marginBottom: '1rem' }}
|
|
455
|
-
/>
|
|
456
|
-
)}
|
|
457
|
-
|
|
458
|
-
{/* Patient Profile Card */}
|
|
459
|
-
<div className={styles.patientCard}>
|
|
460
|
-
<div className={styles.patientGrid}>
|
|
461
|
-
<div className={styles.patientField}>
|
|
462
|
-
<span className={styles.fieldLabel}>Full Name</span>
|
|
463
|
-
<span className={styles.fieldValue}>{patientName || 'N/A'}</span>
|
|
464
|
-
</div>
|
|
465
|
-
<div className={styles.patientField}>
|
|
466
|
-
<span className={styles.fieldLabel}>Section</span>
|
|
467
|
-
<span className={styles.fieldValue}>{initialSection || 'N/A'}</span>
|
|
423
|
+
<div className={styles.card}>
|
|
424
|
+
<div className={styles.cardBody}>
|
|
425
|
+
|
|
426
|
+
{/* Section title */}
|
|
427
|
+
<div className={styles.col12} style={{ marginBottom: '1rem', padding: 0 }}>
|
|
428
|
+
<h2 className={styles.heading} style={{ margin: 0 }}>
|
|
429
|
+
{t('samplecollectionRoomHeading', 'Sample Collection Room')}
|
|
430
|
+
</h2>
|
|
431
|
+
</div>
|
|
432
|
+
|
|
433
|
+
{/* Messages */}
|
|
434
|
+
{errorMsg && (
|
|
435
|
+
<div style={{ padding: '8px', color: '#721c24', backgroundColor: '#f8d7da', border: '1px solid #f5c6cb', borderRadius: '4px', marginBottom: '10px' }}>
|
|
436
|
+
{errorMsg}
|
|
468
437
|
</div>
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
438
|
+
)}
|
|
439
|
+
{successMsg && (
|
|
440
|
+
<div style={{ padding: '8px', color: '#155724', backgroundColor: '#d4edda', border: '1px solid #c3e6cb', borderRadius: '4px', marginBottom: '10px' }}>
|
|
441
|
+
{successMsg}
|
|
472
442
|
</div>
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
443
|
+
)}
|
|
444
|
+
|
|
445
|
+
{/* Patient Card Grid (Col-10 fields, Col-2 avatar) */}
|
|
446
|
+
<div className={styles.row} style={{ marginBottom: '1.5rem' }}>
|
|
447
|
+
<div className={styles.col10}>
|
|
448
|
+
<div className={styles.row}>
|
|
449
|
+
|
|
450
|
+
<div className={styles.formGroupCol}>
|
|
451
|
+
<label className={styles.labelCompact}>Full Name</label>
|
|
452
|
+
<input type="text" className={styles.formControl} value={patientName} readOnly />
|
|
453
|
+
</div>
|
|
454
|
+
|
|
455
|
+
<div className={styles.formGroupCol}>
|
|
456
|
+
<label className={styles.labelCompact}>Section</label>
|
|
457
|
+
<input type="text" className={styles.formControl} value={initialSection} readOnly />
|
|
458
|
+
</div>
|
|
459
|
+
|
|
460
|
+
<div className={styles.formGroupCol}>
|
|
461
|
+
<label className={styles.labelCompact}>PHN No</label>
|
|
462
|
+
<input type="text" className={styles.formControl} value={phnNo} readOnly />
|
|
463
|
+
</div>
|
|
464
|
+
|
|
465
|
+
<div className={styles.formGroupCol}>
|
|
466
|
+
<label className={styles.labelCompact}>Age</label>
|
|
467
|
+
<input type="text" className={styles.formControl} value={patientAge} readOnly />
|
|
468
|
+
</div>
|
|
469
|
+
|
|
470
|
+
<div className={styles.formGroupCol}>
|
|
471
|
+
<label className={styles.labelCompact}>Gender</label>
|
|
472
|
+
<input type="text" className={styles.formControl} value={patientGender} readOnly />
|
|
473
|
+
</div>
|
|
474
|
+
|
|
475
|
+
</div>
|
|
476
476
|
</div>
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
<
|
|
477
|
+
|
|
478
|
+
<div className={styles.col2}>
|
|
479
|
+
<div className={styles.avatarContainer}>
|
|
480
|
+
{profileImgName ? (
|
|
481
|
+
<img
|
|
482
|
+
src={`${window.location.origin}/openmrs/owa/PatientImagesUploaded/${profileImgName}`}
|
|
483
|
+
alt="Patient avatar"
|
|
484
|
+
className={styles.uploadImg}
|
|
485
|
+
onError={(e) => {
|
|
486
|
+
(e.target as HTMLElement).style.display = 'none';
|
|
487
|
+
}}
|
|
488
|
+
/>
|
|
489
|
+
) : (
|
|
490
|
+
<UserAvatar />
|
|
491
|
+
)}
|
|
492
|
+
</div>
|
|
480
493
|
</div>
|
|
481
494
|
</div>
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
495
|
+
|
|
496
|
+
{/* Pending Tests Table */}
|
|
497
|
+
<div className={styles.tableResponsive} style={{ maxHeight: '200px', overflowY: 'auto', border: '1px solid #dee2e6', marginBottom: '1.5rem' }}>
|
|
498
|
+
<table className={`${styles.table} ${styles.tableBordered} ${styles.tableHover} ${styles.tableStriped}`}>
|
|
499
|
+
<thead>
|
|
500
|
+
<tr>
|
|
501
|
+
<th style={{ width: '50px' }}>No</th>
|
|
502
|
+
<th style={{ whiteSpace: 'nowrap' }}>Test Name</th>
|
|
503
|
+
<th style={{ whiteSpace: 'nowrap' }}>Order By</th>
|
|
504
|
+
<th style={{ whiteSpace: 'nowrap' }}>Order Date/Time</th>
|
|
505
|
+
<th style={{ width: '40px' }}></th>
|
|
506
|
+
</tr>
|
|
507
|
+
</thead>
|
|
508
|
+
<tbody>
|
|
509
|
+
{testList.length === 0 ? (
|
|
510
|
+
<tr>
|
|
511
|
+
<td colSpan={5} style={{ textAlign: 'center', padding: '10px' }}>
|
|
512
|
+
No active tests pending collection.
|
|
513
|
+
</td>
|
|
514
|
+
</tr>
|
|
515
|
+
) : (
|
|
516
|
+
testList.map((row, index) => {
|
|
517
|
+
const id = row[8];
|
|
518
|
+
const testName = row[2];
|
|
519
|
+
const orderBy = row[5];
|
|
520
|
+
const orderDateTime = row[6];
|
|
521
|
+
const urgent = row[9];
|
|
522
|
+
const isChecked = selectedIds.includes(String(id));
|
|
523
|
+
|
|
524
|
+
return (
|
|
525
|
+
<tr key={id || index}>
|
|
526
|
+
<td>{index + 1}</td>
|
|
527
|
+
<td>{testName}</td>
|
|
528
|
+
<td>{orderBy}</td>
|
|
529
|
+
<td>{orderDateTime}</td>
|
|
530
|
+
<td style={{ textAlign: 'center' }}>
|
|
531
|
+
<input
|
|
532
|
+
type="checkbox"
|
|
533
|
+
className={styles.formControl}
|
|
534
|
+
style={{ width: '16px', height: '16px', margin: 0 }}
|
|
535
|
+
checked={isChecked}
|
|
536
|
+
onChange={(e) =>
|
|
537
|
+
handleCheckboxChange(e.target.checked, String(id), testName, String(urgent), row)
|
|
538
|
+
}
|
|
539
|
+
/>
|
|
540
|
+
</td>
|
|
541
|
+
</tr>
|
|
542
|
+
);
|
|
543
|
+
})
|
|
544
|
+
)}
|
|
545
|
+
</tbody>
|
|
546
|
+
</table>
|
|
496
547
|
</div>
|
|
497
|
-
</div>
|
|
498
548
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
<
|
|
503
|
-
<
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
<TableHeader>Order By</TableHeader>
|
|
507
|
-
<TableHeader>Order Date/Time</TableHeader>
|
|
508
|
-
<TableHeader className={styles.checkboxCell} />
|
|
509
|
-
</TableRow>
|
|
510
|
-
</TableHead>
|
|
511
|
-
<TableBody>
|
|
512
|
-
{testList.length === 0 ? (
|
|
513
|
-
<TableRow>
|
|
514
|
-
<TableCell colSpan={5} style={{ textAlign: 'center' }}>
|
|
515
|
-
No active tests pending collection.
|
|
516
|
-
</TableCell>
|
|
517
|
-
</TableRow>
|
|
518
|
-
) : (
|
|
519
|
-
testList.map((row, index) => {
|
|
520
|
-
const id = row[8];
|
|
521
|
-
const testName = row[2];
|
|
522
|
-
const orderBy = row[5];
|
|
523
|
-
const orderDateTime = row[6];
|
|
524
|
-
const urgent = row[9]; // 'true' or 'false'
|
|
525
|
-
const isChecked = selectedIds.includes(String(id));
|
|
526
|
-
|
|
527
|
-
return (
|
|
528
|
-
<TableRow key={id || index}>
|
|
529
|
-
<TableCell>{index + 1}</TableCell>
|
|
530
|
-
<TableCell>{testName}</TableCell>
|
|
531
|
-
<TableCell>{orderBy}</TableCell>
|
|
532
|
-
<TableCell>{orderDateTime}</TableCell>
|
|
533
|
-
<TableCell className={styles.checkboxCell}>
|
|
534
|
-
<Checkbox
|
|
535
|
-
id={`chk-${id}`}
|
|
536
|
-
labelText=""
|
|
537
|
-
checked={isChecked}
|
|
538
|
-
onChange={(e, { checked }) =>
|
|
539
|
-
handleCheckboxChange(checked, String(id), testName, String(urgent), row)
|
|
540
|
-
}
|
|
541
|
-
/>
|
|
542
|
-
</TableCell>
|
|
543
|
-
</TableRow>
|
|
544
|
-
);
|
|
545
|
-
})
|
|
546
|
-
)}
|
|
547
|
-
</TableBody>
|
|
548
|
-
</Table>
|
|
549
|
-
</div>
|
|
549
|
+
{/* Test Detail Display inputs */}
|
|
550
|
+
<div className={styles.row} style={{ marginBottom: '1rem' }}>
|
|
551
|
+
|
|
552
|
+
<div className={styles.formGroupCol}>
|
|
553
|
+
<label className={styles.labelCompact}>Test Name</label>
|
|
554
|
+
<input type="text" className={styles.formControl} value={displayTestNames} readOnly />
|
|
555
|
+
</div>
|
|
550
556
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
readOnly
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
value={
|
|
564
|
-
|
|
565
|
-
/>
|
|
566
|
-
<TextInput
|
|
567
|
-
id="txtorderdate"
|
|
568
|
-
labelText="Order Date"
|
|
569
|
-
value={displayOrderDate}
|
|
570
|
-
readOnly
|
|
571
|
-
/>
|
|
572
|
-
<TextInput
|
|
573
|
-
id="txtordertime"
|
|
574
|
-
labelText="Order Time"
|
|
575
|
-
value={displayOrderTime}
|
|
576
|
-
readOnly
|
|
577
|
-
/>
|
|
578
|
-
</div>
|
|
557
|
+
<div className={styles.formGroupCol}>
|
|
558
|
+
<label className={styles.labelCompact}>Order By</label>
|
|
559
|
+
<input type="text" className={styles.formControl} value={displayOrderBy} readOnly />
|
|
560
|
+
</div>
|
|
561
|
+
|
|
562
|
+
<div className={styles.formGroupCol}>
|
|
563
|
+
<label className={styles.labelCompact}>Order Date</label>
|
|
564
|
+
<input type="text" className={styles.formControl} value={displayOrderDate} readOnly />
|
|
565
|
+
</div>
|
|
566
|
+
|
|
567
|
+
<div className={styles.formGroupCol}>
|
|
568
|
+
<label className={styles.labelCompact}>Order Time</label>
|
|
569
|
+
<input type="text" className={styles.formControl} value={displayOrderTime} readOnly />
|
|
570
|
+
</div>
|
|
579
571
|
|
|
580
|
-
<hr style={{ border: 'none', height: '1px', backgroundColor: '#e0e0e0', margin: '20px 0' }} />
|
|
581
|
-
|
|
582
|
-
{/* User inputs */}
|
|
583
|
-
<div className={styles.formGrid}>
|
|
584
|
-
<TextInput
|
|
585
|
-
id="txtdate"
|
|
586
|
-
labelText="Date"
|
|
587
|
-
type="date"
|
|
588
|
-
value={saveDate}
|
|
589
|
-
onChange={(e) => setSaveDate(e.target.value)}
|
|
590
|
-
/>
|
|
591
|
-
<TextInput
|
|
592
|
-
id="txttime"
|
|
593
|
-
labelText="Time"
|
|
594
|
-
type="time"
|
|
595
|
-
value={saveTime}
|
|
596
|
-
onChange={(e) => setSaveTime(e.target.value)}
|
|
597
|
-
/>
|
|
598
|
-
<Select
|
|
599
|
-
id="drpstatus"
|
|
600
|
-
labelText="Collection Status"
|
|
601
|
-
value={saveStatus}
|
|
602
|
-
onChange={(e) => setSaveStatus(e.target.value)}
|
|
603
|
-
>
|
|
604
|
-
<SelectItem value="0" text="--Select--" />
|
|
605
|
-
<SelectItem value="Done" text="Done" />
|
|
606
|
-
<SelectItem value="Not Done" text="Not Done" />
|
|
607
|
-
</Select>
|
|
608
572
|
</div>
|
|
609
573
|
|
|
610
|
-
<
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
574
|
+
<hr style={{ border: 'none', height: '1px', backgroundColor: '#000', margin: '20px 0' }} />
|
|
575
|
+
|
|
576
|
+
{/* Date, Time, Status fields */}
|
|
577
|
+
<div className={styles.row} style={{ marginBottom: '1rem' }}>
|
|
578
|
+
|
|
579
|
+
<div className={styles.formGroupCol}>
|
|
580
|
+
<label className={styles.labelCompact}>Date</label>
|
|
581
|
+
<input
|
|
582
|
+
type="date"
|
|
583
|
+
className={styles.formControl}
|
|
584
|
+
value={saveDate}
|
|
585
|
+
onChange={(e) => setSaveDate(e.target.value)}
|
|
586
|
+
/>
|
|
587
|
+
</div>
|
|
588
|
+
|
|
589
|
+
<div className={styles.formGroupCol}>
|
|
590
|
+
<label className={styles.labelCompact}>Time</label>
|
|
591
|
+
<input
|
|
592
|
+
type="time"
|
|
593
|
+
className={styles.formControl}
|
|
594
|
+
value={saveTime}
|
|
595
|
+
onChange={(e) => setSaveTime(e.target.value)}
|
|
596
|
+
/>
|
|
597
|
+
</div>
|
|
598
|
+
|
|
599
|
+
<div className={styles.formGroupCol}>
|
|
600
|
+
<label className={styles.labelCompact}>Collection Status</label>
|
|
601
|
+
<select
|
|
602
|
+
className={styles.formControl}
|
|
603
|
+
value={saveStatus}
|
|
604
|
+
onChange={(e) => setSaveStatus(e.target.value)}
|
|
605
|
+
>
|
|
606
|
+
<option value="0">--Select--</option>
|
|
607
|
+
<option value="Done">Done</option>
|
|
608
|
+
<option value="Not Done">Not Done</option>
|
|
609
|
+
</select>
|
|
610
|
+
</div>
|
|
611
|
+
|
|
618
612
|
</div>
|
|
619
613
|
|
|
620
|
-
{/* Action buttons */}
|
|
621
|
-
<div className={styles.
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
>
|
|
634
|
-
|
|
635
|
-
|
|
614
|
+
{/* Remark text box and Action buttons row */}
|
|
615
|
+
<div className={styles.row}>
|
|
616
|
+
|
|
617
|
+
<div className={styles.formGroupCol} style={{ flex: '0 0 66.6666%', maxWidth: '66.6666%', alignItems: 'flex-start' }}>
|
|
618
|
+
<label className={styles.labelCompact}>Remark</label>
|
|
619
|
+
<textarea
|
|
620
|
+
className={`${styles.formControl} ${styles.textareaControl}`}
|
|
621
|
+
rows={3}
|
|
622
|
+
value={saveRemark}
|
|
623
|
+
onChange={(e) => setSaveRemark(e.target.value)}
|
|
624
|
+
/>
|
|
625
|
+
</div>
|
|
626
|
+
|
|
627
|
+
<div className={`${styles.formGroupCol} ${styles.justifyContentEnd}`} style={{ flex: '0 0 33.3333%', maxWidth: '33.3333%', alignSelf: 'flex-end' }}>
|
|
628
|
+
<button
|
|
629
|
+
type="button"
|
|
630
|
+
className={`${styles.btn} ${styles.btnSuccess} ${styles.mx2}`}
|
|
631
|
+
style={{ height: '40px', width: '100px' }}
|
|
632
|
+
disabled={saving || testList.length === 0}
|
|
633
|
+
onClick={handleSave}
|
|
634
|
+
>
|
|
635
|
+
{saving ? 'Saving...' : 'Save'}
|
|
636
|
+
</button>
|
|
637
|
+
<button
|
|
638
|
+
type="button"
|
|
639
|
+
className={`${styles.btn} ${styles.btnDanger}`}
|
|
640
|
+
style={{ height: '40px', width: '100px' }}
|
|
641
|
+
disabled={saving}
|
|
642
|
+
onClick={() => navigateTo('/openmrs/spa/samplecollection')}
|
|
643
|
+
>
|
|
644
|
+
Back
|
|
645
|
+
</button>
|
|
646
|
+
</div>
|
|
647
|
+
|
|
636
648
|
</div>
|
|
649
|
+
|
|
637
650
|
</div>
|
|
638
|
-
</
|
|
651
|
+
</div>
|
|
639
652
|
</div>
|
|
640
653
|
);
|
|
641
654
|
};
|