summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/login/page.tsx66
-rw-r--r--app/page.tsx90
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 && (