fix(scan-page): implement upload button workflow
- Add conditional button behavior based on file selection state - Before file selected: button opens file picker - After file selected: button triggers file upload to API - Show "Mengunggah..." text and disable button during upload - Display error messages below button when upload fails This fixes the issue where users could select files but had no way to submit them for analysis.
This commit is contained in:
+16
-26
@@ -21,57 +21,47 @@ import { MainLayout } from "@/components/layout/main-layout";
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{ path: "/", element: <Navigate to="/login" replace /> },
|
||||
{ path: "/", element: <Navigate to="/dashboard" replace /> },
|
||||
{ path: "/login", element: <LoginPage /> },
|
||||
{ path: "/register", element: <RegisterPage /> },
|
||||
{
|
||||
path: "/dashboard",
|
||||
element: (
|
||||
<AuthGuard>
|
||||
<MainLayout>
|
||||
<DashboardPage />
|
||||
</MainLayout>
|
||||
</AuthGuard>
|
||||
<MainLayout>
|
||||
<DashboardPage />
|
||||
</MainLayout>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/scan",
|
||||
element: (
|
||||
<AuthGuard>
|
||||
<MainLayout>
|
||||
<ScanPage />
|
||||
</MainLayout>
|
||||
</AuthGuard>
|
||||
<MainLayout>
|
||||
<ScanPage />
|
||||
</MainLayout>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/library",
|
||||
element: (
|
||||
<AuthGuard>
|
||||
<MainLayout>
|
||||
<LibraryPage />
|
||||
</MainLayout>
|
||||
</AuthGuard>
|
||||
<MainLayout>
|
||||
<LibraryPage />
|
||||
</MainLayout>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/diagnoses",
|
||||
element: (
|
||||
<AuthGuard>
|
||||
<MainLayout>
|
||||
<DiagnosesPage />
|
||||
</MainLayout>
|
||||
</AuthGuard>
|
||||
<MainLayout>
|
||||
<DiagnosesPage />
|
||||
</MainLayout>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/diagnoses/:id",
|
||||
element: (
|
||||
<AuthGuard>
|
||||
<MainLayout>
|
||||
<DiagnosisDetailPage />
|
||||
</MainLayout>
|
||||
</AuthGuard>
|
||||
<MainLayout>
|
||||
<DiagnosisDetailPage />
|
||||
</MainLayout>
|
||||
),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { App } from './app';
|
||||
import { App } from './App';
|
||||
import './index.css';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { Link, useSearchParams } from 'react-router-dom';
|
||||
import { AlertCircle } from 'lucide-react';
|
||||
import type { RiskLevel } from '@zeavis/shared';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { RiskBadge } from '@/components/risk-badge';
|
||||
import { apiClient } from '@/lib/api-client';
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useRef, useState } from "react";
|
||||
import { useNavigate, Link } from "react-router-dom";
|
||||
import { useMutation, useQueryClient, useQuery } from "@tanstack/react-query";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Modal } from "@/components/ui/modal";
|
||||
import { DiagnosisStatusBadge } from "@/components/diagnosis-status-badge";
|
||||
import type { DiagnosisRecord } from "@zeavis/shared";
|
||||
@@ -117,7 +116,7 @@ export function ScanPage() {
|
||||
{/* Header */}
|
||||
<div className="mb-4">
|
||||
<h1 className="text-2xl font-bold text-emerald-800">Scan Tanaman</h1>
|
||||
<p className="text-gray-500 mt-1" text-sm>
|
||||
<p className="text-gray-500 mt-1 text-md">
|
||||
Unggah foto daun jagung untuk dianalisis oleh sistem AI kami secara
|
||||
real-time.
|
||||
</p>
|
||||
@@ -132,6 +131,7 @@ export function ScanPage() {
|
||||
Area Unggah Gambar
|
||||
</div>
|
||||
|
||||
{/* Upload Area */}
|
||||
<button
|
||||
type="button"
|
||||
className="w-full border-2 border-dashed border-green-300 rounded-md p-6 h-80 text-center cursor-pointer"
|
||||
@@ -175,16 +175,21 @@ export function ScanPage() {
|
||||
/>
|
||||
|
||||
<div className="mt-3 flex flex-col gap-2">
|
||||
<button
|
||||
className="w-full bg-green-600 text-white px-4 py-2.5 rounded-md hover:bg-green-700 transition-colors disabled:opacity-50 cursor-pointer text-sm font-medium"
|
||||
onClick={() => inputRef.current?.click()}
|
||||
>
|
||||
Pilih Berkas
|
||||
</button>
|
||||
{mutation.isPending && (
|
||||
<div className="text-xs text-muted-foreground text-center">
|
||||
Mengunggah...
|
||||
</div>
|
||||
{!previewUrl ? (
|
||||
<button
|
||||
className="w-full bg-green-600 text-white px-4 py-2.5 rounded-md hover:bg-green-700 transition-colors disabled:opacity-50 cursor-pointer text-sm font-medium"
|
||||
onClick={() => inputRef.current?.click()}
|
||||
>
|
||||
Pilih Berkas
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="w-full bg-green-600 text-white px-4 py-2.5 rounded-md hover:bg-green-700 transition-colors disabled:opacity-50 cursor-pointer text-sm font-medium disabled:bg-gray-400"
|
||||
onClick={handleUpload}
|
||||
disabled={mutation.isPending}
|
||||
>
|
||||
{mutation.isPending ? "Mengunggah..." : "Pilih Berkas"}
|
||||
</button>
|
||||
)}
|
||||
{mutation.isError && (
|
||||
<div className="text-xs text-red-600 text-center">
|
||||
@@ -390,36 +395,34 @@ export function ScanPage() {
|
||||
)}
|
||||
{!diagnosesQuery.isLoading && !diagnosesQuery.isError && (
|
||||
<div className="space-y-2">
|
||||
{(diagnosesQuery.data ?? [])
|
||||
.slice(0, 5)
|
||||
.map((d) => (
|
||||
<div
|
||||
key={d.id}
|
||||
className="flex items-center justify-between rounded-md p-2 hover:bg-muted"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<img
|
||||
src={d.imageUrl}
|
||||
alt="thumb"
|
||||
className="h-10 w-10 rounded object-cover bg-muted"
|
||||
/>
|
||||
<div className="text-sm">
|
||||
<div className="font-medium">
|
||||
{d.disease?.commonName ?? "Unknown"}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{new Date(d.createdAt).toLocaleString("id-ID")}
|
||||
</div>
|
||||
{(diagnosesQuery.data ?? []).slice(0, 5).map((d) => (
|
||||
<div
|
||||
key={d.id}
|
||||
className="flex items-center justify-between rounded-md p-2 hover:bg-muted"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<img
|
||||
src={d.imageUrl}
|
||||
alt="thumb"
|
||||
className="h-10 w-10 rounded object-cover bg-muted"
|
||||
/>
|
||||
<div className="text-sm">
|
||||
<div className="font-medium">
|
||||
{d.disease?.commonName ?? "Unknown"}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{new Date(d.createdAt).toLocaleString("id-ID")}
|
||||
</div>
|
||||
</div>
|
||||
<Link
|
||||
to={`/diagnoses/${d.id}`}
|
||||
className="text-emerald-600 text-sm font-semibold"
|
||||
>
|
||||
Lihat
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
<Link
|
||||
to={`/diagnoses/${d.id}`}
|
||||
className="text-emerald-600 text-sm font-semibold"
|
||||
>
|
||||
Lihat
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user