partial update revision simplify feature
This commit is contained in:
@@ -687,7 +687,10 @@ interface ICDRecommendation {
|
||||
reasoning: string;
|
||||
}
|
||||
|
||||
async function mockRecommendICD(diagnosis: string, procedure: string): Promise<ICDRecommendation[]> {
|
||||
async function mockRecommendICD(
|
||||
diagnosis: string,
|
||||
procedure: string
|
||||
): Promise<ICDRecommendation[]> {
|
||||
const lowerDx = diagnosis.toLowerCase();
|
||||
const lowerProc = procedure.toLowerCase();
|
||||
|
||||
@@ -738,7 +741,10 @@ async function mockRecommendICD(diagnosis: string, procedure: string): Promise<I
|
||||
});
|
||||
}
|
||||
|
||||
if (lowerProc.includes("insulin") && !suggestions.find((s) => s.code.startsWith("E1"))) {
|
||||
if (
|
||||
lowerProc.includes("insulin") &&
|
||||
!suggestions.find((s) => s.code.startsWith("E1"))
|
||||
) {
|
||||
suggestions.push({
|
||||
code: "E10-E14",
|
||||
description: "Diabetes mellitus (range, sesuaikan subkategori)",
|
||||
@@ -747,7 +753,10 @@ async function mockRecommendICD(diagnosis: string, procedure: string): Promise<I
|
||||
});
|
||||
}
|
||||
|
||||
if (lowerProc.includes("antibiotik") && !suggestions.find((s) => s.code.startsWith("J"))) {
|
||||
if (
|
||||
lowerProc.includes("antibiotik") &&
|
||||
!suggestions.find((s) => s.code.startsWith("J"))
|
||||
) {
|
||||
suggestions.push({
|
||||
code: "J15-J18",
|
||||
description: "Bacterial/unspecified pneumonia (range)",
|
||||
@@ -762,7 +771,8 @@ async function mockRecommendICD(diagnosis: string, procedure: string): Promise<I
|
||||
code: "R69",
|
||||
description: "Illness, unspecified",
|
||||
confidence: 55,
|
||||
reasoning: "Tidak ada kata kunci spesifik terdeteksi. Perlu klarifikasi klinis.",
|
||||
reasoning:
|
||||
"Tidak ada kata kunci spesifik terdeteksi. Perlu klarifikasi klinis.",
|
||||
},
|
||||
{
|
||||
code: "Z00.0",
|
||||
@@ -786,40 +796,74 @@ export default function CostRecommendation() {
|
||||
const [results, setResults] = useState<ICDRecommendation[]>([]);
|
||||
const [visitType, setVisitType] = useState("Kontrol Rutin");
|
||||
const [lastVisitDate, setLastVisitDate] = useState("");
|
||||
const [currentVisitDate, setCurrentVisitDate] = useState(() => new Date().toISOString().slice(0, 10));
|
||||
|
||||
const [currentVisitDate, setCurrentVisitDate] = useState(() =>
|
||||
new Date().toISOString().slice(0, 10)
|
||||
);
|
||||
|
||||
type BpjsMapping = {
|
||||
icdCode: string;
|
||||
bpjsCode: string;
|
||||
description: string;
|
||||
estTariff: number;
|
||||
};
|
||||
|
||||
|
||||
const BPJS_CODE_MAPPINGS: BpjsMapping[] = [
|
||||
{ icdCode: "I10", bpjsCode: "BPJS-I10-01", description: "Hipertensi Esensial", estTariff: 850000 },
|
||||
{ icdCode: "E11", bpjsCode: "BPJS-E11-02", description: "Diabetes Mellitus Tipe 2", estTariff: 1200000 },
|
||||
{ icdCode: "J18.9", bpjsCode: "BPJS-J18-03", description: "Pneumonia", estTariff: 2500000 },
|
||||
{ icdCode: "K29.1", bpjsCode: "BPJS-K29-04", description: "Gastritis Akut", estTariff: 650000 },
|
||||
{ icdCode: "M15.9", bpjsCode: "BPJS-M15-09", description: "Osteoartritis", estTariff: 900000 },
|
||||
{
|
||||
icdCode: "I10",
|
||||
bpjsCode: "BPJS-I10-01",
|
||||
description: "Hipertensi Esensial",
|
||||
estTariff: 850000,
|
||||
},
|
||||
{
|
||||
icdCode: "E11",
|
||||
bpjsCode: "BPJS-E11-02",
|
||||
description: "Diabetes Mellitus Tipe 2",
|
||||
estTariff: 1200000,
|
||||
},
|
||||
{
|
||||
icdCode: "J18.9",
|
||||
bpjsCode: "BPJS-J18-03",
|
||||
description: "Pneumonia",
|
||||
estTariff: 2500000,
|
||||
},
|
||||
{
|
||||
icdCode: "K29.1",
|
||||
bpjsCode: "BPJS-K29-04",
|
||||
description: "Gastritis Akut",
|
||||
estTariff: 650000,
|
||||
},
|
||||
{
|
||||
icdCode: "M15.9",
|
||||
bpjsCode: "BPJS-M15-09",
|
||||
description: "Osteoartritis",
|
||||
estTariff: 900000,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
const findBpjsMappingForICD = (code: string): BpjsMapping | null => {
|
||||
if (!code) return null;
|
||||
const normalized = code === "E10-E14" ? "E11" : code;
|
||||
const exact = BPJS_CODE_MAPPINGS.find((m) => m.icdCode === normalized);
|
||||
if (exact) return exact;
|
||||
return BPJS_CODE_MAPPINGS.find((m) => normalized.startsWith(m.icdCode)) || null;
|
||||
return (
|
||||
BPJS_CODE_MAPPINGS.find((m) => normalized.startsWith(m.icdCode)) || null
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const bpjsRecommendations = results
|
||||
.map((r) => ({ icdCode: r.code, mapping: findBpjsMappingForICD(r.code) }))
|
||||
.filter((x) => x.mapping !== null) as { icdCode: string; mapping: BpjsMapping }[];
|
||||
.filter((x) => x.mapping !== null) as {
|
||||
icdCode: string;
|
||||
mapping: BpjsMapping;
|
||||
}[];
|
||||
|
||||
const getDaysBetween = (a: string, b: string) => {
|
||||
try {
|
||||
const da = new Date(a + "T00:00:00");
|
||||
const db = new Date(b + "T00:00:00");
|
||||
const diff = Math.floor((db.getTime() - da.getTime()) / (1000 * 60 * 60 * 24));
|
||||
const diff = Math.floor(
|
||||
(db.getTime() - da.getTime()) / (1000 * 60 * 60 * 24)
|
||||
);
|
||||
return isNaN(diff) ? null : Math.max(diff, 0);
|
||||
} catch {
|
||||
return null;
|
||||
@@ -829,8 +873,21 @@ export default function CostRecommendation() {
|
||||
const checkReasonConsistency = (type: string, dx: string, proc: string) => {
|
||||
const t = type.toLowerCase();
|
||||
const text = `${dx} ${proc}`.toLowerCase();
|
||||
const followUpKeywords = ["kontrol", "follow up", "lanjutan", "kontrol ulang", "monitoring"];
|
||||
const acuteKeywords = ["akut", "baru", "nyeri hebat", "mendadak", "demam tinggi", "perburukan"];
|
||||
const followUpKeywords = [
|
||||
"kontrol",
|
||||
"follow up",
|
||||
"lanjutan",
|
||||
"kontrol ulang",
|
||||
"monitoring",
|
||||
];
|
||||
const acuteKeywords = [
|
||||
"akut",
|
||||
"baru",
|
||||
"nyeri hebat",
|
||||
"mendadak",
|
||||
"demam tinggi",
|
||||
"perburukan",
|
||||
];
|
||||
const foundFollow = followUpKeywords.some((k) => text.includes(k));
|
||||
const foundAcute = acuteKeywords.some((k) => text.includes(k));
|
||||
const reasons: string[] = [];
|
||||
@@ -838,38 +895,63 @@ export default function CostRecommendation() {
|
||||
|
||||
if (t.includes("kontrol") && foundAcute) {
|
||||
inconsistent = true;
|
||||
reasons.push("Terdapat indikasi kondisi akut pada keterangan, namun tipe kunjungan adalah kontrol.");
|
||||
reasons.push(
|
||||
"Terdapat indikasi kondisi akut pada keterangan, namun tipe kunjungan adalah kontrol."
|
||||
);
|
||||
}
|
||||
if (t.includes("keluhan baru") && foundFollow) {
|
||||
inconsistent = true;
|
||||
reasons.push("Terdapat indikasi tindak lanjut/kontrol pada keterangan, namun dipilih 'Keluhan Baru'.");
|
||||
reasons.push(
|
||||
"Terdapat indikasi tindak lanjut/kontrol pada keterangan, namun dipilih 'Keluhan Baru'."
|
||||
);
|
||||
}
|
||||
return { inconsistent, reasons };
|
||||
};
|
||||
|
||||
const ICD_INTERVAL_RULES: Record<string, { minDays: number; note: string }> = {
|
||||
"I10": { minDays: 14, note: "Kontrol hipertensi" },
|
||||
"E11": { minDays: 30, note: "Kontrol DM tipe 2" },
|
||||
"J18.9": { minDays: 14, note: "Pneumonia" },
|
||||
"K29.1": { minDays: 14, note: "Gastritis akut" },
|
||||
"M15.9": { minDays: 30, note: "Osteoartritis" },
|
||||
};
|
||||
const ICD_INTERVAL_RULES: Record<string, { minDays: number; note: string }> =
|
||||
{
|
||||
I10: { minDays: 14, note: "Kontrol hipertensi" },
|
||||
E11: { minDays: 30, note: "Kontrol DM tipe 2" },
|
||||
"J18.9": { minDays: 14, note: "Pneumonia" },
|
||||
"K29.1": { minDays: 14, note: "Gastritis akut" },
|
||||
"M15.9": { minDays: 30, note: "Osteoartritis" },
|
||||
};
|
||||
|
||||
const getIntervalRuleForCode = (code: string) => {
|
||||
if (ICD_INTERVAL_RULES[code as keyof typeof ICD_INTERVAL_RULES]) return ICD_INTERVAL_RULES[code as keyof typeof ICD_INTERVAL_RULES];
|
||||
if (code.startsWith("E10") || code.startsWith("E11") || code.startsWith("E12") || code.startsWith("E13") || code.startsWith("E14") || code === "E10-E14") {
|
||||
if (ICD_INTERVAL_RULES[code as keyof typeof ICD_INTERVAL_RULES])
|
||||
return ICD_INTERVAL_RULES[code as keyof typeof ICD_INTERVAL_RULES];
|
||||
if (
|
||||
code.startsWith("E10") ||
|
||||
code.startsWith("E11") ||
|
||||
code.startsWith("E12") ||
|
||||
code.startsWith("E13") ||
|
||||
code.startsWith("E14") ||
|
||||
code === "E10-E14"
|
||||
) {
|
||||
return ICD_INTERVAL_RULES.E11;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const daysSinceLast = lastVisitDate && currentVisitDate ? getDaysBetween(lastVisitDate, currentVisitDate) : null;
|
||||
const daysSinceLast =
|
||||
lastVisitDate && currentVisitDate
|
||||
? getDaysBetween(lastVisitDate, currentVisitDate)
|
||||
: null;
|
||||
const consistency = checkReasonConsistency(visitType, diagnosis, procedure);
|
||||
const showInconsistentControlWarning = visitType.toLowerCase().includes("kontrol") && daysSinceLast !== null && daysSinceLast < 30 && consistency.inconsistent;
|
||||
const showInconsistentControlWarning =
|
||||
visitType.toLowerCase().includes("kontrol") &&
|
||||
daysSinceLast !== null &&
|
||||
daysSinceLast < 30 &&
|
||||
consistency.inconsistent;
|
||||
const overclaimItems = results
|
||||
.map((r) => ({ rec: r, rule: getIntervalRuleForCode(r.code) }))
|
||||
.filter((x) => x.rule && daysSinceLast !== null && (daysSinceLast as number) < (x.rule as { minDays: number }).minDays);
|
||||
|
||||
.filter(
|
||||
(x) =>
|
||||
x.rule &&
|
||||
daysSinceLast !== null &&
|
||||
(daysSinceLast as number) < (x.rule as { minDays: number }).minDays
|
||||
);
|
||||
|
||||
const formatCurrency = (amount: number) => {
|
||||
return new Intl.NumberFormat("id-ID", {
|
||||
style: "currency",
|
||||
@@ -884,7 +966,7 @@ export default function CostRecommendation() {
|
||||
try {
|
||||
const recs = await mockRecommendICD(diagnosis, procedure);
|
||||
setResults(recs);
|
||||
} catch (e) {
|
||||
} catch {
|
||||
setError("Gagal membuat rekomendasi. Coba lagi.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@@ -892,7 +974,9 @@ export default function CostRecommendation() {
|
||||
};
|
||||
|
||||
const exportJSON = () => {
|
||||
const blob = new Blob([JSON.stringify(results, null, 2)], { type: "application/json" });
|
||||
const blob = new Blob([JSON.stringify(results, null, 2)], {
|
||||
type: "application/json",
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
@@ -904,24 +988,30 @@ export default function CostRecommendation() {
|
||||
const copyCode = async (code: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(code);
|
||||
} catch {}
|
||||
} catch {
|
||||
// ignore clipboard errors in non-secure contexts
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="max-w-5xl mx-auto">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-2xl font-bold text-gray-900">Asisten Rekomendasi ICD untuk Dokter</h1>
|
||||
<h1 className="text-2xl font-bold text-gray-900">
|
||||
Asisten Rekomendasi ICD untuk Dokter
|
||||
</h1>
|
||||
<p className="text-gray-600 mt-1">
|
||||
Masukkan ringkasan diagnosa dan prosedur. Sistem akan mengusulkan kode ICD yang relevan beserta tingkat keyakinan.
|
||||
Masukkan ringkasan diagnosa dan prosedur. Sistem akan mengusulkan
|
||||
kode ICD yang relevan beserta tingkat keyakinan.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-6 rounded-lg shadow-sm border mb-6">
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1 flex items-center">
|
||||
<FileText className="h-4 w-4 mr-2 text-blue-600" /> Diagnosa Klinis
|
||||
<label className="text-sm font-medium text-gray-700 mb-1 flex items-center">
|
||||
<FileText className="h-4 w-4 mr-2 text-blue-600" /> Diagnosa
|
||||
Klinis
|
||||
</label>
|
||||
<textarea
|
||||
value={diagnosis}
|
||||
@@ -933,7 +1023,9 @@ export default function CostRecommendation() {
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Tipe Kunjungan</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Tipe Kunjungan
|
||||
</label>
|
||||
<select
|
||||
value={visitType}
|
||||
onChange={(e) => setVisitType(e.target.value)}
|
||||
@@ -945,7 +1037,9 @@ export default function CostRecommendation() {
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Tanggal Kunjungan Terakhir</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Tanggal Kunjungan Terakhir
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
value={lastVisitDate}
|
||||
@@ -954,7 +1048,9 @@ export default function CostRecommendation() {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Tanggal Kunjungan Saat Ini</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Tanggal Kunjungan Saat Ini
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
value={currentVisitDate}
|
||||
@@ -964,8 +1060,9 @@ export default function CostRecommendation() {
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1 flex items-center">
|
||||
<Stethoscope className="h-4 w-4 mr-2 text-green-600" /> Prosedur/Tatalaksana Utama
|
||||
<label className="text-sm font-medium text-gray-700 mb-1 flex items-center">
|
||||
<Stethoscope className="h-4 w-4 mr-2 text-green-600" />{" "}
|
||||
Prosedur/Tatalaksana Utama
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -982,7 +1079,9 @@ export default function CostRecommendation() {
|
||||
className="btn-primary flex items-center space-x-2 disabled:opacity-60"
|
||||
>
|
||||
<Search className="h-4 w-4" />
|
||||
<span>{isLoading ? "Menghasilkan..." : "Buat Rekomendasi"}</span>
|
||||
<span>
|
||||
{isLoading ? "Menghasilkan..." : "Buat Rekomendasi"}
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={exportJSON}
|
||||
@@ -993,13 +1092,12 @@ export default function CostRecommendation() {
|
||||
<span>Export JSON</span>
|
||||
</button>
|
||||
</div>
|
||||
{error && (
|
||||
<div className="text-sm text-red-600">{error}</div>
|
||||
)}
|
||||
{error && <div className="text-sm text-red-600">{error}</div>}
|
||||
{/* Global warnings */}
|
||||
{showInconsistentControlWarning && (
|
||||
<div className="mt-2 rounded-md border border-yellow-300 bg-yellow-50 p-3 text-sm text-yellow-800">
|
||||
Kunjungan kontrol < 30 hari dan alasan terdeteksi tidak konsisten dengan tipe kunjungan.
|
||||
Kunjungan kontrol < 30 hari dan alasan terdeteksi tidak
|
||||
konsisten dengan tipe kunjungan.
|
||||
{consistency.reasons.length > 0 && (
|
||||
<ul className="list-disc pl-5 mt-1">
|
||||
{consistency.reasons.map((r, i) => (
|
||||
@@ -1014,32 +1112,49 @@ export default function CostRecommendation() {
|
||||
|
||||
<div className="bg-white rounded-lg shadow-sm border overflow-hidden">
|
||||
<div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
|
||||
<h3 className="text-lg font-medium text-gray-900">Rekomendasi Kode ICD</h3>
|
||||
<h3 className="text-lg font-medium text-gray-900">
|
||||
Rekomendasi Kode ICD
|
||||
</h3>
|
||||
<div className="text-xs text-gray-500">AI Based</div>
|
||||
</div>
|
||||
|
||||
{/* Overclaim/interval banner */}
|
||||
{overclaimItems.length > 0 && (
|
||||
<div className="mx-6 my-4 rounded-md border border-red-300 bg-red-50 p-3 text-sm text-red-800">
|
||||
Peringatan interval: Ditemukan potensi klaim dini pada {overclaimItems.length} kode.
|
||||
Peringatan interval: Ditemukan potensi klaim dini pada{" "}
|
||||
{overclaimItems.length} kode.
|
||||
{daysSinceLast !== null && (
|
||||
<div className="mt-1 text-xs text-red-700">Jarak kunjungan: {daysSinceLast} hari.</div>
|
||||
<div className="mt-1 text-xs text-red-700">
|
||||
Jarak kunjungan: {daysSinceLast} hari.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isLoading ? (
|
||||
<div className="p-10 text-center text-gray-600">Sedang menganalisis...</div>
|
||||
<div className="p-10 text-center text-gray-600">
|
||||
Sedang menganalisis...
|
||||
</div>
|
||||
) : results.length > 0 ? (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Kode ICD</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Deskripsi</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Confidence</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Alasan</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Aksi</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Kode ICD
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Deskripsi
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Confidence
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Alasan
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Aksi
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
@@ -1047,34 +1162,52 @@ export default function CostRecommendation() {
|
||||
<tr key={r.code} className="hover:bg-gray-50">
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="flex items-center text-sm font-medium text-gray-900">
|
||||
<Code className="h-4 w-4 mr-2 text-blue-600" /> {r.code}
|
||||
<Code className="h-4 w-4 mr-2 text-blue-600" />{" "}
|
||||
{r.code}
|
||||
</div>
|
||||
{/* Per-item overclaim label */}
|
||||
{(() => {
|
||||
const rule = getIntervalRuleForCode(r.code);
|
||||
const isOverclaim = rule && daysSinceLast !== null && (daysSinceLast as number) < (rule as { minDays: number }).minDays;
|
||||
const isOverclaim =
|
||||
rule &&
|
||||
daysSinceLast !== null &&
|
||||
(daysSinceLast as number) <
|
||||
(rule as { minDays: number }).minDays;
|
||||
if (!isOverclaim) return null;
|
||||
return (
|
||||
<div className="mt-1 inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-red-100 text-red-800">
|
||||
Interval < {String((rule as { minDays: number }).minDays)} hari ({String((rule as { note: string }).note)})
|
||||
Interval <{" "}
|
||||
{String((rule as { minDays: number }).minDays)}{" "}
|
||||
hari ({String((rule as { note: string }).note)})
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<div className="text-sm text-gray-900">{r.description}</div>
|
||||
<div className="text-sm text-gray-900">
|
||||
{r.description}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 w-48">
|
||||
<div className="text-sm text-gray-700 mb-1">{r.confidence}%</div>
|
||||
<div className="text-sm text-gray-700 mb-1">
|
||||
{r.confidence}%
|
||||
</div>
|
||||
<div className="w-full bg-gray-100 rounded-full h-2">
|
||||
<div
|
||||
className="bg-green-500 h-2 rounded-full"
|
||||
style={{ width: `${Math.max(0, Math.min(100, r.confidence))}%` }}
|
||||
style={{
|
||||
width: `${Math.max(
|
||||
0,
|
||||
Math.min(100, r.confidence)
|
||||
)}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<div className="text-sm text-gray-600">{r.reasoning}</div>
|
||||
<div className="text-sm text-gray-600">
|
||||
{r.reasoning}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm">
|
||||
<button
|
||||
@@ -1092,8 +1225,12 @@ export default function CostRecommendation() {
|
||||
) : (
|
||||
<div className="text-center py-12">
|
||||
<AlertCircle className="mx-auto h-12 w-12 text-gray-400" />
|
||||
<h3 className="mt-2 text-sm font-medium text-gray-900">Belum ada rekomendasi</h3>
|
||||
<p className="mt-1 text-sm text-gray-500">Isi diagnosa/prosedur lalu klik "Buat Rekomendasi".</p>
|
||||
<h3 className="mt-2 text-sm font-medium text-gray-900">
|
||||
Belum ada rekomendasi
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-gray-500">
|
||||
Isi diagnosa/prosedur lalu klik "Buat Rekomendasi".
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -1101,35 +1238,55 @@ export default function CostRecommendation() {
|
||||
{/* BPJS mapping and cost section */}
|
||||
<div className="bg-white rounded-lg shadow-sm border overflow-hidden mt-6">
|
||||
<div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
|
||||
<h3 className="text-lg font-medium text-gray-900">Rekomendasi Kode BPJS & Estimasi Biaya</h3>
|
||||
<h3 className="text-lg font-medium text-gray-900">
|
||||
Rekomendasi Kode BPJS & Estimasi Biaya
|
||||
</h3>
|
||||
<div className="text-xs text-gray-500">Mapping dari ICD</div>
|
||||
</div>
|
||||
{isLoading ? (
|
||||
<div className="p-10 text-center text-gray-600">Menyiapkan mapping BPJS...</div>
|
||||
<div className="p-10 text-center text-gray-600">
|
||||
Menyiapkan mapping BPJS...
|
||||
</div>
|
||||
) : results.length > 0 ? (
|
||||
bpjsRecommendations.length > 0 ? (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Kode ICD</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Kode BPJS</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Deskripsi</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Estimasi Tarif</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Kode ICD
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Kode BPJS
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Deskripsi
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Estimasi Tarif
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{bpjsRecommendations.map(({ icdCode, mapping }) => (
|
||||
<tr key={`${icdCode}-${mapping.bpjsCode}`} className="hover:bg-gray-50">
|
||||
<tr
|
||||
key={`${icdCode}-${mapping.bpjsCode}`}
|
||||
className="hover:bg-gray-50"
|
||||
>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="flex items-center text-sm font-medium text-gray-900">
|
||||
<Code className="h-4 w-4 mr-2 text-blue-600" /> {icdCode}
|
||||
<Code className="h-4 w-4 mr-2 text-blue-600" />{" "}
|
||||
{icdCode}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="text-sm text-gray-900">{mapping.bpjsCode}</div>
|
||||
<div className="text-sm text-gray-900">
|
||||
{mapping.bpjsCode}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 text-sm text-gray-900">
|
||||
{mapping.description}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-sm text-gray-900">{mapping.description}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
<div className="flex items-center">
|
||||
<DollarSign className="h-4 w-4 mr-1 text-green-600" />
|
||||
@@ -1144,15 +1301,24 @@ export default function CostRecommendation() {
|
||||
) : (
|
||||
<div className="text-center py-12">
|
||||
<AlertCircle className="mx-auto h-12 w-12 text-gray-400" />
|
||||
<h3 className="mt-2 text-sm font-medium text-gray-900">Belum ada mapping BPJS untuk rekomendasi ICD saat ini</h3>
|
||||
<p className="mt-1 text-sm text-gray-500">Coba perbarui diagnosa/prosedur untuk melihat rekomendasi BPJS.</p>
|
||||
<h3 className="mt-2 text-sm font-medium text-gray-900">
|
||||
Belum ada mapping BPJS untuk rekomendasi ICD saat ini
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-gray-500">
|
||||
Coba perbarui diagnosa/prosedur untuk melihat rekomendasi
|
||||
BPJS.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="text-center py-12">
|
||||
<AlertCircle className="mx-auto h-12 w-12 text-gray-400" />
|
||||
<h3 className="mt-2 text-sm font-medium text-gray-900">Belum ada rekomendasi</h3>
|
||||
<p className="mt-1 text-sm text-gray-500">Buat rekomendasi ICD terlebih dahulu.</p>
|
||||
<h3 className="mt-2 text-sm font-medium text-gray-900">
|
||||
Belum ada rekomendasi
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-gray-500">
|
||||
Buat rekomendasi ICD terlebih dahulu.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user