feat: persist support ticket drafts

This commit is contained in:
3252a8
2026-05-27 08:10:08 +03:00
parent 69d3400310
commit e90988ea5c
6 changed files with 183 additions and 2 deletions
@@ -7,10 +7,17 @@
import { Skeleton } from "$components/ui/index.js";
import { TicketCard } from "$components/patterns/webapp/index.js";
import { Select, Tabs } from "$components/ui/primitives.js";
import {
clearSupportDraft,
readSupportDraft,
supportDraftScope,
writeSupportDraft,
} from "$lib/webapp/supportDrafts.js";
export let t = (key) => key;
export let maxSubjectLength = 160;
export let maxBodyLength = 4000;
export let user = {};
const supportStore = getContext("supportStore");
let subject = "";
@@ -18,6 +25,7 @@
let category = "other";
let priority = "normal";
let createOpen = false;
let loadedCreateDraftScope = "";
$: ({ tickets, loading, creating, statusFilter, counts } = $supportStore);
$: categoryOptions = [
@@ -56,14 +64,31 @@
categoryOptions.find((option) => option.value === category) || categoryOptions[0];
$: selectedPriority =
priorityOptions.find((option) => option.value === priority) || priorityOptions[0];
$: draftScope = supportDraftScope(user);
$: if (draftScope && draftScope !== loadedCreateDraftScope) {
loadCreateDraft(draftScope);
}
$: if (draftScope && draftScope === loadedCreateDraftScope) {
persistCreateDraft(draftScope, {
subject,
body,
category,
priority,
open: createOpen,
maxSubjectLength,
maxBodyLength,
});
}
onMount(() => {
supportStore.loadList();
});
async function createTicket() {
const currentDraftScope = draftScope;
const ticket = await supportStore.createTicket({ subject, body, category, priority });
if (ticket) {
clearSupportDraft("new", currentDraftScope);
subject = "";
body = "";
category = "other";
@@ -71,6 +96,43 @@
createOpen = false;
}
}
function optionValue(options, value, fallback) {
return options.some((option) => option.value === value) ? value : fallback;
}
function loadCreateDraft(scope) {
const draft = readSupportDraft("new", scope);
subject = typeof draft?.subject === "string" ? draft.subject.slice(0, maxSubjectLength) : "";
body = typeof draft?.body === "string" ? draft.body.slice(0, maxBodyLength) : "";
category = optionValue(categoryOptions, draft?.category, "other");
priority = optionValue(priorityOptions, draft?.priority, "normal");
createOpen = Boolean(draft?.open || subject.trim() || body.trim());
loadedCreateDraftScope = scope;
}
function persistCreateDraft(scope, draft) {
const draftSubject = String(draft.subject || "").slice(0, draft.maxSubjectLength);
const draftBody = String(draft.body || "").slice(0, draft.maxBodyLength);
const hasDraft =
Boolean(draftSubject.trim()) ||
Boolean(draftBody.trim()) ||
draft.category !== "other" ||
draft.priority !== "normal";
if (!hasDraft) {
clearSupportDraft("new", scope);
return;
}
writeSupportDraft("new", scope, "new", {
subject: draftSubject,
body: draftBody,
category: draft.category,
priority: draft.priority,
open: draft.open || Boolean(draftSubject.trim() || draftBody.trim()),
});
}
</script>
<main class="content with-nav support-screen">
@@ -4,10 +4,17 @@
import Card from "$components/ui/card.svelte";
import { ArrowLeft } from "$components/ui/icons.js";
import { TicketComposer, TicketMessageBubble } from "$components/patterns/webapp/index.js";
import {
clearSupportDraft,
readSupportDraft,
supportDraftScope,
writeSupportDraft,
} from "$lib/webapp/supportDrafts.js";
export let t = (key) => key;
export let maxBodyLength = 4000;
export let brand = {};
export let user = {};
export let userAvatarUrl = "";
export let userInitials = "";
@@ -15,12 +22,30 @@
let reply = "";
let messagesScrollEl;
let lastMessageKey = "";
let replyDraftKey = "";
$: ({ openedTicket, messages, detailLoading, sending } = $supportStore);
$: closed = ["resolved", "closed"].includes(openedTicket?.status);
$: ticketId = openedTicket?.ticket_id || "";
$: draftScope = supportDraftScope(user);
$: nextReplyDraftKey = ticketId ? `${draftScope}:${ticketId}` : "";
$: if (nextReplyDraftKey && nextReplyDraftKey !== replyDraftKey) {
const draft = readSupportDraft("reply", draftScope, ticketId);
reply = typeof draft?.body === "string" ? draft.body.slice(0, maxBodyLength) : "";
replyDraftKey = nextReplyDraftKey;
}
$: if (nextReplyDraftKey && replyDraftKey === nextReplyDraftKey && !closed) {
const body = String(reply || "").slice(0, maxBodyLength);
if (body.trim()) writeSupportDraft("reply", draftScope, ticketId, { body });
else clearSupportDraft("reply", draftScope, ticketId);
}
async function send(body) {
await supportStore.sendReply(body);
const currentTicketId = ticketId;
const currentDraftScope = draftScope;
const sent = await supportStore.sendReply(body);
if (!sent) return;
if (currentTicketId) clearSupportDraft("reply", currentDraftScope, currentTicketId);
reply = "";
}