mirror of
https://github.com/fosrl/pangolin.git
synced 2026-06-05 23:28:44 +00:00
104 lines
3.0 KiB
TypeScript
104 lines
3.0 KiB
TypeScript
import type { SelectedLabel } from "@app/components/labels-selector";
|
|
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
|
import { useState, useMemo } from "react";
|
|
import { toast } from "./useToast";
|
|
import { useEnvContext } from "./useEnvContext";
|
|
import { useTranslations } from "next-intl";
|
|
import { useRouter } from "next/navigation";
|
|
|
|
export type LabelToggleAction = {
|
|
label: SelectedLabel;
|
|
action: "attach" | "detach";
|
|
};
|
|
|
|
function computeLabelToggleActions(
|
|
values: SelectedLabel[],
|
|
actions: LabelToggleAction[]
|
|
) {
|
|
let newValues = [...values];
|
|
for (const { action, label } of actions) {
|
|
if (action === "attach") {
|
|
newValues = [...newValues, label];
|
|
} else {
|
|
newValues = newValues.filter((lb) => lb.labelId !== label.labelId);
|
|
}
|
|
}
|
|
|
|
return newValues;
|
|
}
|
|
|
|
type UseOptimisticLabelsArgs = {
|
|
serverLabels: SelectedLabel[] | undefined;
|
|
orgId: string;
|
|
entityId: number;
|
|
entityIdField: string;
|
|
};
|
|
|
|
export function useOptimisticLabels({
|
|
serverLabels,
|
|
orgId,
|
|
entityId,
|
|
entityIdField
|
|
}: UseOptimisticLabelsArgs) {
|
|
const router = useRouter();
|
|
const labels = serverLabels ?? [];
|
|
const api = createApiClient(useEnvContext());
|
|
const t = useTranslations();
|
|
|
|
const [pendingActions, setPendingActions] = useState<LabelToggleAction[]>(
|
|
[]
|
|
);
|
|
|
|
const localLabels = useMemo(
|
|
() => computeLabelToggleActions(labels ?? [], pendingActions),
|
|
[labels, pendingActions]
|
|
);
|
|
|
|
async function toggleLabel(
|
|
label: SelectedLabel,
|
|
action: "attach" | "detach"
|
|
) {
|
|
const oppositeAction = action === "attach" ? "detach" : "attach";
|
|
const existingActionIndex = pendingActions.findIndex(
|
|
(pending) =>
|
|
pending.action === oppositeAction &&
|
|
pending.label.labelId === label.labelId
|
|
);
|
|
|
|
// if there are two actions that cancel each-other
|
|
// they should just be removed
|
|
if (existingActionIndex !== -1) {
|
|
setPendingActions((prevActions) =>
|
|
prevActions.toSpliced(existingActionIndex, 1)
|
|
);
|
|
} else {
|
|
setPendingActions((actions) => [...actions, { label, action }]);
|
|
}
|
|
|
|
try {
|
|
if (action === "attach") {
|
|
await api.put(`/org/${orgId}/label/${label.labelId}/attach`, {
|
|
[entityIdField]: entityId
|
|
});
|
|
} else {
|
|
await api.put(`/org/${orgId}/label/${label.labelId}/detach`, {
|
|
[entityIdField]: entityId
|
|
});
|
|
}
|
|
} catch (e) {
|
|
toast({
|
|
title: t("error"),
|
|
description: formatAxiosError(e, t("errorOccurred")),
|
|
variant: "destructive"
|
|
});
|
|
}
|
|
}
|
|
|
|
async function refresh() {
|
|
router.refresh();
|
|
setPendingActions([]);
|
|
}
|
|
|
|
return { localLabels, toggleLabel, refresh };
|
|
}
|