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:
seriouselly
2026-06-05 19:01:48 +07:00
parent aa033c86a9
commit 065b802344
4 changed files with 59 additions and 67 deletions
+1 -11
View File
@@ -21,57 +21,47 @@ import { MainLayout } from "@/components/layout/main-layout";
const queryClient = new QueryClient(); const queryClient = new QueryClient();
const router = createBrowserRouter([ const router = createBrowserRouter([
{ path: "/", element: <Navigate to="/login" replace /> }, { path: "/", element: <Navigate to="/dashboard" replace /> },
{ path: "/login", element: <LoginPage /> }, { path: "/login", element: <LoginPage /> },
{ path: "/register", element: <RegisterPage /> }, { path: "/register", element: <RegisterPage /> },
{ {
path: "/dashboard", path: "/dashboard",
element: ( element: (
<AuthGuard>
<MainLayout> <MainLayout>
<DashboardPage /> <DashboardPage />
</MainLayout> </MainLayout>
</AuthGuard>
), ),
}, },
{ {
path: "/scan", path: "/scan",
element: ( element: (
<AuthGuard>
<MainLayout> <MainLayout>
<ScanPage /> <ScanPage />
</MainLayout> </MainLayout>
</AuthGuard>
), ),
}, },
{ {
path: "/library", path: "/library",
element: ( element: (
<AuthGuard>
<MainLayout> <MainLayout>
<LibraryPage /> <LibraryPage />
</MainLayout> </MainLayout>
</AuthGuard>
), ),
}, },
{ {
path: "/diagnoses", path: "/diagnoses",
element: ( element: (
<AuthGuard>
<MainLayout> <MainLayout>
<DiagnosesPage /> <DiagnosesPage />
</MainLayout> </MainLayout>
</AuthGuard>
), ),
}, },
{ {
path: "/diagnoses/:id", path: "/diagnoses/:id",
element: ( element: (
<AuthGuard>
<MainLayout> <MainLayout>
<DiagnosisDetailPage /> <DiagnosisDetailPage />
</MainLayout> </MainLayout>
</AuthGuard>
), ),
}, },
{ {
+1 -1
View File
@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client';
import { App } from './app'; import { App } from './App';
import './index.css'; import './index.css';
ReactDOM.createRoot(document.getElementById('root')!).render( ReactDOM.createRoot(document.getElementById('root')!).render(
-1
View File
@@ -3,7 +3,6 @@ import { useQuery } from '@tanstack/react-query';
import { Link, useSearchParams } from 'react-router-dom'; import { Link, useSearchParams } from 'react-router-dom';
import { AlertCircle } from 'lucide-react'; import { AlertCircle } from 'lucide-react';
import type { RiskLevel } from '@zeavis/shared'; import type { RiskLevel } from '@zeavis/shared';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { RiskBadge } from '@/components/risk-badge'; import { RiskBadge } from '@/components/risk-badge';
import { apiClient } from '@/lib/api-client'; import { apiClient } from '@/lib/api-client';
+12 -9
View File
@@ -2,7 +2,6 @@ import { useRef, useState } from "react";
import { useNavigate, Link } from "react-router-dom"; import { useNavigate, Link } from "react-router-dom";
import { useMutation, useQueryClient, useQuery } from "@tanstack/react-query"; import { useMutation, useQueryClient, useQuery } from "@tanstack/react-query";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Modal } from "@/components/ui/modal"; import { Modal } from "@/components/ui/modal";
import { DiagnosisStatusBadge } from "@/components/diagnosis-status-badge"; import { DiagnosisStatusBadge } from "@/components/diagnosis-status-badge";
import type { DiagnosisRecord } from "@zeavis/shared"; import type { DiagnosisRecord } from "@zeavis/shared";
@@ -117,7 +116,7 @@ export function ScanPage() {
{/* Header */} {/* Header */}
<div className="mb-4"> <div className="mb-4">
<h1 className="text-2xl font-bold text-emerald-800">Scan Tanaman</h1> <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 Unggah foto daun jagung untuk dianalisis oleh sistem AI kami secara
real-time. real-time.
</p> </p>
@@ -132,6 +131,7 @@ export function ScanPage() {
Area Unggah Gambar Area Unggah Gambar
</div> </div>
{/* Upload Area */}
<button <button
type="button" type="button"
className="w-full border-2 border-dashed border-green-300 rounded-md p-6 h-80 text-center cursor-pointer" 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"> <div className="mt-3 flex flex-col gap-2">
{!previewUrl ? (
<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" 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()} onClick={() => inputRef.current?.click()}
> >
Pilih Berkas Pilih Berkas
</button> </button>
{mutation.isPending && ( ) : (
<div className="text-xs text-muted-foreground text-center"> <button
Mengunggah... 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"
</div> onClick={handleUpload}
disabled={mutation.isPending}
>
{mutation.isPending ? "Mengunggah..." : "Pilih Berkas"}
</button>
)} )}
{mutation.isError && ( {mutation.isError && (
<div className="text-xs text-red-600 text-center"> <div className="text-xs text-red-600 text-center">
@@ -390,9 +395,7 @@ export function ScanPage() {
)} )}
{!diagnosesQuery.isLoading && !diagnosesQuery.isError && ( {!diagnosesQuery.isLoading && !diagnosesQuery.isError && (
<div className="space-y-2"> <div className="space-y-2">
{(diagnosesQuery.data ?? []) {(diagnosesQuery.data ?? []).slice(0, 5).map((d) => (
.slice(0, 5)
.map((d) => (
<div <div
key={d.id} key={d.id}
className="flex items-center justify-between rounded-md p-2 hover:bg-muted" className="flex items-center justify-between rounded-md p-2 hover:bg-muted"