Files
kandev/apps/web/components/automations/automation-trigger-drafts.ts
T
2026-07-19 14:21:36 +01:00

172 lines
5.5 KiB
TypeScript

import { useRef, useState, type Dispatch, type MutableRefObject, type SetStateAction } from "react";
import {
addTrigger,
deleteTrigger,
getAutomation,
updateTrigger,
} from "@/lib/api/domains/automation-api";
import type { AutomationTrigger, TriggerType } from "@/lib/types/automation";
import { generateUUID } from "@/lib/utils";
import type { PendingTrigger } from "./automation-payload";
function pendingToTrigger(pending: PendingTrigger): AutomationTrigger {
return {
id: pending.tempId,
automation_id: "",
type: pending.type,
config: pending.config,
enabled: pending.enabled,
last_evaluated_at: null,
created_at: "",
updated_at: "",
};
}
export function useAutomationTriggerDrafts(currentId: string | null) {
const [triggers, setTriggers] = useState<AutomationTrigger[]>([]);
const [baselineTriggers, setBaselineTriggers] = useState<AutomationTrigger[]>([]);
const [pending, setPending] = useState<PendingTrigger[]>([]);
const latestPendingRef = useRef(pending);
latestPendingRef.current = pending;
const loadTriggers = (next: AutomationTrigger[]) => {
setTriggers(next);
setBaselineTriggers(next);
setPending([]);
};
const handleAdd = async (type: TriggerType, config: Record<string, unknown>) => {
setPending((items) => [...items, { tempId: generateUUID(), type, config, enabled: true }]);
};
const updateDraft = (triggerId: string, update: Partial<AutomationTrigger>) => {
if (pending.some((trigger) => trigger.tempId === triggerId)) {
setPending((items) =>
items.map((trigger) =>
trigger.tempId === triggerId ? { ...trigger, ...update } : trigger,
),
);
return;
}
setTriggers((items) =>
items.map((trigger) => (trigger.id === triggerId ? { ...trigger, ...update } : trigger)),
);
};
const handleUpdate = async (triggerId: string, config: Record<string, unknown>) => {
updateDraft(triggerId, { config });
};
const handleToggle = async (triggerId: string, enabled: boolean) => {
updateDraft(triggerId, { enabled });
};
const handleDelete = async (triggerId: string) => {
setPending((items) => items.filter((trigger) => trigger.tempId !== triggerId));
setTriggers((items) => items.filter((trigger) => trigger.id !== triggerId));
};
const allTriggers = [...triggers, ...pending.map(pendingToTrigger)];
const persistDrafts = async (): Promise<AutomationTrigger[]> => {
if (!currentId) return allTriggers;
await deleteRemovedTriggers(baselineTriggers, triggers, setBaselineTriggers);
await updateChangedTriggers(baselineTriggers, triggers, setBaselineTriggers);
await createPendingTriggers({
automationId: currentId,
pending,
latestPendingRef,
setPending,
setTriggers,
setBaseline: setBaselineTriggers,
});
const refreshed = await getAutomation(currentId);
setBaselineTriggers(refreshed.triggers ?? []);
return refreshed.triggers ?? [];
};
return {
loadTriggers,
baselineTriggers,
pending,
clearPending: () => setPending([]),
discardDrafts: () => {
setTriggers(baselineTriggers);
setPending([]);
},
persistDrafts,
allTriggers,
handleAdd,
handleUpdate,
handleToggle,
handleDelete,
};
}
async function deleteRemovedTriggers(
baseline: AutomationTrigger[],
current: AutomationTrigger[],
setBaseline: Dispatch<SetStateAction<AutomationTrigger[]>>,
) {
const currentIds = new Set(current.map((trigger) => trigger.id));
for (const trigger of baseline) {
if (currentIds.has(trigger.id)) continue;
await deleteTrigger(trigger.id);
setBaseline((items) => items.filter((item) => item.id !== trigger.id));
}
}
async function updateChangedTriggers(
baseline: AutomationTrigger[],
current: AutomationTrigger[],
setBaseline: Dispatch<SetStateAction<AutomationTrigger[]>>,
) {
for (const trigger of current) {
const saved = baseline.find((item) => item.id === trigger.id);
if (!saved || triggerDraftEquals(trigger, saved)) continue;
await updateTrigger(trigger.id, { config: trigger.config, enabled: trigger.enabled });
setBaseline((items) => items.map((item) => (item.id === trigger.id ? trigger : item)));
}
}
interface CreatePendingTriggersOptions {
automationId: string;
pending: PendingTrigger[];
latestPendingRef: MutableRefObject<PendingTrigger[]>;
setPending: Dispatch<SetStateAction<PendingTrigger[]>>;
setTriggers: Dispatch<SetStateAction<AutomationTrigger[]>>;
setBaseline: Dispatch<SetStateAction<AutomationTrigger[]>>;
}
async function createPendingTriggers({
automationId,
pending,
latestPendingRef,
setPending,
setTriggers,
setBaseline,
}: CreatePendingTriggersOptions) {
for (const draft of pending) {
const created = await addTrigger({
automation_id: automationId,
type: draft.type,
config: draft.config,
enabled: draft.enabled,
});
const latestDraft = latestPendingRef.current.find((item) => item.tempId === draft.tempId);
setPending((items) => items.filter((item) => item.tempId !== draft.tempId));
if (latestDraft) {
setTriggers((items) => [
...items,
{ ...created, config: latestDraft.config, enabled: latestDraft.enabled },
]);
}
setBaseline((items) => [...items, created]);
}
}
function triggerDraftEquals(left: AutomationTrigger, right: AutomationTrigger): boolean {
return (
left.enabled === right.enabled && JSON.stringify(left.config) === JSON.stringify(right.config)
);
}