import { useState } from "react"; import { TrendingUp, DollarSign, Calendar, Search, Filter, Eye, Download, AlertCircle, User, FileText, Calculator, Target, Code, Stethoscope, } from "lucide-react"; import DatePicker from "react-datepicker"; import "react-datepicker/dist/react-datepicker.css"; interface CostRecommendation { id: string; patientId: string; patientName: string; age: number; gender: string; diagnosis: string; icdCode: string; procedureCode?: string; medicalRecordNumber: string; admissionDate: string; dischargeDate?: string; roomType: string; currentTreatment: string; estimatedCost: number; recommendedTreatment: string; recommendedCost: number; potentialSavings: number; riskLevel: "low" | "medium" | "high"; confidence: number; department: string; doctor: string; createdDate: string; status: "pending" | "approved" | "rejected" | "implemented"; medicalHistory?: string; allergies?: string; currentMedications?: string; vitalSigns?: { bloodPressure: string; heartRate: number; temperature: number; oxygenSaturation: number; }; labResults?: string; notes?: string; } interface CostStats { totalRecommendations: number; potentialSavings: number; implementedSavings: number; avgSavingsPerCase: number; approvalRate: number; } export function LegacyCostRecommendation() { const [searchTerm, setSearchTerm] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); const [riskFilter, setRiskFilter] = useState("all"); const formatDate = (dateString: string) => { const date = new Date(dateString); return date.toLocaleDateString("id-ID", { day: "numeric", month: "short", year: "numeric", hour: "2-digit", minute: "2-digit", }); }; const formatCurrency = (amount: number) => { return new Intl.NumberFormat("id-ID", { style: "currency", currency: "IDR", minimumFractionDigits: 0, }).format(amount); }; const getRiskColor = (risk: string) => { switch (risk) { case "low": return "bg-green-100 text-green-800"; case "medium": return "bg-yellow-100 text-yellow-800"; case "high": return "bg-red-100 text-red-800"; default: return "bg-gray-100 text-gray-800"; } }; const getStatusColor = (status: string) => { switch (status) { case "approved": return "bg-green-100 text-green-800"; case "pending": return "bg-yellow-100 text-yellow-800"; case "rejected": return "bg-red-100 text-red-800"; case "implemented": return "bg-blue-100 text-blue-800"; default: return "bg-gray-100 text-gray-800"; } }; const getStatusText = (status: string) => { switch (status) { case "approved": return "Disetujui"; case "pending": return "Menunggu"; case "rejected": return "Ditolak"; case "implemented": return "Diterapkan"; default: return status; } }; const getRiskText = (risk: string) => { switch (risk) { case "low": return "Rendah"; case "medium": return "Sedang"; case "high": return "Tinggi"; default: return risk; } }; // Sample cost recommendations data const [recommendations] = useState([ { id: "1", patientId: "P001", patientName: "Ahmad Santoso", age: 45, gender: "Laki-laki", diagnosis: "Hipertensi Esensial", icdCode: "I10", procedureCode: "99213", medicalRecordNumber: "MR2024001", admissionDate: "2024-01-10T08:00:00Z", dischargeDate: "2024-01-11T16:00:00Z", roomType: "Ruang Rawat Jalan", currentTreatment: "Amlodipine 10mg + Monitoring Harian", estimatedCost: 2500000, recommendedTreatment: "Amlodipine 5mg + Monitoring Mingguan", recommendedCost: 1800000, potentialSavings: 700000, riskLevel: "low", confidence: 92, department: "Poli Dalam", doctor: "Dr. Sarah Wijaya", createdDate: "2024-01-15T14:30:00Z", status: "pending", medicalHistory: "Riwayat hipertensi keluarga, tidak ada riwayat stroke", allergies: "Tidak ada alergi obat yang diketahui", currentMedications: "Amlodipine 10mg 1x1, Aspirin 80mg 1x1", vitalSigns: { bloodPressure: "140/90", heartRate: 78, temperature: 36.5, oxygenSaturation: 98, }, labResults: "Kolesterol Total: 220 mg/dl, HDL: 45 mg/dl, LDL: 140 mg/dl", notes: "Pasien menunjukkan respons baik dengan dosis rendah", }, { id: "2", patientId: "P002", patientName: "Siti Nurhaliza", age: 52, gender: "Perempuan", diagnosis: "Diabetes Mellitus Tipe 2", icdCode: "E11", procedureCode: "99214", medicalRecordNumber: "MR2024002", admissionDate: "2024-01-12T09:30:00Z", dischargeDate: "2024-01-14T14:00:00Z", roomType: "Ruang Rawat Inap Kelas II", currentTreatment: "Insulin + Metformin + Konsultasi Harian", estimatedCost: 4200000, recommendedTreatment: "Metformin + Diet Program + Konsultasi Mingguan", recommendedCost: 2800000, potentialSavings: 1400000, riskLevel: "medium", confidence: 87, department: "Poli Endokrin", doctor: "Dr. Rahman Hidayat", createdDate: "2024-01-15T10:15:00Z", status: "approved", medicalHistory: "DM Tipe 2 sejak 5 tahun, riwayat hipertensi", allergies: "Alergi sulfa", currentMedications: "Insulin Lantus 20 unit, Metformin 500mg 2x1", vitalSigns: { bloodPressure: "130/85", heartRate: 82, temperature: 36.8, oxygenSaturation: 96, }, labResults: "HbA1c: 8.2%, Glukosa Puasa: 180 mg/dl, Kreatinin: 1.1 mg/dl", notes: "Pasien cocok dengan program diet terstruktur", }, { id: "3", patientId: "P003", patientName: "Budi Prasetyo", age: 38, gender: "Laki-laki", diagnosis: "Pneumonia", icdCode: "J18.9", procedureCode: "99223", medicalRecordNumber: "MR2024003", admissionDate: "2024-01-11T15:00:00Z", dischargeDate: "2024-01-13T10:30:00Z", roomType: "Ruang Rawat Inap Kelas I", currentTreatment: "Antibiotik IV + Rawat Inap 7 hari", estimatedCost: 8500000, recommendedTreatment: "Antibiotik Oral + Rawat Jalan", recommendedCost: 3200000, potentialSavings: 5300000, riskLevel: "high", confidence: 78, department: "Poli Paru", doctor: "Dr. Linda Sari", createdDate: "2024-01-14T16:45:00Z", status: "implemented", medicalHistory: "Riwayat merokok 15 tahun, berhenti 2 tahun lalu", allergies: "Alergi penisilin", currentMedications: "Ceftriaxone 2g IV, Azithromycin 500mg", vitalSigns: { bloodPressure: "120/80", heartRate: 92, temperature: 38.2, oxygenSaturation: 94, }, labResults: "Leukosit: 12,000/μL, CRP: 15 mg/L, Procalcitonin: 0.8 ng/mL", notes: "Monitoring ketat diperlukan untuk rawat jalan", }, { id: "4", patientId: "P004", patientName: "Maria Lopez", age: 29, gender: "Perempuan", diagnosis: "Gastritis Akut", icdCode: "K29.1", procedureCode: "99212", medicalRecordNumber: "MR2024004", admissionDate: "2024-01-13T08:15:00Z", roomType: "Ruang Rawat Jalan", currentTreatment: "PPI + Antasida + Konsultasi Harian", estimatedCost: 1500000, recommendedTreatment: "H2 Blocker + Diet + Konsultasi Mingguan", recommendedCost: 800000, potentialSavings: 700000, riskLevel: "low", confidence: 94, department: "Poli Dalam", doctor: "Dr. Sarah Wijaya", createdDate: "2024-01-13T11:20:00Z", status: "rejected", medicalHistory: "Tidak ada riwayat penyakit kronis", allergies: "Alergi H2 blocker (ranitidin)", currentMedications: "Omeprazole 20mg 1x1, Antasida 3x1", vitalSigns: { bloodPressure: "110/70", heartRate: 75, temperature: 36.4, oxygenSaturation: 99, }, labResults: "Hemoglobin: 12.5 g/dl, H. pylori: Negatif", notes: "Pasien memiliki riwayat alergi terhadap H2 blocker", }, { id: "5", patientId: "P005", patientName: "Andi Kusuma", age: 67, gender: "Laki-laki", diagnosis: "Osteoarthritis", icdCode: "M15.9", procedureCode: "99215", medicalRecordNumber: "MR2024005", admissionDate: "2024-01-12T07:45:00Z", roomType: "Ruang Rawat Jalan", currentTreatment: "NSAID + Fisioterapi Intensif", estimatedCost: 3200000, recommendedTreatment: "Paracetamol + Fisioterapi Standar + Olahraga", recommendedCost: 1900000, potentialSavings: 1300000, riskLevel: "low", confidence: 89, department: "Ortopedi", doctor: "Dr. Kevin Tan", createdDate: "2024-01-12T09:30:00Z", status: "approved", medicalHistory: "Osteoarthritis bilateral knee sejak 10 tahun", allergies: "Tidak ada alergi obat yang diketahui", currentMedications: "Diclofenac 50mg 2x1, Glucosamine 500mg 2x1", vitalSigns: { bloodPressure: "135/85", heartRate: 68, temperature: 36.3, oxygenSaturation: 97, }, labResults: "Fungsi ginjal normal, tidak ada tanda inflamasi sistemik", notes: "Pasien menunjukkan respons baik dengan terapi konservatif", }, ]); // Calculate statistics const stats: CostStats = { totalRecommendations: recommendations.length, potentialSavings: recommendations.reduce( (sum, r) => sum + r.potentialSavings, 0 ), implementedSavings: recommendations .filter((r) => r.status === "implemented") .reduce((sum, r) => sum + r.potentialSavings, 0), avgSavingsPerCase: recommendations.reduce((sum, r) => sum + r.potentialSavings, 0) / recommendations.length, approvalRate: (recommendations.filter( (r) => r.status === "approved" || r.status === "implemented" ).length / recommendations.length) * 100, }; // Filter recommendations const filteredRecommendations = recommendations.filter((rec) => { const matchesSearch = rec.patientName.toLowerCase().includes(searchTerm.toLowerCase()) || rec.diagnosis.toLowerCase().includes(searchTerm.toLowerCase()) || rec.icdCode.toLowerCase().includes(searchTerm.toLowerCase()) || rec.doctor.toLowerCase().includes(searchTerm.toLowerCase()); const matchesStatus = statusFilter === "all" || rec.status === statusFilter; const matchesRisk = riskFilter === "all" || rec.riskLevel === riskFilter; return matchesSearch && matchesStatus && matchesRisk; }); return (
{/* Header */}

