Changeset 6c00695


Ignore:
Timestamp:
05/29/26 20:18:54 (4 months ago)
Author:
MBK <marija.karapandzova@…>
Branches:
master
Children:
b63d7b5
Parents:
f18b11b
Message:

Add endpoint for lab technitians to view submitted tests and implement filters

Files:
4 edited

Legend:

Unmodified
Added
Removed
  • backend/src/main/java/medora/controller/LabController.java

    rf18b11b r6c00695  
    9292
    9393    @GetMapping
    94     public ResponseEntity<?> getAllLabTests() {
    95         try {
     94    public ResponseEntity<?> getAllLabTests(HttpServletRequest httpRequest) {
     95        try {
     96            String role = securityUtil.getRoleFromRequest(httpRequest);
     97            if (role == null) {
     98                return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
     99                        .body(Map.of("error", "Unauthorized"));
     100            }
     101
     102            // BILLING_ADMIN cannot access lab tests
     103            if (role.equals("BILLING_ADMIN")) {
     104                return ResponseEntity.status(HttpStatus.FORBIDDEN)
     105                        .body(Map.of("error", "You do not have permission to access lab tests"));
     106            }
     107
    96108            logger.info("Fetching all lab tests");
    97109            List<LabTests> tests = labService.getAllLabTests();
     
    307319            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
    308320                    .body(Map.of("error", "Failed to fetch pending lab tests: " + e.getMessage()));
     321        }
     322    }
     323
     324    @GetMapping("/results")
     325    public ResponseEntity<?> getAllSubmittedLabResults(HttpServletRequest httpRequest) {
     326        try {
     327            String role = securityUtil.getRoleFromRequest(httpRequest);
     328            if (role == null) {
     329                return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
     330                        .body(Map.of("error", "Unauthorized"));
     331            }
     332
     333            if (!role.equals("LAB_TECHNICIAN")) {
     334                return ResponseEntity.status(HttpStatus.FORBIDDEN)
     335                        .body(Map.of("error", "Only lab technicians can view submitted results"));
     336            }
     337
     338            logger.info("Fetching all submitted lab results");
     339            List<MedicalRecordLabResults> results = labService.getAllSubmittedLabResults();
     340            List<Map<String, Object>> dtos = results.stream()
     341                    .map(recordResult -> {
     342                        LabResults labResult = recordResult.getLabResult();
     343                        MedicalRecord medicalRecord = recordResult.getMedicalRecord();
     344                        String patientName = "N/A";
     345                        if (medicalRecord != null && medicalRecord.getPatient() != null) {
     346                            Patient patient = medicalRecord.getPatient();
     347                            patientName = patient.getFirstName() + " " + patient.getLastName();
     348                        }
     349                        Map<String, Object> resultMap = new java.util.HashMap<>();
     350                        resultMap.put("resultId", labResult.getResultId());
     351                        resultMap.put("testId", labResult.getLabTest().getTestId());
     352                        resultMap.put("testName", labResult.getLabTest().getTestName());
     353                        resultMap.put("patientName", patientName);
     354                        resultMap.put("doctorName", "");
     355                        resultMap.put("testDate", labResult.getResultDate());
     356                        resultMap.put("resultDate", labResult.getResultDate());
     357                        resultMap.put("createdDate", labResult.getResultDate());
     358                        resultMap.put("results", labResult.getResults());
     359                        resultMap.put("description", labResult.getLabTest().getDescription() != null ? labResult.getLabTest().getDescription() : "");
     360                        return resultMap;
     361                    })
     362                    .collect(Collectors.toList());
     363            return ResponseEntity.ok(dtos);
     364        } catch (RuntimeException e) {
     365            logger.error("Error fetching submitted lab results: {}", e.getMessage());
     366            return ResponseEntity.badRequest()
     367                    .body(Map.of("error", e.getMessage()));
     368        } catch (Exception e) {
     369            logger.error("Unexpected error fetching submitted lab results: {}", e.getMessage());
     370            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
     371                    .body(Map.of("error", "Failed to fetch submitted lab results: " + e.getMessage()));
    309372        }
    310373    }
  • backend/src/main/java/medora/service/LabService.java

    rf18b11b r6c00695  
    1313
    1414/**
    15  *
     15 * LabService handles lab test and lab result operations.
    1616 * UC013 – Record Lab Test Request
    1717 * UC014 – Store Lab Results
     
    5353    }
    5454
    55 
     55    // ================= LAB TEST =================
    5656
    5757    @Transactional
     
    107107    }
    108108
    109     // UC013
     109    // ================= LAB TEST REQUESTS (UC013) =================
    110110
    111111    @Transactional
     
    172172    }
    173173
    174     //UC014, UC015
     174    // ================= LAB RESULTS (UC014, UC015) =================
    175175
    176176    @Transactional
     
    245245                .toList();
    246246    }
     247
     248    @Transactional(readOnly = true)
     249    public List<MedicalRecordLabResults> getAllSubmittedLabResults() {
     250        logger.info("Fetching all submitted lab results");
     251        return medicalRecordLabResultRepository.findAll();
     252    }
    247253}
  • frontend/src/pages/lab-tests/LabTestList.js

    rf18b11b r6c00695  
    1818  const [labResults, setLabResults] = useState([]);
    1919  const [pendingTests, setPendingTests] = useState([]);
     20  const [submittedTests, setSubmittedTests] = useState([]);
    2021  const [selectedTest, setSelectedTest] = useState(null);
    2122
     
    3233  });
    3334
     35  // Filter states for pending tests
     36  const [pendingFilters, setPendingFilters] = useState({
     37    testName: '',
     38    patientName: '',
     39    testDate: '',
     40  });
     41
     42  // Filter states for submitted tests
     43  const [submittedFilters, setSubmittedFilters] = useState({
     44    testName: '',
     45    patientName: '',
     46    testDate: '',
     47  });
     48
    3449  useEffect(() => {
    3550    if (isLabTechnician) {
    36       loadPendingTests();
     51      loadLabTestsData();
    3752    }
    3853  }, [isLabTechnician]);
    3954
    40   const loadPendingTests = async () => {
     55  const loadLabTestsData = async () => {
    4156    try {
    4257      setLoading(true);
    43       const response = await labService.getPendingLabTests();
    44       setPendingTests(response.data || []);
     58      setError(null);
     59
     60      // Load pending tests
     61      const pendingRes = await labService.getPendingLabTests();
     62      setPendingTests(pendingRes.data || []);
     63
     64      // Try to load submitted tests, but don't fail if this endpoint doesn't exist
     65      try {
     66        const submittedRes = await labService.getAllSubmittedLabResults();
     67        setSubmittedTests(submittedRes.data || []);
     68      } catch (err) {
     69        console.warn('Could not load submitted tests:', err);
     70        setSubmittedTests([]);
     71      }
    4572    } catch (err) {
    4673      setError('Failed to load pending lab tests');
     74      setPendingTests([]);
    4775    } finally {
    4876      setLoading(false);
     
    169197
    170198      // Refresh pending tests
    171       await loadPendingTests();
     199      await loadLabTestsData();
    172200
    173201      // Reset form
     
    181209      setError(null);
    182210      alert('Lab result submitted successfully!');
     211
     212      // Refresh the lab tests data
     213      await loadLabTestsData();
     214
     215      // Reset form
     216      setSelectedTest(null);
     217      setSubmitFormData({
     218        results: '',
     219        resultDate: new Date().toISOString().split('T')[0],
     220      });
    183221    } catch (err) {
    184222      setError('Failed to submit lab result: ' + (err.response?.data?.error || err.message));
     
    190228  if (isLabTechnician) {
    191229    return (
     230        <div>
     231          <h1 className="text-3xl font-bold mb-6" style={{ color: '#7c3aed' }}>Lab Tests - Submit Results</h1>
     232
     233          {error && <ErrorAlert message={error} onClose={() => setError(null)} />}
     234
     235          {selectedTest ? (
     236              // Submit Result Form
     237              <div className="bg-white rounded-lg shadow p-6 mb-6">
     238                <h2 className="text-xl font-bold mb-4">Submit Lab Result</h2>
     239                <div className="mb-4 p-4 bg-gray-50 rounded-lg">
     240                  <p className="mb-2"><strong>Test:</strong> {selectedTest.testName}</p>
     241                  <p className="mb-2"><strong>Patient:</strong> {selectedTest.patientName}</p>
     242                  <p className="mb-2"><strong>Doctor:</strong> {selectedTest.doctorName}</p>
     243                  <p className="mb-2"><strong>Test Date:</strong> {selectedTest.testDate}</p>
     244                  {selectedTest.notes && <p><strong>Notes:</strong> {selectedTest.notes}</p>}
     245                </div>
     246
     247                <form onSubmit={handleSubmitResult} className="space-y-4">
     248                  <div>
     249                    <label className="block text-sm font-semibold mb-2">Test Results *</label>
     250                    <textarea
     251                        value={submitFormData.results}
     252                        onChange={(e) => setSubmitFormData({ ...submitFormData, results: e.target.value })}
     253                        placeholder="Enter detailed test results"
     254                        className="w-full px-4 py-2 border rounded-lg"
     255                        rows="4"
     256                        required
     257                    />
     258                  </div>
     259
     260                  <div>
     261                    <label className="block text-sm font-semibold mb-2">Result Date *</label>
     262                    <input
     263                        type="date"
     264                        value={submitFormData.resultDate}
     265                        onChange={(e) => setSubmitFormData({ ...submitFormData, resultDate: e.target.value })}
     266                        className="w-full px-4 py-2 border rounded-lg"
     267                        required
     268                    />
     269                  </div>
     270
     271                  <div className="flex gap-4">
     272                    <button
     273                        type="submit"
     274                        disabled={loading}
     275                        style={{
     276                          background: loading ? '#d1d5db' : '#bfdbfe',
     277                          color: '#1e1035',
     278                          padding: '8px 24px',
     279                          borderRadius: '6px',
     280                          border: 'none',
     281                          cursor: loading ? 'not-allowed' : 'pointer',
     282                          fontSize: '14px',
     283                          fontWeight: '400'
     284                        }}
     285                        onMouseEnter={(e) => !loading && (e.currentTarget.style.background = '#93c5fd')}
     286                        onMouseLeave={(e) => !loading && (e.currentTarget.style.background = '#bfdbfe')}
     287                    >
     288                      {loading ? 'Submitting...' : 'Submit Result'}
     289                    </button>
     290                    <button
     291                        type="button"
     292                        onClick={() => {
     293                          setSelectedTest(null);
     294                          setSubmitFormData({
     295                            results: '',
     296                            resultDate: new Date().toISOString().split('T')[0],
     297                          });
     298                        }}
     299                        className="bg-gray-400 text-white px-6 py-2 rounded hover:bg-gray-500"
     300                    >
     301                      Cancel
     302                    </button>
     303                  </div>
     304                </form>
     305              </div>
     306          ) : (
     307              // Pending Tests List
     308              <div className="bg-white rounded-lg shadow overflow-hidden">
     309                <div className="p-6 border-b">
     310                  <h2 className="text-xl font-bold">Pending Lab Tests ({pendingTests.length})</h2>
     311                </div>
     312
     313                {pendingTests.length === 0 ? (
     314                    <div className="p-6 text-center text-gray-600">
     315                      No pending lab tests
     316                    </div>
     317                ) : (
     318                    <>
     319                      {/* Filters */}
     320                      <div className="p-6 border-b bg-gray-50">
     321                        <h3 className="text-sm font-semibold mb-4">Filters</h3>
     322                        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
     323                          <div>
     324                            <label className="block text-sm font-semibold mb-2">Test</label>
     325                            <input
     326                                type="text"
     327                                placeholder="Filter by test name..."
     328                                value={pendingFilters.testName}
     329                                onChange={(e) => setPendingFilters({...pendingFilters, testName: e.target.value})}
     330                                className="w-full px-3 py-2 border rounded-lg text-sm"
     331                            />
     332                          </div>
     333                          <div>
     334                            <label className="block text-sm font-semibold mb-2">Patient</label>
     335                            <input
     336                                type="text"
     337                                placeholder="Filter by patient name..."
     338                                value={pendingFilters.patientName}
     339                                onChange={(e) => setPendingFilters({...pendingFilters, patientName: e.target.value})}
     340                                className="w-full px-3 py-2 border rounded-lg text-sm"
     341                            />
     342                          </div>
     343                          <div>
     344                            <label className="block text-sm font-semibold mb-2">Test Date</label>
     345                            <input
     346                                type="date"
     347                                value={pendingFilters.testDate}
     348                                onChange={(e) => setPendingFilters({...pendingFilters, testDate: e.target.value})}
     349                                className="w-full px-3 py-2 border rounded-lg text-sm"
     350                            />
     351                          </div>
     352                        </div>
     353                        <button
     354                            onClick={() => setPendingFilters({testName: '', patientName: '', testDate: ''})}
     355                            className="mt-4 bg-gray-300 text-gray-700 px-4 py-2 rounded text-sm hover:bg-gray-400"
     356                        >
     357                          Clear Filters
     358                        </button>
     359                      </div>
     360
     361                      {/* Filtered Table */}
     362                      <table className="w-full">
     363                        <thead className="bg-gray-100">
     364                        <tr>
     365                          <th className="px-6 py-3 text-left text-sm font-semibold">Test</th>
     366                          <th className="px-6 py-3 text-left text-sm font-semibold">Patient</th>
     367                          <th className="px-6 py-3 text-left text-sm font-semibold">Doctor</th>
     368                          <th className="px-6 py-3 text-left text-sm font-semibold">Requested</th>
     369                          <th className="px-6 py-3 text-left text-sm font-semibold">Test Date</th>
     370                          <th className="px-6 py-3 text-left text-sm font-semibold">Notes</th>
     371                          <th className="px-6 py-3 text-left text-sm font-semibold">Action</th>
     372                        </tr>
     373                        </thead>
     374                        <tbody>
     375                        {pendingTests
     376                            .filter((test) => {
     377                              const testName = (test.testName || '').toLowerCase();
     378                              const patientName = (test.patientName || '').toLowerCase();
     379                              const testDate = (test.testDate || '');
     380
     381                              return (
     382                                  testName.includes(pendingFilters.testName.toLowerCase()) &&
     383                                  patientName.includes(pendingFilters.patientName.toLowerCase()) &&
     384                                  (pendingFilters.testDate === '' || testDate === pendingFilters.testDate)
     385                              );
     386                            })
     387                            .map((test, index) => (
     388                                <tr key={index} className="border-t hover:bg-gray-50">
     389                                  <td className="px-6 py-3 font-medium">{test.testName}</td>
     390                                  <td className="px-6 py-3">{test.patientName}</td>
     391                                  <td className="px-6 py-3">{test.doctorName}</td>
     392                                  <td className="px-6 py-3 text-green-600">{test.requestDate}</td>
     393                                  <td className="px-6 py-3 text-purple-600">{test.testDate}</td>
     394                                  <td className="px-6 py-3 text-gray-600 text-sm">{test.notes || '-'}</td>
     395                                  <td className="px-6 py-3">
     396                                    <button
     397                                        onClick={() => setSelectedTest(test)}
     398                                        style={{
     399                                          background: '#bfdbfe',
     400                                          color: '#1e1035',
     401                                          padding: '6px 12px',
     402                                          borderRadius: '4px',
     403                                          border: 'none',
     404                                          cursor: 'pointer',
     405                                          fontSize: '12px',
     406                                          fontWeight: '400'
     407                                        }}
     408                                        onMouseEnter={(e) => e.currentTarget.style.background = '#93c5fd'}
     409                                        onMouseLeave={(e) => e.currentTarget.style.background = '#bfdbfe'}
     410                                    >
     411                                      Submit Result
     412                                    </button>
     413                                  </td>
     414                                </tr>
     415                            ))}
     416                        </tbody>
     417                      </table>
     418                    </>
     419                )}
     420              </div>
     421          )}
     422
     423          {/* Submitted Tests History */}
     424          {!selectedTest && (
     425              <div className="bg-white rounded-lg shadow overflow-hidden mt-6">
     426                <div className="p-6 border-b">
     427                  <h2 className="text-xl font-bold">Submitted Lab Results History ({submittedTests.length})</h2>
     428                </div>
     429
     430                {submittedTests.length === 0 ? (
     431                    <div className="p-6 text-center text-gray-600">
     432                      No submitted lab results yet
     433                    </div>
     434                ) : (
     435                    <>
     436                      {/* Filters */}
     437                      <div className="p-6 border-b bg-gray-50">
     438                        <h3 className="text-sm font-semibold mb-4">Filters</h3>
     439                        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
     440                          <div>
     441                            <label className="block text-sm font-semibold mb-2">Test</label>
     442                            <input
     443                                type="text"
     444                                placeholder="Filter by test name..."
     445                                value={submittedFilters.testName}
     446                                onChange={(e) => setSubmittedFilters({...submittedFilters, testName: e.target.value})}
     447                                className="w-full px-3 py-2 border rounded-lg text-sm"
     448                            />
     449                          </div>
     450                          <div>
     451                            <label className="block text-sm font-semibold mb-2">Patient</label>
     452                            <input
     453                                type="text"
     454                                placeholder="Filter by patient name..."
     455                                value={submittedFilters.patientName}
     456                                onChange={(e) => setSubmittedFilters({...submittedFilters, patientName: e.target.value})}
     457                                className="w-full px-3 py-2 border rounded-lg text-sm"
     458                            />
     459                          </div>
     460                          <div>
     461                            <label className="block text-sm font-semibold mb-2">Test Date</label>
     462                            <input
     463                                type="date"
     464                                value={submittedFilters.testDate}
     465                                onChange={(e) => setSubmittedFilters({...submittedFilters, testDate: e.target.value})}
     466                                className="w-full px-3 py-2 border rounded-lg text-sm"
     467                            />
     468                          </div>
     469                        </div>
     470                        <button
     471                            onClick={() => setSubmittedFilters({testName: '', patientName: '', testDate: ''})}
     472                            className="mt-4 bg-gray-300 text-gray-700 px-4 py-2 rounded text-sm hover:bg-gray-400"
     473                        >
     474                          Clear Filters
     475                        </button>
     476                      </div>
     477
     478                      {/* Filtered Results Table */}
     479                      <table className="w-full">
     480                        <thead className="bg-gray-100">
     481                        <tr>
     482                          <th className="px-6 py-3 text-left text-sm font-semibold">Test</th>
     483                          <th className="px-6 py-3 text-left text-sm font-semibold">Patient</th>
     484                          <th className="px-6 py-3 text-left text-sm font-semibold">Test Date</th>
     485                          <th className="px-6 py-3 text-left text-sm font-semibold">Result Date</th>
     486                          <th className="px-6 py-3 text-left text-sm font-semibold">Status</th>
     487                        </tr>
     488                        </thead>
     489                        <tbody>
     490                        {submittedTests
     491                            .filter((test) => {
     492                              const testName = (test.testName || '').toLowerCase();
     493                              const patientName = (test.patientName || '').toLowerCase();
     494                              const testDate = (test.testDate || '');
     495
     496                              return (
     497                                  testName.includes(submittedFilters.testName.toLowerCase()) &&
     498                                  patientName.includes(submittedFilters.patientName.toLowerCase()) &&
     499                                  (submittedFilters.testDate === '' || testDate === submittedFilters.testDate)
     500                              );
     501                            })
     502                            .map((test, index) => (
     503                                <tr key={index} className="border-t hover:bg-gray-50">
     504                                  <td className="px-6 py-3 font-medium">{test.testName || test.description || 'Lab Test'}</td>
     505                                  <td className="px-6 py-3">{test.patientName || 'N/A'}</td>
     506                                  <td className="px-6 py-3">{test.testDate || 'N/A'}</td>
     507                                  <td className="px-6 py-3 text-green-600">{test.resultDate || test.createdDate || 'N/A'}</td>
     508                                  <td className="px-6 py-3">
     509                            <span className="px-3 py-1 bg-green-100 text-green-800 rounded-full text-sm font-semibold">
     510                              Submitted
     511                            </span>
     512                                  </td>
     513                                </tr>
     514                            ))}
     515                        </tbody>
     516                      </table>
     517                    </>
     518                )}
     519              </div>
     520          )}
     521        </div>
     522    );
     523  }
     524
     525  return (
    192526      <div>
    193         <h1 className="text-3xl font-bold mb-6" style={{ color: '#7c3aed' }}>Lab Tests - Submit Results</h1>
     527        <h1 className="text-3xl font-bold mb-6" style={{ color: '#7c3aed' }}>Lab Tests</h1>
    194528
    195529        {error && <ErrorAlert message={error} onClose={() => setError(null)} />}
    196530
    197         {selectedTest ? (
    198           // Submit Result Form
    199           <div className="bg-white rounded-lg shadow p-6 mb-6">
    200             <h2 className="text-xl font-bold mb-4">Submit Lab Result</h2>
    201             <div className="mb-4 p-4 bg-gray-50 rounded-lg">
    202               <p className="mb-2"><strong>Test:</strong> {selectedTest.testName}</p>
    203               <p className="mb-2"><strong>Patient:</strong> {selectedTest.patientName}</p>
    204               <p className="mb-2"><strong>Doctor:</strong> {selectedTest.doctorName}</p>
    205               <p className="mb-2"><strong>Test Date:</strong> {selectedTest.testDate}</p>
    206               {selectedTest.notes && <p><strong>Notes:</strong> {selectedTest.notes}</p>}
    207             </div>
    208 
    209             <form onSubmit={handleSubmitResult} className="space-y-4">
    210               <div>
    211                 <label className="block text-sm font-semibold mb-2">Test Results *</label>
    212                 <textarea
    213                   value={submitFormData.results}
    214                   onChange={(e) => setSubmitFormData({ ...submitFormData, results: e.target.value })}
    215                   placeholder="Enter detailed test results"
    216                   className="w-full px-4 py-2 border rounded-lg"
    217                   rows="4"
    218                   required
     531        {/* Search Form */}
     532        <div className="bg-white rounded-lg shadow p-6 mb-6">
     533          <h2 className="text-xl font-bold mb-4">Search Patient</h2>
     534          <form onSubmit={handleSearch} className="space-y-4">
     535            <div className="flex gap-4">
     536              <div className="flex-1">
     537                <label className="block text-sm font-semibold mb-2">Patient EMBG</label>
     538                <input
     539                    type="text"
     540                    value={embg}
     541                    onChange={(e) => setEmbg(e.target.value)}
     542                    placeholder="e.g., 1402994123456"
     543                    className="w-full px-4 py-2 border rounded-lg"
    219544                />
    220545              </div>
    221 
    222               <div>
    223                 <label className="block text-sm font-semibold mb-2">Result Date *</label>
    224                 <input
    225                   type="date"
    226                   value={submitFormData.resultDate}
    227                   onChange={(e) => setSubmitFormData({ ...submitFormData, resultDate: e.target.value })}
    228                   className="w-full px-4 py-2 border rounded-lg"
    229                   required
    230                 />
    231               </div>
    232 
    233               <div className="flex gap-4">
     546              <div className="flex items-end">
    234547                <button
    235                   type="submit"
    236                   disabled={loading}
    237                   style={{
    238                     background: loading ? '#d1d5db' : '#bfdbfe',
    239                     color: '#1e1035',
    240                     padding: '8px 24px',
    241                     borderRadius: '6px',
    242                     border: 'none',
    243                     cursor: loading ? 'not-allowed' : 'pointer',
    244                     fontSize: '14px',
    245                     fontWeight: '400'
    246                   }}
    247                   onMouseEnter={(e) => !loading && (e.currentTarget.style.background = '#93c5fd')}
    248                   onMouseLeave={(e) => !loading && (e.currentTarget.style.background = '#bfdbfe')}
     548                    type="submit"
     549                    disabled={loading}
     550                    className="bg-purple-600 text-white px-8 py-2 rounded-full hover:bg-purple-700 disabled:bg-gray-400"
    249551                >
    250                   {loading ? 'Submitting...' : 'Submit Result'}
    251                 </button>
    252                 <button
    253                   type="button"
    254                   onClick={() => {
    255                     setSelectedTest(null);
    256                     setSubmitFormData({
    257                       results: '',
    258                       resultDate: new Date().toISOString().split('T')[0],
    259                     });
    260                   }}
    261                   className="bg-gray-400 text-white px-6 py-2 rounded hover:bg-gray-500"
    262                 >
    263                   Cancel
     552                  {loading ? 'Searching...' : 'Search'}
    264553                </button>
    265554              </div>
    266             </form>
    267           </div>
    268         ) : (
    269           // Pending Tests List
    270           <div className="bg-white rounded-lg shadow overflow-hidden">
    271             <div className="p-6 border-b">
    272               <h2 className="text-xl font-bold">Pending Lab Tests ({pendingTests.length})</h2>
    273555            </div>
    274 
    275             {pendingTests.length === 0 ? (
    276               <div className="p-6 text-center text-gray-600">
    277                 No pending lab tests
     556          </form>
     557        </div>
     558
     559        {/* Patient Lab Tests */}
     560        {searched && patient && (
     561            <div className="space-y-6">
     562              {/* Patient Info */}
     563              <div className="bg-white rounded-lg shadow p-6">
     564                <div className="flex justify-between items-start mb-4">
     565                  <h2 className="text-2xl font-bold">{patient.firstName} {patient.lastName}</h2>
     566                  {!showRequestForm && (
     567                      <button
     568                          onClick={() => setShowRequestForm(true)}
     569                          className="bg-green-600 text-white px-4 py-2 rounded hover:bg-green-700"
     570                      >
     571                        Request Lab Test
     572                      </button>
     573                  )}
     574                </div>
     575                <div className="grid grid-cols-4 gap-4">
     576                  <div>
     577                    <p className="text-sm text-gray-600">EMBG</p>
     578                    <p className="font-semibold">{patient.embg}</p>
     579                  </div>
     580                  <div>
     581                    <p className="text-sm text-gray-600">Blood Type</p>
     582                    <p className="font-semibold">{patient.bloodType || 'N/A'}</p>
     583                  </div>
     584                  <div>
     585                    <p className="text-sm text-gray-600">Date of Birth</p>
     586                    <p className="font-semibold">{patient.dateOfBirth}</p>
     587                  </div>
     588                </div>
    278589              </div>
    279             ) : (
    280               <table className="w-full">
    281                 <thead className="bg-gray-100">
    282                   <tr>
    283                     <th className="px-6 py-3 text-left text-sm font-semibold">Test</th>
    284                     <th className="px-6 py-3 text-left text-sm font-semibold">Patient</th>
    285                     <th className="px-6 py-3 text-left text-sm font-semibold">Doctor</th>
    286                     <th className="px-6 py-3 text-left text-sm font-semibold">Requested</th>
    287                     <th className="px-6 py-3 text-left text-sm font-semibold">Test Date</th>
    288                     <th className="px-6 py-3 text-left text-sm font-semibold">Notes</th>
    289                     <th className="px-6 py-3 text-left text-sm font-semibold">Action</th>
    290                   </tr>
    291                 </thead>
    292                 <tbody>
    293                   {pendingTests.map((test, index) => (
    294                     <tr key={index} className="border-t hover:bg-gray-50">
    295                       <td className="px-6 py-3 font-medium">{test.testName}</td>
    296                       <td className="px-6 py-3">{test.patientName}</td>
    297                       <td className="px-6 py-3">{test.doctorName}</td>
    298                       <td className="px-6 py-3 text-green-600">{test.requestDate}</td>
    299                       <td className="px-6 py-3 text-purple-600">{test.testDate}</td>
    300                       <td className="px-6 py-3 text-gray-600 text-sm">{test.notes || '-'}</td>
    301                       <td className="px-6 py-3">
     590
     591              {/* Request Lab Test Form */}
     592              {showRequestForm && (
     593                  <div className="bg-white rounded-lg shadow p-6">
     594                    <h3 className="text-xl font-bold mb-4">Request Lab Test</h3>
     595                    <form onSubmit={handleRequestTest} className="space-y-4">
     596                      <div className="grid grid-cols-2 gap-4">
     597                        <div>
     598                          <label className="block text-sm font-semibold mb-2">Test</label>
     599                          <select
     600                              value={requestData.testId}
     601                              onChange={(e) => setRequestData({ ...requestData, testId: e.target.value })}
     602                              className="w-full px-4 py-2 border rounded-lg"
     603                              required
     604                          >
     605                            <option value="">Select a test</option>
     606                            {availableTests.map((test) => (
     607                                <option key={test.testId} value={test.testId}>
     608                                  {test.testName} (${test.cost})
     609                                </option>
     610                            ))}
     611                          </select>
     612                        </div>
     613                        <div>
     614                          <label className="block text-sm font-semibold mb-2">Test Date</label>
     615                          <input
     616                              type="date"
     617                              value={requestData.testDate}
     618                              onChange={(e) => setRequestData({ ...requestData, testDate: e.target.value })}
     619                              className="w-full px-4 py-2 border rounded-lg"
     620                          />
     621                        </div>
     622                      </div>
     623                      <div>
     624                        <label className="block text-sm font-semibold mb-2">Notes</label>
     625                        <textarea
     626                            value={requestData.notes}
     627                            onChange={(e) => setRequestData({ ...requestData, notes: e.target.value })}
     628                            placeholder="Additional notes for the lab technician"
     629                            className="w-full px-4 py-2 border rounded-lg"
     630                            rows="3"
     631                        />
     632                      </div>
     633                      <div className="flex gap-4">
    302634                        <button
    303                           onClick={() => setSelectedTest(test)}
    304                           style={{
    305                             background: '#bfdbfe',
    306                             color: '#1e1035',
    307                             padding: '6px 12px',
    308                             borderRadius: '4px',
    309                             border: 'none',
    310                             cursor: 'pointer',
    311                             fontSize: '12px',
    312                             fontWeight: '400'
    313                           }}
    314                           onMouseEnter={(e) => e.currentTarget.style.background = '#93c5fd'}
    315                           onMouseLeave={(e) => e.currentTarget.style.background = '#bfdbfe'}
     635                            type="submit"
     636                            disabled={loading}
     637                            className="bg-green-600 text-white px-6 py-2 rounded hover:bg-green-700 disabled:bg-gray-400"
    316638                        >
    317                           Submit Result
     639                          {loading ? 'Requesting...' : 'Request Test'}
    318640                        </button>
    319                       </td>
    320                     </tr>
    321                   ))}
    322                 </tbody>
    323               </table>
    324             )}
    325           </div>
     641                        <button
     642                            type="button"
     643                            onClick={() => setShowRequestForm(false)}
     644                            className="bg-gray-400 text-white px-6 py-2 rounded hover:bg-gray-500"
     645                        >
     646                          Cancel
     647                        </button>
     648                      </div>
     649                    </form>
     650                  </div>
     651              )}
     652
     653              {/* Test Requests */}
     654              {labTestRequests && labTestRequests.length > 0 && (
     655                  <div className="bg-white rounded-lg shadow p-6">
     656                    <h3 className="text-xl font-bold mb-4">Lab Test Requests</h3>
     657                    <div className="space-y-3">
     658                      {labTestRequests.map((request) => (
     659                          <div key={request.testId} className="border-l-4 border-purple-500 pl-4 py-2">
     660                            <p className="font-semibold text-lg">{request.testName}</p>
     661                            <p className="text-sm text-gray-600">Requested by: {request.doctorName}</p>
     662                            <p className="text-sm text-gray-600">Test Date: {request.requestDate}</p>
     663                            {request.notes && (
     664                                <p className="text-sm text-gray-600">Notes: {request.notes}</p>
     665                            )}
     666                          </div>
     667                      ))}
     668                    </div>
     669                  </div>
     670              )}
     671
     672              {/* Lab Test Results */}
     673              {labResults && labResults.length > 0 && (
     674                  <div className="bg-white rounded-lg shadow p-6">
     675                    <h3 className="text-xl font-bold mb-4">Lab Test Results</h3>
     676                    <div className="space-y-4">
     677                      {labResults.map((result) => (
     678                          <div key={result.resultId} className="border-l-4 border-green-500 pl-4 py-3 bg-green-50 rounded">
     679                            <p className="font-semibold text-lg text-green-700">{result.testName}</p>
     680                            <p className="text-sm text-gray-700 mt-2"><strong>Results:</strong> {result.results}</p>
     681                            <p className="text-sm text-gray-600">Result Date: {result.resultDate}</p>
     682                          </div>
     683                      ))}
     684                    </div>
     685                  </div>
     686              )}
     687
     688              {/* Link to submit results */}
     689              {labTestRequests && labTestRequests.length > 0 && (
     690                  <div className="bg-yellow-50 rounded-lg p-6">
     691                    <h3 className="text-lg font-semibold text-yellow-800 mb-3">Lab Technician: Submit Test Results</h3>
     692                    <p className="text-sm text-gray-700 mb-4">
     693                      {labTestRequests.length} test{labTestRequests.length !== 1 ? 's' : ''} awaiting results
     694                    </p>
     695                    <a
     696                        href="/lab-tests/results"
     697                        className="inline-block bg-yellow-600 text-white px-6 py-2 rounded hover:bg-yellow-700"
     698                    >
     699                      Submit Lab Results
     700                    </a>
     701                  </div>
     702              )}
     703
     704              {/* No requests message */}
     705              {!labTestRequests || labTestRequests.length === 0 && !labResults?.length && (
     706                  <div className="bg-blue-50 rounded-lg p-6 text-center">
     707                    <p className="text-gray-600">No lab test requests for this patient</p>
     708                  </div>
     709              )}
     710            </div>
     711        )}
     712
     713        {/* No search performed */}
     714        {!searched && (
     715            <div className="bg-gray-50 rounded-lg p-12 text-center">
     716              <p className="text-gray-600 text-lg">Enter a patient EMBG to request lab tests</p>
     717            </div>
    326718        )}
    327719      </div>
    328     );
    329   }
    330 
    331   return (
    332     <div>
    333       <h1 className="text-3xl font-bold mb-6" style={{ color: '#7c3aed' }}>Lab Tests</h1>
    334 
    335       {error && <ErrorAlert message={error} onClose={() => setError(null)} />}
    336 
    337       {/* Search Form */}
    338       <div className="bg-white rounded-lg shadow p-6 mb-6">
    339         <h2 className="text-xl font-bold mb-4">Search Patient</h2>
    340         <form onSubmit={handleSearch} className="space-y-4">
    341           <div className="flex gap-4">
    342             <div className="flex-1">
    343               <label className="block text-sm font-semibold mb-2">Patient EMBG</label>
    344               <input
    345                 type="text"
    346                 value={embg}
    347                 onChange={(e) => setEmbg(e.target.value)}
    348                 placeholder="e.g., 1402994123456"
    349                 className="w-full px-4 py-2 border rounded-lg"
    350               />
    351             </div>
    352             <div className="flex items-end">
    353               <button
    354                 type="submit"
    355                 disabled={loading}
    356                 className="bg-purple-600 text-white px-8 py-2 rounded-full hover:bg-purple-700 disabled:bg-gray-400"
    357               >
    358                 {loading ? 'Searching...' : 'Search'}
    359               </button>
    360             </div>
    361           </div>
    362         </form>
    363       </div>
    364 
    365       {/* Patient Lab Tests */}
    366       {searched && patient && (
    367         <div className="space-y-6">
    368           {/* Patient Info */}
    369           <div className="bg-white rounded-lg shadow p-6">
    370             <div className="flex justify-between items-start mb-4">
    371               <h2 className="text-2xl font-bold">{patient.firstName} {patient.lastName}</h2>
    372               {!showRequestForm && (
    373                 <button
    374                   onClick={() => setShowRequestForm(true)}
    375                   className="bg-green-600 text-white px-4 py-2 rounded hover:bg-green-700"
    376                 >
    377                   Request Lab Test
    378                 </button>
    379               )}
    380             </div>
    381             <div className="grid grid-cols-4 gap-4">
    382               <div>
    383                 <p className="text-sm text-gray-600">EMBG</p>
    384                 <p className="font-semibold">{patient.embg}</p>
    385               </div>
    386               <div>
    387                 <p className="text-sm text-gray-600">Blood Type</p>
    388                 <p className="font-semibold">{patient.bloodType || 'N/A'}</p>
    389               </div>
    390               <div>
    391                 <p className="text-sm text-gray-600">Date of Birth</p>
    392                 <p className="font-semibold">{patient.dateOfBirth}</p>
    393               </div>
    394             </div>
    395           </div>
    396 
    397           {/* Request Lab Test Form */}
    398           {showRequestForm && (
    399             <div className="bg-white rounded-lg shadow p-6">
    400               <h3 className="text-xl font-bold mb-4">Request Lab Test</h3>
    401               <form onSubmit={handleRequestTest} className="space-y-4">
    402                 <div className="grid grid-cols-2 gap-4">
    403                   <div>
    404                     <label className="block text-sm font-semibold mb-2">Test</label>
    405                     <select
    406                       value={requestData.testId}
    407                       onChange={(e) => setRequestData({ ...requestData, testId: e.target.value })}
    408                       className="w-full px-4 py-2 border rounded-lg"
    409                       required
    410                     >
    411                       <option value="">Select a test</option>
    412                       {availableTests.map((test) => (
    413                         <option key={test.testId} value={test.testId}>
    414                           {test.testName} (${test.cost})
    415                         </option>
    416                       ))}
    417                     </select>
    418                   </div>
    419                   <div>
    420                     <label className="block text-sm font-semibold mb-2">Test Date</label>
    421                     <input
    422                       type="date"
    423                       value={requestData.testDate}
    424                       onChange={(e) => setRequestData({ ...requestData, testDate: e.target.value })}
    425                       className="w-full px-4 py-2 border rounded-lg"
    426                     />
    427                   </div>
    428                 </div>
    429                 <div>
    430                   <label className="block text-sm font-semibold mb-2">Notes</label>
    431                   <textarea
    432                     value={requestData.notes}
    433                     onChange={(e) => setRequestData({ ...requestData, notes: e.target.value })}
    434                     placeholder="Additional notes for the lab technician"
    435                     className="w-full px-4 py-2 border rounded-lg"
    436                     rows="3"
    437                   />
    438                 </div>
    439                 <div className="flex gap-4">
    440                   <button
    441                     type="submit"
    442                     disabled={loading}
    443                     className="bg-green-600 text-white px-6 py-2 rounded hover:bg-green-700 disabled:bg-gray-400"
    444                   >
    445                     {loading ? 'Requesting...' : 'Request Test'}
    446                   </button>
    447                   <button
    448                     type="button"
    449                     onClick={() => setShowRequestForm(false)}
    450                     className="bg-gray-400 text-white px-6 py-2 rounded hover:bg-gray-500"
    451                   >
    452                     Cancel
    453                   </button>
    454                 </div>
    455               </form>
    456             </div>
    457           )}
    458 
    459           {/* Test Requests */}
    460           {labTestRequests && labTestRequests.length > 0 && (
    461             <div className="bg-white rounded-lg shadow p-6">
    462               <h3 className="text-xl font-bold mb-4">Lab Test Requests</h3>
    463               <div className="space-y-3">
    464                 {labTestRequests.map((request) => (
    465                   <div key={request.testId} className="border-l-4 border-purple-500 pl-4 py-2">
    466                     <p className="font-semibold text-lg">{request.testName}</p>
    467                     <p className="text-sm text-gray-600">Requested by: {request.doctorName}</p>
    468                     <p className="text-sm text-gray-600">Test Date: {request.requestDate}</p>
    469                     {request.notes && (
    470                       <p className="text-sm text-gray-600">Notes: {request.notes}</p>
    471                     )}
    472                   </div>
    473                 ))}
    474               </div>
    475             </div>
    476           )}
    477 
    478           {/* Lab Test Results */}
    479           {labResults && labResults.length > 0 && (
    480             <div className="bg-white rounded-lg shadow p-6">
    481               <h3 className="text-xl font-bold mb-4">Lab Test Results</h3>
    482               <div className="space-y-4">
    483                 {labResults.map((result) => (
    484                   <div key={result.resultId} className="border-l-4 border-green-500 pl-4 py-3 bg-green-50 rounded">
    485                     <p className="font-semibold text-lg text-green-700">{result.testName}</p>
    486                     <p className="text-sm text-gray-700 mt-2"><strong>Results:</strong> {result.results}</p>
    487                     <p className="text-sm text-gray-600">Result Date: {result.resultDate}</p>
    488                   </div>
    489                 ))}
    490               </div>
    491             </div>
    492           )}
    493 
    494           {/* Link to submit results */}
    495           {labTestRequests && labTestRequests.length > 0 && (
    496             <div className="bg-yellow-50 rounded-lg p-6">
    497               <h3 className="text-lg font-semibold text-yellow-800 mb-3">Lab Technician: Submit Test Results</h3>
    498               <p className="text-sm text-gray-700 mb-4">
    499                 {labTestRequests.length} test{labTestRequests.length !== 1 ? 's' : ''} awaiting results
    500               </p>
    501               <a
    502                 href="/lab-tests/results"
    503                 className="inline-block bg-yellow-600 text-white px-6 py-2 rounded hover:bg-yellow-700"
    504               >
    505                 Submit Lab Results
    506               </a>
    507             </div>
    508           )}
    509 
    510           {/* No requests message */}
    511           {!labTestRequests || labTestRequests.length === 0 && !labResults?.length && (
    512             <div className="bg-blue-50 rounded-lg p-6 text-center">
    513               <p className="text-gray-600">No lab test requests for this patient</p>
    514             </div>
    515           )}
    516         </div>
    517       )}
    518 
    519       {/* No search performed */}
    520       {!searched && (
    521         <div className="bg-gray-50 rounded-lg p-12 text-center">
    522           <p className="text-gray-600 text-lg">Enter a patient EMBG to request lab tests</p>
    523         </div>
    524       )}
    525     </div>
    526720  );
    527721}
  • frontend/src/services/labService.js

    rf18b11b r6c00695  
    4747  },
    4848
    49   // Get pending lab tests (for lab technicians)
     49  // Get pending lab tests for lab technicians
    5050  getPendingLabTests: () => {
    5151    return api.get('/lab-tests/requests/pending');
     52  },
     53
     54  // Get all submitted lab results for lab technicians
     55  getAllSubmittedLabResults: () => {
     56    return api.get('/lab-tests/results');
    5257  },
    5358};
Note: See TracChangeset for help on using the changeset viewer.