1import { Badge, Tooltip } from "frappe-ui";
2import { h } from "vue";
3import { dayjsLocal } from "./timezone";
4import { __ } from "./translation";
5import { isSlaMissed, serverInstant, shortDuration } from "./utils";
6
7/** Tallest bars faded per level: High is fully solid, Low nearly empty. */
8const FADED_BARS: Record<string, number> = { High: 0, Medium: 1, Low: 2 };
9
10// Shortest first; fading counts from the tallest.
11const BARS = [
12 { x: 0, y: 8, height: 4 },
13 { x: 4, y: 4, height: 8 },
14 { x: 8, y: 0, height: 12 },
15];
16
17export function PriorityIcon({ level }: { level: string }) {
18 return level === "Urgent" ? urgentIcon() : levelBars(level);
19}
20PriorityIcon.props = ["level"];
21
22// No SLA: nothing promised.
23export function responseBadge(row: any, due?: string) {
24 if (!row.sla) return null;
25 return slaBadge(row.first_responded_on, due, "amber");
26}
27
28export function resolutionBadge(row: any, due: string | undefined, paused: boolean) {
29 if (!row.sla) return null;
30 if (paused) return badge(__("Paused"), "blue");
31 return slaBadge(row.resolution_date, due, "violet");
32}
33
34function urgentIcon() {
35 const glyph = "fill-surface-gray-1";
36 // Fills its slot, so callers set the size.
37 return h("svg", { class: "size-full", viewBox: "0 0 14 14", fill: "none" }, [
38 h("rect", { width: 14, height: 14, rx: 4, class: "fill-ink-gray-6" }),
39 h("rect", { x: 6.25, y: 3, width: 1.5, height: 4.75, rx: 0.75, class: glyph }),
40 h("circle", { cx: 7, cy: 10, r: 0.9, class: glyph }),
41 ]);
42}
43
44function levelBars(level: string) {
45 const faded = FADED_BARS[level] ?? 0;
46 const bars = BARS.map(({ x, y, height }, index) =>
47 h("rect", {
48 x,
49 y,
50 width: 2.5,
51 height,
52 rx: 0.5,
53 class: BARS.length - 1 - index < faded ? "fill-ink-gray-3" : "fill-ink-gray-6",
54 })
55 );
56 return h("svg", { class: "h-3 w-3", viewBox: "0 0 10 12", fill: "none" }, bars);
57}
58
59function slaBadge(metOn: string | undefined, due: string | undefined, countdownTheme: string) {
60 // No due date: any met time counts as fulfilled.
61 if (metOn) return outcomeBadge(!isSlaMissed(metOn, due));
62 if (!due) return null;
63 if (serverInstant(due).isBefore()) return outcomeBadge(false);
64 return h(Tooltip, { text: dayjsLocal(due).format("LLLL") }, () =>
65 badge(shortDuration(due), countdownTheme)
66 );
67}
68
69function outcomeBadge(fulfilled: boolean) {
70 return fulfilled ? badge(__("Fulfilled"), "gray") : badge(__("Failed"), "red");
71}
72
73function badge(label: string, theme: string) {
74 return h(Badge, { label, theme, variant: "subtle" });
75}
76