summaryrefslogtreecommitdiff
path: root/app/page.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'app/page.tsx')
-rw-r--r--app/page.tsx224
1 files changed, 140 insertions, 84 deletions
diff --git a/app/page.tsx b/app/page.tsx
index fe94498..af5ada3 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -6,66 +6,32 @@ import SjCamera from "./lib/camera/component/sjCamera";
import DispatchCamera from "./lib/camera/component/dispatchCamera";
import useCameraStore from "./lib/camera/hooks/useCameraStore";
import Header from "./lib/camera/component/hedear";
-import { Button } from "@mui/material";
+import {
+ Button,
+ FormControl,
+ InputLabel,
+ MenuItem,
+ Select,
+ FormHelperText,
+} from "@mui/material";
import { SaveAsOutlined } from "@mui/icons-material";
import axios from "axios";
import odooApi from "./lib/api/odooApi";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { getAuth } from "./lib/api/auth";
+import { getCookie } from "cookies-next";
+import { clearOdooSession } from "./lib/api/clearOdooSession";
-type AuthLike = { email?: string; token?: string } | string | null;
-
-const DRIVER_EMAILS = new Set(
- ["driverindoteknik@gmail.com", "sulistianaridwan8@gmail.com"].map(
- (e) => e.toLowerCase()
- )
-);
-const DISPATCH_EMAILS = new Set(
- ["rahmat.afiudin@gmail.com", "indraprtama60@gmail.com", "it@fixcomart.co.id"].map((e) => e.toLowerCase())
-);
-
-function extractEmailFromAuth(auth: AuthLike): string | null {
- // object with email
- if (auth && typeof auth === "object" && "email" in auth && typeof auth.email === "string") {
- return auth.email;
- }
- // object with token (JWT)
- if (auth && typeof auth === "object" && "token" in auth && typeof auth.token === "string") {
- const t = auth.token;
- const parts = t.split(".");
- if (parts.length === 3) {
- try {
- const payload = JSON.parse(atob(parts[1]));
- const email: unknown =
- payload?.email ?? payload?.preferred_username ?? payload?.sub ?? null;
- return typeof email === "string" ? email : null;
- } catch {
- return null;
- }
- }
- }
- // JWT string
- if (typeof auth === "string") {
- const parts = auth.split(".");
- if (parts.length === 3) {
- try {
- const payload = JSON.parse(atob(parts[1]));
- const email: unknown =
- payload?.email ?? payload?.preferred_username ?? payload?.sub ?? null;
- return typeof email === "string" ? email : null;
- } catch {
- return null;
- }
- }
- }
- return null;
-}
+type Role = "driver" | "dispatch";
+type ShipMethod = "" | "self_pickup" | "indoteknik_delivery" | "ekspedisi";
export default function Home() {
- const [isLogin, setIsLogin] = useState<boolean>(true);
+ const [isLogin, setIsLogin] = useState<boolean>(false); // start false biar nggak nge-flash
const [isDriver, setIsDriver] = useState<boolean>(false);
const [isDispatch, setIsDispatch] = useState<boolean>(false);
+ const [shippingMethod, setShippingMethod] = useState<ShipMethod>("");
+ const [shipTouched, setShipTouched] = useState(false);
const {
barcode,
@@ -81,19 +47,20 @@ export default function Home() {
const [isLoading, setIsLoading] = useState<boolean>(false);
const router = useRouter();
+ // Single effect: auth gate + set role
useEffect(() => {
- const token = getAuth() as AuthLike;
- if (!token) {
- router.push("/login");
- } else {
- setIsLogin(true);
- const email = extractEmailFromAuth(token);
- const lower = (email ?? "").toLowerCase();
- const dispatchFlag = DISPATCH_EMAILS.has(lower);
- const driverFlag = DRIVER_EMAILS.has(lower) && !dispatchFlag;
- setIsDispatch(dispatchFlag);
- setIsDriver(driverFlag);
+ const auth = getAuth();
+ if (!auth) {
+ void clearOdooSession(process.env.NEXT_PUBLIC_ODOO_API_HOST ?? "");
+ router.replace("/login");
+ return;
}
+ const roleCookie = (
+ getCookie("web_role") as string | undefined
+ )?.toLowerCase() as Role | undefined;
+ setIsDriver(roleCookie === "driver");
+ setIsDispatch(roleCookie === "dispatch");
+ setIsLogin(true);
}, [router]);
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
@@ -106,13 +73,24 @@ export default function Home() {
return;
}
+ // Dispatch: shipping method wajib
+ if (isDispatch) setShipTouched(true);
+ if (isDispatch && !shippingMethod) {
+ alert("Shipping Method wajib dipilih.");
+ setIsLoading(false);
+ return;
+ }
+
+ // Validasi foto sesuai role & shipping method
if (isDispatch) {
if (!imageDispatch) {
alert("Foto Dispatch Wajib Diisi");
setIsLoading(false);
return;
}
+ // SJ opsional untuk self_pickup & ekspedisi
} else {
+ // Driver: SJ & Penerima wajib
if (!imageSj || !imagePackage) {
alert("Barcode, Foto SJ, dan Foto Penerima harus tersedia.");
setIsLoading(false);
@@ -121,41 +99,41 @@ export default function Home() {
}
try {
- // siapkan base64 tanpa prefix, kalau kosong biarkan undefined
const newSjImage = imageSj ? imageSj.replace(/^.*?,/, "") : undefined;
- const newPackageImage = imagePackage ? imagePackage.replace(/^.*?,/, "") : undefined;
+ const newPackageImage = imagePackage
+ ? imagePackage.replace(/^.*?,/, "")
+ : undefined;
const newDispatchImage =
imageDispatch && imageDispatch.startsWith("data:")
? imageDispatch.replace(/^.*?,/, "")
- : imageDispatch || undefined; // kalau sudah base64 tanpa prefix
+ : imageDispatch || undefined;
- // tandai mana yang BENAR-BENAR akan dikirim
+ // Kirim hanya yang ada
const submittedSj = !!newSjImage;
const submittedPackage = !!newPackageImage;
const submittedDispatch = !!newDispatchImage && !isDriver;
- // bangun payload: hanya isi yang ada
const data: Record<string, string> = {};
if (submittedSj) data.sj_document = newSjImage!;
if (submittedPackage) data.paket_document = newPackageImage!;
if (submittedDispatch) data.dispatch_document = newDispatchImage!;
+ if (isDispatch && shippingMethod) data.shipping_method = shippingMethod;
const response = (await odooApi(
"PUT",
`/api/v1/stock-picking/${barcode}/documentation`,
data
- )) as { status: { code: number } };
+ )) as { status?: { code?: number } };
if (response?.status?.code === 200) {
alert("Berhasil Submit Data");
- // barcode bebas: kalau mau tetap kosongkan
setBarcode("");
-
+ // Bersihkan HANYA yang dikirim
if (submittedSj) setImageSj("");
if (submittedPackage) setImagePackage("");
if (submittedDispatch) setImageDispatch("");
} else if (response?.status?.code === 404) {
- alert("Gagal Submit Data, Picking Code Tidak Ditemukan ");
+ alert("Gagal Submit Data, Picking Code Tidak Ditemukan");
} else {
alert("Gagal Submit Data, Silahkan Coba Lagi");
}
@@ -172,31 +150,112 @@ export default function Home() {
}
};
+ // === UI helpers ===
+ // dispatch: SJ hanya utk self_pickup & ekspedisi
+ const showSjForDispatch =
+ isDispatch &&
+ (shippingMethod === "self_pickup" || shippingMethod === "ekspedisi");
+ // dispatch: kamera dispatch tampil utk semua method (asal sudah dipilih)
+ const showDispatchForDispatch = isDispatch && shippingMethod !== "";
+ // preview SJ: sembunyikan kalau dispatch belum pilih method
+ const showSjPreview = !!imageSj && (!isDispatch || showSjForDispatch);
+ // preview Dispatch: sembunyikan kalau dispatch belum pilih method
+ const showDispatchPreview =
+ !!imageDispatch && (!isDispatch || showDispatchForDispatch);
+
return (
<div className="bg-white h-screen overflow-auto">
<Header />
- {isLogin && (
+ {isLogin ? (
<div className="py-4 px-4 sm:px-96 pt-20">
<form onSubmit={handleSubmit}>
<div>
<BarcodeScanner />
</div>
+
+ {/* Shipping Method (khusus dispatch) */}
+ {isDispatch && (
+ <div className="mt-4">
+ <FormControl
+ fullWidth
+ size="small"
+ required
+ error={shipTouched && !shippingMethod}
+ >
+ <InputLabel id="shipping-label" shrink>
+ Shipping Method
+ </InputLabel>
+ <Select
+ labelId="shipping-label"
+ id="shipping"
+ label="Shipping Method"
+ value={shippingMethod}
+ displayEmpty
+ onChange={(e) =>
+ setShippingMethod(e.target.value as ShipMethod)
+ }
+ onBlur={() => setShipTouched(true)}
+ renderValue={(selected) => {
+ if (!selected)
+ return (
+ <span style={{ opacity: 0.6 }}>
+ Pilih Shipping Method
+ </span>
+ );
+ const map: Record<string, string> = {
+ self_pickup: "Self Pickup",
+ indoteknik_delivery: "Indoteknik Delivery",
+ ekspedisi: "Ekspedisi",
+ };
+ return map[selected as string] ?? "";
+ }}
+ >
+ <MenuItem value="">
+ <em>Pilih Shipping Method</em>
+ </MenuItem>
+ <MenuItem value="self_pickup">Self Pickup</MenuItem>
+ <MenuItem value="indoteknik_delivery">
+ Indoteknik Delivery
+ </MenuItem>
+ <MenuItem value="ekspedisi">Ekspedisi</MenuItem>
+ </Select>
+ {shipTouched && !shippingMethod && (
+ <FormHelperText>
+ Wajib pilih shipping method.
+ </FormHelperText>
+ )}
+ </FormControl>
+ </div>
+ )}
+
<div className="h-4" />
+
<div className="flex justify-between">
- <SjCamera />
- {!isDispatch && <PackageCamera />}
- {!isDriver && <DispatchCamera />}
+ {isDispatch ? (
+ <>
+ {showSjForDispatch && <SjCamera />}
+ {showDispatchForDispatch && <DispatchCamera />}
+ </>
+ ) : (
+ <>
+ {/* driver / non-dispatch */}
+ <SjCamera />
+ <PackageCamera />
+ </>
+ )}
</div>
+
<div className="h-2" />
- {imageSj && (
+ {/* Preview SJ */}
+ {showSjPreview && (
<>
<label className="block mt-2 text-sm font-medium text-gray-700 text-center">
Gambar Foto Surat Jalan
</label>
<div className="relative w-full h-[300px] border-2 border-gray-200 p-2 rounded-sm">
<Image
- src={imageSj}
+ src={imageSj!}
alt="Captured"
fill
unoptimized
@@ -207,9 +266,8 @@ export default function Home() {
</>
)}
- <div className="h-2" />
-
- {imagePackage && (
+ {/* Preview Penerima (hanya non-dispatch) */}
+ {!isDispatch && imagePackage && (
<>
<label className="block mt-2 text-sm font-medium text-gray-700 text-center">
Gambar Foto Penerima
@@ -227,16 +285,15 @@ export default function Home() {
</>
)}
- <div className="h-2" />
-
- {!isDriver && imageDispatch && (
+ {/* Preview Dispatch */}
+ {showDispatchPreview && (
<>
<label className="block mt-2 text-sm font-medium text-gray-700 text-center">
Gambar Foto Dispatch
</label>
<div className="relative w-full h-[300px] border-2 border-gray-200 p-2 rounded-sm">
<Image
- src={imageDispatch}
+ src={imageDispatch!}
alt="Captured"
fill
unoptimized
@@ -260,8 +317,7 @@ export default function Home() {
</Button>
</form>
</div>
- )}
- {!isLogin && (
+ ) : (
<div className="py-4 px-4 sm:px-96 pt-20">
<div className="text-center">
<p className="text-2xl font-bold">Loading...</p>