Update frontend code

This commit is contained in:
2026-09-02 18:23:05 +05:30
parent 8c25b84616
commit d46f78bbd0
25 changed files with 1213 additions and 279 deletions
+76 -35
View File
@@ -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>