Update frontend code
This commit is contained in:
+76
-35
@@ -4,7 +4,7 @@ import { createColumnHelper } from '@tanstack/react-table';
|
||||
import { Table } from '../components/Table';
|
||||
import Pagination from '../components/Pagination';
|
||||
import { CustomSelect } from '../components/CustomSelect';
|
||||
import { getDrugs, authenticate, addMedicine, createOrderDraft, updateOrderItems, precheckOrder, dispenseOrder } from '../services/api';
|
||||
import { getDrugs, authenticate, addMedicine, createOrderDraft, updateOrderItems, precheckOrder, dispenseOrder, downloadInvoice, fetchSyncedPatients } from '../services/api';
|
||||
|
||||
interface PharmacyItem {
|
||||
id: string;
|
||||
@@ -103,10 +103,13 @@ const Pharmacy = () => {
|
||||
const [precheckResult, setPrecheckResult] = useState<any>(null);
|
||||
const [isProcessingDispense, setIsProcessingDispense] = useState(false);
|
||||
|
||||
const [dispensingId, setDispensingId] = useState<string | null>(null);
|
||||
|
||||
const resetDispenseModal = () => {
|
||||
setIsDispenseModalOpen(false);
|
||||
setDispenseStep(1);
|
||||
setOrderDraftId(null);
|
||||
setDispensingId(null);
|
||||
setPatientName('');
|
||||
setSelectedItems([]);
|
||||
setPrecheckResult(null);
|
||||
@@ -140,11 +143,12 @@ const Pharmacy = () => {
|
||||
if (!orderDraftId) return;
|
||||
try {
|
||||
setIsProcessingDispense(true);
|
||||
await dispenseOrder(orderDraftId, {
|
||||
const res = await dispenseOrder(orderDraftId, {
|
||||
subject_ref: patientName,
|
||||
prescriber_name: prescriberName,
|
||||
payment_method: 'cash'
|
||||
});
|
||||
setDispensingId(res.dispensing_id);
|
||||
setDispenseStep(3);
|
||||
loadData(); // refresh inventory
|
||||
} catch (e: any) {
|
||||
@@ -172,20 +176,28 @@ const Pharmacy = () => {
|
||||
};
|
||||
|
||||
const mapAndSetData = (drugs: any[]) => {
|
||||
const mappedData: PharmacyItem[] = drugs.map((drug: any) => ({
|
||||
id: drug.product_id,
|
||||
medicine: `${drug.brand_name} ${drug.strength || ''}`.trim(),
|
||||
subtitle: drug.generic_name || '',
|
||||
badge: drug.schedule || '',
|
||||
stock: parseInt(drug.total_stock) || 0,
|
||||
rawPrice: parseFloat(drug.purchase_price) || parseFloat(drug.mrp) || 0,
|
||||
price: new Intl.NumberFormat('en-IN', { style: 'currency', currency: 'INR' }).format(parseFloat(drug.purchase_price) || parseFloat(drug.mrp) || 0),
|
||||
manufacture: drug.manufacturer || 'Unknown',
|
||||
type: drug.form || 'Unknown'
|
||||
}));
|
||||
const mappedData: PharmacyItem[] = drugs.map((drug: any) => {
|
||||
const mrp = parseFloat(drug.mrp) || 0;
|
||||
const packSize = parseInt(drug.pack_size, 10) || 1;
|
||||
const unitPrice = mrp / packSize;
|
||||
|
||||
return {
|
||||
id: drug.product_id,
|
||||
medicine: `${drug.brand_name} ${drug.strength || ''}`.trim(),
|
||||
subtitle: drug.generic_name || '',
|
||||
badge: drug.schedule || '',
|
||||
stock: parseInt(drug.total_stock) || 0,
|
||||
rawPrice: unitPrice,
|
||||
price: new Intl.NumberFormat('en-IN', { style: 'currency', currency: 'INR' }).format(unitPrice),
|
||||
manufacture: drug.manufacturer || 'Unknown',
|
||||
type: drug.form || 'Unknown'
|
||||
};
|
||||
});
|
||||
setInventory(mappedData);
|
||||
};
|
||||
|
||||
const [hospitalPatients, setHospitalPatients] = useState<any[]>([]);
|
||||
|
||||
const loadData = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
@@ -194,10 +206,17 @@ const Pharmacy = () => {
|
||||
await authenticate();
|
||||
}
|
||||
|
||||
const data = await getDrugs();
|
||||
const [data, patients] = await Promise.all([
|
||||
getDrugs(),
|
||||
fetchSyncedPatients().catch(() => []) // fail gracefully if hospital api is down
|
||||
]);
|
||||
|
||||
if (data && data.drugs) {
|
||||
mapAndSetData(data.drugs);
|
||||
}
|
||||
if (patients) {
|
||||
setHospitalPatients(patients);
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.response && error.response.status === 401) {
|
||||
try {
|
||||
@@ -206,12 +225,14 @@ const Pharmacy = () => {
|
||||
if (retryData && retryData.drugs) {
|
||||
mapAndSetData(retryData.drugs);
|
||||
}
|
||||
const retryPatients = await fetchSyncedPatients().catch(() => []);
|
||||
setHospitalPatients(retryPatients);
|
||||
} catch (retryError) {
|
||||
console.error("Failed to fetch drugs after retry:", retryError);
|
||||
console.error("Failed to fetch data after retry:", retryError);
|
||||
setError("Authentication failed. Please try logging in again.");
|
||||
}
|
||||
} else {
|
||||
console.error("Failed to fetch drugs:", error);
|
||||
console.error("Failed to fetch data:", error);
|
||||
setError("Failed to connect to the server. Please try again later.");
|
||||
}
|
||||
} finally {
|
||||
@@ -221,6 +242,12 @@ const Pharmacy = () => {
|
||||
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.get('action') === 'dispense') {
|
||||
setIsDispenseModalOpen(true);
|
||||
// Clean up URL after opening
|
||||
window.history.replaceState({}, '', '/pharmacy');
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Filter inventory based on search query
|
||||
@@ -299,7 +326,7 @@ const Pharmacy = () => {
|
||||
|
||||
{/* Action Bar */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4" style={{ marginBottom: '40px', marginTop: '16px' }}>
|
||||
<div className="relative w-full max-w-2xl group">
|
||||
<div className="relative w-full flex-1 max-w-none group">
|
||||
<div className="absolute inset-y-0 left-0 flex items-center pointer-events-none transition-colors text-slate-400 group-focus-within:text-blue-500" style={{ paddingLeft: '16px' }}>
|
||||
<Search size={20} strokeWidth={2} />
|
||||
</div>
|
||||
@@ -309,19 +336,11 @@ const Pharmacy = () => {
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder="Search medicines, OTC, or prescriptions..."
|
||||
className="w-full py-3 bg-slate-50 border border-slate-200 hover:border-slate-300 hover:bg-slate-100/50 rounded-xl focus:bg-white focus:outline-none focus:ring-4 focus:ring-blue-500/10 focus:border-blue-500 shadow-sm placeholder:text-slate-400 transition-all text-slate-800"
|
||||
style={{ paddingLeft: '44px', paddingRight: '16px', fontSize: '15px' }}
|
||||
style={{ height: '46px', paddingLeft: '44px', paddingRight: '16px', fontSize: '15px' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => setIsDispenseModalOpen(true)}
|
||||
className="flex items-center gap-2 bg-emerald-600 text-white px-5 py-2.5 rounded-xl font-semibold hover:bg-emerald-700 transition-colors shadow-sm whitespace-nowrap"
|
||||
>
|
||||
<Package size={18} strokeWidth={2.5} />
|
||||
Manual Dispense
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="w-full flex flex-col bg-white border border-slate-200 rounded-2xl shadow-sm relative" style={{ marginTop: '16px' }}>
|
||||
@@ -471,15 +490,21 @@ const Pharmacy = () => {
|
||||
<div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2" style={{ gap: '16px', marginBottom: '16px' }}>
|
||||
<div>
|
||||
<label className="block text-xs font-bold uppercase tracking-wider text-slate-500" style={{ marginBottom: '8px' }}>Patient Name</label>
|
||||
<label className="block text-xs font-bold uppercase tracking-wider text-slate-500" style={{ marginBottom: '8px' }}>Patient Name (from Hospital)</label>
|
||||
<input
|
||||
type="text"
|
||||
value={patientName}
|
||||
onChange={e => setPatientName(e.target.value)}
|
||||
list="patients-list"
|
||||
className="w-full bg-slate-50 border border-slate-200 rounded-xl focus:bg-white focus:outline-none focus:ring-4 focus:ring-blue-500/10 focus:border-blue-500"
|
||||
style={{ padding: '12px 16px' }}
|
||||
placeholder="e.g. John Doe"
|
||||
/>
|
||||
<datalist id="patients-list">
|
||||
{hospitalPatients.map((p, idx) => (
|
||||
<option key={idx} value={p.full_name}>{p.patient_code}</option>
|
||||
))}
|
||||
</datalist>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs font-bold uppercase tracking-wider text-slate-500" style={{ marginBottom: '8px' }}>Prescriber Name</label>
|
||||
@@ -597,13 +622,29 @@ const Pharmacy = () => {
|
||||
</div>
|
||||
<h3 className="font-bold text-emerald-800 text-xl" style={{ marginBottom: '8px' }}>Dispense Successful!</h3>
|
||||
<p className="text-emerald-600" style={{ marginBottom: '24px' }}>Stock has been deducted and logged.</p>
|
||||
<button
|
||||
onClick={resetDispenseModal}
|
||||
className="bg-slate-800 text-white font-semibold rounded-xl hover:bg-slate-900 transition-colors"
|
||||
style={{ padding: '10px 24px' }}
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
<div className="flex justify-center gap-4">
|
||||
<button
|
||||
onClick={async () => {
|
||||
try {
|
||||
await downloadInvoice(dispensingId || orderDraftId!);
|
||||
} catch (e) {
|
||||
alert("Failed to download invoice");
|
||||
console.error(e);
|
||||
}
|
||||
}}
|
||||
className="bg-blue-600 text-white font-semibold rounded-xl hover:bg-blue-700 transition-colors flex items-center gap-2"
|
||||
style={{ padding: '10px 24px' }}
|
||||
>
|
||||
Download Invoice
|
||||
</button>
|
||||
<button
|
||||
onClick={resetDispenseModal}
|
||||
className="bg-slate-800 text-white font-semibold rounded-xl hover:bg-slate-900 transition-colors"
|
||||
style={{ padding: '10px 24px' }}
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user