Dashboard tag filter now covers every tag, not just ones already in use; add a default tag

The dashboard's tag filter chips were derived from tags already attached to
a currently-loaded ticket — a brand-new tag (e.g. VIP was the only one ever
used) had nothing to filter by until someone happened to apply it. The
filter bar now lists every tag defined in Settings, fetched server-side
alongside the ticket list.

Also added a "default tag" concept: mark one tag as default in Settings
(star icon), and it's auto-attached to every new ticket the moment it's
created, across every channel (email/telegram/widget/manual) — matches the
request to have "В порядке очереди" applied by default.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GteWhnWKTmnXcsd5jx6H7u
This commit is contained in:
ogrechkoandClaude Sonnet 5 committed 2026-08-19 08:09:14 +00:00
1 parent 5af360fd15
commit b11ec711c5
10 files changed
+1139 -15

No files matched your search

+23 -2
View File
@@ -89,7 +89,7 @@ async function getTagsForTickets(ticketIds: string[]): Promise<Map<string, TagDT
for (const row of rows) {
const list = map.get(row.ticketId) ?? [];
list.push({ id: row.tag.id, name: row.tag.name, color: row.tag.color });
list.push({ id: row.tag.id, name: row.tag.name, color: row.tag.color, isDefault: row.tag.isDefault });
map.set(row.ticketId, list);
}
return map;
@@ -158,6 +158,13 @@ async function appendMessage(params: {
}));
}
if (params.ticketIsNew) {
const defaultTagId = await getDefaultTagId();
if (defaultTagId) {
await db.insert(ticketTags).values({ ticketId: params.ticket.id, tagId: defaultTagId });
}
}
const ticket = await touchTicket(params.ticket.id, { lastMessageAt: new Date() });
const name = await customerName(ticket.customerId);
const ticketTagsList = await getTagsForTicket(ticket.id);
@@ -685,7 +692,7 @@ export async function deleteCannedResponse(id: string) {
export async function listTags(): Promise<TagDTO[]> {
const rows = await db.query.tags.findMany({ orderBy: tagsTable.name });
return rows.map((r) => ({ id: r.id, name: r.name, color: r.color }));
return rows.map((r) => ({ id: r.id, name: r.name, color: r.color, isDefault: r.isDefault }));
}
export async function createTag(name: string, color: TagDTO["color"]) {
@@ -697,6 +704,20 @@ export async function deleteTag(id: string) {
await db.delete(tagsTable).where(eq(tagsTable.id, id));
}
/** At most one default at a time — pass null to clear it entirely. */
export async function setDefaultTag(id: string | null): Promise<TagDTO[]> {
await db.update(tagsTable).set({ isDefault: false }).where(eq(tagsTable.isDefault, true));
if (id) {
await db.update(tagsTable).set({ isDefault: true }).where(eq(tagsTable.id, id));
}
return listTags();
}
async function getDefaultTagId(): Promise<string | null> {
const row = await db.query.tags.findFirst({ where: eq(tagsTable.isDefault, true) });
return row?.id ?? null;
}
/** Full replace — the ticket ends up tagged with exactly this set. */
export async function setTicketTags(ticketId: string, tagIds: string[]): Promise<TagDTO[]> {
await db.delete(ticketTags).where(eq(ticketTags.ticketId, ticketId));