Add classification status, metrics, and UI updates
This commit is contained in:
parent
e639b0ac57
commit
44e69d13c5
|
|
@ -34,11 +34,20 @@ public function index()
|
|||
// Hitung Total Scan (sudah terfilter role)
|
||||
$totalScan = (clone $query)->count();
|
||||
|
||||
// Rata-rata akurasi dari yang berhasil
|
||||
$avgAccuracy = (clone $query)->where('status', 'Berhasil')->avg('confidence') ?? 0;
|
||||
|
||||
// Tingkat keberhasilan (Total Berhasil / Total Scan)
|
||||
$totalBerhasil = (clone $query)->where('status', 'Berhasil')->count();
|
||||
$successRate = $totalScan > 0 ? ($totalBerhasil / $totalScan) * 100 : 0;
|
||||
|
||||
return inertia('admin/Dashboard', [
|
||||
'user' => $user,
|
||||
'chartData' => $chartData,
|
||||
'recentHistory' => $recentHistory,
|
||||
'totalScan' => $totalScan,
|
||||
'avgAccuracy' => round($avgAccuracy, 2),
|
||||
'successRate' => round($successRate, 2),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -44,11 +44,15 @@ public function predict(Request $request)
|
|||
// 2. Jika sukses, simpan gambar dan hasil ke database
|
||||
$path = $image->store('classifications', 'public');
|
||||
|
||||
$confidence = (float) filter_var($data['confidence'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
|
||||
$status = $confidence >= 90 ? 'Berhasil' : 'Gagal';
|
||||
|
||||
Classification::create([
|
||||
'user_id' => auth()->id() ?? null,
|
||||
'image_path' => $path,
|
||||
'result' => $data['label'],
|
||||
'confidence' => (float) filter_var($data['confidence'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION),
|
||||
'confidence' => $confidence,
|
||||
'status' => $status,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ class Classification extends Model
|
|||
'user_id',
|
||||
'image_path',
|
||||
'result',
|
||||
'confidence'
|
||||
'confidence',
|
||||
'status'
|
||||
];
|
||||
|
||||
// Relasi ke User (Satu klasifikasi dimiliki satu User)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('classifications', function (Blueprint $table) {
|
||||
$table->string('status')->after('confidence')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('classifications', function (Blueprint $table) {
|
||||
$table->dropColumn('status');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -79,12 +79,33 @@
|
|||
</div>
|
||||
|
||||
<transition enter-active-class="transition duration-500 ease-out" enter-from-class="transform scale-95 opacity-0" enter-to-class="transform scale-100 opacity-100">
|
||||
<div v-if="predictionResult" class="mt-8 p-8 rounded-3xl bg-emerald-600 shadow-[0_0_50px_-12px_rgba(16,185,129,0.5)] text-center relative overflow-hidden">
|
||||
<div
|
||||
v-if="predictionResult"
|
||||
class="mt-8 p-8 rounded-3xl shadow-[0_0_50px_-12px_rgba(16,185,129,0.5)] text-center relative overflow-hidden transition-colors duration-500"
|
||||
:class="parseFloat(predictionResult.confidence.replace('%', '')) >= 90
|
||||
? 'bg-emerald-600 shadow-emerald-500/50'
|
||||
: 'bg-red-600 shadow-red-500/50'"
|
||||
>
|
||||
<div class="relative z-10">
|
||||
<p class="text-xs font-black text-emerald-100 uppercase tracking-[0.3em] mb-2">Hasil Klasifikasi AI</p>
|
||||
<h4 class="text-5xl font-black text-white italic mb-2 tracking-tighter">{{ predictionResult.label }}</h4>
|
||||
<div class="inline-block px-4 py-1.5 bg-black/20 rounded-full backdrop-blur-md">
|
||||
<span class="text-emerald-500 font-bold text-sm uppercase tracking-widest">Confidence: {{ predictionResult.confidence }}</span>
|
||||
<h4 class="text-5xl font-black text-white italic mb-4 tracking-tighter">{{ predictionResult.label }}</h4>
|
||||
|
||||
<div class="flex flex-wrap justify-center gap-3">
|
||||
<div class="px-4 py-1.5 bg-black/20 rounded-full backdrop-blur-md border border-white/10">
|
||||
<span class="text-emerald-400 font-bold text-xs uppercase tracking-widest">Confidence: {{ predictionResult.confidence }}</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="px-4 py-1.5 rounded-full backdrop-blur-md border flex items-center gap-2"
|
||||
:class="parseFloat(predictionResult.confidence.replace('%', '')) >= 90
|
||||
? 'bg-emerald-500/20 border-emerald-500/50 text-emerald-200'
|
||||
: 'bg-red-500/20 border-red-500/50 text-red-200'"
|
||||
>
|
||||
<div :class="['w-1.5 h-1.5 rounded-full', parseFloat(predictionResult.confidence.replace('%', '')) >= 90 ? 'bg-emerald-400' : 'bg-red-400']"></div>
|
||||
<span class="font-bold text-xs uppercase tracking-widest">
|
||||
{{ parseFloat(predictionResult.confidence.replace('%', '')) >= 90 ? 'Berhasil' : 'Gagal' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Bean class="absolute -right-10 -bottom-10 w-40 h-40 text-white/10 rotate-12" />
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@ const props = defineProps<{
|
|||
user: User,
|
||||
chartData: Array<{ result: string, total: number }>,
|
||||
recentHistory: Array<any>,
|
||||
totalScan: number
|
||||
totalScan: number,
|
||||
avgAccuracy: number,
|
||||
successRate: number
|
||||
}>();
|
||||
|
||||
// Konfigurasi Warna & Data untuk Pie Chart
|
||||
|
|
@ -49,6 +51,7 @@ const chartOptions = {
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -77,7 +80,27 @@ const chartOptions = {
|
|||
<p class="text-xs text-muted-foreground">Akumulasi seluruh pemindaian</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle class="text-sm font-medium">Rata-rata Akurasi</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold text-emerald-600">{{ avgAccuracy }}%</div>
|
||||
<p class="text-xs text-muted-foreground">Dari klasifikasi berstatus berhasil</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader class="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle class="text-sm font-medium">Tingkat Keberhasilan</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold text-blue-600">{{ successRate }}%</div>
|
||||
<p class="text-xs text-muted-foreground">Persentase status berhasil dari total scan</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-7">
|
||||
<Card class="col-span-1 lg:col-span-3">
|
||||
|
|
|
|||
|
|
@ -132,6 +132,15 @@ const toggleScanner = () => {
|
|||
{{ new Date(item.created_at).toLocaleDateString('id-ID', { day: 'numeric', month: 'short' }) }}
|
||||
</div>
|
||||
<h3 class="text-xl font-black text-emerald-600 dark:text-emerald-500 italic truncate leading-none">{{ item.result }}</h3>
|
||||
<div
|
||||
class="mt-2 inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-bold uppercase tracking-wider border"
|
||||
:class="(item.status === 'Berhasil' || (!item.status && Number(item.confidence) >= 90))
|
||||
? 'bg-emerald-50 text-emerald-600 border-emerald-200 dark:bg-emerald-950/30 dark:text-emerald-400 dark:border-emerald-800'
|
||||
: 'bg-red-50 text-red-600 border-red-200 dark:bg-red-950/30 dark:text-red-400 dark:border-red-800'"
|
||||
>
|
||||
<div :class="['w-1.5 h-1.5 rounded-full mr-1.5', (item.status === 'Berhasil' || (!item.status && Number(item.confidence) >= 90)) ? 'bg-emerald-500' : 'bg-red-500']"></div>
|
||||
{{ item.status || (Number(item.confidence) >= 90 ? 'Berhasil' : 'Gagal') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<p class="text-[9px] font-bold text-zinc-400 uppercase tracking-widest">Confidence</p>
|
||||
|
|
@ -142,7 +151,8 @@ const toggleScanner = () => {
|
|||
<div class="space-y-2">
|
||||
<div class="h-1.5 w-full bg-zinc-100 dark:bg-zinc-800 rounded-full overflow-hidden">
|
||||
<div
|
||||
class="h-full bg-emerald-500 rounded-full transition-all duration-1000"
|
||||
class="h-full rounded-full transition-all duration-1000"
|
||||
:class="(item.status === 'Berhasil' || (!item.status && Number(item.confidence) >= 90)) ? 'bg-emerald-500' : 'bg-red-500'"
|
||||
:style="{ width: item.confidence + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue