mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-07 16:19:01 +00:00
141 lines
4.5 KiB
TypeScript
141 lines
4.5 KiB
TypeScript
import { useEffect } from "react";
|
|
import { useForm, useWatch } from "react-hook-form";
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
import { Dialog, DialogActions, DialogContent, DialogTitle } from "@mui/material";
|
|
import { Text } from "@/components/ui/text";
|
|
import { useVendorPortal } from "@/app/v/_components/vendor-portal-context";
|
|
import { vendorPortalApi } from "@/domain/vendor-portal/api/vendor-portal-api";
|
|
import {
|
|
REFUSE_REASON_MAX_LENGTH,
|
|
refuseDispatchSchema,
|
|
type RefuseDispatchValues,
|
|
} from "@/domain/vendor-portal/schemas/refuse-dispatch-schema";
|
|
import { queryKeys } from "@/infra/query-key/query-key";
|
|
import { guardDialogClose } from "@/lib/dialog-guard";
|
|
|
|
type RefuseDispatchDialogProps = {
|
|
open: boolean;
|
|
dispatchId: number;
|
|
onClose: () => void;
|
|
onRefused: () => void;
|
|
};
|
|
|
|
export function RefuseDispatchDialog({
|
|
open,
|
|
dispatchId,
|
|
onClose,
|
|
onRefused,
|
|
}: RefuseDispatchDialogProps) {
|
|
const { token } = useVendorPortal();
|
|
const queryClient = useQueryClient();
|
|
|
|
const {
|
|
register,
|
|
handleSubmit,
|
|
control,
|
|
reset,
|
|
formState: { errors },
|
|
} = useForm<RefuseDispatchValues>({
|
|
resolver: zodResolver(refuseDispatchSchema),
|
|
mode: "onChange",
|
|
defaultValues: { reason: "" },
|
|
});
|
|
|
|
const reasonValue = useWatch({ control, name: "reason" }) ?? "";
|
|
const isOverLimit = reasonValue.length > REFUSE_REASON_MAX_LENGTH;
|
|
|
|
const refuseMutation = useMutation({
|
|
mutationFn: (values: RefuseDispatchValues) => {
|
|
const trimmed = values.reason.trim();
|
|
return vendorPortalApi.refuse(token, dispatchId, trimmed || undefined);
|
|
},
|
|
onSuccess: async () => {
|
|
await Promise.all([
|
|
queryClient.invalidateQueries({
|
|
queryKey: queryKeys.vendorPortal.dispatch(token, dispatchId),
|
|
}),
|
|
queryClient.invalidateQueries({
|
|
queryKey: queryKeys.vendorPortal.dispatches(token),
|
|
}),
|
|
]);
|
|
onRefused();
|
|
onClose();
|
|
},
|
|
});
|
|
|
|
const { reset: resetMutation } = refuseMutation;
|
|
|
|
useEffect(() => {
|
|
if (!open) {
|
|
reset({ reason: "" });
|
|
resetMutation();
|
|
}
|
|
}, [open, reset, resetMutation]);
|
|
|
|
const submit = handleSubmit((values) => refuseMutation.mutate(values));
|
|
const errorMessage = refuseMutation.error
|
|
? "Unable to refuse this dispatch. Please try again."
|
|
: "";
|
|
|
|
return (
|
|
<Dialog
|
|
open={open}
|
|
onClose={guardDialogClose(refuseMutation.isPending, onClose)}
|
|
aria-labelledby="refuse-dispatch-title"
|
|
>
|
|
<DialogTitle id="refuse-dispatch-title">Refuse dispatch</DialogTitle>
|
|
<form onSubmit={(event) => void submit(event)}>
|
|
<DialogContent>
|
|
<Text variant="body" className="vp-muted" sx={{ marginBottom: 2 }}>
|
|
Refusing ends your assignment for this dispatch. A reason is optional but helps your
|
|
dispatcher reassign the work.
|
|
</Text>
|
|
<label htmlFor="refuse-reason" className="vp-muted" style={{ fontSize: 13 }}>
|
|
Reason (optional)
|
|
</label>
|
|
<textarea
|
|
id="refuse-reason"
|
|
className="vp-textarea"
|
|
placeholder="Tell the dispatcher why you are refusing…"
|
|
aria-invalid={Boolean(errors.reason)}
|
|
aria-describedby={errors.reason ? "refuse-reason-error" : "refuse-reason-counter"}
|
|
maxLength={REFUSE_REASON_MAX_LENGTH}
|
|
{...register("reason")}
|
|
style={{ marginTop: 6 }}
|
|
/>
|
|
<div
|
|
id="refuse-reason-counter"
|
|
className="vp-muted"
|
|
style={{ fontSize: 12, textAlign: "right" }}
|
|
>
|
|
{reasonValue.length}/{REFUSE_REASON_MAX_LENGTH}
|
|
</div>
|
|
<Text variant="error" when={isOverLimit} id="refuse-reason-error">
|
|
{errors.reason?.message ?? "Reason must be 500 characters or fewer."}
|
|
</Text>
|
|
<Text variant="error" when={Boolean(errorMessage)}>
|
|
{errorMessage}
|
|
</Text>
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<button
|
|
type="button"
|
|
className="vp-btn vp-btn-ghost"
|
|
onClick={onClose}
|
|
disabled={refuseMutation.isPending}
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
className="vp-btn vp-btn-warn"
|
|
disabled={refuseMutation.isPending || isOverLimit}
|
|
>
|
|
{refuseMutation.isPending ? "Refusing…" : "Refuse dispatch"}
|
|
</button>
|
|
</DialogActions>
|
|
</form>
|
|
</Dialog>
|
|
);
|
|
}
|