Compare commits

...
8 Commits
Author SHA1 Message Date
Selly SupriyatinandGitHub 3b5839ebd5 Merge pull request #42 from ATLAS-PJK-GM007/selly/frontend
style(sidebar): standardize capitalization of feature titles
2026-06-16 02:48:40 +07:00
MythEclipseandClaude e2742a7a60 fix(auth): fix Android deep link intent URL format for Google OAuth
The intent:// URL in renderTauriDeepLinkPage was intent:/path (single slash),
producing zeavisedu:/login?token=xxx — a non-hierarchical URL that new URL()
cannot parse. Fixed to intent://login/path which produces a proper
hierarchical URI (zeavisedu://login/login?token=xxx).

Also hardened setupDeepLinkHandler to handle both double-slash (://) and
single-slash (:/) custom-scheme URLs as fallback.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-16 02:44:33 +07:00
seriouselly ae22adee40 style(sidebar): standardize capitalization of feature titles
- Update navigation labels in the sidebar for consistent casing and better visual uniformity.
2026-06-16 02:38:01 +07:00
Selly SupriyatinandGitHub ee02bd4f9a Merge pull request #41 from ATLAS-PJK-GM007/selly/frontend
Selly/frontend
2026-06-16 02:14:36 +07:00
seriouselly 310d8adc8d Merge branch 'main' of https://github.com/ATLAS-PJK-GM007/ZeaVis-Edu into selly/frontend 2026-06-16 02:13:32 +07:00
seriouselly ccbcd99053 refactor(ui): remove local image assets and use external URLs
- Delete the `assets/images` directory to reduce repository size and footprint.
- Update `dashboard-page.tsx` to fetch background images from external URLs instead of local file imports.
2026-06-16 02:13:17 +07:00
seriouselly 52e60b99dd Revert "refactor(ui): remove local image assets and use external URLs"
This reverts commit e19321a2b7.
2026-06-16 02:10:32 +07:00
seriouselly e19321a2b7 refactor(ui): remove local image assets and use external URLs
- Delete the `assets/images` directory to reduce repository size and footprint.
- Update `dashboard-page.tsx` to fetch background images from external URLs instead of local file imports.
2026-06-16 02:09:36 +07:00
10 changed files with 85 additions and 64 deletions
+3 -1
View File
@@ -80,7 +80,9 @@ function renderTauriDeepLinkPage(targetUrl: string): Response {
const escapedPath = pathAndQuery.replace(/"/g, '&quot;'); const escapedPath = pathAndQuery.replace(/"/g, '&quot;');
// intent:// scheme: Chrome on Android opens the target app by package name // intent:// scheme: Chrome on Android opens the target app by package name
// browser_fallback_url: shown if the app isn't installed // browser_fallback_url: shown if the app isn't installed
const intentUrl = `intent:${escapedPath}#Intent;scheme=zeavisedu;package=com.zeavis.edu;S.browser_fallback_url=${encodeURIComponent(targetUrl)};end`; // Use intent://login/... to produce data URI zeavisedu://login/login?token=xxx
// which new URL() can parse (single-slash non-hierarchical URLs break WebView)
const intentUrl = `intent://login${escapedPath}#Intent;scheme=zeavisedu;package=com.zeavis.edu;S.browser_fallback_url=${encodeURIComponent(targetUrl)};end`;
const html = `<!DOCTYPE html> const html = `<!DOCTYPE html>
<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"> <html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

+12 -7
View File
@@ -35,20 +35,25 @@ export async function setupDeepLinkHandler(): Promise<void> {
const { onOpenUrl } = await import('@tauri-apps/plugin-deep-link'); const { onOpenUrl } = await import('@tauri-apps/plugin-deep-link');
onOpenUrl((urls) => { onOpenUrl((urls) => {
for (const url of urls) { for (const url of urls) {
// url looks like: zeavisedu://zeavisedu.asepharyana.my.id/login?token=xxx // url looks like: zeavisedu://login/login?token=xxx
// Extract path + query after the host // Extract path + query after the host
try { try {
const u = new URL(url); const u = new URL(url);
const target = u.pathname + u.search + u.hash; const target = u.pathname + u.search + u.hash;
if (target && target !== '/') { if (target && target !== '/') {
window.location.href = target; window.location.href = target;
return;
} }
} catch { } catch { /* try fallback below */ }
// If URL parsing fails, try to extract everything after the scheme
const match = url.match(/^[^:]+:\/\/(?:[^/]+)?(\/.*)?$/); // Fallback: extract everything after scheme, handling both // and /
if (match?.[1]) { let match = url.match(/^[^:]+:\/\/(?:[^/]+)?(\/.*)?$/);
window.location.href = match[1]; if (!match) {
} // Also handle single-slash non-hierarchical URLs (e.g. zeavisedu:/path)
match = url.match(/^[^:]+:\/(\/.*)?$/);
}
if (match?.[1]) {
window.location.href = match[1];
} }
} }
}); });
+3 -3
View File
@@ -54,12 +54,12 @@ export function CatalogPage() {
<div className="space-y-6 max-w-5xl mx-auto pb-10"> <div className="space-y-6 max-w-5xl mx-auto pb-10">
{/* Page Title */} {/* Page Title */}
<div> <div>
<h1 className="text-2xl md:text-3xl font-extrabold text-[#214B11]"> <h1 className="text-2xl font-bold text-emerald-800">
Pustaka Penyakit Pustaka Penyakit
</h1> </h1>
<p className="mt-1 text-muted-foreground text-sm md:text-base"> <p className="text-gray-500 mt-1 text-md">
Referensi lengkap penyakit dan kondisi daun jagung yang dapat Referensi lengkap penyakit dan kondisi daun jagung yang dapat
dideteksi oleh sistem AI ZeaVis Edu. dideteksi oleh sistem AI ZeaVis Edu
</p> </p>
</div> </div>
+43 -29
View File
@@ -6,7 +6,6 @@ import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { useUiStore } from "@/store/ui-store"; import { useUiStore } from "@/store/ui-store";
import { apiClient } from "@/lib/api-client"; import { apiClient } from "@/lib/api-client";
import bg from "@/assets/images/dashboard-bg.webp";
export function DashboardPage() { export function DashboardPage() {
const { dashboardCompact } = useUiStore(); const { dashboardCompact } = useUiStore();
@@ -23,16 +22,17 @@ export function DashboardPage() {
const summary = summaryQuery.data; const summary = summaryQuery.data;
const diseases = diseasesQuery.data ?? []; const diseases = diseasesQuery.data ?? [];
const diseasesQuick = useMemo(() => const diseasesQuick = useMemo(
diseases () =>
.sort((a, b) => a.displayOrder - b.displayOrder) diseases
.map((d) => ({ .sort((a, b) => a.displayOrder - b.displayOrder)
name: d.commonName, .map((d) => ({
sci: d.label, name: d.commonName,
color: d.accentColor, sci: d.label,
slug: d.slug, color: d.accentColor,
})), slug: d.slug,
[diseases] })),
[diseases],
); );
const missionCards = [ const missionCards = [
@@ -74,7 +74,11 @@ export function DashboardPage() {
{/* Hero header */} {/* Hero header */}
<header <header
className="relative overflow-hidden rounded-3xl bg-cover bg-center bg-no-repeat shadow-sm" className="relative overflow-hidden rounded-3xl bg-cover bg-center bg-no-repeat shadow-sm"
style={{ backgroundImage: `url(${bg})` }} style={{
backgroundImage: `url(https://cdn.pixabay.com/photo/2014/09/09/19/07/corn-field-440338_1280.jpg)`,
backgroundPosition: "bottom",
backgroundSize: "cover",
}}
> >
<div className="absolute inset-0 bg-gradient-to-b from-[#2F6E1A]/60 to-black/30" /> <div className="absolute inset-0 bg-gradient-to-b from-[#2F6E1A]/60 to-black/30" />
<div className="relative z-10 flex flex-col md:flex-row items-center justify-between gap-6 p-6 md:p-10"> <div className="relative z-10 flex flex-col md:flex-row items-center justify-between gap-6 p-6 md:p-10">
@@ -82,7 +86,9 @@ export function DashboardPage() {
<span className="inline-block rounded-full bg-[#1E8A2A]/80 px-4 py-2 text-xs font-semibold"> <span className="inline-block rounded-full bg-[#1E8A2A]/80 px-4 py-2 text-xs font-semibold">
AI FOR SMART EDUCATION AI FOR SMART EDUCATION
</span> </span>
<h1 className="text-2xl md:text-4xl font-extrabold">Selamat Datang di</h1> <h1 className="text-2xl md:text-4xl font-extrabold">
Selamat Datang di
</h1>
<h2 className="text-2xl md:text-4xl font-extrabold tracking-tight text-[#9AD872]"> <h2 className="text-2xl md:text-4xl font-extrabold tracking-tight text-[#9AD872]">
ZeaVis Edu ZeaVis Edu
</h2> </h2>
@@ -107,10 +113,7 @@ export function DashboardPage() {
variant="outline" variant="outline"
className="px-4 md:px-6 py-3 md:py-6 text-sm md:text-lg font-semibold text-white hover:bg-[#1E8A2A]" className="px-4 md:px-6 py-3 md:py-6 text-sm md:text-lg font-semibold text-white hover:bg-[#1E8A2A]"
> >
<Link <Link to="/catalog" className="inline-flex items-center gap-2">
to="/catalog"
className="inline-flex items-center gap-2"
>
Pustaka Penyakit Pustaka Penyakit
<ChevronRight className="h-5 w-6" /> <ChevronRight className="h-5 w-6" />
</Link> </Link>
@@ -169,7 +172,10 @@ export function DashboardPage() {
<div className="text-3xl font-bold"> <div className="text-3xl font-bold">
{summary.imageClassificationCount} {summary.imageClassificationCount}
</div> </div>
<Link to="/diagnoses" className="text-emerald-600 ml-auto hover:underline"> <Link
to="/diagnoses"
className="text-emerald-600 ml-auto hover:underline"
>
Lihat daftar Lihat daftar
</Link> </Link>
</CardContent> </CardContent>
@@ -186,7 +192,10 @@ export function DashboardPage() {
<div className="text-3xl font-bold text-amber-600"> <div className="text-3xl font-bold text-amber-600">
{summary.needsReviewCount} {summary.needsReviewCount}
</div> </div>
<Link to="/diagnoses?status=needs_review" className="text-amber-600 ml-auto hover:underline"> <Link
to="/diagnoses?status=needs_review"
className="text-amber-600 ml-auto hover:underline"
>
Lihat daftar Lihat daftar
</Link> </Link>
</CardContent> </CardContent>
@@ -200,10 +209,11 @@ export function DashboardPage() {
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="h-full flex flex-col justify-start pt-2"> <CardContent className="h-full flex flex-col justify-start pt-2">
<div className="text-3xl font-bold text-red-600"> <div className="text-3xl font-bold text-red-600"></div>
<Link
</div> to="/diagnoses?status=failed"
<Link to="/diagnoses?status=failed" className="text-red-600 ml-auto hover:underline"> className="text-red-600 ml-auto hover:underline"
>
Lihat daftar Lihat daftar
</Link> </Link>
</CardContent> </CardContent>
@@ -220,7 +230,10 @@ export function DashboardPage() {
<div className="text-3xl font-bold text-red-600"> <div className="text-3xl font-bold text-red-600">
{summary.riskDistribution.high} {summary.riskDistribution.high}
</div> </div>
<Link to="/catalog?risk=high" className="text-red-600 ml-auto hover:underline"> <Link
to="/catalog?risk=high"
className="text-red-600 ml-auto hover:underline"
>
Lihat pustaka Lihat pustaka
</Link> </Link>
</CardContent> </CardContent>
@@ -276,7 +289,8 @@ export function DashboardPage() {
Penyakit yang Dapat Dideteksi Penyakit yang Dapat Dideteksi
</h3> </h3>
<p className="text-[15px] font-normal text-muted-foreground"> <p className="text-[15px] font-normal text-muted-foreground">
{diseases.length} kelas penyakit dan kondisi daun jagung dalam sistem kami {diseases.length} kelas penyakit dan kondisi daun jagung dalam
sistem kami
</p> </p>
</div> </div>
<Link <Link
@@ -295,9 +309,7 @@ export function DashboardPage() {
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4">
{diseasesQuick.map((d) => ( {diseasesQuick.map((d) => (
<Link key={d.slug} to={`/catalog/${d.slug}`}> <Link key={d.slug} to={`/catalog/${d.slug}`}>
<Card <Card className="rounded-2xl bg-white p-4 shadow-sm h-full transition-transform hover:scale-[1.03] hover:shadow-md cursor-pointer">
className="rounded-2xl bg-white p-4 shadow-sm h-full transition-transform hover:scale-[1.03] hover:shadow-md cursor-pointer"
>
<CardContent className="h-full p-4 flex flex-col justify-between"> <CardContent className="h-full p-4 flex flex-col justify-between">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div <div
@@ -324,7 +336,9 @@ export function DashboardPage() {
{/* Scan quick access */} {/* Scan quick access */}
<div className="mt-12 flex flex-col sm:flex-row items-center gap-4 sm:gap-6 bg-[#1E8A2A] rounded-3xl p-5 sm:p-6"> <div className="mt-12 flex flex-col sm:flex-row items-center gap-4 sm:gap-6 bg-[#1E8A2A] rounded-3xl p-5 sm:p-6">
<div className="flex-1 text-white"> <div className="flex-1 text-white">
<h3 className="text-2xl font-bold">Siap Mendeteksi Penyakit Daun?</h3> <h3 className="text-2xl font-bold">
Siap Mendeteksi Penyakit Daun?
</h3>
<p className="text-sm text-[#9AD872] font-normal mt-2"> <p className="text-sm text-[#9AD872] font-normal mt-2">
Unggah foto daun jagung Anda dan dapatkan hasil analisis AI Unggah foto daun jagung Anda dan dapatkan hasil analisis AI
dalam hitungan detik. dalam hitungan detik.
+1 -1
View File
@@ -75,7 +75,7 @@ export function DiagnosesPage() {
<div className="space-y-6"> <div className="space-y-6">
<div className="flex items-center justify-between gap-4"> <div className="flex items-center justify-between gap-4">
<div> <div>
<h1 className="text-2xl font-bold text-emerald-800">Diagnosa Tanaman</h1> <h1 className="text-2xl font-bold text-emerald-800">Diagnosa Penyakit</h1>
<p className="text-gray-500 mt-1 text-md"> <p className="text-gray-500 mt-1 text-md">
Lihat hasil diagnosa dari scan yang telah dilakukan Lihat hasil diagnosa dari scan yang telah dilakukan
</p> </p>
+1 -1
View File
@@ -71,7 +71,7 @@ export function ExpertReviewsPage() {
<div className="space-y-6"> <div className="space-y-6">
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3"> <div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
<div> <div>
<h1 className="text-2xl font-bold text-emerald-800">Review Pakar</h1> <h1 className="text-2xl font-bold text-emerald-800">Tinjauan Pakar</h1>
<p className="text-gray-500 mt-1 text-md"> <p className="text-gray-500 mt-1 text-md">
Tinjau hasil diagnosa dari scan yang telah dilakukan dan berikan Tinjau hasil diagnosa dari scan yang telah dilakukan dan berikan
feedback untuk meningkatkan akurasi sistem AI ZeaVis Edu feedback untuk meningkatkan akurasi sistem AI ZeaVis Edu
+19 -18
View File
@@ -36,21 +36,18 @@ export function ScanPage() {
// Camera mode state // Camera mode state
const [useCamera, setUseCamera] = useState(false); const [useCamera, setUseCamera] = useState(false);
const handleCameraCapture = useCallback( const handleCameraCapture = useCallback((file: File) => {
(file: File) => { setFileName(file.name);
setFileName(file.name); const url = URL.createObjectURL(file);
const url = URL.createObjectURL(file); setPreviewUrl(url);
setPreviewUrl(url);
const img = new Image(); const img = new Image();
img.onload = () => { img.onload = () => {
setImageDimensions({ width: img.width, height: img.height }); setImageDimensions({ width: img.width, height: img.height });
}; };
img.src = url; img.src = url;
setUseCamera(false); setUseCamera(false);
}, }, []);
[],
);
const mutation = useMutation({ const mutation = useMutation({
mutationFn: (file: File) => apiClient.createDiagnosis(file), mutationFn: (file: File) => apiClient.createDiagnosis(file),
@@ -107,10 +104,10 @@ export function ScanPage() {
{/* Main Header */} {/* Main Header */}
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 sm:gap-4"> <div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 sm:gap-4">
<div> <div>
<h1 className="text-2xl font-bold text-emerald-800">Scan Tanaman</h1> <h1 className="text-2xl font-bold text-emerald-800">Pindai Daun</h1>
<p className="text-gray-500 mt-1 text-md"> <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>
</div> </div>
<Button asChild variant="outline"> <Button asChild variant="outline">
@@ -170,12 +167,16 @@ export function ScanPage() {
className="w-full border-2 border-dashed border-green-300 rounded-md p-10 h-60 text-center cursor-pointer" className="w-full border-2 border-dashed border-green-300 rounded-md p-10 h-60 text-center cursor-pointer"
onClick={() => inputRef.current?.click()} onClick={() => inputRef.current?.click()}
> >
<Upload className="mx-auto text-green-500 mb-3" size={48} /> <Upload
className="mx-auto text-green-500 mb-3"
size={48}
/>
<h3 className="font-semibold text-base text-gray-800 mb-1"> <h3 className="font-semibold text-base text-gray-800 mb-1">
Seret & Lepas Foto Daun Seret & Lepas Foto Daun
</h3> </h3>
<p className="text-xs text-gray-500 mb-3"> <p className="text-xs text-gray-500 mb-3">
atau klik untuk memilih file berkas dari perangkat Anda atau klik untuk memilih file berkas dari perangkat
Anda
</p> </p>
<div className="flex flex-wrap gap-2 justify-center"> <div className="flex flex-wrap gap-2 justify-center">
<div className="bg-green-100 text-green-700 px-3 py-1 rounded-full inline-flex items-center gap-1 text-xs font-medium"> <div className="bg-green-100 text-green-700 px-3 py-1 rounded-full inline-flex items-center gap-1 text-xs font-medium">
+3 -4
View File
@@ -286,12 +286,11 @@ export function TelemetryPage() {
{/* Header */} {/* Header */}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div> <div>
<h1 className="text-2xl md:text-[28px] font-extrabold text-[#214B11] flex items-center gap-3"> <h1 className="text-2xl font-bold text-emerald-800">
<Activity className="h-7 w-7 text-[#48A111]" />
Telemetry Dashboard Telemetry Dashboard
</h1> </h1>
<p className="text-sm text-muted-foreground mt-0.5"> <p className="text-gray-500 mt-1 text-md">
Real-time metrics from Prometheus Real-time monitoring dari performa sistem dan aplikasi ZeaVis Edu
{error && <span className="text-amber-600 ml-2">(partial {error})</span>} {error && <span className="text-amber-600 ml-2">(partial {error})</span>}
</p> </p>
</div> </div>