Reloading redesign
This commit is contained in:
@@ -5,9 +5,10 @@ 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 { autosaveCampaignVersion } from "../../api/campaigns";
|
||||
import { useCampaignWorkspaceData } from "./hooks/useCampaignWorkspaceData";
|
||||
import { asArray, asRecord, formatDateTime, getTemplateSection, isAuditLockedVersion, versionLockReason } from "./utils/campaignView";
|
||||
import { asArray, asRecord, formatDateTime, isAuditLockedVersion, versionLockReason } from "./utils/campaignView";
|
||||
import { cloneJson, ensureCampaignDraft, getBool, getText, updateNested } from "./utils/draftEditor";
|
||||
import { useRegisterCampaignUnsavedChanges } from "./context/UnsavedChangesContext";
|
||||
|
||||
@@ -38,23 +39,23 @@ export default function TemplateDataPage({ settings, campaignId }: { settings: A
|
||||
const [previewOpen, setPreviewOpen] = useState(false);
|
||||
const [previewIndex, setPreviewIndex] = useState(0);
|
||||
const [undefinedDialog, setUndefinedDialog] = useState<UndefinedPlaceholder | null>(null);
|
||||
const loadedVersionId = useRef<string | null>(null);
|
||||
const subjectRef = useRef<HTMLInputElement | null>(null);
|
||||
const textRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
const htmlRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
|
||||
const version = data.currentVersion;
|
||||
const locked = isAuditLockedVersion(version);
|
||||
const template = draft ? asRecord(draft.template) : getTemplateSection(version);
|
||||
const fields = useMemo(() => asArray(draft?.fields).map(asRecord), [draft]);
|
||||
const displayDraft = draft ?? ensureCampaignDraft(null);
|
||||
const template = asRecord(displayDraft.template);
|
||||
const fields = useMemo(() => asArray(displayDraft.fields).map(asRecord), [displayDraft.fields]);
|
||||
const localFieldNames = useMemo(() => fields.map((field) => String(field.name || field.id || "")).filter(Boolean), [fields]);
|
||||
const globalFieldNames = useMemo(() => uniqueSorted([...localFieldNames, ...Object.keys(asRecord(draft?.global_values))]), [draft?.global_values, localFieldNames]);
|
||||
const globalFieldNames = useMemo(() => uniqueSorted([...localFieldNames, ...Object.keys(asRecord(displayDraft.global_values))]), [displayDraft.global_values, localFieldNames]);
|
||||
const allAvailableNames = useMemo(() => new Set([...localFieldNames, ...globalFieldNames]), [localFieldNames, globalFieldNames]);
|
||||
const entries = asRecord(draft?.entries);
|
||||
const entries = asRecord(displayDraft.entries);
|
||||
const inlineEntries = useMemo(() => asArray(entries.inline).map(asRecord), [entries.inline]);
|
||||
const previewEntries = inlineEntries.length > 0 ? inlineEntries : [{}];
|
||||
const previewEntry = previewEntries[Math.min(previewIndex, previewEntries.length - 1)] ?? {};
|
||||
const ignoreEmptyFields = getBool(asRecord(draft?.validation_policy), "ignore_empty_fields", false);
|
||||
const ignoreEmptyFields = getBool(asRecord(displayDraft.validation_policy), "ignore_empty_fields", false);
|
||||
const templateText = `${getText(template, "subject")}\n${getText(template, "text")}\n${getText(template, "html")}`;
|
||||
const usedPlaceholders = useMemo(() => extractTemplatePlaceholders(templateText), [templateText]);
|
||||
const invalidNamespacePlaceholders = useMemo(() => uniquePlaceholders(usedPlaceholders.filter((field) => !field.validNamespace)), [usedPlaceholders]);
|
||||
@@ -64,15 +65,13 @@ export default function TemplateDataPage({ settings, campaignId }: { settings: A
|
||||
...field,
|
||||
reason: field.validNamespace ? "missing-field" : "invalid-namespace"
|
||||
}))), [usedPlaceholders, allAvailableNames]);
|
||||
const previewContext = useMemo(() => buildPreviewContext(draft, previewEntry), [draft, previewEntry]);
|
||||
const previewContext = useMemo(() => buildPreviewContext(displayDraft, previewEntry), [displayDraft, previewEntry]);
|
||||
const previewSubject = renderPreviewText(getText(template, "subject"), previewContext, ignoreEmptyFields);
|
||||
const previewText = renderPreviewText(getText(template, "text"), previewContext, ignoreEmptyFields);
|
||||
const previewHtml = renderPreviewText(getText(template, "html"), previewContext, ignoreEmptyFields);
|
||||
|
||||
useEffect(() => {
|
||||
if (!version) return;
|
||||
if (loadedVersionId.current === version.id) return;
|
||||
loadedVersionId.current = version.id;
|
||||
setDraft(ensureCampaignDraft(version));
|
||||
setDirty(false);
|
||||
setPreviewIndex(0);
|
||||
@@ -196,7 +195,7 @@ export default function TemplateDataPage({ settings, campaignId }: { settings: A
|
||||
{localError && <div className="alert danger">{localError}</div>}
|
||||
{locked && <div className="alert info">This version is read-only. {versionLockReason(version)}</div>}
|
||||
|
||||
{draft && (
|
||||
<LoadingFrame loading={loading || !draft} label="Loading campaign draft…">
|
||||
<>
|
||||
<div className="dashboard-grid template-editor-grid">
|
||||
<Card title="Editable template" actions={<Button onClick={() => setPreviewOpen(true)}>Preview</Button>}>
|
||||
@@ -281,7 +280,7 @@ export default function TemplateDataPage({ settings, campaignId }: { settings: A
|
||||
<Button variant="primary" onClick={() => saveTemplate("manual")} disabled={!dirty || locked || !draft}>Save</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</LoadingFrame>
|
||||
|
||||
{previewOpen && (
|
||||
<TemplatePreviewOverlay
|
||||
|
||||
Reference in New Issue
Block a user