103 lines
3.4 KiB
TypeScript
103 lines
3.4 KiB
TypeScript
import React, { useEffect, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
import { CustomButton, CustomInput, CustomModal } from "../../components/custom";
|
|
import { orgApi } from "./OrgApi";
|
|
import type { OrgUnit } from "./OrgTypes";
|
|
|
|
|
|
const SeatAllocationDialog: React.FC<{
|
|
unit: OrgUnit | null;
|
|
availableToThisUnit: number | null;
|
|
onClose: () => void;
|
|
onSaved: () => void | Promise<void>;
|
|
}> = ({ unit, availableToThisUnit, onClose, onSaved }) => {
|
|
const { t } = useTranslation(["organisation", "common"]);
|
|
|
|
const [value, setValue] = useState("");
|
|
const [isBusy, setIsBusy] = useState(false);
|
|
const [error, setError] = useState("");
|
|
|
|
useEffect(() => {
|
|
setValue(unit?.seat_limit == null ? "" : String(unit.seat_limit));
|
|
setError("");
|
|
}, [unit]);
|
|
|
|
if (!unit) return null;
|
|
|
|
const trimmed = value.trim();
|
|
const parsed = trimmed === "" ? null : Number(trimmed);
|
|
const isValid =
|
|
parsed === null || (Number.isInteger(parsed) && parsed >= 0);
|
|
|
|
const save = async () => {
|
|
if (!isValid) {
|
|
setError(t("seats.notAWholeNumber"));
|
|
return;
|
|
}
|
|
setError("");
|
|
setIsBusy(true);
|
|
try {
|
|
await orgApi.setSeats(unit.id, parsed);
|
|
await onSaved();
|
|
onClose();
|
|
} catch (failure) {
|
|
setError(
|
|
failure instanceof Error && failure.message
|
|
? failure.message
|
|
: t("seats.saveFailed")
|
|
);
|
|
} finally {
|
|
setIsBusy(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<CustomModal isOpen onClose={onClose} title={t("seats.title", { name: unit.name })}>
|
|
<div className="space-y-4">
|
|
<p className="text-sm text-[var(--text-secondary)]">
|
|
{t("seats.explanation")}
|
|
</p>
|
|
|
|
<CustomInput
|
|
label={t("seats.limit")}
|
|
type="number"
|
|
min={unit.seats_used}
|
|
placeholder={t("seats.uncapped")}
|
|
value={value}
|
|
onChange={(event) => setValue(event.target.value)}
|
|
/>
|
|
|
|
<p className="text-sm text-[var(--text-secondary)]">
|
|
{t("seats.currentUse", { count: unit.seats_used })}
|
|
{availableToThisUnit !== null && (
|
|
<>
|
|
{" · "}
|
|
{t("seats.availableToThisUnit", { count: availableToThisUnit })}
|
|
</>
|
|
)}
|
|
</p>
|
|
|
|
<p className="text-sm text-[var(--text-secondary)]">
|
|
{t("seats.emptyMeansUncapped")}
|
|
</p>
|
|
|
|
{error && (
|
|
<p className="text-sm text-red-600 dark:text-red-400">{error}</p>
|
|
)}
|
|
|
|
<div className="flex justify-end gap-2">
|
|
<CustomButton variant="secondary" onClick={onClose} disabled={isBusy}>
|
|
{t("common:actions.cancel")}
|
|
</CustomButton>
|
|
<CustomButton onClick={save} disabled={isBusy || !isValid}>
|
|
{t("common:actions.save")}
|
|
</CustomButton>
|
|
</div>
|
|
</div>
|
|
</CustomModal>
|
|
);
|
|
};
|
|
|
|
export default SeatAllocationDialog;
|