diff options
| author | Miqdad <ahmadmiqdad27@gmail.com> | 2025-09-30 15:29:56 +0700 |
|---|---|---|
| committer | Miqdad <ahmadmiqdad27@gmail.com> | 2025-09-30 15:29:56 +0700 |
| commit | 117be0f4b29ef8eb5622f7bf300fec570677cc23 (patch) | |
| tree | f801b8d3de04242ed20b37eab24e761240dedd60 | |
| parent | 0626907b555ead7991d03c374edc096254aced8d (diff) | |
<Miqdad> add role selection when login
| -rw-r--r-- | app/login/page.tsx | 66 | ||||
| -rw-r--r-- | app/page.tsx | 90 |
2 files changed, 88 insertions, 68 deletions
diff --git a/app/login/page.tsx b/app/login/page.tsx index 3ea2190..3ccefa4 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -6,12 +6,16 @@ import { FormLabel, TextField, Typography, + Select, + MenuItem, + SelectChangeEvent, } from "@mui/material"; import Header from "../lib/camera/component/hedear"; import odooApi from "../lib/api/odooApi"; import { getAuth, setAuth } from "../lib/api/auth"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; +import { setCookie } from "cookies-next"; // Ambil tipe parameter untuk setAuth agar sesuai tepat dengan definisinya type AuthProps = Parameters<typeof setAuth>[0]; @@ -24,17 +28,24 @@ type LoginResult = { }; type LoginResponse = { status?: LoginStatus; result?: LoginResult }; +type Role = "driver" | "dispatch"; + const Login = () => { const router = useRouter(); - // state untuk validasi MUI helperText + // state input const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); + const [role, setRole] = useState<"" | Role>(""); + // state error helperText const [emailError, setEmailError] = useState(false); const [emailErrorMessage, setEmailErrorMessage] = useState(""); const [passwordError, setPasswordError] = useState(false); const [passwordErrorMessage, setPasswordErrorMessage] = useState(""); + const [roleError, setRoleError] = useState(false); + const [roleErrorMessage, setRoleErrorMessage] = useState(""); + const [loading, setLoading] = useState(false); useEffect(() => { @@ -42,7 +53,7 @@ const Login = () => { if (token) router.push("/"); }, [router]); - const validateInputs = (e: string, p: string) => { + const validateInputs = (e: string, p: string, r: "" | Role) => { let ok = true; if (!e || !/\S+@\S+\.\S+/.test(e)) { @@ -63,9 +74,27 @@ const Login = () => { setPasswordErrorMessage(""); } + if (!r) { + setRoleError(true); + setRoleErrorMessage("Please select a role."); + ok = false; + } else { + setRoleError(false); + setRoleErrorMessage(""); + } + return ok; }; + const handleRoleChange = (e: SelectChangeEvent) => { + const val = e.target.value as Role | ""; + setRole(val); + if (val) { + setRoleError(false); + setRoleErrorMessage(""); + } + }; + const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); @@ -73,26 +102,31 @@ const Login = () => { const fd = new FormData(event.currentTarget); const rawEmail = fd.get("email"); const rawPassword = fd.get("password"); + const rawRole = fd.get("role"); const emailStr = typeof rawEmail === "string" ? rawEmail.trim() : ""; const passwordStr = typeof rawPassword === "string" ? rawPassword : ""; + const roleStr: "" | Role = rawRole === "driver" || rawRole === "dispatch" ? rawRole : ""; - if (!validateInputs(emailStr, passwordStr)) return; + if (!validateInputs(emailStr, passwordStr, roleStr)) return; try { setLoading(true); + // Kirim web_role juga (opsional, kalau backend abaikan juga tidak masalah) const res = (await odooApi("POST", "/api/v1/user/login", { email: emailStr, password: passwordStr, + web_role: roleStr, })) as unknown as LoginResponse; const auth = res?.result; if (res?.status?.code === 200 && auth?.is_auth) { - // Cast auth.user ke AuthProps → cocok dengan setAuth if (auth.user && typeof auth.user === "object") { setAuth(auth.user as AuthProps); } + // Simpan pilihan role agar bisa dipakai di halaman lain + setCookie("web_role", roleStr, { path: "/" }); router.push("/"); return; } @@ -179,6 +213,30 @@ const Login = () => { /> </FormControl> + {/* Role selection */} + <FormControl error={roleError}> + <FormLabel htmlFor="role">Role</FormLabel> + <Select + id="role" + name="role" + value={role} + onChange={handleRoleChange} + displayEmpty + size="small" + > + <MenuItem value=""> + <em>Pilih role</em> + </MenuItem> + <MenuItem value="driver">Driver</MenuItem> + <MenuItem value="dispatch">Dispatch</MenuItem> + </Select> + {roleError && ( + <Typography variant="caption" color="error" sx={{ mt: 0.5 }}> + {roleErrorMessage || "Please select a role."} + </Typography> + )} + </FormControl> + <Button type="submit" fullWidth variant="contained" disabled={loading}> {loading ? "Loading..." : "Sign in"} </Button> diff --git a/app/page.tsx b/app/page.tsx index fe94498..4841c09 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -13,54 +13,9 @@ 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"; -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"; export default function Home() { const [isLogin, setIsLogin] = useState<boolean>(true); @@ -82,18 +37,18 @@ export default function Home() { const router = useRouter(); useEffect(() => { - const token = getAuth() as AuthLike; - if (!token) { + const auth = getAuth(); + if (!auth) { 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); + return; } + // baca role dari cookie yang diset saat login + const roleCookie = (getCookie("web_role") as string | undefined)?.toLowerCase() as Role | undefined; + const role: Role | null = roleCookie === "driver" || roleCookie === "dispatch" ? roleCookie : null; + + setIsLogin(true); + setIsDriver(role === "driver"); + setIsDispatch(role === "dispatch"); }, [router]); const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => { @@ -106,13 +61,16 @@ export default function Home() { return; } + // Validasi sesuai role yang dipilih di login if (isDispatch) { + // Dispatch: dispatch wajib, SJ opsional if (!imageDispatch) { alert("Foto Dispatch Wajib Diisi"); setIsLoading(false); return; } } else { + // Driver (atau non-dispatch): SJ & Penerima wajib if (!imageSj || !imagePackage) { alert("Barcode, Foto SJ, dan Foto Penerima harus tersedia."); setIsLoading(false); @@ -127,14 +85,13 @@ export default function Home() { const newDispatchImage = imageDispatch && imageDispatch.startsWith("data:") ? imageDispatch.replace(/^.*?,/, "") - : imageDispatch || undefined; // kalau sudah base64 tanpa prefix + : imageDispatch || undefined; - // tandai mana yang BENAR-BENAR akan dikirim + // hanya kirim field yang ada const submittedSj = !!newSjImage; const submittedPackage = !!newPackageImage; - const submittedDispatch = !!newDispatchImage && !isDriver; + const submittedDispatch = !!newDispatchImage && !isDriver; // dispatch hanya untuk non-driver - // bangun payload: hanya isi yang ada const data: Record<string, string> = {}; if (submittedSj) data.sj_document = newSjImage!; if (submittedPackage) data.paket_document = newPackageImage!; @@ -144,18 +101,17 @@ export default function Home() { "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(""); 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"); } @@ -181,12 +137,18 @@ export default function Home() { <div> <BarcodeScanner /> </div> + <div className="h-4" /> + <div className="flex justify-between"> + {/* SJ: driver wajib, dispatch opsional → tampil untuk semua */} <SjCamera /> + {/* Penerima: tidak diperlukan dispatch → sembunyikan saat dispatch */} {!isDispatch && <PackageCamera />} + {/* Dispatch: wajib untuk dispatch; non-driver boleh tampil */} {!isDriver && <DispatchCamera />} </div> + <div className="h-2" /> {imageSj && ( |
