Code cleanup and deduplication
This commit is contained in:
@@ -1,16 +1,14 @@
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useMemo, useRef } from "react";
|
||||
import type { ApiSettings } from "../../types";
|
||||
import Button from "../../components/Button";
|
||||
import Card from "../../components/Card";
|
||||
import FormField from "../../components/FormField";
|
||||
import PageTitle from "../../components/PageTitle";
|
||||
import LoadingFrame from "../../components/LoadingFrame";
|
||||
import ToggleSwitch from "../../components/ToggleSwitch";
|
||||
import { autosaveCampaignVersion } from "../../api/campaigns";
|
||||
import { useCampaignWorkspaceData } from "./hooks/useCampaignWorkspaceData";
|
||||
import { asRecord, formatDateTime, getCampaignJson, isAuditLockedVersion, isRecord, versionLockReason } from "./utils/campaignView";
|
||||
import { ensureCampaignDraft, getBool, getText, updateNested } from "./utils/draftEditor";
|
||||
import { useRegisterCampaignUnsavedChanges } from "./context/UnsavedChangesContext";
|
||||
import { useCampaignDraftEditor } from "./hooks/useCampaignDraftEditor";
|
||||
import { asRecord, isAuditLockedVersion, isRecord, versionLockReason } from "./utils/campaignView";
|
||||
import { getBool, getText, updateNested } from "./utils/draftEditor";
|
||||
import FieldValueInput from "./components/FieldValueInput";
|
||||
|
||||
const fieldTypeOptions = ["string", "integer", "double", "date", "password"];
|
||||
@@ -26,35 +24,35 @@ type FieldDefinition = {
|
||||
|
||||
export default function CampaignFieldsPage({ settings, campaignId }: { settings: ApiSettings; campaignId: string }) {
|
||||
const { data, loading, error, reload, setError } = useCampaignWorkspaceData(settings, campaignId);
|
||||
const [draft, setDraft] = useState<Record<string, unknown> | null>(null);
|
||||
const [dirty, setDirty] = useState(false);
|
||||
const [saveState, setSaveState] = useState("Loaded");
|
||||
const [localError, setLocalError] = useState("");
|
||||
const fieldValueKeys = useRef<string[]>([]);
|
||||
|
||||
const version = data.currentVersion;
|
||||
const locked = isAuditLockedVersion(version);
|
||||
const displayDraft = draft ?? ensureCampaignDraft(null);
|
||||
const { draft, setDraft, displayDraft, dirty, saveState, setSaveState, localError, setLocalError, markDirty, saveDraft } = useCampaignDraftEditor({
|
||||
settings,
|
||||
campaignId,
|
||||
version,
|
||||
locked,
|
||||
reload,
|
||||
setError,
|
||||
currentStep: "campaign-fields",
|
||||
unsavedTitle: "Unsaved fields",
|
||||
unsavedMessage: "Campaign fields have unsaved changes. Save them before leaving, or discard them and continue.",
|
||||
transformLoadedDraft: (loadedVersion, loadedDraft) => migrateFieldOverridePolicy(loadedDraft, asRecord(loadedVersion.editor_state)),
|
||||
onLoaded: (_loadedVersion, loadedDraft) => {
|
||||
fieldValueKeys.current = normalizeFields(loadedDraft.fields).map((field) => field.name);
|
||||
}
|
||||
});
|
||||
const fields = useMemo(() => normalizeFields(displayDraft.fields), [displayDraft.fields]);
|
||||
const globalValues = asRecord(displayDraft.global_values);
|
||||
const fieldNameWarning = useMemo(() => describeFieldNameProblem(fields), [fields]);
|
||||
const canSave = dirty && !locked && Boolean(draft) && !fieldNameWarning;
|
||||
|
||||
useEffect(() => {
|
||||
if (!version) return;
|
||||
const nextDraft = migrateFieldOverridePolicy(ensureCampaignDraft(version), asRecord(version.editor_state));
|
||||
fieldValueKeys.current = normalizeFields(nextDraft.fields).map((field) => field.name);
|
||||
setDraft(nextDraft);
|
||||
setDirty(false);
|
||||
setSaveState(version.autosaved_at ? `Loaded saved draft ${formatDateTime(version.autosaved_at)}` : "Loaded");
|
||||
}, [version]);
|
||||
|
||||
|
||||
function patchDraft(path: string[], value: unknown) {
|
||||
if (locked) return;
|
||||
setDraft((current) => updateNested(current ?? {}, path, value));
|
||||
setDirty(true);
|
||||
setLocalError("");
|
||||
markDirty();
|
||||
}
|
||||
|
||||
function patchFields(nextFields: FieldDefinition[]) {
|
||||
@@ -97,8 +95,7 @@ export default function CampaignFieldsPage({ settings, campaignId }: { settings:
|
||||
const nextDraft = updateNested(current ?? {}, ["fields"], nextFields);
|
||||
return updateNested(nextDraft, ["global_values"], nextGlobalValues);
|
||||
});
|
||||
setDirty(true);
|
||||
setLocalError("");
|
||||
markDirty();
|
||||
}
|
||||
|
||||
function addField() {
|
||||
@@ -110,8 +107,7 @@ export default function CampaignFieldsPage({ settings, campaignId }: { settings:
|
||||
const nextDraft = updateNested(current ?? {}, ["fields"], nextFields);
|
||||
return updateNested(nextDraft, ["global_values"], nextGlobalValues);
|
||||
});
|
||||
setDirty(true);
|
||||
setLocalError("");
|
||||
markDirty();
|
||||
}
|
||||
|
||||
function deleteField(index: number) {
|
||||
@@ -126,8 +122,7 @@ export default function CampaignFieldsPage({ settings, campaignId }: { settings:
|
||||
const nextDraft = updateNested(current ?? {}, ["fields"], nextFields);
|
||||
return updateNested(nextDraft, ["global_values"], nextGlobalValues);
|
||||
});
|
||||
setDirty(true);
|
||||
setLocalError("");
|
||||
markDirty();
|
||||
}
|
||||
|
||||
function setGlobalValue(key: string, value: unknown) {
|
||||
@@ -138,44 +133,16 @@ export default function CampaignFieldsPage({ settings, campaignId }: { settings:
|
||||
setField(index, { can_override: allowed });
|
||||
}
|
||||
|
||||
async function saveDraft(mode: "auto" | "manual" = "manual"): Promise<boolean> {
|
||||
if (!draft || !version || locked) return false;
|
||||
async function saveFields(): Promise<boolean> {
|
||||
const fieldProblem = describeFieldNameProblem(fields);
|
||||
if (fieldProblem) {
|
||||
setLocalError(fieldProblem);
|
||||
setSaveState("Save blocked");
|
||||
return false;
|
||||
}
|
||||
setSaveState("Saving…");
|
||||
setError("");
|
||||
setLocalError("");
|
||||
try {
|
||||
const saved = await autosaveCampaignVersion(settings, campaignId, version.id, {
|
||||
campaign_json: draft,
|
||||
current_flow: "manual",
|
||||
current_step: "campaign-fields",
|
||||
workflow_state: "editing",
|
||||
is_complete: false
|
||||
});
|
||||
setDraft(getCampaignJson(saved));
|
||||
setDirty(false);
|
||||
setSaveState(`Saved ${formatDateTime(saved.autosaved_at ?? saved.updated_at)}`);
|
||||
await reload();
|
||||
return true;
|
||||
} catch (err) {
|
||||
const text = err instanceof Error ? err.message : String(err);
|
||||
setLocalError(text);
|
||||
setSaveState("Save failed");
|
||||
return false;
|
||||
}
|
||||
return saveDraft("manual");
|
||||
}
|
||||
|
||||
useRegisterCampaignUnsavedChanges(dirty && !locked ? {
|
||||
title: "Unsaved fields",
|
||||
message: "Fields have unsaved changes. Save them before leaving, or discard them and continue.",
|
||||
onSave: () => saveDraft("manual"),
|
||||
onDiscard: () => setDirty(false)
|
||||
} : null);
|
||||
|
||||
return (
|
||||
<div className="content-pad workspace-data-page">
|
||||
@@ -186,7 +153,7 @@ export default function CampaignFieldsPage({ settings, campaignId }: { settings:
|
||||
</div>
|
||||
<div className="button-row compact-actions">
|
||||
<Button onClick={reload} disabled={loading}>Reload</Button>
|
||||
<Button variant="primary" onClick={() => saveDraft("manual")} disabled={!canSave}>Save</Button>
|
||||
<Button variant="primary" onClick={saveFields} disabled={!canSave}>Save</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -240,7 +207,7 @@ export default function CampaignFieldsPage({ settings, campaignId }: { settings:
|
||||
</Card>
|
||||
|
||||
<div className="button-row page-bottom-actions">
|
||||
<Button variant="primary" onClick={() => saveDraft("manual")} disabled={!canSave}>Save</Button>
|
||||
<Button variant="primary" onClick={saveFields} disabled={!canSave}>Save</Button>
|
||||
</div>
|
||||
</>
|
||||
</LoadingFrame>
|
||||
|
||||
Reference in New Issue
Block a user