Cost Recommendation

Rekomendasi optimalisasi biaya perawatan berdasarkan analisis medis

{/* Statistics Cards */}

Total Rekomendasi

{stats.totalRecommendations}

Potensi Penghematan

{formatCurrency(stats.potentialSavings)}

Penghematan Terealisasi

{formatCurrency(stats.implementedSavings)}

Tingkat Persetujuan

{Math.round(stats.approvalRate)}%

{/* Filters and Search */}
setSearchTerm(e.target.value)} className="pl-9 pr-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-green-500 focus:border-transparent w-80" />
{/* Recommendations Table */}

Rekomendasi Optimalisasi Biaya

{filteredRecommendations.map((rec) => ( ))}
Pasien & Medical Record Medical Code Treatment Saat Ini Rekomendasi Potensi Penghematan Status & Risiko Tanggal Aksi
{rec.patientName}
{rec.diagnosis}
MR: {rec.medicalRecordNumber}
{rec.age} th, {rec.gender} | {rec.roomType}
ICD: {rec.icdCode}
{rec.procedureCode && (
CPT: {rec.procedureCode}
)}
Dept: {rec.department}
{rec.currentTreatment}
Estimasi: {formatCurrency(rec.estimatedCost)}
{rec.recommendedTreatment}
Biaya: {formatCurrency(rec.recommendedCost)}
{formatCurrency(rec.potentialSavings)}
{Math.round( (rec.potentialSavings / rec.estimatedCost) * 100 )} % penghematan
{getStatusText(rec.status)}
{getRiskText(rec.riskLevel)}
{rec.confidence}%
{formatDate(rec.createdDate)}
Admission: {formatDate(rec.admissionDate)}
{rec.dischargeDate && (
Discharge: {formatDate(rec.dischargeDate)}
)}
Dr. {rec.doctor.replace("Dr. ", "")}
{/* Empty State */} {filteredRecommendations.length === 0 && (

Tidak ada rekomendasi ditemukan

Tidak ada rekomendasi yang sesuai dengan kriteria pencarian.

)}
); } interface ICDRecommendation { code: string; description: string; confidence: number; reasoning: string; } async function mockRecommendICD( diagnosis: string, procedure: string ): Promise { const lowerDx = diagnosis.toLowerCase(); const lowerProc = procedure.toLowerCase(); const suggestions: ICDRecommendation[] = []; if (lowerDx.includes("hipertensi") || lowerDx.includes("hypertension")) { suggestions.push({ code: "I10", description: "Essential (primary) hypertension", confidence: 92, reasoning: "Kata kunci 'hipertensi' terdeteksi pada diagnosis klinis.", }); } if (lowerDx.includes("diabetes") || lowerDx.includes("dm")) { suggestions.push({ code: "E11", description: "Type 2 diabetes mellitus", confidence: 88, reasoning: "Terdapat indikasi DM tipe 2 pada gambaran diagnosis.", }); } if (lowerDx.includes("pneumonia") || lowerDx.includes("infeksi paru")) { suggestions.push({ code: "J18.9", description: "Pneumonia, unspecified organism", confidence: 82, reasoning: "Gejala dan istilah yang mengarah ke pneumonia terdeteksi.", }); } if (lowerDx.includes("gastritis") || lowerDx.includes("dispepsia")) { suggestions.push({ code: "K29.1", description: "Acute gastritis", confidence: 80, reasoning: "Istilah gastritis muncul pada diagnosis.", }); } if (lowerDx.includes("osteoarthritis") || lowerDx.includes("nyeri sendi")) { suggestions.push({ code: "M15.9", description: "Polyosteoarthritis, unspecified", confidence: 76, reasoning: "Keluhan terkait osteoartritis/nyeri sendi teridentifikasi.", }); } if ( lowerProc.includes("insulin") && !suggestions.find((s) => s.code.startsWith("E1")) ) { suggestions.push({ code: "E10-E14", description: "Diabetes mellitus (range, sesuaikan subkategori)", confidence: 70, reasoning: "Penggunaan insulin mengarah pada kategori diabetes mellitus.", }); } if ( lowerProc.includes("antibiotik") && !suggestions.find((s) => s.code.startsWith("J")) ) { suggestions.push({ code: "J15-J18", description: "Bacterial/unspecified pneumonia (range)", confidence: 68, reasoning: "Terapi antibiotik berkolerasi dengan infeksi respiratori.", }); } if (suggestions.length === 0) { suggestions.push( { code: "R69", description: "Illness, unspecified", confidence: 55, reasoning: "Tidak ada kata kunci spesifik terdeteksi. Perlu klarifikasi klinis.", }, { code: "Z00.0", description: "General medical examination", confidence: 48, reasoning: "Pertimbangkan evaluasi umum jika kasus bersifat screening.", } ); } suggestions.sort((a, b) => b.confidence - a.confidence); await new Promise((r) => setTimeout(r, 600)); return suggestions.slice(0, 6); } export default function CostRecommendation() { const [diagnosis, setDiagnosis] = useState(""); const [procedure, setProcedure] = useState(""); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [results, setResults] = useState([]); const [visitType, setVisitType] = useState("Kontrol Rutin"); const [lastVisitDate, setLastVisitDate] = useState( undefined ); const [currentVisitDate, setCurrentVisitDate] = useState( new Date() ); 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, }, ]; 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 ); }; const bpjsRecommendations = results .map((r) => ({ icdCode: r.code, mapping: findBpjsMappingForICD(r.code) })) .filter((x) => x.mapping !== null) as { icdCode: string; mapping: BpjsMapping; }[]; const getDaysBetween = (a: Date | undefined, b: Date | undefined) => { try { if (!a || !b) return null; const diff = Math.floor( (b.getTime() - a.getTime()) / (1000 * 60 * 60 * 24) ); return isNaN(diff) ? null : Math.max(diff, 0); } catch { return null; } }; 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 foundFollow = followUpKeywords.some((k) => text.includes(k)); const foundAcute = acuteKeywords.some((k) => text.includes(k)); const reasons: string[] = []; let inconsistent = false; if (t.includes("kontrol") && foundAcute) { inconsistent = true; 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'." ); } return { inconsistent, reasons }; }; const ICD_INTERVAL_RULES: Record = { 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" ) { return ICD_INTERVAL_RULES.E11; } return undefined; }; 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 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 ); const formatCurrency = (amount: number) => { return new Intl.NumberFormat("id-ID", { style: "currency", currency: "IDR", minimumFractionDigits: 0, }).format(amount); }; const handleGenerate = async () => { setIsLoading(true); setError(null); try { const recs = await mockRecommendICD(diagnosis, procedure); setResults(recs); } catch { setError("Gagal membuat rekomendasi. Coba lagi."); } finally { setIsLoading(false); } }; const exportJSON = () => { 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; a.download = "icd_recommendations.json"; a.click(); URL.revokeObjectURL(url); }; const copyCode = async (code: string) => { try { await navigator.clipboard.writeText(code); } catch { // ignore clipboard errors in non-secure contexts } }; return (

Asisten Rekomendasi ICD untuk Dokter

Masukkan ringkasan diagnosa dan prosedur. Sistem akan mengusulkan kode ICD yang relevan beserta tingkat keyakinan.