134 lines
3.2 KiB
TypeScript
134 lines
3.2 KiB
TypeScript
import {
|
|
Dialog,
|
|
DialogTitle,
|
|
DialogContent,
|
|
DialogActions,
|
|
Button,
|
|
TextField,
|
|
Alert,
|
|
CircularProgress,
|
|
} from "@mui/material";
|
|
import { useState, useEffect, useMemo } from "react";
|
|
|
|
interface StatusChangeDialogProps {
|
|
open: boolean;
|
|
transactionId?: number | null;
|
|
newStatus: string;
|
|
onClose: () => void;
|
|
onStatusUpdated?: () => void;
|
|
}
|
|
|
|
const StatusChangeDialog = ({
|
|
open,
|
|
transactionId,
|
|
newStatus,
|
|
onClose,
|
|
onStatusUpdated,
|
|
}: StatusChangeDialogProps) => {
|
|
const [reason, setReason] = useState("");
|
|
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (!open) {
|
|
setReason("");
|
|
setErrorMessage(null);
|
|
setIsSubmitting(false);
|
|
}
|
|
}, [open]);
|
|
|
|
const isValid = useMemo(() => {
|
|
const noSpaces = reason.replace(/\s/g, "");
|
|
const length = noSpaces.length;
|
|
return length >= 12 && length <= 400;
|
|
}, [reason]);
|
|
|
|
const handleSave = async () => {
|
|
if (!transactionId || !newStatus || !isValid) return;
|
|
|
|
setErrorMessage(null);
|
|
setIsSubmitting(true);
|
|
|
|
try {
|
|
const payload = {
|
|
data: {
|
|
status: newStatus,
|
|
notes: reason.trim(),
|
|
},
|
|
fields: ["Status", "Notes"],
|
|
};
|
|
|
|
const response = await fetch(
|
|
`/api/dashboard/transactions/${transactionId}`,
|
|
{
|
|
method: "PUT",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(payload),
|
|
}
|
|
);
|
|
|
|
const result = await response.json();
|
|
|
|
if (!response.ok) {
|
|
throw new Error(
|
|
result?.message || result?.error || "Failed to update transaction"
|
|
);
|
|
}
|
|
|
|
setReason("");
|
|
setErrorMessage(null);
|
|
onStatusUpdated?.();
|
|
onClose();
|
|
} catch (err) {
|
|
setErrorMessage(
|
|
err instanceof Error ? err.message : "Failed to update transaction"
|
|
);
|
|
} finally {
|
|
setIsSubmitting(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Dialog open={open} onClose={onClose} fullWidth maxWidth="sm">
|
|
<DialogTitle>Change Status</DialogTitle>
|
|
<DialogContent>
|
|
You want to change the status to <b>{newStatus}</b>. Please provide a
|
|
reason for the change.
|
|
<TextField
|
|
label="Reason for change"
|
|
variant="outlined"
|
|
fullWidth
|
|
multiline
|
|
rows={4}
|
|
value={reason}
|
|
onChange={e => setReason(e.target.value)}
|
|
helperText="Reason must be between 12 and 400 characters"
|
|
sx={{ mt: 2 }}
|
|
/>
|
|
{errorMessage && (
|
|
<Alert severity="error" sx={{ mt: 2 }}>
|
|
{errorMessage}
|
|
</Alert>
|
|
)}
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<Button onClick={onClose} disabled={isSubmitting}>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
variant="contained"
|
|
onClick={handleSave}
|
|
disabled={!isValid || isSubmitting || !transactionId}
|
|
startIcon={isSubmitting ? <CircularProgress size={18} /> : undefined}
|
|
>
|
|
Save
|
|
</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
);
|
|
};
|
|
|
|
export default StatusChangeDialog;
|