Files
saas_frontend/src/application/organisation/SeatAllocationDialog.tsx
T
2026-08-31 20:40:20 -04:00

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;