diff --git a/app/Support/ModuleRegistration.php b/app/Support/ModuleRegistration.php index aa51851..5f14ed3 100644 --- a/app/Support/ModuleRegistration.php +++ b/app/Support/ModuleRegistration.php @@ -17,6 +17,8 @@ public static function register(string $modulePath): void 'title' => 'tasksprojects::menu.title', 'link' => '/admin/modules/tasks-projects', 'icon' => 'ClipboardDocumentListIcon', + // Lower sorts first within the sidebar group; official modules use 10, 20, ... + 'priority' => 10, ]); Registry::registerSettings('tasks-projects', [ diff --git a/dist/init.js b/dist/init.js index 8120ed3..ed09696 100644 --- a/dist/init.js +++ b/dist/init.js @@ -1,6 +1,6 @@ -const { Fragment: e, computed: t, createBlock: n, createCommentVNode: r, createElementBlock: i, createElementVNode: a, createTextVNode: o, createVNode: s, defineComponent: c, getCurrentInstance: l, h: u, normalizeClass: d, normalizeStyle: f, onBeforeUnmount: p, onMounted: m, openBlock: h, reactive: g, ref: _, renderList: v, resolveComponent: y, toDisplayString: b, unref: x, vShow: S, watch: C, withCtx: w, withDirectives: T, withModifiers: E } = window.__invoiceshelf_vue; +const { Fragment: e, Teleport: t, computed: n, createBlock: r, createCommentVNode: i, createElementBlock: a, createElementVNode: o, createTextVNode: s, createVNode: c, defineComponent: l, getCurrentInstance: u, h: d, normalizeClass: f, normalizeStyle: p, onBeforeUnmount: m, onMounted: h, openBlock: g, reactive: _, ref: v, renderList: y, resolveComponent: b, toDisplayString: x, unref: S, vModelText: C, vShow: w, watch: T, withCtx: E, withDirectives: D, withKeys: O, withModifiers: k } = window.__invoiceshelf_vue; //#region resources/js/messages.ts -var D = { en: { tasks_projects: { +var A = { en: { tasks_projects: { general: { home: "Home", filter: "Filter", @@ -61,58 +61,58 @@ var D = { en: { tasks_projects: { delete_failed: "Unable to delete the project.", customers_failed: "Unable to load the customers." } -} } }, O = "/api/v1/tasks-projects", k = { - projects: `${O}/projects`, - project: (e) => `${O}/projects/${e}`, - archiveProject: (e) => `${O}/projects/${e}/archive`, - unarchiveProject: (e) => `${O}/projects/${e}/unarchive`, - members: `${O}/members`, - settings: `${O}/settings` -}, A = { customers: "/api/v1/customers" }; -async function j(e, t) { - let { data: n } = await e.get(k.projects, { params: t }); +} } }, j = "/api/v1/tasks-projects", M = { + projects: `${j}/projects`, + project: (e) => `${j}/projects/${e}`, + archiveProject: (e) => `${j}/projects/${e}/archive`, + unarchiveProject: (e) => `${j}/projects/${e}/unarchive`, + members: `${j}/members`, + settings: `${j}/settings` +}, N = { customers: "/api/v1/customers" }; +async function P(e, t) { + let { data: n } = await e.get(M.projects, { params: t }); return n; } -async function M(e, t) { - let { data: n } = await e.post(k.projects, t); +async function F(e, t) { + let { data: n } = await e.post(M.projects, t); return n.data; } -async function ee(e, t, n) { - let { data: r } = await e.put(k.project(t), n); +async function I(e, t, n) { + let { data: r } = await e.put(M.project(t), n); return r.data; } -async function N(e, t) { - let { data: n } = await e.post(k.archiveProject(t)); +async function L(e, t) { + let { data: n } = await e.post(M.archiveProject(t)); return n.data; } -async function P(e, t) { - let { data: n } = await e.post(k.unarchiveProject(t)); +async function R(e, t) { + let { data: n } = await e.post(M.unarchiveProject(t)); return n.data; } -async function F(e, t) { - await e.delete(k.project(t)); +async function z(e, t) { + await e.delete(M.project(t)); } -async function te(e) { - let { data: t } = await e.get(k.members); +async function ee(e) { + let { data: t } = await e.get(M.members); return t.data; } -async function ne(e, t = 100) { - let { data: n } = await e.get(A.customers, { params: { limit: t } }); +async function te(e, t = 100) { + let { data: n } = await e.get(N.customers, { params: { limit: t } }); return n.data; } //#endregion //#region resources/js/support/errors.ts -function I(e) { +function ne(e) { if (typeof e != "object" || !e) return null; let t = e.response; return typeof t?.data != "object" || t.data === null ? null : t.data; } -function L(e, t) { - let n = I(e)?.message; +function B(e, t) { + let n = ne(e)?.message; return typeof n == "string" && n !== "" ? n : t; } function re(e) { - let t = I(e)?.errors, n = {}; + let t = ne(e)?.errors, n = {}; if (typeof t != "object" || !t) return n; for (let [e, r] of Object.entries(t)) Array.isArray(r) && typeof r[0] == "string" && (n[e] = r[0]); return n; @@ -148,27 +148,27 @@ function le(e) { let t = String(e.getMonth() + 1).padStart(2, "0"), n = String(e.getDate()).padStart(2, "0"); return `${e.getFullYear()}-${t}-${n}`; } -function R(e) { +function ue(e) { let t = Math.max(0, Math.round(e ?? 0)), n = Math.floor(t / 60), r = t % 60; return n === 0 ? `${r}m` : r === 0 ? `${n}h` : `${n}h ${r}m`; } -function ue(e) { +function de(e) { let t = e.trim().split(/\s+/).filter(Boolean); return t.length === 0 ? "?" : (t[0].charAt(0) + (t.length > 1 ? t[t.length - 1].charAt(0) : "")).toUpperCase(); } -function de(e) { +function fe(e) { if (!e) return !1; let t = /* @__PURE__ */ new Date(), n = `${t.getFullYear()}-${String(t.getMonth() + 1).padStart(2, "0")}-${String(t.getDate()).padStart(2, "0")}`; return e.slice(0, 10) < n; } //#endregion //#region resources/js/support/i18n.ts -function fe() { - return l()?.appContext.config.globalProperties.$t ?? ((e) => e); +function V() { + return u()?.appContext.config.globalProperties.$t ?? ((e) => e); } //#endregion //#region resources/js/components/ProjectFormModal.vue?vue&type=script&setup=true&lang.ts -var pe = { class: "flex w-full items-center justify-between" }, me = { class: "space-y-5 px-6 py-6" }, he = { class: "flex flex-wrap items-center gap-2" }, ge = ["aria-label", "onClick"], _e = { class: "flex justify-end space-x-3 border-t border-line-default px-6 py-4" }, ve = /* @__PURE__ */ c({ +var pe = { class: "flex w-full items-center justify-between" }, me = { class: "space-y-5 px-6 py-6" }, he = { class: "flex flex-wrap items-center gap-2" }, ge = ["aria-label", "onClick"], _e = { class: "flex justify-end space-x-3 border-t border-line-default px-6 py-4" }, ve = /* @__PURE__ */ l({ __name: "ProjectFormModal", props: { show: { type: Boolean }, @@ -177,8 +177,8 @@ var pe = { class: "flex w-full items-center justify-between" }, me = { class: "s project: {} }, emits: ["close", "saved"], - setup(r, { emit: c }) { - let l = r, u = c, p = [ + setup(t, { emit: i }) { + let l = t, u = i, d = [ "#2563eb", "#0891b2", "#059669", @@ -187,7 +187,7 @@ var pe = { class: "flex w-full items-center justify-between" }, me = { class: "s "#dc2626", "#7c3aed", "#64748b" - ], m = fe(), S = g({ + ], m = V(), h = _({ name: "", identifier: "", description: "", @@ -195,99 +195,99 @@ var pe = { class: "flex w-full items-center justify-between" }, me = { class: "s defaultRate: "", budgetHours: "", dueDate: "" - }), T = _(null), D = _([]), O = _(!1), k = _({}), A = _(!1), j = t(() => l.project !== null), N = t(() => j.value ? m("tasks_projects.projects.edit_project") : m("tasks_projects.projects.new_project")); - C(() => l.show, (e) => { - e && (P(), I()); + }), C = v(null), w = v([]), D = v(!1), O = v({}), A = v(!1), j = n(() => l.project !== null), M = n(() => j.value ? m("tasks_projects.projects.edit_project") : m("tasks_projects.projects.new_project")); + T(() => l.show, (e) => { + e && (N(), R()); }, { immediate: !0 }); - function P() { + function N() { let e = l.project; - S.name = e?.name ?? "", S.identifier = e?.identifier ?? "", S.description = e?.description ?? "", S.colour = e?.colour ?? "", S.defaultRate = ie(e?.default_rate ?? null), S.budgetHours = oe(e?.budget_minutes ?? null), S.dueDate = e?.due_date ?? "", k.value = {}, T.value = F(e?.customer_id ?? null); + h.name = e?.name ?? "", h.identifier = e?.identifier ?? "", h.description = e?.description ?? "", h.colour = e?.colour ?? "", h.defaultRate = ie(e?.default_rate ?? null), h.budgetHours = oe(e?.budget_minutes ?? null), h.dueDate = e?.due_date ?? "", O.value = {}, C.value = P(e?.customer_id ?? null); } - function F(e) { - return e === null ? null : D.value.find((t) => t.id === e) ?? null; + function P(e) { + return e === null ? null : w.value.find((t) => t.id === e) ?? null; } - function te(e) { + function L(e) { return e.display_name || e.name || `#${e.id}`; } - async function I() { - if (!O.value) try { - let e = await ne(l.client); - D.value = e.map((e) => ({ + async function R() { + if (!D.value) try { + let e = await te(l.client); + w.value = e.map((e) => ({ id: e.id, - label: te(e) - })), O.value = !0, T.value = F(l.project?.customer_id ?? null); + label: L(e) + })), D.value = !0, C.value = P(l.project?.customer_id ?? null); } catch (e) { - l.notify("error", L(e, m("tasks_projects.projects.customers_failed"))); + l.notify("error", B(e, m("tasks_projects.projects.customers_failed"))); } } - function ce() { + function z() { return { - name: S.name.trim(), - customer_id: T.value?.id ?? null, - identifier: S.identifier.trim() || null, - description: S.description.trim() || null, - colour: S.colour || null, - default_rate: ae(S.defaultRate), - budget_minutes: se(S.budgetHours), - due_date: S.dueDate || null + name: h.name.trim(), + customer_id: C.value?.id ?? null, + identifier: h.identifier.trim() || null, + description: h.description.trim() || null, + colour: h.colour || null, + default_rate: ae(h.defaultRate), + budget_minutes: se(h.budgetHours), + due_date: h.dueDate || null }; } - function R(e) { - S.dueDate = e ? le(e) : ""; + function ee(e) { + h.dueDate = e ? le(e) : ""; } - async function ue() { + async function ne() { if (!A.value) { - if (S.name.trim() === "") { - k.value = { name: m("tasks_projects.projects.name_required") }; + if (h.name.trim() === "") { + O.value = { name: m("tasks_projects.projects.name_required") }; return; } - A.value = !0, k.value = {}; + A.value = !0, O.value = {}; try { - let e = l.project, t = e ? await ee(l.client, e.id, ce()) : await M(l.client, ce()); + let e = l.project, t = e ? await I(l.client, e.id, z()) : await F(l.client, z()); u("saved", t); } catch (e) { - k.value = re(e), l.notify("error", L(e, m("tasks_projects.projects.save_failed"))); + O.value = re(e), l.notify("error", B(e, m("tasks_projects.projects.save_failed"))); } finally { A.value = !1; } } } - return (t, c) => { - let l = y("BaseIcon"), g = y("BaseInput"), _ = y("BaseInputGroup"), C = y("BaseSelectInput"), O = y("BaseDatePicker"), M = y("BaseInputGrid"), ee = y("BaseTextarea"), P = y("BaseButton"), F = y("BaseModal"); - return h(), n(F, { - show: r.show, - onClose: c[9] ||= (e) => u("close") + return (n, i) => { + let l = b("BaseIcon"), _ = b("BaseInput"), v = b("BaseInputGroup"), T = b("BaseSelectInput"), D = b("BaseDatePicker"), N = b("BaseInputGrid"), P = b("BaseTextarea"), F = b("BaseButton"), I = b("BaseModal"); + return g(), r(I, { + show: t.show, + onClose: i[9] ||= (e) => u("close") }, { - header: w(() => [a("div", pe, [a("span", null, b(N.value), 1), s(l, { + header: E(() => [o("div", pe, [o("span", null, x(M.value), 1), c(l, { name: "XMarkIcon", class: "h-6 w-6 cursor-pointer text-subtle hover:text-body", - onClick: c[0] ||= (e) => u("close") + onClick: i[0] ||= (e) => u("close") })])]), - default: w(() => [a("form", { onSubmit: E(ue, ["prevent"]) }, [a("div", me, [ - s(M, null, { - default: w(() => [ - s(_, { - label: x(m)("tasks_projects.projects.fields.name"), - error: k.value.name, + default: E(() => [o("form", { onSubmit: k(ne, ["prevent"]) }, [o("div", me, [ + c(N, null, { + default: E(() => [ + c(v, { + label: S(m)("tasks_projects.projects.fields.name"), + error: O.value.name, required: "" }, { - default: w(() => [s(g, { - modelValue: S.name, - "onUpdate:modelValue": c[1] ||= (e) => S.name = e, - invalid: !!k.value.name, + default: E(() => [c(_, { + modelValue: h.name, + "onUpdate:modelValue": i[1] ||= (e) => h.name = e, + invalid: !!O.value.name, type: "text" }, null, 8, ["modelValue", "invalid"])]), _: 1 }, 8, ["label", "error"]), - s(_, { - label: x(m)("tasks_projects.projects.fields.identifier"), - error: k.value.identifier, - "help-text": x(m)("tasks_projects.projects.fields.identifier_help") + c(v, { + label: S(m)("tasks_projects.projects.fields.identifier"), + error: O.value.identifier, + "help-text": S(m)("tasks_projects.projects.fields.identifier_help") }, { - default: w(() => [s(g, { - modelValue: S.identifier, - "onUpdate:modelValue": c[2] ||= (e) => S.identifier = e, - invalid: !!k.value.identifier, + default: E(() => [c(_, { + modelValue: h.identifier, + "onUpdate:modelValue": i[2] ||= (e) => h.identifier = e, + invalid: !!O.value.identifier, type: "text", maxlength: "32" }, null, 8, ["modelValue", "invalid"])]), @@ -297,16 +297,16 @@ var pe = { class: "flex w-full items-center justify-between" }, me = { class: "s "error", "help-text" ]), - s(_, { - label: x(m)("tasks_projects.projects.fields.customer"), - error: k.value.customer_id, - "help-text": x(m)("tasks_projects.projects.fields.customer_help") + c(v, { + label: S(m)("tasks_projects.projects.fields.customer"), + error: O.value.customer_id, + "help-text": S(m)("tasks_projects.projects.fields.customer_help") }, { - default: w(() => [s(C, { - modelValue: T.value, - "onUpdate:modelValue": c[3] ||= (e) => T.value = e, - options: D.value, - placeholder: x(m)("tasks_projects.projects.fields.customer_placeholder"), + default: E(() => [c(T, { + modelValue: C.value, + "onUpdate:modelValue": i[3] ||= (e) => C.value = e, + options: w.value, + placeholder: S(m)("tasks_projects.projects.fields.customer_placeholder"), "label-key": "label" }, null, 8, [ "modelValue", @@ -319,25 +319,25 @@ var pe = { class: "flex w-full items-center justify-between" }, me = { class: "s "error", "help-text" ]), - s(_, { - label: x(m)("tasks_projects.projects.fields.due_date"), - error: k.value.due_date + c(v, { + label: S(m)("tasks_projects.projects.fields.due_date"), + error: O.value.due_date }, { - default: w(() => [s(O, { - "model-value": S.dueDate, - "onUpdate:modelValue": R + default: E(() => [c(D, { + "model-value": h.dueDate, + "onUpdate:modelValue": ee }, null, 8, ["model-value"])]), _: 1 }, 8, ["label", "error"]), - s(_, { - label: x(m)("tasks_projects.projects.fields.default_rate"), - error: k.value.default_rate, - "help-text": x(m)("tasks_projects.projects.fields.default_rate_help") + c(v, { + label: S(m)("tasks_projects.projects.fields.default_rate"), + error: O.value.default_rate, + "help-text": S(m)("tasks_projects.projects.fields.default_rate_help") }, { - default: w(() => [s(g, { - modelValue: S.defaultRate, - "onUpdate:modelValue": c[4] ||= (e) => S.defaultRate = e, - invalid: !!k.value.default_rate, + default: E(() => [c(_, { + modelValue: h.defaultRate, + "onUpdate:modelValue": i[4] ||= (e) => h.defaultRate = e, + invalid: !!O.value.default_rate, type: "number", step: "0.01", min: "0" @@ -348,14 +348,14 @@ var pe = { class: "flex w-full items-center justify-between" }, me = { class: "s "error", "help-text" ]), - s(_, { - label: x(m)("tasks_projects.projects.fields.budget_hours"), - error: k.value.budget_minutes + c(v, { + label: S(m)("tasks_projects.projects.fields.budget_hours"), + error: O.value.budget_minutes }, { - default: w(() => [s(g, { - modelValue: S.budgetHours, - "onUpdate:modelValue": c[5] ||= (e) => S.budgetHours = e, - invalid: !!k.value.budget_minutes, + default: E(() => [c(_, { + modelValue: h.budgetHours, + "onUpdate:modelValue": i[5] ||= (e) => h.budgetHours = e, + invalid: !!O.value.budget_minutes, type: "number", step: "0.25", min: "0" @@ -365,50 +365,50 @@ var pe = { class: "flex w-full items-center justify-between" }, me = { class: "s ]), _: 1 }), - s(_, { - label: x(m)("tasks_projects.projects.fields.colour"), - error: k.value.colour + c(v, { + label: S(m)("tasks_projects.projects.fields.colour"), + error: O.value.colour }, { - default: w(() => [a("div", he, [(h(), i(e, null, v(p, (e) => a("button", { + default: E(() => [o("div", he, [(g(), a(e, null, y(d, (e) => o("button", { key: e, type: "button", - class: d(["h-7 w-7 rounded-full border-2 transition", S.colour === e ? "border-heading" : "border-line-default"]), - style: f({ backgroundColor: e }), + class: f(["h-7 w-7 rounded-full border-2 transition", h.colour === e ? "border-heading" : "border-line-default"]), + style: p({ backgroundColor: e }), "aria-label": e, - onClick: (t) => S.colour = S.colour === e ? "" : e - }, null, 14, ge)), 64)), a("button", { + onClick: (t) => h.colour = h.colour === e ? "" : e + }, null, 14, ge)), 64)), o("button", { type: "button", class: "rounded-md border border-line-default px-2 py-1 text-xs text-muted hover:bg-hover", - onClick: c[6] ||= (e) => S.colour = "" - }, b(x(m)("tasks_projects.projects.fields.colour_none")), 1)])]), + onClick: i[6] ||= (e) => h.colour = "" + }, x(S(m)("tasks_projects.projects.fields.colour_none")), 1)])]), _: 1 }, 8, ["label", "error"]), - s(_, { - label: x(m)("tasks_projects.projects.fields.description"), - error: k.value.description + c(v, { + label: S(m)("tasks_projects.projects.fields.description"), + error: O.value.description }, { - default: w(() => [s(ee, { - modelValue: S.description, - "onUpdate:modelValue": c[7] ||= (e) => S.description = e, + default: E(() => [c(P, { + modelValue: h.description, + "onUpdate:modelValue": i[7] ||= (e) => h.description = e, row: 3, - invalid: !!k.value.description + invalid: !!O.value.description }, null, 8, ["modelValue", "invalid"])]), _: 1 }, 8, ["label", "error"]) - ]), a("div", _e, [s(P, { + ]), o("div", _e, [c(F, { type: "button", variant: "primary-outline", - onClick: c[8] ||= (e) => u("close") + onClick: i[8] ||= (e) => u("close") }, { - default: w(() => [o(b(x(m)("tasks_projects.general.cancel")), 1)]), + default: E(() => [s(x(S(m)("tasks_projects.general.cancel")), 1)]), _: 1 - }), s(P, { + }), c(F, { type: "submit", variant: "primary", loading: A.value, disabled: A.value }, { - default: w(() => [o(b(j.value ? x(m)("tasks_projects.general.update") : x(m)("tasks_projects.general.save")), 1)]), + default: E(() => [s(x(j.value ? S(m)("tasks_projects.general.update") : S(m)("tasks_projects.general.save")), 1)]), _: 1 }, 8, ["loading", "disabled"])])], 32)]), _: 1 @@ -427,7 +427,7 @@ var pe = { class: "flex w-full items-center justify-between" }, me = { class: "s }, Ee = { key: 0 }, De = { key: 1, class: "text-subtle" -}, Oe = 10, ke = 350, Ae = /* @__PURE__ */ c({ +}, Oe = 10, ke = 350, Ae = /* @__PURE__ */ l({ __name: "ProjectsIndexPage", props: { client: { type: [Function, Object] }, @@ -435,10 +435,10 @@ var pe = { class: "flex w-full items-center justify-between" }, me = { class: "s router: {} }, setup(e) { - let c = e, l = fe(), u = _(null), m = _(!1), v = _(!0), E = _(0), D = _(!1), O = _(null), k = _(null), A = g({ + let t = e, l = V(), u = v(null), d = v(!1), h = v(!0), y = v(0), C = v(!1), O = v(null), k = v(null), A = _({ search: "", status: "ACTIVE" - }), M = t(() => [ + }), j = n(() => [ { id: "ACTIVE", label: l("tasks_projects.projects.status.active") @@ -451,12 +451,12 @@ var pe = { class: "flex w-full items-center justify-between" }, me = { class: "s id: "ALL", label: l("tasks_projects.projects.status.all") } - ]), ee = t({ - get: () => M.value.find((e) => e.id === A.status) ?? M.value[0], + ]), M = n({ + get: () => j.value.find((e) => e.id === A.status) ?? j.value[0], set: (e) => { A.status = e.id; } - }), te = t(() => [ + }), N = n(() => [ { key: "name", label: l("tasks_projects.projects.columns.name"), @@ -490,19 +490,19 @@ var pe = { class: "flex w-full items-center justify-between" }, me = { class: "s sortable: !1, tdClass: "text-right text-sm font-medium" } - ]), ne = t(() => A.search.trim() !== "" || A.status !== "ACTIVE"), I = t(() => !v.value && E.value === 0 && !ne.value), re; - C(() => A.search, () => { - clearTimeout(re), re = setTimeout(() => ae(), ke); - }), C(() => A.status, () => ae()), p(() => clearTimeout(re)); - async function ie({ page: e }) { - let t = { + ]), F = n(() => A.search.trim() !== "" || A.status !== "ACTIVE"), I = n(() => !h.value && y.value === 0 && !F.value), ee; + T(() => A.search, () => { + clearTimeout(ee), ee = setTimeout(() => ne(), ke); + }), T(() => A.status, () => ne()), m(() => clearTimeout(ee)); + async function te({ page: e }) { + let n = { page: e, limit: Oe }; - A.status !== "ALL" && (t.status = A.status), A.search.trim() !== "" && (t.search = A.search.trim()), v.value = !0; + A.status !== "ALL" && (n.status = A.status), A.search.trim() !== "" && (n.search = A.search.trim()), h.value = !0; try { - let e = await j(c.client, t); - return E.value = e.meta.total, { + let e = await P(t.client, n); + return y.value = e.meta.total, { data: e.data, pagination: { totalPages: e.meta.last_page, @@ -512,7 +512,7 @@ var pe = { class: "flex w-full items-center justify-between" }, me = { class: "s } }; } catch (e) { - return c.notify("error", L(e, l("tasks_projects.projects.load_failed"))), { + return t.notify("error", B(e, l("tasks_projects.projects.load_failed"))), { data: [], pagination: { totalPages: 1, @@ -522,248 +522,2261 @@ var pe = { class: "flex w-full items-center justify-between" }, me = { class: "s } }; } finally { - v.value = !1; + h.value = !1; } } - function ae(e = !1) { + function ne(e = !1) { u.value?.refresh(e); } - function oe() { - m.value && se(), m.value = !m.value; + function re() { + d.value && ie(), d.value = !d.value; + } + function ie() { + A.search = "", A.status = "ACTIVE"; + } + function ae() { + O.value = null, C.value = !0; + } + function oe(e) { + O.value = e, C.value = !0; + } + function se(e) { + let n = O.value ? l("tasks_projects.projects.updated", { name: e.name }) : l("tasks_projects.projects.created", { name: e.name }); + C.value = !1, O.value = null, t.notify("success", n), ne(); + } + async function le(e) { + k.value = e.id; + try { + e.status === "ARCHIVED" ? (await R(t.client, e.id), t.notify("success", l("tasks_projects.projects.unarchived", { name: e.name }))) : (await L(t.client, e.id), t.notify("success", l("tasks_projects.projects.archived", { name: e.name }))), ne(!0); + } catch (e) { + t.notify("error", B(e, l("tasks_projects.projects.save_failed"))); + } finally { + k.value = null; + } + } + async function ue(e) { + if (window.confirm(l("tasks_projects.projects.delete_confirm", { name: e.name }))) { + k.value = e.id; + try { + await z(t.client, e.id), t.notify("success", l("tasks_projects.projects.deleted", { name: e.name })), ne(!0); + } catch (e) { + t.notify("error", B(e, l("tasks_projects.projects.delete_failed"))); + } finally { + k.value = null; + } + } + } + function de(e) { + return e === "ACTIVE" ? "bg-primary-50! text-primary-500!" : "bg-surface-tertiary! text-muted!"; + } + function fe(e) { + return l(e === "ACTIVE" ? "tasks_projects.projects.status.active" : "tasks_projects.projects.status.archived"); + } + return (t, n) => { + let m = b("BaseBreadcrumbItem"), h = b("BaseBreadcrumb"), _ = b("BaseIcon"), v = b("BaseButton"), y = b("router-link"), T = b("BasePageHeader"), P = b("BaseInput"), F = b("BaseInputGroup"), L = b("BaseSelectInput"), R = b("BaseFilterWrapper"), z = b("BaseEmptyPlaceholder"), ee = b("BaseBadge"), ne = b("BaseFormatMoney"), B = b("BaseDropdownItem"), V = b("BaseDropdown"), pe = b("BaseTable"), me = b("BasePage"); + return g(), r(me, null, { + default: E(() => [ + c(T, { title: S(l)("tasks_projects.projects.title") }, { + actions: E(() => [o("div", ye, [ + c(y, { to: "/admin/modules/tasks-projects/board" }, { + default: E(() => [c(v, { variant: "white" }, { + left: E((e) => [c(_, { + name: "ViewColumnsIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: E(() => [s(" " + x(S(l)("tasks_projects.board.title")), 1)]), + _: 1 + })]), + _: 1 + }), + c(v, { + variant: "primary-outline", + onClick: re + }, { + right: E((e) => [d.value ? (g(), r(_, { + key: 1, + name: "XMarkIcon", + class: f(e.class) + }, null, 8, ["class"])) : (g(), r(_, { + key: 0, + name: "FunnelIcon", + class: f(e.class) + }, null, 8, ["class"]))]), + default: E(() => [s(x(S(l)("tasks_projects.general.filter")) + " ", 1)]), + _: 1 + }), + c(v, { + variant: "primary", + onClick: ae + }, { + left: E((e) => [c(_, { + name: "PlusIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: E(() => [s(" " + x(S(l)("tasks_projects.projects.new_project")), 1)]), + _: 1 + }) + ])]), + default: E(() => [c(h, null, { + default: E(() => [c(m, { + title: S(l)("tasks_projects.general.home"), + to: "/admin/dashboard" + }, null, 8, ["title"]), c(m, { + title: S(l)("tasks_projects.projects.title"), + to: "#", + active: "" + }, null, 8, ["title"])]), + _: 1 + })]), + _: 1 + }, 8, ["title"]), + c(R, { + show: d.value, + class: "mt-3", + onClear: ie + }, { + default: E(() => [c(F, { + label: S(l)("tasks_projects.general.search"), + class: "mt-2 flex-1" + }, { + default: E(() => [c(P, { + modelValue: A.search, + "onUpdate:modelValue": n[0] ||= (e) => A.search = e, + type: "text", + name: "search", + autocomplete: "off", + placeholder: S(l)("tasks_projects.projects.search_placeholder") + }, null, 8, ["modelValue", "placeholder"])]), + _: 1 + }, 8, ["label"]), c(F, { + label: S(l)("tasks_projects.projects.columns.status"), + class: "mt-2 flex-1" + }, { + default: E(() => [c(L, { + modelValue: M.value, + "onUpdate:modelValue": n[1] ||= (e) => M.value = e, + options: j.value, + "label-key": "label" + }, null, 8, ["modelValue", "options"])]), + _: 1 + }, 8, ["label"])]), + _: 1 + }, 8, ["show"]), + D(c(z, { + title: S(l)("tasks_projects.projects.empty_title"), + description: S(l)("tasks_projects.projects.empty_description") + }, { + actions: E(() => [c(v, { + variant: "primary", + onClick: ae + }, { + left: E((e) => [c(_, { + name: "PlusIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: E(() => [s(" " + x(S(l)("tasks_projects.projects.new_project")), 1)]), + _: 1 + })]), + default: E(() => [c(_, { + name: "FolderIcon", + class: "mt-5 mb-4 h-16 w-16 text-subtle" + })]), + _: 1 + }, 8, ["title", "description"]), [[w, I.value]]), + D(o("div", be, [c(pe, { + ref_key: "tableRef", + ref: u, + data: te, + columns: N.value, + class: "mt-3" + }, { + "cell-name": E(({ row: e }) => [o("div", xe, [o("span", { + class: f(["mr-3 inline-block h-2.5 w-2.5 shrink-0 rounded-full", e.data.colour ? "" : "bg-line-default"]), + style: p(e.data.colour ? { backgroundColor: e.data.colour } : void 0) + }, null, 6), o("span", null, [c(y, { + class: "hover:text-primary-500", + to: { + name: "extension.page.tasks-projects.project", + params: { id: e.data.id } + } + }, { + default: E(() => [s(x(e.data.name), 1)]), + _: 2 + }, 1032, ["to"]), e.data.identifier ? (g(), a("span", Se, x(e.data.identifier), 1)) : i("", !0)])])]), + "cell-status": E(({ row: e }) => [c(ee, { class: f(["rounded-full", de(e.data.status)]) }, { + default: E(() => [s(x(fe(e.data.status)), 1)]), + _: 2 + }, 1032, ["class"])]), + "cell-customer": E(({ row: e }) => [e.data.customer_id ? (g(), a("span", Ce, "#" + x(e.data.customer_id), 1)) : (g(), a("span", we, x(S(l)("tasks_projects.projects.internal")), 1))]), + "cell-default_rate": E(({ row: e }) => [e.data.default_rate === null ? (g(), a("span", Te, "-")) : (g(), r(ne, { + key: 0, + amount: e.data.default_rate + }, null, 8, ["amount"]))]), + "cell-due_date": E(({ row: e }) => [e.data.due_date ? (g(), a("span", Ee, x(S(ce)(e.data.due_date)), 1)) : (g(), a("span", De, "-"))]), + "cell-actions": E(({ row: e }) => [c(V, { "content-loading": k.value === e.data.id }, { + activator: E(() => [c(_, { + name: "EllipsisHorizontalIcon", + class: "h-5 text-muted" + })]), + default: E(() => [ + c(B, { onClick: (t) => oe(e.data) }, { + default: E(() => [c(_, { + name: "PencilIcon", + class: "mr-3 h-5 w-5 text-subtle group-hover:text-muted" + }), s(" " + x(S(l)("tasks_projects.general.edit")), 1)]), + _: 1 + }, 8, ["onClick"]), + c(B, { onClick: (t) => le(e.data) }, { + default: E(() => [c(_, { + name: e.data.status === "ARCHIVED" ? "ArrowPathIcon" : "ArchiveBoxIcon", + class: "mr-3 h-5 w-5 text-subtle group-hover:text-muted" + }, null, 8, ["name"]), s(" " + x(e.data.status === "ARCHIVED" ? S(l)("tasks_projects.projects.unarchive") : S(l)("tasks_projects.projects.archive")), 1)]), + _: 2 + }, 1032, ["onClick"]), + c(B, { onClick: (t) => ue(e.data) }, { + default: E(() => [c(_, { + name: "TrashIcon", + class: "mr-3 h-5 w-5 text-subtle group-hover:text-muted" + }), s(" " + x(S(l)("tasks_projects.general.delete")), 1)]), + _: 1 + }, 8, ["onClick"]) + ]), + _: 2 + }, 1032, ["content-loading"])]), + _: 1 + }, 8, ["columns"])], 512), [[w, !I.value]]), + c(ve, { + show: C.value, + client: e.client, + notify: e.notify, + project: O.value, + onClose: n[2] ||= (e) => C.value = !1, + onSaved: se + }, null, 8, [ + "show", + "client", + "notify", + "project" + ]) + ]), + _: 1 + }); + }; + } +}), je = "/api/v1/tasks-projects", Me = { + timeEntries: `${je}/time-entries`, + timeEntry: (e) => `${je}/time-entries/${e}`, + timer: `${je}/timer`, + timerStart: `${je}/timer/start`, + timerStop: `${je}/timer/stop`, + taskStatuses: `${je}/task-statuses`, + taskStatus: (e) => `${je}/task-statuses/${e}`, + reorderTaskStatuses: `${je}/task-statuses/reorder`, + tasks: `${je}/tasks`, + task: (e) => `${je}/tasks/${e}`, + members: `${je}/members`, + settings: `${je}/settings` +}, Ne = { bootstrap: "/api/v1/bootstrap" }, Pe = 100, Fe = 5, Ie = 10; +async function Le(e, t) { + let { data: n } = await e.get(Me.timeEntries, { params: t }); + return n; +} +async function Re(e, t) { + let n = []; + for (let r = 1; r <= Fe; r += 1) { + let i = await Le(e, { + ...t, + page: r, + limit: Pe + }); + if (n.push(...i.data ?? []), !i.meta || r >= i.meta.last_page) break; + } + return n; +} +async function ze(e, t) { + let { data: n } = await e.post(Me.timeEntries, t); + return n.data; +} +async function Be(e, t, n) { + let { data: r } = await e.put(Me.timeEntry(t), n); + return r.data; +} +async function Ve(e, t) { + await e.delete(Me.timeEntry(t)); +} +async function He(e) { + let { data: t } = await e.get(Me.timer); + return t?.data ?? null; +} +async function Ue(e, t) { + let { data: n } = await e.post(Me.timerStart, t); + return n.data; +} +async function We(e) { + let { data: t } = await e.post(Me.timerStop); + return t.data; +} +async function Ge(e) { + await e.delete(Me.timer); +} +async function Ke(e) { + let { data: t } = await e.get(Me.taskStatuses); + return t.data ?? []; +} +async function qe(e, t) { + let { data: n } = await e.post(Me.taskStatuses, t); + return n.data; +} +async function Je(e, t, n) { + let { data: r } = await e.put(Me.taskStatus(t), n); + return r.data; +} +async function Ye(e, t) { + await e.delete(Me.taskStatus(t)); +} +async function Xe(e, t) { + let { data: n } = await e.post(Me.reorderTaskStatuses, { ids: t }); + return n.data ?? []; +} +async function Ze(e, t, n = Ie) { + let r = { limit: n }; + t.trim() !== "" && (r.search = t.trim()); + let { data: i } = await e.get(Me.tasks, { params: r }); + return i.data ?? []; +} +async function Qe(e, t) { + let { data: n } = await e.get(Me.task(t)); + return n.data; +} +async function $e(e) { + let { data: t } = await e.get(Me.members); + return t.data ?? []; +} +async function et(e) { + let { data: t } = await e.get(Me.settings); + return t.data; +} +async function tt(e) { + let { data: t } = await e.get(Ne.bootstrap), n = t?.current_user?.id; + return typeof n == "number" ? n : null; +} +//#endregion +//#region resources/js/stores/tasks.ts +var nt = _({}), rt = /* @__PURE__ */ new Set(), it = 5; +function at(e) { + return e === null ? "" : nt[e] ?? `#${e}`; +} +function ot(e) { + e && typeof e.id == "number" && typeof e.name == "string" && (nt[e.id] = e.name); +} +async function st(e, t) { + let n = [...new Set(t)].filter((e) => typeof e == "number" && nt[e] === void 0 && !rt.has(e)); + for (let e of n) rt.add(e); + for (let t = 0; t < n.length; t += it) await Promise.all(n.slice(t, t + it).map(async (t) => { + try { + ot(await Qe(e, t)); + } catch {} finally { + rt.delete(t); + } + })); +} +function ct() { + for (let e of Object.keys(nt)) delete nt[Number(e)]; + rt.clear(); +} +//#endregion +//#region resources/js/support/http.ts +function lt(e) { + if (typeof e != "object" || !e) return null; + let t = e.response?.status; + return typeof t == "number" ? t : null; +} +function ut(e) { + return lt(e) === 409; +} +function dt(e) { + return lt(e) === 403; +} +//#endregion +//#region resources/js/support/time.ts +var ft = 60, pt = 60, mt = 7; +function ht(e) { + let t = Number.isFinite(e) && e > 0 ? Math.floor(e) : 0, n = Math.floor(t / 3600), r = Math.floor(t % 3600 / pt), i = t % pt; + return `${n}:${Mt(r)}:${Mt(i)}`; +} +function gt(e) { + let t = e !== null && Number.isFinite(e) && e > 0 ? Math.round(e) : 0; + return `${Math.floor(t / ft)}:${Mt(t % ft)}`; +} +function _t(e) { + let t = e.trim(); + if (t === "") return null; + let n = /^(\d+):([0-5]?\d)$/.exec(t); + if (n) return Number(n[1]) * ft + Number(n[2]); + if (!/^\d+([.,]\d+)?$/.test(t)) return null; + let r = Number(t.replace(",", ".")); + return Number.isNaN(r) ? null : Math.round(r * ft); +} +function vt(e) { + let t = kt(e); + return t === null ? "" : Tt(t); +} +function yt(e) { + let t = kt(e); + return t === null ? "" : `${Mt(t.getHours())}:${Mt(t.getMinutes())}`; +} +function bt(e, t = "09:00") { + let n = At(e), r = /^(\d{1,2}):([0-5]\d)$/.exec(t.trim()); + if (n === null || r === null) return null; + let i = Number(r[1]); + return i > 23 ? null : (n.setHours(i, Number(r[2]), 0, 0), n.toISOString()); +} +function xt(e, t) { + let n = new Date(e); + return n.setTime(n.getTime() + t * pt * 1e3), n.toISOString(); +} +function St(e, t) { + let n = Number.isInteger(t) && t >= 0 && t <= 6 ? t : 1, r = jt(e), i = (r.getDay() - n + mt) % mt; + return r.setDate(r.getDate() - i), r; +} +function Ct(e) { + return Array.from({ length: mt }, (t, n) => wt(e, n)); +} +function wt(e, t) { + let n = jt(e); + return n.setDate(n.getDate() + t), n; +} +function Tt(e) { + return `${e.getFullYear()}-${Mt(e.getMonth() + 1)}-${Mt(e.getDate())}`; +} +function Et(e) { + return { + weekday: e.toLocaleDateString(void 0, { weekday: "short" }), + day: e.toLocaleDateString(void 0, { + day: "numeric", + month: "short" + }) + }; +} +function Dt(e) { + return Tt(e) === Tt(/* @__PURE__ */ new Date()); +} +function Ot(e) { + let t = kt(e); + return t === null ? 0 : Math.max(0, Math.floor((Date.now() - t.getTime()) / 1e3)); +} +function kt(e) { + if (!e) return null; + let t = new Date(e); + return Number.isNaN(t.getTime()) ? null : t; +} +function At(e) { + let t = /^(\d{4})-(\d{2})-(\d{2})/.exec(e.trim()); + if (t === null) return null; + let n = new Date(Number(t[1]), Number(t[2]) - 1, Number(t[3]), 0, 0, 0, 0); + return Number.isNaN(n.getTime()) ? null : n; +} +function jt(e) { + let t = new Date(e.getTime()); + return t.setHours(0, 0, 0, 0), t; +} +function Mt(e) { + return String(e).padStart(2, "0"); +} +//#endregion +//#region resources/js/stores/timer.ts +var H = _({ + running: null, + elapsedSeconds: 0, + busy: !1 +}), Nt; +function Pt() { + H.elapsedSeconds = H.running === null ? 0 : Ot(H.running.started_at); +} +function Ft() { + Pt(), Nt === void 0 && (Nt = setInterval(Pt, 1e3)); +} +function It() { + Nt !== void 0 && (clearInterval(Nt), Nt = void 0), H.elapsedSeconds = 0; +} +function Lt(e, t) { + if (H.running = e && typeof e.id == "number" ? e : null, H.running === null) { + It(); + return; + } + Ft(), t && typeof H.running.task_id == "number" && st(t, [H.running.task_id]); +} +function Rt(e, t, n) { + e?.notify("error", B(t, e.t(n))); +} +var U = { + get running() { + return H.running; + }, + get elapsedSeconds() { + return H.elapsedSeconds; + }, + get busy() { + return H.busy; + }, + async refresh(e) { + try { + Lt(await He(e), e); + } catch { + Lt(null); + } + }, + async start(e, t, n = null, r) { + if (H.busy) return null; + H.busy = !0; + try { + let r = await Ue(e, { + task_id: t, + description: n + }); + return Lt(r, e), r; + } catch (t) { + return ut(t) ? (r?.notify("warning", r.t("tasks_projects.timer.already_running")), await this.refresh(e)) : Rt(r, t, "tasks_projects.timer.start_failed"), null; + } finally { + H.busy = !1; + } + }, + async stop(e, t) { + if (H.busy || H.running === null) return null; + H.busy = !0; + try { + let t = await We(e); + return Lt(null), t; + } catch (n) { + return Rt(t, n, "tasks_projects.timer.stop_failed"), await this.refresh(e), null; + } finally { + H.busy = !1; + } + }, + async discard(e, t) { + if (H.busy || H.running === null) return !1; + H.busy = !0; + try { + return await Ge(e), Lt(null), !0; + } catch (n) { + return Rt(t, n, "tasks_projects.timer.discard_failed"), await this.refresh(e), !1; + } finally { + H.busy = !1; + } + }, + reset() { + H.busy = !1, Lt(null); + } +}, zt = { + key: 0, + class: "fixed right-6 bottom-20 z-40 flex flex-col items-end gap-3" +}, Bt = ["aria-label"], Vt = { class: "flex items-center justify-between border-b border-line-default px-4 py-3" }, Ht = { class: "text-sm font-semibold text-heading" }, Ut = ["aria-label"], Wt = { + key: 0, + class: "space-y-4 px-4 py-4" +}, Gt = { class: "truncate text-sm font-medium text-heading" }, Kt = { class: "mt-1 text-2xl font-semibold tabular-nums text-primary-500" }, qt = { + key: 0, + class: "mt-1 text-xs text-muted" +}, Jt = { class: "flex items-center gap-2" }, Yt = { + key: 1, + class: "space-y-3 px-4 py-4" +}, Xt = { class: "block" }, Zt = { class: "sr-only" }, Qt = ["placeholder"], $t = { + key: 0, + class: "text-xs text-muted" +}, en = { + key: 1, + class: "max-h-48 space-y-1 overflow-y-auto" +}, tn = ["onClick"], nn = { + key: 2, + class: "text-xs text-muted" +}, rn = ["placeholder", "aria-label"], an = { class: "flex items-center justify-between" }, on = ["title", "aria-label"], sn = { + key: 0, + class: "tabular-nums" +}, cn = 300, ln = /* @__PURE__ */ l({ + __name: "QuickStartOverlay", + props: { + client: { type: [Function, Object] }, + notify: { type: Function }, + enabled: { type: Boolean } + }, + emits: ["open-timesheet"], + setup(l, { emit: u }) { + let d = l, p = u, h = V(), _ = v(!1), w = v(""), k = v([]), A = v(!1), j = v(null), M = v(""), N, P = n(() => ({ + notify: d.notify, + t: h + })), F = n(() => at(U.running?.task_id ?? null)), I = n(() => ht(U.elapsedSeconds)); + T(() => d.enabled, (e) => { + e || z(); + }), T(_, (e) => { + e && U.running === null && L(); + }), T(w, () => { + clearTimeout(N), N = setTimeout(() => void L(), cn); + }), m(() => clearTimeout(N)); + async function L() { + A.value = !0; + try { + let e = await Ze(d.client, w.value); + k.value = e, e.forEach(ot); + } catch (e) { + k.value = [], d.notify("error", B(e, h("tasks_projects.time.tasks_failed"))); + } finally { + A.value = !1; + } + } + function R(e) { + j.value = e, ot(e); + } + function z() { + _.value = !1, w.value = "", k.value = [], j.value = null, M.value = ""; + } + async function ee() { + let e = j.value; + e !== null && await U.start(d.client, e.id, M.value.trim() || null, P.value) !== null && (d.notify("success", h("tasks_projects.timer.started", { name: e.name })), z()); + } + async function te() { + let e = F.value, t = await U.stop(d.client, P.value); + t !== null && (d.notify("success", h("tasks_projects.timer.stopped", { + name: e, + duration: gt(t.duration_minutes) + })), z()); + } + async function ne() { + window.confirm(h("tasks_projects.timer.discard_confirm")) && await U.discard(d.client, P.value) && (d.notify("success", h("tasks_projects.timer.discarded")), z()); + } + return (n, u) => { + let d = b("BaseIcon"), m = b("BaseButton"); + return g(), r(t, { to: "body" }, [l.enabled ? (g(), a("div", zt, [_.value ? (g(), a("section", { + key: 0, + class: "w-80 max-w-[calc(100vw-3rem)] rounded-xl border border-line-default bg-surface shadow-2xl", + "aria-label": S(h)("tasks_projects.timer.panel_title"), + onKeydown: O(z, ["esc"]) + }, [o("header", Vt, [o("h2", Ht, x(S(h)("tasks_projects.timer.panel_title")), 1), o("button", { + type: "button", + class: "rounded p-1 text-subtle hover:bg-hover hover:text-heading", + "aria-label": S(h)("tasks_projects.timer.close"), + onClick: z + }, [c(d, { + name: "XMarkIcon", + class: "h-5 w-5" + })], 8, Ut)]), S(U).running === null ? (g(), a("div", Yt, [ + o("label", Xt, [o("span", Zt, x(S(h)("tasks_projects.timer.search_tasks")), 1), D(o("input", { + "onUpdate:modelValue": u[0] ||= (e) => w.value = e, + type: "search", + autocomplete: "off", + class: "w-full rounded-md border border-line-default bg-surface px-3 py-2 text-sm text-body outline-hidden focus:border-primary-400 focus:ring-1 focus:ring-primary-400", + placeholder: S(h)("tasks_projects.timer.search_tasks") + }, null, 8, Qt), [[C, w.value]])]), + A.value ? (g(), a("p", $t, x(S(h)("tasks_projects.general.search")), 1)) : k.value.length > 0 ? (g(), a("ul", en, [(g(!0), a(e, null, y(k.value, (e) => (g(), a("li", { key: e.id }, [o("button", { + type: "button", + class: f(["w-full truncate rounded-md px-2 py-2 text-left text-sm hover:bg-hover", j.value?.id === e.id ? "bg-hover-strong font-medium text-heading" : "text-body"]), + onClick: (t) => R(e) + }, x(e.name), 11, tn)]))), 128))])) : (g(), a("p", nn, x(S(h)("tasks_projects.timer.no_tasks")), 1)), + D(o("input", { + "onUpdate:modelValue": u[1] ||= (e) => M.value = e, + type: "text", + class: "w-full rounded-md border border-line-default bg-surface px-3 py-2 text-sm text-body outline-hidden focus:border-primary-400 focus:ring-1 focus:ring-primary-400", + placeholder: S(h)("tasks_projects.timer.description_placeholder"), + "aria-label": S(h)("tasks_projects.time.fields.description") + }, null, 8, rn), [[C, M.value]]), + o("div", an, [o("button", { + type: "button", + class: "text-xs text-primary-500 hover:underline", + onClick: u[2] ||= (e) => p("open-timesheet") + }, x(S(h)("tasks_projects.timer.open_timesheet")), 1), c(m, { + variant: "primary", + disabled: j.value === null || S(U).busy, + onClick: ee + }, { + left: E((e) => [c(d, { + name: "PlayIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: E(() => [s(" " + x(S(h)("tasks_projects.timer.start")), 1)]), + _: 1 + }, 8, ["disabled"])]) + ])) : (g(), a("div", Wt, [o("div", null, [ + o("p", Gt, x(F.value), 1), + o("p", Kt, x(I.value), 1), + S(U).running.description ? (g(), a("p", qt, x(S(U).running.description), 1)) : i("", !0) + ]), o("div", Jt, [c(m, { + variant: "primary", + disabled: S(U).busy, + onClick: te + }, { + left: E((e) => [c(d, { + name: "StopIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: E(() => [s(" " + x(S(h)("tasks_projects.timer.stop")), 1)]), + _: 1 + }, 8, ["disabled"]), c(m, { + variant: "primary-outline", + disabled: S(U).busy, + onClick: ne + }, { + default: E(() => [s(x(S(h)("tasks_projects.timer.discard")), 1)]), + _: 1 + }, 8, ["disabled"])])]))], 40, Bt)) : i("", !0), o("button", { + type: "button", + class: "flex items-center gap-2 rounded-full bg-btn-primary px-4 py-3 text-sm font-medium text-white shadow-lg hover:bg-btn-primary-hover", + title: S(h)("tasks_projects.timer.quick_start"), + "aria-label": S(h)("tasks_projects.timer.quick_start"), + onClick: u[3] ||= (e) => _.value = !_.value + }, [c(d, { + name: S(U).running === null ? "ClockIcon" : "StopIcon", + class: "h-5 w-5 text-white" + }, null, 8, ["name"]), S(U).running === null ? i("", !0) : (g(), a("span", sn, x(I.value), 1))], 8, on)])) : i("", !0)]); + }; + } +}), un = { + key: 0, + class: "relative float-left m-0 ml-2" +}, dn = ["title"], fn = ["aria-label"], pn = { class: "font-medium tabular-nums" }, mn = [ + "disabled", + "title", + "aria-label" +], hn = /* @__PURE__ */ l({ + __name: "TimerChip", + props: { + client: { type: [Function, Object] }, + notify: { type: Function } + }, + emits: ["open"], + setup(e, { emit: t }) { + let r = e, s = t, l = V(), u = n(() => at(U.running?.task_id ?? null)), d = n(() => ht(U.elapsedSeconds)); + async function f() { + let e = u.value, t = await U.stop(r.client, { + notify: r.notify, + t: l + }); + t !== null && r.notify("success", l("tasks_projects.timer.stopped", { + name: e, + duration: gt(t.duration_minutes) + })); + } + return (e, t) => { + let n = b("BaseIcon"); + return S(U).running === null ? i("", !0) : (g(), a("li", un, [o("div", { + class: "flex h-8 items-center gap-2 rounded-lg bg-white/20 px-2 text-sm text-white md:h-9 md:px-3", + title: S(l)("tasks_projects.timer.running") + }, [ + t[1] ||= o("span", { class: "inline-block h-2 w-2 shrink-0 animate-pulse rounded-full bg-white" }, null, -1), + o("button", { + type: "button", + class: "hidden max-w-32 truncate hover:underline lg:block", + "aria-label": S(l)("tasks_projects.timer.open_timesheet"), + onClick: t[0] ||= (e) => s("open") + }, x(u.value), 9, fn), + o("span", pn, x(d.value), 1), + o("button", { + type: "button", + class: "rounded p-1 hover:bg-white/20 disabled:opacity-50", + disabled: S(U).busy, + title: S(l)("tasks_projects.timer.stop"), + "aria-label": S(l)("tasks_projects.timer.stop"), + onClick: f + }, [c(n, { + name: "StopIcon", + class: "h-4 w-4 text-white" + })], 8, mn) + ], 8, dn)])); + }; + } +}), gn = { en: { tasks_projects: { + time: { + title: "Time", + my_time: "My time", + all_time: "All time", + this_week: "This week", + previous_week: "Previous week", + next_week: "Next week", + week_total: "Week total", + day_total: "Total", + add_entry: "Add entry", + new_entry: "New time entry", + edit_entry: "Edit time entry", + view_entry: "Time entry", + no_entries: "Nothing logged.", + empty_title: "No time logged yet", + empty_description: "Log an entry by hand, or start the timer on a task.", + unknown_member: "Removed member", + unknown_user: "Unable to identify the signed-in user. Reload the page and try again.", + billable: "Billable", + non_billable: "Not billable", + billed: "Billed", + unbilled: "Unbilled", + stamped_notice: "This entry is already on an invoice. Invoiced time is history and cannot be changed.", + created: "The time entry was saved.", + updated: "The time entry was updated.", + deleted: "The time entry was deleted.", + delete_confirm: "Delete this time entry?", + load_failed: "Unable to load the time entries.", + save_failed: "Unable to save the time entry.", + delete_failed: "Unable to delete the time entry.", + members_failed: "Unable to load the members.", + projects_failed: "Unable to load the projects.", + tasks_failed: "Unable to load the tasks.", + columns: { + date: "Date", + member: "Member", + task: "Task", + description: "Description", + duration: "Duration", + billable: "Billable", + amount: "Amount" + }, + filters: { + member: "Member", + project: "Project", + from: "From", + to: "To", + billing: "Billing", + all: "All", + any_member: "Everyone", + any_project: "Any project" + }, + fields: { + task: "Task", + task_placeholder: "Search by task name or number", + date: "Date", + mode: "Entry", + duration: "Duration", + duration_help: "Hours and minutes, as 1:30 or 1.5.", + start: "Start", + end: "End", + description: "Description", + billable: "Billable" + }, + mode: { + duration: "Duration", + range: "Start and end" + }, + task_required: "Pick a task.", + date_required: "Pick a date.", + duration_invalid: "Enter a duration like 1:30 or 1.5.", + range_invalid: "Enter a start and an end time, with the end after the start." + }, + timer: { + running: "Timer running", + quick_start: "Start a timer", + panel_title: "Quick start", + start: "Start", + stop: "Stop", + discard: "Discard", + close: "Close", + open_timesheet: "Open my time", + elapsed: "Elapsed", + search_tasks: "Search tasks", + no_tasks: "No tasks match that search.", + description_placeholder: "What are you working on? (optional)", + started: "The timer is running on {name}.", + stopped: "Logged {duration} on {name}.", + discarded: "The running timer was discarded.", + discard_confirm: "Discard the running timer? The elapsed time is not saved.", + already_running: "A timer is already running. It has been reloaded.", + start_failed: "Unable to start the timer.", + stop_failed: "Unable to stop the timer.", + discard_failed: "Unable to discard the timer." + }, + settings: { + title: "Tasks and Projects", + general_title: "General", + general_description: "The default hourly rate, the rounding increment, the first day of the week and who may see other members time.", + open_module_settings: "Open module settings", + statuses_title: "Task statuses", + statuses_description: "The columns of the board. One status is the default, where new tasks land; a closed status counts as done.", + status_name: "Name", + colour: "Colour", + colour_none: "None", + is_default: "Default", + is_closed: "Closed", + add_status: "Add status", + new_status: "New status", + move_up: "Move up", + move_down: "Move down", + no_statuses: "No statuses yet.", + status_created: "{name} was added.", + status_updated: "{name} was updated.", + status_deleted: "{name} was deleted.", + status_reordered: "The order was saved.", + status_delete_confirm: "Delete {name}?", + status_name_required: "Enter a status name.", + load_failed: "Unable to load the task statuses.", + save_failed: "Unable to save the task status.", + delete_failed: "Unable to delete the task status.", + reorder_failed: "Unable to save the new order.", + forbidden: "Your role does not allow managing the board columns." + } +} } }, _n = { class: "relative table-container" }, vn = { class: "block max-w-64 truncate" }, yn = { class: "tabular-nums" }, bn = { + key: 1, + class: "text-subtle" +}, xn = /* @__PURE__ */ l({ + __name: "AllTimeTable", + props: { + client: { type: [Function, Object] }, + notify: { type: Function }, + members: {}, + projects: {}, + reloadToken: {} + }, + emits: ["edit", "delete"], + setup(e, { emit: t }) { + let l = e, u = t, d = V(), p = v(null), m = _({ + memberId: null, + projectId: null, + from: "", + to: "", + billing: "ALL" + }), h = n(() => [{ + id: 0, + label: d("tasks_projects.time.filters.any_member") + }, ...l.members.map((e) => ({ + id: e.id, + label: e.name + }))]), y = n(() => [{ + id: 0, + label: d("tasks_projects.time.filters.any_project") + }, ...l.projects.map((e) => ({ + id: e.id, + label: e.name + }))]), C = n(() => [ + { + id: "ALL", + label: d("tasks_projects.time.filters.all") + }, + { + id: "BILLED", + label: d("tasks_projects.time.billed") + }, + { + id: "UNBILLED", + label: d("tasks_projects.time.unbilled") + } + ]), w = n({ + get: () => A(h.value, m.memberId ?? 0), + set: (e) => { + m.memberId = typeof e.id == "number" && e.id > 0 ? e.id : null; + } + }), D = n({ + get: () => A(y.value, m.projectId ?? 0), + set: (e) => { + m.projectId = typeof e.id == "number" && e.id > 0 ? e.id : null; + } + }), O = n({ + get: () => A(C.value, m.billing), + set: (e) => { + m.billing = typeof e.id == "string" ? e.id : "ALL"; + } + }), k = n(() => [ + { + key: "date", + label: d("tasks_projects.time.columns.date"), + sortable: !1 + }, + { + key: "member", + label: d("tasks_projects.time.columns.member"), + sortable: !1 + }, + { + key: "task", + label: d("tasks_projects.time.columns.task"), + sortable: !1, + tdClass: "font-medium text-heading" + }, + { + key: "description", + label: d("tasks_projects.time.columns.description"), + sortable: !1 + }, + { + key: "duration", + label: d("tasks_projects.time.columns.duration"), + sortable: !1 + }, + { + key: "billable", + label: d("tasks_projects.time.columns.billable"), + sortable: !1 + }, + { + key: "amount", + label: d("tasks_projects.time.columns.amount"), + sortable: !1 + }, + { + key: "actions", + label: d("tasks_projects.general.actions"), + sortable: !1, + tdClass: "text-right text-sm font-medium" + } + ]); + T(m, () => j()), T(() => l.reloadToken, () => j(!0)); + function A(e, t) { + return e.find((e) => e.id === t) ?? e[0]; + } + function j(e = !1) { + p.value?.refresh(e); + } + function M() { + m.memberId = null, m.projectId = null, m.from = "", m.to = "", m.billing = "ALL"; + } + function N(e) { + m.from = e ? le(e) : ""; + } + function P(e) { + m.to = e ? le(e) : ""; + } + function F(e) { + let t = l.members.find((t) => t.id === e); + return t === void 0 ? l.members.length === 0 ? `#${e}` : d("tasks_projects.time.unknown_member") : t.name; + } + async function I({ page: e }) { + let t = { + page: e, + limit: 25 + }; + m.memberId !== null && (t.user_id = m.memberId), m.projectId !== null && (t.project_id = m.projectId), m.from !== "" && (t.from = m.from), m.to !== "" && (t.to = m.to), m.billing !== "ALL" && (t.billed = m.billing === "BILLED"); + try { + let e = await Le(l.client, t), n = e.data ?? []; + return st(l.client, n.map((e) => e.task_id).filter((e) => typeof e == "number")), { + data: n, + pagination: L(e.meta, n.length) + }; + } catch (e) { + return l.notify("error", B(e, d("tasks_projects.time.load_failed"))), { + data: [], + pagination: L(null, 0) + }; + } + } + function L(e, t) { + return { + totalPages: e?.last_page ?? 1, + currentPage: e?.current_page ?? 1, + totalCount: e?.total ?? t, + count: t, + limit: e?.per_page ?? 25 + }; + } + return (e, t) => { + let n = b("BaseSelectInput"), l = b("BaseInputGroup"), _ = b("BaseDatePicker"), v = b("BaseFilterWrapper"), T = b("BaseBadge"), A = b("BaseFormatMoney"), j = b("BaseIcon"), L = b("BaseDropdownItem"), R = b("BaseDropdown"), z = b("BaseTable"); + return g(), a("section", null, [c(v, { + show: "", + "row-on-xl": "", + class: "mt-3", + onClear: M + }, { + default: E(() => [ + c(l, { + label: S(d)("tasks_projects.time.filters.member"), + class: "mt-2 flex-1" + }, { + default: E(() => [c(n, { + modelValue: w.value, + "onUpdate:modelValue": t[0] ||= (e) => w.value = e, + options: h.value, + "label-key": "label" + }, null, 8, ["modelValue", "options"])]), + _: 1 + }, 8, ["label"]), + c(l, { + label: S(d)("tasks_projects.time.filters.project"), + class: "mt-2 flex-1" + }, { + default: E(() => [c(n, { + modelValue: D.value, + "onUpdate:modelValue": t[1] ||= (e) => D.value = e, + options: y.value, + "label-key": "label" + }, null, 8, ["modelValue", "options"])]), + _: 1 + }, 8, ["label"]), + c(l, { + label: S(d)("tasks_projects.time.filters.from"), + class: "mt-2 flex-1" + }, { + default: E(() => [c(_, { + "model-value": m.from, + "onUpdate:modelValue": N + }, null, 8, ["model-value"])]), + _: 1 + }, 8, ["label"]), + c(l, { + label: S(d)("tasks_projects.time.filters.to"), + class: "mt-2 flex-1" + }, { + default: E(() => [c(_, { + "model-value": m.to, + "onUpdate:modelValue": P + }, null, 8, ["model-value"])]), + _: 1 + }, 8, ["label"]), + c(l, { + label: S(d)("tasks_projects.time.filters.billing"), + class: "mt-2 flex-1" + }, { + default: E(() => [c(n, { + modelValue: O.value, + "onUpdate:modelValue": t[2] ||= (e) => O.value = e, + options: C.value, + "label-key": "label" + }, null, 8, ["modelValue", "options"])]), + _: 1 + }, 8, ["label"]) + ]), + _: 1 + }), o("div", _n, [c(z, { + ref_key: "tableRef", + ref: p, + data: I, + columns: k.value, + class: "mt-3" + }, { + "cell-date": E(({ row: e }) => [s(x(S(ce)(S(vt)(e.data.started_at))), 1)]), + "cell-member": E(({ row: e }) => [s(x(F(e.data.user_id)), 1)]), + "cell-task": E(({ row: e }) => [s(x(S(at)(e.data.task_id)), 1)]), + "cell-description": E(({ row: e }) => [o("span", vn, x(e.data.description || "-"), 1)]), + "cell-duration": E(({ row: e }) => [o("span", yn, x(S(gt)(e.data.duration_minutes)), 1)]), + "cell-billable": E(({ row: e }) => [c(T, { class: f(["rounded-full", e.data.billable ? "bg-primary-50! text-primary-500!" : "bg-surface-tertiary! text-muted!"]) }, { + default: E(() => [s(x(e.data.billable ? S(d)("tasks_projects.time.billable") : S(d)("tasks_projects.time.non_billable")), 1)]), + _: 2 + }, 1032, ["class"])]), + "cell-amount": E(({ row: e }) => [e.data.billable ? (g(), r(A, { + key: 0, + amount: e.data.amount + }, null, 8, ["amount"])) : (g(), a("span", bn, "-"))]), + "cell-actions": E(({ row: e }) => [c(R, null, { + activator: E(() => [c(j, { + name: "EllipsisHorizontalIcon", + class: "h-5 text-muted" + })]), + default: E(() => [c(L, { onClick: (t) => u("edit", e.data) }, { + default: E(() => [c(j, { + name: "PencilIcon", + class: "mr-3 h-5 w-5 text-subtle group-hover:text-muted" + }), s(" " + x(S(d)("tasks_projects.general.edit")), 1)]), + _: 1 + }, 8, ["onClick"]), e.data.invoice_id === null ? (g(), r(L, { + key: 0, + onClick: (t) => u("delete", e.data) + }, { + default: E(() => [c(j, { + name: "TrashIcon", + class: "mr-3 h-5 w-5 text-subtle group-hover:text-muted" + }), s(" " + x(S(d)("tasks_projects.general.delete")), 1)]), + _: 1 + }, 8, ["onClick"])) : i("", !0)]), + _: 2 + }, 1024)]), + _: 1 + }, 8, ["columns"])])]); + }; + } +}), Sn = { class: "flex w-full items-center justify-between" }, Cn = { class: "space-y-5 px-6 py-6" }, wn = { + key: 0, + class: "rounded-md bg-alert-warning-bg px-3 py-2 text-sm text-alert-warning-text" +}, Tn = { class: "inline-flex overflow-hidden rounded-md border border-line-default" }, En = ["disabled", "onClick"], Dn = { + key: 1, + class: "text-sm text-muted" +}, On = { class: "flex items-center justify-between border-t border-line-default px-6 py-4" }, kn = { key: 1 }, An = { class: "flex space-x-3" }, jn = "09:00", Mn = /* @__PURE__ */ l({ + __name: "TimeEntryModal", + props: { + show: { type: Boolean }, + client: { type: [Function, Object] }, + notify: { type: Function }, + entry: {}, + defaultDate: {} + }, + emits: [ + "close", + "saved", + "deleted" + ], + setup(t, { emit: l }) { + let u = t, d = l, p = ["duration", "range"], m = V(), h = _({ + date: "", + mode: "duration", + duration: "", + start: jn, + end: "", + description: "", + billable: !0 + }), C = v(null), w = v({}), D = v(!1), O = v(!1), A = n(() => u.entry !== null), j = n(() => u.entry?.invoice_id != null), M = n(() => j.value ? m("tasks_projects.time.view_entry") : A.value ? m("tasks_projects.time.edit_entry") : m("tasks_projects.time.new_entry")); + T(() => u.show, (e) => { + e && N(); + }, { immediate: !0 }); + function N() { + let e = u.entry; + w.value = {}, C.value = null, h.date = e ? vt(e.started_at) : u.defaultDate ?? Tt(/* @__PURE__ */ new Date()), h.duration = e ? gt(e.duration_minutes) : "", h.start = e?.started_at ? yt(e.started_at) : jn, h.end = e?.ended_at ? yt(e.ended_at) : "", h.description = e?.description ?? "", h.billable = !e || e.billable, h.mode = e !== null && P(e) ? "range" : "duration", h.date === "" && (h.date = u.defaultDate ?? Tt(/* @__PURE__ */ new Date())), e !== null && F(e.task_id); + } + function P(e) { + if (!e.started_at || !e.ended_at) return !1; + let t = new Date(e.started_at).getTime(), n = new Date(e.ended_at).getTime(); + return Number.isNaN(t) || Number.isNaN(n) ? !1 : Math.round((n - t) / 6e4) === e.duration_minutes; + } + async function F(e) { + try { + let t = await Qe(u.client, e); + C.value = t, ot(t); + } catch {} + } + async function I(e) { + try { + let t = await Ze(u.client, e ?? ""); + return t.forEach(ot), t; + } catch (e) { + return u.notify("error", B(e, m("tasks_projects.time.tasks_failed"))), []; + } + } + function L(e) { + h.date = e ? le(e) : ""; + } + function R(e) { + C.value = e, e !== null && u.entry === null && (h.billable = e.billable !== !1); + } + function z() { + let e = {}, t = C.value; + (t === null || typeof t.id != "number") && (e.task_id = m("tasks_projects.time.task_required")), h.date === "" && (e.date = m("tasks_projects.time.date_required")); + let n = bt(h.date, h.mode === "range" ? h.start : jn); + n === null && (e.started_at = m("tasks_projects.time.range_invalid")); + let r = h.mode === "duration" ? _t(h.duration) : null; + h.mode === "duration" && r === null && (e.duration_minutes = m("tasks_projects.time.duration_invalid")); + let i = h.mode === "range" ? bt(h.date, h.end) : null; + if (h.mode === "range" && (i === null || n === null || i <= n) && (e.ended_at = m("tasks_projects.time.range_invalid")), w.value = e, Object.keys(e).length > 0 || t === null || n === null) return null; + let a = { + task_id: t.id, + started_at: n, + description: h.description.trim() || null, + billable: h.billable + }; + return h.mode === "duration" && r !== null ? (a.duration_minutes = r, a.ended_at = xt(n, r)) : a.ended_at = i, a; + } + async function ee() { + if (D.value || j.value) return; + let e = z(); + if (e !== null) { + D.value = !0; + try { + let t = u.entry, n = t ? await Be(u.client, t.id, e) : await ze(u.client, e); + d("saved", n); + } catch (e) { + w.value = re(e), u.notify("error", B(e, m("tasks_projects.time.save_failed"))); + } finally { + D.value = !1; + } + } + } + async function te() { + let e = u.entry; + if (!(e === null || O.value || j.value) && window.confirm(m("tasks_projects.time.delete_confirm"))) { + O.value = !0; + try { + await Ve(u.client, e.id), d("deleted", e); + } catch (e) { + u.notify("error", B(e, m("tasks_projects.time.delete_failed"))); + } finally { + O.value = !1; + } + } + } + return (n, l) => { + let u = b("BaseIcon"), _ = b("BaseMultiselect"), v = b("BaseInputGroup"), T = b("BaseDatePicker"), N = b("BaseInputGrid"), P = b("BaseInput"), F = b("BaseTextarea"), z = b("BaseSwitch"), ne = b("BaseButton"), B = b("BaseModal"); + return g(), r(B, { + show: t.show, + onClose: l[8] ||= (e) => d("close") + }, { + header: E(() => [o("div", Sn, [o("span", null, x(M.value), 1), c(u, { + name: "XMarkIcon", + class: "h-6 w-6 cursor-pointer text-subtle hover:text-body", + onClick: l[0] ||= (e) => d("close") + })])]), + default: E(() => [o("form", { onSubmit: k(ee, ["prevent"]) }, [o("div", Cn, [ + j.value ? (g(), a("p", wn, x(S(m)("tasks_projects.time.stamped_notice")), 1)) : i("", !0), + c(v, { + label: S(m)("tasks_projects.time.fields.task"), + error: w.value.task_id, + required: "" + }, { + default: E(() => [c(_, { + "model-value": C.value, + options: I, + disabled: j.value, + invalid: !!w.value.task_id, + placeholder: S(m)("tasks_projects.time.fields.task_placeholder"), + "initial-search": C.value?.name ?? "", + delay: 400, + "filter-results": !1, + "value-prop": "id", + "track-by": "name", + label: "name", + object: "", + searchable: "", + "preserve-search": "", + "resolve-on-load": "", + "onUpdate:modelValue": l[1] ||= (e) => R(e) + }, null, 8, [ + "model-value", + "disabled", + "invalid", + "placeholder", + "initial-search" + ])]), + _: 1 + }, 8, ["label", "error"]), + c(N, null, { + default: E(() => [c(v, { + label: S(m)("tasks_projects.time.fields.date"), + error: w.value.date, + required: "" + }, { + default: E(() => [c(T, { + "model-value": h.date, + disabled: j.value, + invalid: !!w.value.date, + "onUpdate:modelValue": L + }, null, 8, [ + "model-value", + "disabled", + "invalid" + ])]), + _: 1 + }, 8, ["label", "error"]), c(v, { label: S(m)("tasks_projects.time.fields.mode") }, { + default: E(() => [o("div", Tn, [(g(), a(e, null, y(p, (e) => o("button", { + key: e, + type: "button", + class: f(["px-3 py-2 text-sm", h.mode === e ? "bg-primary-500 text-white" : "bg-surface text-body hover:bg-hover"]), + disabled: j.value, + onClick: (t) => h.mode = e + }, x(S(m)(`tasks_projects.time.mode.${e}`)), 11, En)), 64))])]), + _: 1 + }, 8, ["label"])]), + _: 1 + }), + h.mode === "duration" ? (g(), r(v, { + key: 1, + label: S(m)("tasks_projects.time.fields.duration"), + error: w.value.duration_minutes, + "help-text": S(m)("tasks_projects.time.fields.duration_help"), + required: "" + }, { + default: E(() => [c(P, { + modelValue: h.duration, + "onUpdate:modelValue": l[2] ||= (e) => h.duration = e, + type: "text", + inputmode: "text", + placeholder: "1:30", + disabled: j.value, + invalid: !!w.value.duration_minutes + }, null, 8, [ + "modelValue", + "disabled", + "invalid" + ])]), + _: 1 + }, 8, [ + "label", + "error", + "help-text" + ])) : (g(), r(N, { key: 2 }, { + default: E(() => [c(v, { + label: S(m)("tasks_projects.time.fields.start"), + error: w.value.started_at, + required: "" + }, { + default: E(() => [c(P, { + modelValue: h.start, + "onUpdate:modelValue": l[3] ||= (e) => h.start = e, + type: "time", + disabled: j.value, + invalid: !!w.value.started_at + }, null, 8, [ + "modelValue", + "disabled", + "invalid" + ])]), + _: 1 + }, 8, ["label", "error"]), c(v, { + label: S(m)("tasks_projects.time.fields.end"), + error: w.value.ended_at, + required: "" + }, { + default: E(() => [c(P, { + modelValue: h.end, + "onUpdate:modelValue": l[4] ||= (e) => h.end = e, + type: "time", + disabled: j.value, + invalid: !!w.value.ended_at + }, null, 8, [ + "modelValue", + "disabled", + "invalid" + ])]), + _: 1 + }, 8, ["label", "error"])]), + _: 1 + })), + c(v, { + label: S(m)("tasks_projects.time.fields.description"), + error: w.value.description + }, { + default: E(() => [c(F, { + modelValue: h.description, + "onUpdate:modelValue": l[5] ||= (e) => h.description = e, + row: 3, + disabled: j.value, + invalid: !!w.value.description + }, null, 8, [ + "modelValue", + "disabled", + "invalid" + ])]), + _: 1 + }, 8, ["label", "error"]), + c(v, { + label: S(m)("tasks_projects.time.fields.billable"), + error: w.value.billable + }, { + default: E(() => [j.value ? (g(), a("span", Dn, x(h.billable ? S(m)("tasks_projects.time.billable") : S(m)("tasks_projects.time.non_billable")), 1)) : (g(), r(z, { + key: 0, + modelValue: h.billable, + "onUpdate:modelValue": l[6] ||= (e) => h.billable = e, + class: "flex" + }, null, 8, ["modelValue"]))]), + _: 1 + }, 8, ["label", "error"]) + ]), o("div", On, [A.value && !j.value ? (g(), r(ne, { + key: 0, + type: "button", + variant: "danger", + size: "sm", + loading: O.value, + disabled: O.value, + onClick: te + }, { + default: E(() => [s(x(S(m)("tasks_projects.general.delete")), 1)]), + _: 1 + }, 8, ["loading", "disabled"])) : (g(), a("span", kn)), o("div", An, [c(ne, { + type: "button", + variant: "primary-outline", + onClick: l[7] ||= (e) => d("close") + }, { + default: E(() => [s(x(j.value ? S(m)("tasks_projects.timer.close") : S(m)("tasks_projects.general.cancel")), 1)]), + _: 1 + }), j.value ? i("", !0) : (g(), r(ne, { + key: 0, + type: "submit", + variant: "primary", + loading: D.value, + disabled: D.value + }, { + default: E(() => [s(x(A.value ? S(m)("tasks_projects.general.update") : S(m)("tasks_projects.general.save")), 1)]), + _: 1 + }, 8, ["loading", "disabled"]))])])], 32)]), + _: 1 + }, 8, ["show"]); + }; + } +}), Nn = { class: "mt-4 flex flex-wrap items-center justify-between gap-3" }, Pn = { class: "flex items-center gap-2" }, Fn = { class: "ml-1 text-sm text-muted" }, In = { class: "flex items-center gap-2 text-sm" }, Ln = { class: "text-muted" }, Rn = { class: "text-lg font-semibold tabular-nums text-heading" }, zn = { + key: 0, + class: "mt-6 text-sm text-muted" +}, Bn = { + key: 1, + class: "mt-4 grid gap-3 sm:grid-cols-2 lg:grid-cols-4 xl:grid-cols-7" +}, Vn = { class: "flex items-baseline justify-between" }, Hn = { class: "text-xs font-semibold tracking-wide text-heading uppercase" }, Un = { class: "text-xs text-muted" }, Wn = { class: "text-sm font-medium tabular-nums text-heading" }, Gn = { class: "mt-3 flex-1 space-y-2" }, Kn = ["onClick"], qn = { class: "flex items-center justify-between gap-2" }, Jn = { class: "truncate text-xs font-medium text-heading" }, Yn = { class: "shrink-0 text-xs tabular-nums text-muted" }, Xn = { + key: 0, + class: "mt-1 block truncate text-xs text-muted" +}, Zn = { class: "mt-1 flex items-center gap-1" }, Qn = { class: "text-[11px] text-subtle" }, $n = { + key: 0, + class: "text-[11px] text-subtle" +}, er = { + key: 0, + class: "py-2 text-xs text-subtle" +}, tr = ["onClick"], nr = { + key: 2, + class: "mt-4 text-center text-sm text-subtle" +}, rr = /* @__PURE__ */ l({ + __name: "WeekTimesheet", + props: { + client: { type: [Function, Object] }, + notify: { type: Function }, + userId: {}, + weekStart: {}, + reloadToken: {} + }, + emits: ["add", "edit"], + setup(t, { emit: l }) { + let u = t, d = l, p = V(), m = v(St(/* @__PURE__ */ new Date(), u.weekStart)), h = v([]), _ = v(!1), C = n(() => Ct(m.value)), w = n(() => { + let e = C.value[0], t = C.value[C.value.length - 1]; + return `${Et(e).day} - ${Et(t).day}`; + }), D = n(() => C.value.map((e) => { + let t = Tt(e), n = h.value.filter((e) => vt(e.started_at) === t), r = Et(e); + return { + key: t, + weekday: r.weekday, + day: r.day, + today: Dt(e), + entries: n, + minutes: A(n) + }; + })), O = n(() => A(h.value)), k = n(() => !_.value && h.value.length === 0); + T(() => u.weekStart, (e) => { + m.value = St(m.value, e); + }), T([ + m, + () => u.userId, + () => u.reloadToken + ], () => void j(), { immediate: !0 }); + function A(e) { + return e.reduce((e, t) => e + (t.duration_minutes ?? 0), 0); + } + async function j() { + if (u.userId === null) { + h.value = []; + return; + } + _.value = !0; + try { + let e = await Re(u.client, { + user_id: u.userId, + from: Tt(C.value[0]), + to: Tt(C.value[C.value.length - 1]) + }); + h.value = e, st(u.client, e.map((e) => e.task_id).filter((e) => typeof e == "number")); + } catch (e) { + h.value = [], u.notify("error", B(e, p("tasks_projects.time.load_failed"))); + } finally { + _.value = !1; + } + } + function M(e) { + m.value = wt(m.value, e * 7); + } + function N() { + m.value = St(/* @__PURE__ */ new Date(), u.weekStart); + } + return (n, l) => { + let u = b("BaseIcon"), m = b("BaseButton"), h = b("BaseSpinner"); + return g(), a("section", null, [ + o("header", Nn, [o("div", Pn, [ + c(m, { + variant: "white", + size: "sm", + title: S(p)("tasks_projects.time.previous_week"), + onClick: l[0] ||= (e) => M(-1) + }, { + default: E(() => [c(u, { + name: "ChevronLeftIcon", + class: "h-4 w-4" + })]), + _: 1 + }, 8, ["title"]), + c(m, { + variant: "white", + size: "sm", + onClick: N + }, { + default: E(() => [s(x(S(p)("tasks_projects.time.this_week")), 1)]), + _: 1 + }), + c(m, { + variant: "white", + size: "sm", + title: S(p)("tasks_projects.time.next_week"), + onClick: l[1] ||= (e) => M(1) + }, { + default: E(() => [c(u, { + name: "ChevronRightIcon", + class: "h-4 w-4" + })]), + _: 1 + }, 8, ["title"]), + o("span", Fn, x(w.value), 1) + ]), o("div", In, [ + o("span", Ln, x(S(p)("tasks_projects.time.week_total")), 1), + o("span", Rn, x(S(gt)(O.value)), 1), + _.value ? (g(), r(h, { + key: 0, + class: "h-4 w-4 text-primary-500" + })) : i("", !0) + ])]), + t.userId === null ? (g(), a("p", zn, x(S(p)("tasks_projects.time.unknown_user")), 1)) : (g(), a("div", Bn, [(g(!0), a(e, null, y(D.value, (t) => (g(), a("article", { + key: t.key, + class: f(["flex min-h-40 flex-col rounded-xl border bg-surface p-3", t.today ? "border-primary-400" : "border-line-default"]) + }, [ + o("header", Vn, [o("div", null, [o("p", Hn, x(t.weekday), 1), o("p", Un, x(t.day), 1)]), o("span", Wn, x(S(gt)(t.minutes)), 1)]), + o("ul", Gn, [(g(!0), a(e, null, y(t.entries, (e) => (g(), a("li", { key: e.id }, [o("button", { + type: "button", + class: "w-full rounded-md border border-line-light px-2 py-2 text-left hover:bg-hover", + onClick: (t) => d("edit", e) + }, [ + o("span", qn, [o("span", Jn, x(S(at)(e.task_id)), 1), o("span", Yn, x(S(gt)(e.duration_minutes)), 1)]), + e.description ? (g(), a("span", Xn, x(e.description), 1)) : i("", !0), + o("span", Zn, [ + o("span", { class: f(["inline-block h-1.5 w-1.5 rounded-full", e.billable ? "bg-status-green" : "bg-line-strong"]) }, null, 2), + o("span", Qn, x(e.billable ? S(p)("tasks_projects.time.billable") : S(p)("tasks_projects.time.non_billable")), 1), + e.invoice_id === null ? i("", !0) : (g(), a("span", $n, " - " + x(S(p)("tasks_projects.time.billed")), 1)) + ]) + ], 8, Kn)]))), 128)), t.entries.length === 0 ? (g(), a("li", er, x(S(p)("tasks_projects.time.no_entries")), 1)) : i("", !0)]), + o("button", { + type: "button", + class: "mt-2 flex items-center justify-center gap-1 rounded-md border border-dashed border-line-default py-1.5 text-xs text-muted hover:bg-hover hover:text-heading", + onClick: (e) => d("add", t.key) + }, [c(u, { + name: "PlusIcon", + class: "h-4 w-4" + }), s(" " + x(S(p)("tasks_projects.time.add_entry")), 1)], 8, tr) + ], 2))), 128))])), + k.value && t.userId !== null ? (g(), a("p", nr, x(S(p)("tasks_projects.time.empty_description")), 1)) : i("", !0) + ]); + }; + } +}), ir = { + default_rate: 0, + rounding_minutes: 1, + week_start: 1, + members_see_all_time: !1, + rounding_increments: [ + 1, + 6, + 15, + 30 + ] +}, ar = _({ + adminMode: !1, + userId: null, + settings: { ...ir }, + companySession: 0, + loading: !1 +}); +async function or(e) { + if (ar.adminMode) return; + ar.loading = !0; + let [t, n] = await Promise.all([tt(e).catch(() => null), et(e).catch(() => null)]); + ar.userId = t, ar.settings = lr(n), ar.loading = !1; +} +function sr() { + ar.userId = null, ar.settings = { ...ir }, ar.companySession += 1, ar.loading = !1; +} +function cr(e) { + ar.adminMode = e; +} +function lr(e) { + if (typeof e != "object" || !e) return { ...ir }; + let t = Array.isArray(e.rounding_increments) ? e.rounding_increments.filter((e) => typeof e == "number") : ir.rounding_increments; + return { + default_rate: ur(e.default_rate, ir.default_rate), + rounding_minutes: ur(e.rounding_minutes, ir.rounding_minutes), + week_start: dr(e.week_start), + members_see_all_time: e.members_see_all_time === !0, + rounding_increments: t.length > 0 ? t : ir.rounding_increments + }; +} +function ur(e, t) { + return typeof e == "number" && Number.isFinite(e) ? e : t; +} +function dr(e) { + return typeof e == "number" && Number.isInteger(e) && e >= 0 && e <= 6 ? e : ir.week_start; +} +//#endregion +//#region resources/js/pages/TimePage.vue?vue&type=script&setup=true&lang.ts +var fr = { class: "flex items-center justify-end space-x-5" }, pr = { + key: 0, + class: "hidden items-center gap-2 text-sm text-muted sm:flex" +}, mr = { + key: 0, + class: "mt-4 flex gap-6 border-b border-line-default" +}, hr = 5, gr = /* @__PURE__ */ l({ + __name: "TimePage", + props: { + client: { type: [Function, Object] }, + notify: { type: Function }, + router: {} + }, + setup(e) { + let t = e, l = V(), u = v("MINE"), d = v(!1), p = v([]), m = v([]), _ = v(!1), y = v(null), C = v(Tt(/* @__PURE__ */ new Date())), w = v(0), T = n(() => ar.settings.week_start), D = n(() => ar.userId); + h(() => void O()); + async function O() { + ar.userId === null && await or(t.client), d.value = ar.settings.members_see_all_time || await k(), d.value && await Promise.all([A(), j()]); + } + async function k() { + try { + return ((await Le(t.client, { limit: hr })).data ?? []).some((e) => e.user_id !== ar.userId); + } catch { + return !1; + } + } + async function A() { + try { + p.value = await $e(t.client); + } catch { + p.value = []; + } + } + async function j() { + try { + let e = await P(t.client, { limit: 100 }); + m.value = e.data ?? []; + } catch { + m.value = []; + } + } + function M(e) { + y.value = null, C.value = e ?? Tt(/* @__PURE__ */ new Date()), _.value = !0; + } + function N(e) { + y.value = e, _.value = !0; + } + function F() { + let e = y.value ? l("tasks_projects.time.updated") : l("tasks_projects.time.created"); + _.value = !1, y.value = null, t.notify("success", e), w.value += 1; + } + function I() { + _.value = !1, y.value = null, t.notify("success", l("tasks_projects.time.deleted")), w.value += 1; + } + async function L(e) { + if (window.confirm(l("tasks_projects.time.delete_confirm"))) try { + await Ve(t.client, e.id), t.notify("success", l("tasks_projects.time.deleted")), w.value += 1; + } catch (e) { + t.notify("error", B(e, l("tasks_projects.time.delete_failed"))); + } + } + function R(e) { + return u.value === e ? "border-primary-500 text-primary-500" : "border-transparent text-muted hover:border-line-strong hover:text-heading"; + } + return (t, n) => { + let h = b("BaseBreadcrumbItem"), v = b("BaseBreadcrumb"), O = b("BaseIcon"), k = b("BaseButton"), A = b("BasePageHeader"), j = b("BasePage"); + return g(), r(j, null, { + default: E(() => [ + c(A, { title: S(l)("tasks_projects.time.title") }, { + actions: E(() => [o("div", fr, [S(U).running === null ? i("", !0) : (g(), a("span", pr, [c(O, { + name: "ClockIcon", + class: "h-4 w-4 text-primary-500" + }), s(" " + x(S(l)("tasks_projects.timer.running")), 1)])), c(k, { + variant: "primary", + onClick: n[0] ||= (e) => M() + }, { + left: E((e) => [c(O, { + name: "PlusIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: E(() => [s(" " + x(S(l)("tasks_projects.time.add_entry")), 1)]), + _: 1 + })])]), + default: E(() => [c(v, null, { + default: E(() => [ + c(h, { + title: S(l)("tasks_projects.general.home"), + to: "/admin/dashboard" + }, null, 8, ["title"]), + c(h, { + title: S(l)("tasks_projects.projects.title"), + to: "/admin/modules/tasks-projects" + }, null, 8, ["title"]), + c(h, { + title: S(l)("tasks_projects.time.title"), + to: "#", + active: "" + }, null, 8, ["title"]) + ]), + _: 1 + })]), + _: 1 + }, 8, ["title"]), + d.value ? (g(), a("nav", mr, [o("button", { + type: "button", + class: f(["-mb-px border-b-2 px-1 pb-3 text-sm font-medium", R("MINE")]), + onClick: n[1] ||= (e) => u.value = "MINE" + }, x(S(l)("tasks_projects.time.my_time")), 3), o("button", { + type: "button", + class: f(["-mb-px border-b-2 px-1 pb-3 text-sm font-medium", R("ALL")]), + onClick: n[2] ||= (e) => u.value = "ALL" + }, x(S(l)("tasks_projects.time.all_time")), 3)])) : i("", !0), + u.value === "MINE" ? (g(), r(rr, { + key: 1, + client: e.client, + notify: e.notify, + "user-id": D.value, + "week-start": T.value, + "reload-token": w.value, + onAdd: M, + onEdit: N + }, null, 8, [ + "client", + "notify", + "user-id", + "week-start", + "reload-token" + ])) : (g(), r(xn, { + key: 2, + client: e.client, + notify: e.notify, + members: p.value, + projects: m.value, + "reload-token": w.value, + onEdit: N, + onDelete: L + }, null, 8, [ + "client", + "notify", + "members", + "projects", + "reload-token" + ])), + c(Mn, { + show: _.value, + client: e.client, + notify: e.notify, + entry: y.value, + "default-date": C.value, + onClose: n[3] ||= (e) => _.value = !1, + onSaved: F, + onDeleted: I + }, null, 8, [ + "show", + "client", + "notify", + "entry", + "default-date" + ]) + ]), + _: 1 + }); + }; + } +}), _r = { + key: 0, + class: "text-sm text-muted" +}, vr = { key: 1 }, yr = { + key: 0, + class: "flex items-center gap-2 text-sm text-muted" +}, br = { + key: 1, + class: "text-sm text-muted" +}, xr = { + key: 2, + class: "divide-y divide-line-light" +}, Sr = { + key: 0, + class: "space-y-3" +}, Cr = { class: "flex flex-wrap items-center gap-2" }, wr = ["aria-label", "onClick"], Tr = { class: "flex flex-wrap items-center gap-6" }, Er = { class: "flex items-center gap-2 text-sm text-body" }, Dr = { class: "flex items-center gap-2 text-sm text-body" }, Or = { class: "flex gap-3" }, kr = { + key: 1, + class: "flex items-center gap-3" +}, Ar = { class: "min-w-0 flex-1 truncate text-sm font-medium text-heading" }, jr = { class: "flex items-center gap-1" }, Mr = [ + "disabled", + "title", + "aria-label", + "onClick" +], Nr = [ + "disabled", + "title", + "aria-label", + "onClick" +], Pr = [ + "title", + "aria-label", + "onClick" +], Fr = [ + "disabled", + "title", + "aria-label", + "onClick" +], Ir = { + key: 3, + class: "mt-4 space-y-3 rounded-lg border border-line-default p-3" +}, Lr = { class: "flex flex-wrap items-center gap-2" }, Rr = ["aria-label", "onClick"], zr = { class: "flex flex-wrap items-center gap-6" }, Br = { class: "flex items-center gap-2 text-sm text-body" }, Vr = { class: "flex items-center gap-2 text-sm text-body" }, Hr = { class: "flex gap-3" }, Ur = /* @__PURE__ */ l({ + __name: "TaskStatusEditor", + props: { + client: { type: [Function, Object] }, + notify: { type: Function } + }, + setup(t) { + let l = t, u = [ + "#94a3b8", + "#3b82f6", + "#22c55e", + "#f59e0b", + "#ef4444", + "#a855f7", + "#0891b2", + "#64748b" + ], d = V(), m = v([]), C = v(!0), w = v(!1), T = v(!1), D = v(null), O = v(!1), k = _({ + name: "", + colour: "", + is_default: !1, + is_closed: !1 + }), A = n(() => !C.value && m.value.length === 0); + h(() => void j()); + async function j() { + C.value = !0; + try { + m.value = await Ke(l.client), w.value = !1; + } catch (e) { + m.value = [], w.value = dt(e), w.value || l.notify("error", B(e, d("tasks_projects.settings.load_failed"))); + } finally { + C.value = !1; + } } - function se() { - A.search = "", A.status = "ACTIVE"; + function M(e) { + O.value = !1, D.value = e.id, k.name = e.name, k.colour = e.colour ?? "", k.is_default = e.is_default, k.is_closed = e.is_closed; } - function le() { - O.value = null, D.value = !0; + function N() { + D.value = null, O.value = !0, k.name = "", k.colour = u[0], k.is_default = !1, k.is_closed = !1; } - function R(e) { - O.value = e, D.value = !0; + function P() { + D.value = null, O.value = !1; } - function ue(e) { - let t = O.value ? l("tasks_projects.projects.updated", { name: e.name }) : l("tasks_projects.projects.created", { name: e.name }); - D.value = !1, O.value = null, c.notify("success", t), ae(); + function F() { + return { + name: k.name.trim(), + colour: k.colour || null, + is_default: k.is_default, + is_closed: k.is_closed + }; } - async function de(e) { - k.value = e.id; + async function I() { + if (T.value) return; + if (k.name.trim() === "") { + l.notify("error", d("tasks_projects.settings.status_name_required")); + return; + } + let e = D.value, t = k.name.trim(); + T.value = !0; try { - e.status === "ARCHIVED" ? (await P(c.client, e.id), c.notify("success", l("tasks_projects.projects.unarchived", { name: e.name }))) : (await N(c.client, e.id), c.notify("success", l("tasks_projects.projects.archived", { name: e.name }))), ae(!0); + e === null ? (await qe(l.client, F()), l.notify("success", d("tasks_projects.settings.status_created", { name: t }))) : (await Je(l.client, e, F()), l.notify("success", d("tasks_projects.settings.status_updated", { name: t }))), P(), await j(); } catch (e) { - c.notify("error", L(e, l("tasks_projects.projects.save_failed"))); + l.notify("error", B(e, d("tasks_projects.settings.save_failed"))); } finally { - k.value = null; + T.value = !1; } } - async function pe(e) { - if (window.confirm(l("tasks_projects.projects.delete_confirm", { name: e.name }))) { - k.value = e.id; + async function L(e) { + if (!T.value && window.confirm(d("tasks_projects.settings.status_delete_confirm", { name: e.name }))) { + T.value = !0; try { - await F(c.client, e.id), c.notify("success", l("tasks_projects.projects.deleted", { name: e.name })), ae(!0); + await Ye(l.client, e.id), l.notify("success", d("tasks_projects.settings.status_deleted", { name: e.name })), P(), await j(); } catch (e) { - c.notify("error", L(e, l("tasks_projects.projects.delete_failed"))); + l.notify("error", B(e, d("tasks_projects.settings.delete_failed"))); } finally { - k.value = null; + T.value = !1; } } } - function me(e) { - return e === "ACTIVE" ? "bg-primary-50! text-primary-500!" : "bg-surface-tertiary! text-muted!"; - } - function he(e) { - return l(e === "ACTIVE" ? "tasks_projects.projects.status.active" : "tasks_projects.projects.status.archived"); + async function R(e, t) { + let n = e + t; + if (T.value || n < 0 || n >= m.value.length) return; + let r = [...m.value]; + r.splice(n, 0, ...r.splice(e, 1)), m.value = r, T.value = !0; + try { + m.value = await Xe(l.client, r.map((e) => e.id)), l.notify("success", d("tasks_projects.settings.status_reordered")); + } catch (e) { + l.notify("error", B(e, d("tasks_projects.settings.reorder_failed"))), await j(); + } finally { + T.value = !1; + } } - return (t, c) => { - let p = y("BaseBreadcrumbItem"), g = y("BaseBreadcrumb"), _ = y("BaseIcon"), v = y("BaseButton"), C = y("router-link"), E = y("BasePageHeader"), j = y("BaseInput"), N = y("BaseInputGroup"), P = y("BaseSelectInput"), F = y("BaseFilterWrapper"), ne = y("BaseEmptyPlaceholder"), L = y("BaseBadge"), re = y("BaseFormatMoney"), ae = y("BaseDropdownItem"), fe = y("BaseDropdown"), ge = y("BaseTable"), _e = y("BasePage"); - return h(), n(_e, null, { - default: w(() => [ - s(E, { title: x(l)("tasks_projects.projects.title") }, { - actions: w(() => [a("div", ye, [ - s(C, { to: "/admin/modules/tasks-projects/board" }, { - default: w(() => [s(v, { variant: "white" }, { - left: w((e) => [s(_, { - name: "ViewColumnsIcon", - class: d(e.class) - }, null, 8, ["class"])]), - default: w(() => [o(" " + b(x(l)("tasks_projects.board.title")), 1)]), - _: 1 - })]), - _: 1 - }), - s(v, { - variant: "primary-outline", - onClick: oe - }, { - right: w((e) => [m.value ? (h(), n(_, { - key: 1, - name: "XMarkIcon", - class: d(e.class) - }, null, 8, ["class"])) : (h(), n(_, { - key: 0, - name: "FunnelIcon", - class: d(e.class) - }, null, 8, ["class"]))]), - default: w(() => [o(b(x(l)("tasks_projects.general.filter")) + " ", 1)]), - _: 1 - }), - s(v, { - variant: "primary", - onClick: le - }, { - left: w((e) => [s(_, { - name: "PlusIcon", - class: d(e.class) - }, null, 8, ["class"])]), - default: w(() => [o(" " + b(x(l)("tasks_projects.projects.new_project")), 1)]), - _: 1 - }) - ])]), - default: w(() => [s(g, null, { - default: w(() => [s(p, { - title: x(l)("tasks_projects.general.home"), - to: "/admin/dashboard" - }, null, 8, ["title"]), s(p, { - title: x(l)("tasks_projects.projects.title"), - to: "#", - active: "" - }, null, 8, ["title"])]), - _: 1 - })]), - _: 1 - }, 8, ["title"]), - s(F, { - show: m.value, - class: "mt-3", - onClear: se - }, { - default: w(() => [s(N, { - label: x(l)("tasks_projects.general.search"), - class: "mt-2 flex-1" - }, { - default: w(() => [s(j, { - modelValue: A.search, - "onUpdate:modelValue": c[0] ||= (e) => A.search = e, - type: "text", - name: "search", - autocomplete: "off", - placeholder: x(l)("tasks_projects.projects.search_placeholder") - }, null, 8, ["modelValue", "placeholder"])]), - _: 1 - }, 8, ["label"]), s(N, { - label: x(l)("tasks_projects.projects.columns.status"), - class: "mt-2 flex-1" - }, { - default: w(() => [s(P, { - modelValue: ee.value, - "onUpdate:modelValue": c[1] ||= (e) => ee.value = e, - options: M.value, - "label-key": "label" - }, null, 8, ["modelValue", "options"])]), - _: 1 - }, 8, ["label"])]), - _: 1 - }, 8, ["show"]), - T(s(ne, { - title: x(l)("tasks_projects.projects.empty_title"), - description: x(l)("tasks_projects.projects.empty_description") - }, { - actions: w(() => [s(v, { - variant: "primary", - onClick: le - }, { - left: w((e) => [s(_, { - name: "PlusIcon", - class: d(e.class) - }, null, 8, ["class"])]), - default: w(() => [o(" " + b(x(l)("tasks_projects.projects.new_project")), 1)]), - _: 1 - })]), - default: w(() => [s(_, { - name: "FolderIcon", - class: "mt-5 mb-4 h-16 w-16 text-subtle" - })]), - _: 1 - }, 8, ["title", "description"]), [[S, I.value]]), - T(a("div", be, [s(ge, { - ref_key: "tableRef", - ref: u, - data: ie, - columns: te.value, - class: "mt-3" + return (t, n) => { + let l = b("BaseSpinner"), h = b("BaseInput"), _ = b("BaseInputGroup"), v = b("BaseSwitch"), j = b("BaseButton"), F = b("BaseBadge"), z = b("BaseIcon"); + return g(), a("div", null, [w.value ? (g(), a("p", _r, x(S(d)("tasks_projects.settings.forbidden")), 1)) : (g(), a("div", vr, [C.value ? (g(), a("div", yr, [c(l, { class: "h-4 w-4 text-primary-500" })])) : A.value ? (g(), a("p", br, x(S(d)("tasks_projects.settings.no_statuses")), 1)) : (g(), a("ul", xr, [(g(!0), a(e, null, y(m.value, (t, l) => (g(), a("li", { + key: t.id, + class: "py-3" + }, [D.value === t.id ? (g(), a("div", Sr, [ + c(_, { + label: S(d)("tasks_projects.settings.status_name"), + required: "" + }, { + default: E(() => [c(h, { + modelValue: k.name, + "onUpdate:modelValue": n[0] ||= (e) => k.name = e, + type: "text", + maxlength: "255" + }, null, 8, ["modelValue"])]), + _: 1 + }, 8, ["label"]), + c(_, { label: S(d)("tasks_projects.settings.colour") }, { + default: E(() => [o("div", Cr, [(g(), a(e, null, y(u, (e) => o("button", { + key: e, + type: "button", + class: f(["h-7 w-7 rounded-full border-2 transition", k.colour === e ? "border-heading" : "border-line-default"]), + style: p({ backgroundColor: e }), + "aria-label": e, + onClick: (t) => k.colour = e + }, null, 14, wr)), 64)), o("button", { + type: "button", + class: "rounded-md border border-line-default px-2 py-1 text-xs text-muted hover:bg-hover", + onClick: n[1] ||= (e) => k.colour = "" + }, x(S(d)("tasks_projects.settings.colour_none")), 1)])]), + _: 1 + }, 8, ["label"]), + o("div", Tr, [o("label", Er, [c(v, { + modelValue: k.is_default, + "onUpdate:modelValue": n[2] ||= (e) => k.is_default = e, + class: "flex" + }, null, 8, ["modelValue"]), s(" " + x(S(d)("tasks_projects.settings.is_default")), 1)]), o("label", Dr, [c(v, { + modelValue: k.is_closed, + "onUpdate:modelValue": n[3] ||= (e) => k.is_closed = e, + class: "flex" + }, null, 8, ["modelValue"]), s(" " + x(S(d)("tasks_projects.settings.is_closed")), 1)])]), + o("div", Or, [c(j, { + variant: "primary", + size: "sm", + disabled: T.value, + onClick: I + }, { + default: E(() => [s(x(S(d)("tasks_projects.general.save")), 1)]), + _: 1 + }, 8, ["disabled"]), c(j, { + variant: "primary-outline", + size: "sm", + onClick: P + }, { + default: E(() => [s(x(S(d)("tasks_projects.general.cancel")), 1)]), + _: 1 + })]) + ])) : (g(), a("div", kr, [ + o("span", { + class: f(["inline-block h-3 w-3 shrink-0 rounded-full", t.colour ? "" : "bg-line-default"]), + style: p(t.colour ? { backgroundColor: t.colour } : void 0) + }, null, 6), + o("span", Ar, x(t.name), 1), + t.is_default ? (g(), r(F, { + key: 0, + class: "rounded-full bg-primary-50! text-primary-500!" + }, { + default: E(() => [s(x(S(d)("tasks_projects.settings.is_default")), 1)]), + _: 1 + })) : i("", !0), + t.is_closed ? (g(), r(F, { + key: 1, + class: "rounded-full bg-surface-tertiary! text-muted!" + }, { + default: E(() => [s(x(S(d)("tasks_projects.settings.is_closed")), 1)]), + _: 1 + })) : i("", !0), + o("div", jr, [ + o("button", { + type: "button", + class: "rounded p-1 text-subtle hover:bg-hover hover:text-heading disabled:opacity-40", + disabled: T.value || l === 0, + title: S(d)("tasks_projects.settings.move_up"), + "aria-label": S(d)("tasks_projects.settings.move_up"), + onClick: (e) => R(l, -1) + }, [c(z, { + name: "ChevronUpIcon", + class: "h-4 w-4" + })], 8, Mr), + o("button", { + type: "button", + class: "rounded p-1 text-subtle hover:bg-hover hover:text-heading disabled:opacity-40", + disabled: T.value || l === m.value.length - 1, + title: S(d)("tasks_projects.settings.move_down"), + "aria-label": S(d)("tasks_projects.settings.move_down"), + onClick: (e) => R(l, 1) + }, [c(z, { + name: "ChevronDownIcon", + class: "h-4 w-4" + })], 8, Nr), + o("button", { + type: "button", + class: "rounded p-1 text-subtle hover:bg-hover hover:text-heading", + title: S(d)("tasks_projects.general.edit"), + "aria-label": S(d)("tasks_projects.general.edit"), + onClick: (e) => M(t) + }, [c(z, { + name: "PencilIcon", + class: "h-4 w-4" + })], 8, Pr), + o("button", { + type: "button", + class: "rounded p-1 text-subtle hover:bg-hover hover:text-alert-error-text", + disabled: T.value, + title: S(d)("tasks_projects.general.delete"), + "aria-label": S(d)("tasks_projects.general.delete"), + onClick: (e) => L(t) + }, [c(z, { + name: "TrashIcon", + class: "h-4 w-4" + })], 8, Fr) + ]) + ]))]))), 128))])), O.value ? (g(), a("div", Ir, [ + c(_, { + label: S(d)("tasks_projects.settings.status_name"), + required: "" + }, { + default: E(() => [c(h, { + modelValue: k.name, + "onUpdate:modelValue": n[4] ||= (e) => k.name = e, + type: "text", + maxlength: "255" + }, null, 8, ["modelValue"])]), + _: 1 + }, 8, ["label"]), + c(_, { label: S(d)("tasks_projects.settings.colour") }, { + default: E(() => [o("div", Lr, [(g(), a(e, null, y(u, (e) => o("button", { + key: e, + type: "button", + class: f(["h-7 w-7 rounded-full border-2 transition", k.colour === e ? "border-heading" : "border-line-default"]), + style: p({ backgroundColor: e }), + "aria-label": e, + onClick: (t) => k.colour = e + }, null, 14, Rr)), 64))])]), + _: 1 + }, 8, ["label"]), + o("div", zr, [o("label", Br, [c(v, { + modelValue: k.is_default, + "onUpdate:modelValue": n[5] ||= (e) => k.is_default = e, + class: "flex" + }, null, 8, ["modelValue"]), s(" " + x(S(d)("tasks_projects.settings.is_default")), 1)]), o("label", Vr, [c(v, { + modelValue: k.is_closed, + "onUpdate:modelValue": n[6] ||= (e) => k.is_closed = e, + class: "flex" + }, null, 8, ["modelValue"]), s(" " + x(S(d)("tasks_projects.settings.is_closed")), 1)])]), + o("div", Hr, [c(j, { + variant: "primary", + size: "sm", + disabled: T.value, + onClick: I + }, { + default: E(() => [s(x(S(d)("tasks_projects.general.save")), 1)]), + _: 1 + }, 8, ["disabled"]), c(j, { + variant: "primary-outline", + size: "sm", + onClick: P + }, { + default: E(() => [s(x(S(d)("tasks_projects.general.cancel")), 1)]), + _: 1 + })]) + ])) : C.value ? i("", !0) : (g(), r(j, { + key: 4, + variant: "primary-outline", + size: "sm", + class: "mt-4", + onClick: N + }, { + left: E((e) => [c(z, { + name: "PlusIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: E(() => [s(" " + x(S(d)("tasks_projects.settings.add_status")), 1)]), + _: 1 + }))]))]); + }; + } +}), Wr = { class: "space-y-6" }, Gr = "/admin/settings/modules", Kr = /* @__PURE__ */ l({ + __name: "TimeSettingsPage", + props: { + client: { type: [Function, Object] }, + notify: { type: Function }, + router: {} + }, + setup(e) { + let t = V(); + return (n, r) => { + let i = b("BaseIcon"), o = b("BaseButton"), l = b("router-link"), u = b("BaseSettingCard"); + return g(), a("div", Wr, [c(u, { + title: S(t)("tasks_projects.settings.general_title"), + description: S(t)("tasks_projects.settings.general_description") + }, { + action: E(() => [c(l, { to: Gr }, { + default: E(() => [c(o, { + variant: "primary-outline", + size: "sm" }, { - "cell-name": w(({ row: e }) => [a("div", xe, [a("span", { - class: d(["mr-3 inline-block h-2.5 w-2.5 shrink-0 rounded-full", e.data.colour ? "" : "bg-line-default"]), - style: f(e.data.colour ? { backgroundColor: e.data.colour } : void 0) - }, null, 6), a("span", null, [s(C, { - class: "hover:text-primary-500", - to: { - name: "extension.page.tasks-projects.project", - params: { id: e.data.id } - } - }, { - default: w(() => [o(b(e.data.name), 1)]), - _: 2 - }, 1032, ["to"]), e.data.identifier ? (h(), i("span", Se, b(e.data.identifier), 1)) : r("", !0)])])]), - "cell-status": w(({ row: e }) => [s(L, { class: d(["rounded-full", me(e.data.status)]) }, { - default: w(() => [o(b(he(e.data.status)), 1)]), - _: 2 - }, 1032, ["class"])]), - "cell-customer": w(({ row: e }) => [e.data.customer_id ? (h(), i("span", Ce, "#" + b(e.data.customer_id), 1)) : (h(), i("span", we, b(x(l)("tasks_projects.projects.internal")), 1))]), - "cell-default_rate": w(({ row: e }) => [e.data.default_rate === null ? (h(), i("span", Te, "-")) : (h(), n(re, { - key: 0, - amount: e.data.default_rate - }, null, 8, ["amount"]))]), - "cell-due_date": w(({ row: e }) => [e.data.due_date ? (h(), i("span", Ee, b(x(ce)(e.data.due_date)), 1)) : (h(), i("span", De, "-"))]), - "cell-actions": w(({ row: e }) => [s(fe, { "content-loading": k.value === e.data.id }, { - activator: w(() => [s(_, { - name: "EllipsisHorizontalIcon", - class: "h-5 text-muted" - })]), - default: w(() => [ - s(ae, { onClick: (t) => R(e.data) }, { - default: w(() => [s(_, { - name: "PencilIcon", - class: "mr-3 h-5 w-5 text-subtle group-hover:text-muted" - }), o(" " + b(x(l)("tasks_projects.general.edit")), 1)]), - _: 1 - }, 8, ["onClick"]), - s(ae, { onClick: (t) => de(e.data) }, { - default: w(() => [s(_, { - name: e.data.status === "ARCHIVED" ? "ArrowPathIcon" : "ArchiveBoxIcon", - class: "mr-3 h-5 w-5 text-subtle group-hover:text-muted" - }, null, 8, ["name"]), o(" " + b(e.data.status === "ARCHIVED" ? x(l)("tasks_projects.projects.unarchive") : x(l)("tasks_projects.projects.archive")), 1)]), - _: 2 - }, 1032, ["onClick"]), - s(ae, { onClick: (t) => pe(e.data) }, { - default: w(() => [s(_, { - name: "TrashIcon", - class: "mr-3 h-5 w-5 text-subtle group-hover:text-muted" - }), o(" " + b(x(l)("tasks_projects.general.delete")), 1)]), - _: 1 - }, 8, ["onClick"]) - ]), - _: 2 - }, 1032, ["content-loading"])]), + right: E((e) => [c(i, { + name: "ArrowTopRightOnSquareIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: E(() => [s(" " + x(S(t)("tasks_projects.settings.open_module_settings")), 1)]), _: 1 - }, 8, ["columns"])], 512), [[S, !I.value]]), - s(ve, { - show: D.value, - client: e.client, - notify: e.notify, - project: O.value, - onClose: c[2] ||= (e) => D.value = !1, - onSaved: ue - }, null, 8, [ - "show", - "client", - "notify", - "project" - ]) - ]), + })]), + _: 1 + })]), _: 1 - }); + }, 8, ["title", "description"]), c(u, { + title: S(t)("tasks_projects.settings.statuses_title"), + description: S(t)("tasks_projects.settings.statuses_description") + }, { + default: E(() => [c(Ur, { + client: e.client, + notify: e.notify + }, null, 8, ["client", "notify"])]), + _: 1 + }, 8, ["title", "description"])]); }; } -}), je = { en: { tasks_projects: { +}), qr = "tasks-projects", Jr = `/admin/modules/${qr}/time`; +function Yr(e) { + e.addMessages(gn); + let t = (t, n) => { + e.notify(t, n); + }, n = () => { + e.router.push(Jr); + }; + e.registerPage({ + id: "time", + module: qr, + path: "time", + component: Qr(e, gr), + meta: { + ability: `${qr}:view-own-time`, + title: "tasks_projects.time.title" + } + }), e.registerHeaderAction({ + id: `${qr}.timer-chip`, + priority: 30, + visible: () => U.running !== null, + component: l({ setup: () => () => d(hn, { + client: e.client, + notify: t, + onOpen: n + }) }) + }), e.registerCompanyLayoutOverlay({ + id: `${qr}.quick-start`, + component: l({ setup: () => () => d(ln, { + key: ar.companySession, + client: e.client, + notify: t, + enabled: !ar.adminMode, + onOpenTimesheet: n + }) }) + }), e.registerCompanySettingsPage({ + id: `${qr}.settings`, + title: "tasks_projects.settings.title", + icon: "ClockIcon", + path: qr, + priority: 70, + component: Qr(e, Kr) + }), e.on("bootstrap:completed", ({ adminMode: t }) => { + Xr(e, t); + }), e.on("company:changing", () => { + Zr(); + }), e.on("company:changed", ({ companyId: t }) => { + Xr(e, t === null); + }); +} +async function Xr(e, t) { + if (cr(t), t) { + Zr(); + return; + } + await or(e.client), await U.refresh(e.client); +} +function Zr() { + U.reset(), ct(), sr(); +} +function Qr(e, t) { + return l({ setup: (n, { attrs: r }) => () => d(t, { + ...r, + client: e.client, + notify: (t, n) => { + e.notify(t, n); + }, + router: e.router + }) }); +} +//#endregion +//#region resources/js/messages/board.ts +var $r = { en: { tasks_projects: { board: { title: "Board", load_failed: "Unable to load the board.", @@ -898,24 +2911,24 @@ var pe = { class: "flex w-full items-center justify-between" }, me = { class: "s } } }; //#endregion //#region node_modules/.pnpm/sortablejs@1.15.7/node_modules/sortablejs/modular/sortable.esm.js -function Me(e, t, n) { - return (t = ze(t)) in e ? Object.defineProperty(e, t, { +function ei(e, t, n) { + return (t = si(t)) in e ? Object.defineProperty(e, t, { value: n, enumerable: !0, configurable: !0, writable: !0 }) : e[t] = n, e; } -function Ne() { - return Ne = Object.assign ? Object.assign.bind() : function(e) { +function ti() { + return ti = Object.assign ? Object.assign.bind() : function(e) { for (var t = 1; t < arguments.length; t++) { var n = arguments[t]; for (var r in n) ({}).hasOwnProperty.call(n, r) && (e[r] = n[r]); } return e; - }, Ne.apply(null, arguments); + }, ti.apply(null, arguments); } -function Pe(e, t) { +function ni(e, t) { var n = Object.keys(e); if (Object.getOwnPropertySymbols) { var r = Object.getOwnPropertySymbols(e); @@ -925,27 +2938,27 @@ function Pe(e, t) { } return n; } -function Fe(e) { +function ri(e) { for (var t = 1; t < arguments.length; t++) { var n = arguments[t] == null ? {} : arguments[t]; - t % 2 ? Pe(Object(n), !0).forEach(function(t) { - Me(e, t, n[t]); - }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(n)) : Pe(Object(n)).forEach(function(t) { + t % 2 ? ni(Object(n), !0).forEach(function(t) { + ei(e, t, n[t]); + }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(n)) : ni(Object(n)).forEach(function(t) { Object.defineProperty(e, t, Object.getOwnPropertyDescriptor(n, t)); }); } return e; } -function Ie(e, t) { +function ii(e, t) { if (e == null) return {}; - var n, r, i = Le(e, t); + var n, r, i = ai(e, t); if (Object.getOwnPropertySymbols) { var a = Object.getOwnPropertySymbols(e); for (r = 0; r < a.length; r++) n = a[r], t.indexOf(n) === -1 && {}.propertyIsEnumerable.call(e, n) && (i[n] = e[n]); } return i; } -function Le(e, t) { +function ai(e, t) { if (e == null) return {}; var n = {}; for (var r in e) if ({}.hasOwnProperty.call(e, r)) { @@ -954,7 +2967,7 @@ function Le(e, t) { } return n; } -function Re(e, t) { +function oi(e, t) { if (typeof e != "object" || !e) return e; var n = e[Symbol.toPrimitive]; if (n !== void 0) { @@ -964,33 +2977,33 @@ function Re(e, t) { } return (t === "string" ? String : Number)(e); } -function ze(e) { - var t = Re(e, "string"); +function si(e) { + var t = oi(e, "string"); return typeof t == "symbol" ? t : t + ""; } -function Be(e) { +function ci(e) { "@babel/helpers - typeof"; - return Be = typeof Symbol == "function" && typeof Symbol.iterator == "symbol" ? function(e) { + return ci = typeof Symbol == "function" && typeof Symbol.iterator == "symbol" ? function(e) { return typeof e; } : function(e) { return e && typeof Symbol == "function" && e.constructor === Symbol && e !== Symbol.prototype ? "symbol" : typeof e; - }, Be(e); + }, ci(e); } -var Ve = "1.15.7"; -function He(e) { +var li = "1.15.7"; +function ui(e) { if (typeof window < "u" && window.navigator) return !!/*@__PURE__*/ navigator.userAgent.match(e); } -var Ue = He(/(?:Trident.*rv[ :]?11\.|msie|iemobile|Windows Phone)/i), We = He(/Edge/i), Ge = He(/firefox/i), Ke = He(/safari/i) && !He(/chrome/i) && !He(/android/i), qe = He(/iP(ad|od|hone)/i), Je = He(/chrome/i) && He(/android/i), Ye = { +var di = ui(/(?:Trident.*rv[ :]?11\.|msie|iemobile|Windows Phone)/i), fi = ui(/Edge/i), pi = ui(/firefox/i), mi = ui(/safari/i) && !ui(/chrome/i) && !ui(/android/i), hi = ui(/iP(ad|od|hone)/i), gi = ui(/chrome/i) && ui(/android/i), _i = { capture: !1, passive: !1 }; -function z(e, t, n) { - e.addEventListener(t, n, !Ue && Ye); +function W(e, t, n) { + e.addEventListener(t, n, !di && _i); } -function B(e, t, n) { - e.removeEventListener(t, n, !Ue && Ye); +function G(e, t, n) { + e.removeEventListener(t, n, !di && _i); } -function Xe(e, t) { +function vi(e, t) { if (t) { if (t[0] === ">" && (t = t.substring(1)), e) try { if (e.matches) return e.matches(t); @@ -1002,41 +3015,41 @@ function Xe(e, t) { return !1; } } -function Ze(e) { +function yi(e) { return e.host && e !== document && e.host.nodeType && e.host !== e ? e.host : e.parentNode; } -function Qe(e, t, n, r) { +function bi(e, t, n, r) { if (e) { n ||= document; do { - if (t != null && (t[0] === ">" ? e.parentNode === n && Xe(e, t) : Xe(e, t)) || r && e === n) return e; + if (t != null && (t[0] === ">" ? e.parentNode === n && vi(e, t) : vi(e, t)) || r && e === n) return e; if (e === n) break; - } while (e = Ze(e)); + } while (e = yi(e)); } return null; } -var $e = /\s+/g; -function et(e, t, n) { - e && t && (e.classList ? e.classList[n ? "add" : "remove"](t) : e.className = ((" " + e.className + " ").replace($e, " ").replace(" " + t + " ", " ") + (n ? " " + t : "")).replace($e, " ")); +var xi = /\s+/g; +function Si(e, t, n) { + e && t && (e.classList ? e.classList[n ? "add" : "remove"](t) : e.className = ((" " + e.className + " ").replace(xi, " ").replace(" " + t + " ", " ") + (n ? " " + t : "")).replace(xi, " ")); } -function V(e, t, n) { +function K(e, t, n) { var r = e && e.style; if (r) { if (n === void 0) return document.defaultView && document.defaultView.getComputedStyle ? n = document.defaultView.getComputedStyle(e, "") : e.currentStyle && (n = e.currentStyle), t === void 0 ? n : n[t]; !(t in r) && t.indexOf("webkit") === -1 && (t = "-webkit-" + t), r[t] = n + (typeof n == "string" ? "" : "px"); } } -function tt(e, t) { +function Ci(e, t) { var n = ""; if (typeof e == "string") n = e; else do { - var r = V(e, "transform"); + var r = K(e, "transform"); r && r !== "none" && (n = r + " " + n); } while (!t && (e = e.parentNode)); var i = window.DOMMatrix || window.WebKitCSSMatrix || window.CSSMatrix || window.MSCSSMatrix; return i && new i(n); } -function nt(e, t, n) { +function wi(e, t, n) { if (e) { var r = e.getElementsByTagName(t), i = 0, a = r.length; if (n) for (; i < a; i++) n(r[i], i); @@ -1044,21 +3057,21 @@ function nt(e, t, n) { } return []; } -function rt() { +function Ti() { return document.scrollingElement || document.documentElement; } -function H(e, t, n, r, i) { +function q(e, t, n, r, i) { if (e.getBoundingClientRect || e === window) { var a, o, s, c, l, u, d; - if (e !== window && e.parentNode && e !== rt() ? (a = e.getBoundingClientRect(), o = a.top, s = a.left, c = a.bottom, l = a.right, u = a.height, d = a.width) : (o = 0, s = 0, c = window.innerHeight, l = window.innerWidth, u = window.innerHeight, d = window.innerWidth), (t || n) && e !== window && (i ||= e.parentNode, !Ue)) do - if (i && i.getBoundingClientRect && (V(i, "transform") !== "none" || n && V(i, "position") !== "static")) { + if (e !== window && e.parentNode && e !== Ti() ? (a = e.getBoundingClientRect(), o = a.top, s = a.left, c = a.bottom, l = a.right, u = a.height, d = a.width) : (o = 0, s = 0, c = window.innerHeight, l = window.innerWidth, u = window.innerHeight, d = window.innerWidth), (t || n) && e !== window && (i ||= e.parentNode, !di)) do + if (i && i.getBoundingClientRect && (K(i, "transform") !== "none" || n && K(i, "position") !== "static")) { var f = i.getBoundingClientRect(); - o -= f.top + parseInt(V(i, "border-top-width")), s -= f.left + parseInt(V(i, "border-left-width")), c = o + a.height, l = s + a.width; + o -= f.top + parseInt(K(i, "border-top-width")), s -= f.left + parseInt(K(i, "border-left-width")), c = o + a.height, l = s + a.width; break; } while (i = i.parentNode); if (r && e !== window) { - var p = tt(i || e), m = p && p.a, h = p && p.d; + var p = Ci(i || e), m = p && p.a, h = p && p.d; p && (o /= h, s /= m, d /= m, u /= h, c = o + u, l = s + d); } return { @@ -1071,18 +3084,18 @@ function H(e, t, n, r, i) { }; } } -function it(e, t, n) { - for (var r = ut(e, !0), i = H(e)[t]; r;) { - var a = H(r)[n], o = void 0; +function Ei(e, t, n) { + for (var r = Mi(e, !0), i = q(e)[t]; r;) { + var a = q(r)[n], o = void 0; if (o = n === "top" || n === "left" ? i >= a : i <= a, !o) return r; - if (r === rt()) break; - r = ut(r, !1); + if (r === Ti()) break; + r = Mi(r, !1); } return !1; } -function at(e, t, n, r) { +function Di(e, t, n, r) { for (var i = 0, a = 0, o = e.children; a < o.length;) { - if (o[a].style.display !== "none" && o[a] !== Q.ghost && (r || o[a] !== Q.dragged) && Qe(o[a], n.draggable, e, !1)) { + if (o[a].style.display !== "none" && o[a] !== $.ghost && (r || o[a] !== $.dragged) && bi(o[a], n.draggable, e, !1)) { if (i === t) return o[a]; i++; } @@ -1090,96 +3103,96 @@ function at(e, t, n, r) { } return null; } -function ot(e, t) { - for (var n = e.lastElementChild; n && (n === Q.ghost || V(n, "display") === "none" || t && !Xe(n, t));) n = n.previousElementSibling; +function Oi(e, t) { + for (var n = e.lastElementChild; n && (n === $.ghost || K(n, "display") === "none" || t && !vi(n, t));) n = n.previousElementSibling; return n || null; } -function st(e, t) { +function ki(e, t) { var n = 0; if (!e || !e.parentNode) return -1; - for (; e = e.previousElementSibling;) e.nodeName.toUpperCase() !== "TEMPLATE" && e !== Q.clone && (!t || Xe(e, t)) && n++; + for (; e = e.previousElementSibling;) e.nodeName.toUpperCase() !== "TEMPLATE" && e !== $.clone && (!t || vi(e, t)) && n++; return n; } -function ct(e) { - var t = 0, n = 0, r = rt(); +function Ai(e) { + var t = 0, n = 0, r = Ti(); if (e) do { - var i = tt(e), a = i.a, o = i.d; + var i = Ci(e), a = i.a, o = i.d; t += e.scrollLeft * a, n += e.scrollTop * o; } while (e !== r && (e = e.parentNode)); return [t, n]; } -function lt(e, t) { +function ji(e, t) { for (var n in e) if (e.hasOwnProperty(n)) { for (var r in t) if (t.hasOwnProperty(r) && t[r] === e[n][r]) return Number(n); } return -1; } -function ut(e, t) { - if (!e || !e.getBoundingClientRect) return rt(); +function Mi(e, t) { + if (!e || !e.getBoundingClientRect) return Ti(); var n = e, r = !1; do if (n.clientWidth < n.scrollWidth || n.clientHeight < n.scrollHeight) { - var i = V(n); + var i = K(n); if (n.clientWidth < n.scrollWidth && (i.overflowX == "auto" || i.overflowX == "scroll") || n.clientHeight < n.scrollHeight && (i.overflowY == "auto" || i.overflowY == "scroll")) { - if (!n.getBoundingClientRect || n === document.body) return rt(); + if (!n.getBoundingClientRect || n === document.body) return Ti(); if (r || t) return n; r = !0; } } while (n = n.parentNode); - return rt(); + return Ti(); } -function dt(e, t) { +function Ni(e, t) { if (e && t) for (var n in t) t.hasOwnProperty(n) && (e[n] = t[n]); return e; } -function ft(e, t) { +function Pi(e, t) { return Math.round(e.top) === Math.round(t.top) && Math.round(e.left) === Math.round(t.left) && Math.round(e.height) === Math.round(t.height) && Math.round(e.width) === Math.round(t.width); } -var pt; -function mt(e, t) { +var Fi; +function Ii(e, t) { return function() { - if (!pt) { + if (!Fi) { var n = arguments, r = this; - n.length === 1 ? e.call(r, n[0]) : e.apply(r, n), pt = setTimeout(function() { - pt = void 0; + n.length === 1 ? e.call(r, n[0]) : e.apply(r, n), Fi = setTimeout(function() { + Fi = void 0; }, t); } }; } -function ht() { - clearTimeout(pt), pt = void 0; +function Li() { + clearTimeout(Fi), Fi = void 0; } -function gt(e, t, n) { +function Ri(e, t, n) { e.scrollLeft += t, e.scrollTop += n; } -function _t(e) { +function zi(e) { var t = window.Polymer, n = window.jQuery || window.Zepto; return t && t.dom ? t.dom(e).cloneNode(!0) : n ? n(e).clone(!0)[0] : e.cloneNode(!0); } -function vt(e, t, n) { +function Bi(e, t, n) { var r = {}; return Array.from(e.children).forEach(function(i) { - if (Qe(i, t.draggable, e, !1) && !i.animated && i !== n) { - var a = H(i); + if (bi(i, t.draggable, e, !1) && !i.animated && i !== n) { + var a = q(i); r.left = Math.min(r.left ?? Infinity, a.left), r.top = Math.min(r.top ?? Infinity, a.top), r.right = Math.max(r.right ?? -Infinity, a.right), r.bottom = Math.max(r.bottom ?? -Infinity, a.bottom); } }), r.width = r.right - r.left, r.height = r.bottom - r.top, r.x = r.left, r.y = r.top, r; } -var U = "Sortable" + (/* @__PURE__ */ new Date()).getTime(); -function yt() { +var Vi = "Sortable" + (/* @__PURE__ */ new Date()).getTime(); +function Hi() { var e = [], t; return { captureAnimationState: function() { e = [], this.options.animation && [].slice.call(this.el.children).forEach(function(t) { - if (V(t, "display") !== "none" && t !== Q.ghost) { + if (K(t, "display") !== "none" && t !== $.ghost) { e.push({ target: t, - rect: H(t) + rect: q(t) }); - var n = Fe({}, e[e.length - 1].rect); + var n = ri({}, e[e.length - 1].rect); if (t.thisAnimationDuration) { - var r = tt(t, !0); + var r = Ci(t, !0); r && (n.top -= r.f, n.left -= r.e); } t.fromRect = n; @@ -1190,7 +3203,7 @@ function yt() { e.push(t); }, removeAnimationState: function(t) { - e.splice(lt(e, { target: t }), 1); + e.splice(ji(e, { target: t }), 1); }, animateAll: function(n) { var r = this; @@ -1200,8 +3213,8 @@ function yt() { } var i = !1, a = 0; e.forEach(function(e) { - var t = 0, n = e.target, o = n.fromRect, s = H(n), c = n.prevFromRect, l = n.prevToRect, u = e.rect, d = tt(n, !0); - d && (s.top -= d.f, s.left -= d.e), n.toRect = s, n.thisAnimationDuration && ft(c, s) && !ft(o, s) && (u.top - s.top) / (u.left - s.left) === (o.top - s.top) / (o.left - s.left) && (t = xt(u, c, l, r.options)), ft(s, o) || (n.prevFromRect = o, n.prevToRect = s, t ||= r.options.animation, r.animate(n, u, s, t)), t && (i = !0, a = Math.max(a, t), clearTimeout(n.animationResetTimer), n.animationResetTimer = setTimeout(function() { + var t = 0, n = e.target, o = n.fromRect, s = q(n), c = n.prevFromRect, l = n.prevToRect, u = e.rect, d = Ci(n, !0); + d && (s.top -= d.f, s.left -= d.e), n.toRect = s, n.thisAnimationDuration && Pi(c, s) && !Pi(o, s) && (u.top - s.top) / (u.left - s.left) === (o.top - s.top) / (o.left - s.left) && (t = Wi(u, c, l, r.options)), Pi(s, o) || (n.prevFromRect = o, n.prevToRect = s, t ||= r.options.animation, r.animate(n, u, s, t)), t && (i = !0, a = Math.max(a, t), clearTimeout(n.animationResetTimer), n.animationResetTimer = setTimeout(function() { n.animationTime = 0, n.prevFromRect = null, n.fromRect = null, n.prevToRect = null, n.thisAnimationDuration = null; }, t), n.thisAnimationDuration = t); }), clearTimeout(t), i ? t = setTimeout(function() { @@ -1210,27 +3223,27 @@ function yt() { }, animate: function(e, t, n, r) { if (r) { - V(e, "transition", ""), V(e, "transform", ""); - var i = tt(this.el), a = i && i.a, o = i && i.d, s = (t.left - n.left) / (a || 1), c = (t.top - n.top) / (o || 1); - e.animatingX = !!s, e.animatingY = !!c, V(e, "transform", "translate3d(" + s + "px," + c + "px,0)"), this.forRepaintDummy = bt(e), V(e, "transition", "transform " + r + "ms" + (this.options.easing ? " " + this.options.easing : "")), V(e, "transform", "translate3d(0,0,0)"), typeof e.animated == "number" && clearTimeout(e.animated), e.animated = setTimeout(function() { - V(e, "transition", ""), V(e, "transform", ""), e.animated = !1, e.animatingX = !1, e.animatingY = !1; + K(e, "transition", ""), K(e, "transform", ""); + var i = Ci(this.el), a = i && i.a, o = i && i.d, s = (t.left - n.left) / (a || 1), c = (t.top - n.top) / (o || 1); + e.animatingX = !!s, e.animatingY = !!c, K(e, "transform", "translate3d(" + s + "px," + c + "px,0)"), this.forRepaintDummy = Ui(e), K(e, "transition", "transform " + r + "ms" + (this.options.easing ? " " + this.options.easing : "")), K(e, "transform", "translate3d(0,0,0)"), typeof e.animated == "number" && clearTimeout(e.animated), e.animated = setTimeout(function() { + K(e, "transition", ""), K(e, "transform", ""), e.animated = !1, e.animatingX = !1, e.animatingY = !1; }, r); } } }; } -function bt(e) { +function Ui(e) { return e.offsetWidth; } -function xt(e, t, n, r) { +function Wi(e, t, n, r) { return Math.sqrt((t.top - e.top) ** 2 + (t.left - e.left) ** 2) / Math.sqrt((t.top - n.top) ** 2 + (t.left - n.left) ** 2) * r.animation; } -var St = [], Ct = { initializeByDefault: !0 }, wt = { +var Gi = [], Ki = { initializeByDefault: !0 }, qi = { mount: function(e) { - for (var t in Ct) Ct.hasOwnProperty(t) && !(t in e) && (e[t] = Ct[t]); - St.forEach(function(t) { + for (var t in Ki) Ki.hasOwnProperty(t) && !(t in e) && (e[t] = Ki[t]); + Gi.forEach(function(t) { if (t.pluginName === e.pluginName) throw `Sortable: Cannot mount plugin ${e.pluginName} more than once`; - }), St.push(e); + }), Gi.push(e); }, pluginEvent: function(e, t, n) { var r = this; @@ -1238,16 +3251,16 @@ var St = [], Ct = { initializeByDefault: !0 }, wt = { r.eventCanceled = !0; }; var i = e + "Global"; - St.forEach(function(r) { - t[r.pluginName] && (t[r.pluginName][i] && t[r.pluginName][i](Fe({ sortable: t }, n)), t.options[r.pluginName] && t[r.pluginName][e] && t[r.pluginName][e](Fe({ sortable: t }, n))); + Gi.forEach(function(r) { + t[r.pluginName] && (t[r.pluginName][i] && t[r.pluginName][i](ri({ sortable: t }, n)), t.options[r.pluginName] && t[r.pluginName][e] && t[r.pluginName][e](ri({ sortable: t }, n))); }); }, initializePlugins: function(e, t, n, r) { - for (var i in St.forEach(function(r) { + for (var i in Gi.forEach(function(r) { var i = r.pluginName; if (e.options[i] || r.initializeByDefault) { var a = new r(e, t, e.options); - a.sortable = e, a.options = e.options, e[i] = a, Ne(n, a.defaults); + a.sortable = e, a.options = e.options, e[i] = a, ti(n, a.defaults); } }), e.options) if (e.options.hasOwnProperty(i)) { var a = this.modifyOption(e, i, e.options[i]); @@ -1256,59 +3269,59 @@ var St = [], Ct = { initializeByDefault: !0 }, wt = { }, getEventProperties: function(e, t) { var n = {}; - return St.forEach(function(r) { - typeof r.eventProperties == "function" && Ne(n, r.eventProperties.call(t[r.pluginName], e)); + return Gi.forEach(function(r) { + typeof r.eventProperties == "function" && ti(n, r.eventProperties.call(t[r.pluginName], e)); }), n; }, modifyOption: function(e, t, n) { var r; - return St.forEach(function(i) { + return Gi.forEach(function(i) { e[i.pluginName] && i.optionListeners && typeof i.optionListeners[t] == "function" && (r = i.optionListeners[t].call(e[i.pluginName], n)); }), r; } }; -function Tt(e) { +function Ji(e) { var t = e.sortable, n = e.rootEl, r = e.name, i = e.targetEl, a = e.cloneEl, o = e.toEl, s = e.fromEl, c = e.oldIndex, l = e.newIndex, u = e.oldDraggableIndex, d = e.newDraggableIndex, f = e.originalEvent, p = e.putSortable, m = e.extraEventProperties; - if (t ||= n && n[U], t) { + if (t ||= n && n[Vi], t) { var h, g = t.options, _ = "on" + r.charAt(0).toUpperCase() + r.substr(1); - window.CustomEvent && !Ue && !We ? h = new CustomEvent(r, { + window.CustomEvent && !di && !fi ? h = new CustomEvent(r, { bubbles: !0, cancelable: !0 }) : (h = document.createEvent("Event"), h.initEvent(r, !0, !0)), h.to = o || n, h.from = s || n, h.item = i || n, h.clone = a, h.oldIndex = c, h.newIndex = l, h.oldDraggableIndex = u, h.newDraggableIndex = d, h.originalEvent = f, h.pullMode = p ? p.lastPutMode : void 0; - var v = Fe(Fe({}, m), wt.getEventProperties(r, t)); + var v = ri(ri({}, m), qi.getEventProperties(r, t)); for (var y in v) h[y] = v[y]; n && n.dispatchEvent(h), g[_] && g[_].call(t, h); } } -var Et = ["evt"], Dt = function(e, t) { - var n = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : {}, r = n.evt, i = Ie(n, Et); - wt.pluginEvent.bind(Q)(e, t, Fe({ - dragEl: G, - parentEl: K, - ghostEl: q, - rootEl: J, - nextEl: Ot, - lastDownEl: kt, - cloneEl: Y, - cloneHidden: At, - dragStarted: Gt, - putSortable: X, - activeSortable: Q.active, +var Yi = ["evt"], Xi = function(e, t) { + var n = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : {}, r = n.evt, i = ii(n, Yi); + qi.pluginEvent.bind($)(e, t, ri({ + dragEl: J, + parentEl: Y, + ghostEl: X, + rootEl: Z, + nextEl: Qi, + lastDownEl: $i, + cloneEl: Q, + cloneHidden: ea, + dragStarted: ga, + putSortable: oa, + activeSortable: $.active, originalEvent: r, - oldIndex: jt, - oldDraggableIndex: Nt, - newIndex: Mt, - newDraggableIndex: Pt, - hideGhostForTarget: un, - unhideGhostForTarget: dn, + oldIndex: ta, + oldDraggableIndex: ra, + newIndex: na, + newDraggableIndex: ia, + hideGhostForTarget: Fa, + unhideGhostForTarget: Ia, cloneNowHidden: function() { - At = !0; + ea = !0; }, cloneNowShown: function() { - At = !1; + ea = !1; }, dispatchSortableEvent: function(e) { - W({ + Zi({ sortable: t, name: e, originalEvent: r @@ -1316,46 +3329,46 @@ var Et = ["evt"], Dt = function(e, t) { } }, i)); }; -function W(e) { - Tt(Fe({ - putSortable: X, - cloneEl: Y, - targetEl: G, - rootEl: J, - oldIndex: jt, - oldDraggableIndex: Nt, - newIndex: Mt, - newDraggableIndex: Pt +function Zi(e) { + Ji(ri({ + putSortable: oa, + cloneEl: Q, + targetEl: J, + rootEl: Z, + oldIndex: ta, + oldDraggableIndex: ra, + newIndex: na, + newDraggableIndex: ia }, e)); } -var G, K, q, J, Ot, kt, Y, At, jt, Mt, Nt, Pt, Ft, X, It = !1, Lt = !1, Rt = [], zt, Bt, Vt, Ht, Ut, Wt, Gt, Kt, qt, Jt = !1, Yt = !1, Xt, Z, Zt = [], Qt = !1, $t = [], en = typeof document < "u", tn = qe, nn = We || Ue ? "cssFloat" : "float", rn = en && !Je && !qe && "draggable" in document.createElement("div"), an = function() { - if (en) { - if (Ue) return !1; +var J, Y, X, Z, Qi, $i, Q, ea, ta, na, ra, ia, aa, oa, sa = !1, ca = !1, la = [], ua, da, fa, pa, ma, ha, ga, _a, va, ya = !1, ba = !1, xa, Sa, Ca = [], wa = !1, Ta = [], Ea = typeof document < "u", Da = hi, Oa = fi || di ? "cssFloat" : "float", ka = Ea && !gi && !hi && "draggable" in document.createElement("div"), Aa = function() { + if (Ea) { + if (di) return !1; var e = document.createElement("x"); return e.style.cssText = "pointer-events:auto", e.style.pointerEvents === "auto"; } -}(), on = function(e, t) { - var n = V(e), r = parseInt(n.width) - parseInt(n.paddingLeft) - parseInt(n.paddingRight) - parseInt(n.borderLeftWidth) - parseInt(n.borderRightWidth), i = at(e, 0, t), a = at(e, 1, t), o = i && V(i), s = a && V(a), c = o && parseInt(o.marginLeft) + parseInt(o.marginRight) + H(i).width, l = s && parseInt(s.marginLeft) + parseInt(s.marginRight) + H(a).width; +}(), ja = function(e, t) { + var n = K(e), r = parseInt(n.width) - parseInt(n.paddingLeft) - parseInt(n.paddingRight) - parseInt(n.borderLeftWidth) - parseInt(n.borderRightWidth), i = Di(e, 0, t), a = Di(e, 1, t), o = i && K(i), s = a && K(a), c = o && parseInt(o.marginLeft) + parseInt(o.marginRight) + q(i).width, l = s && parseInt(s.marginLeft) + parseInt(s.marginRight) + q(a).width; if (n.display === "flex") return n.flexDirection === "column" || n.flexDirection === "column-reverse" ? "vertical" : "horizontal"; if (n.display === "grid") return n.gridTemplateColumns.split(" ").length <= 1 ? "vertical" : "horizontal"; if (i && o.float && o.float !== "none") { var u = o.float === "left" ? "left" : "right"; return a && (s.clear === "both" || s.clear === u) ? "vertical" : "horizontal"; } - return i && (o.display === "block" || o.display === "flex" || o.display === "table" || o.display === "grid" || c >= r && n[nn] === "none" || a && n[nn] === "none" && c + l > r) ? "vertical" : "horizontal"; -}, sn = function(e, t, n) { + return i && (o.display === "block" || o.display === "flex" || o.display === "table" || o.display === "grid" || c >= r && n[Oa] === "none" || a && n[Oa] === "none" && c + l > r) ? "vertical" : "horizontal"; +}, Ma = function(e, t, n) { var r = n ? e.left : e.top, i = n ? e.right : e.bottom, a = n ? e.width : e.height, o = n ? t.left : t.top, s = n ? t.right : t.bottom, c = n ? t.width : t.height; return r === o || i === s || r + a / 2 === o + c / 2; -}, cn = function(e, t) { +}, Na = function(e, t) { var n; - return Rt.some(function(r) { - var i = r[U].options.emptyInsertThreshold; - if (i && !ot(r)) { - var a = H(r), o = e >= a.left - i && e <= a.right + i, s = t >= a.top - i && t <= a.bottom + i; + return la.some(function(r) { + var i = r[Vi].options.emptyInsertThreshold; + if (i && !Oi(r)) { + var a = q(r), o = e >= a.left - i && e <= a.right + i, s = t >= a.top - i && t <= a.bottom + i; if (o && s) return n = r; } }), n; -}, ln = function(e) { +}, Pa = function(e) { function t(e, n) { return function(r, i, a, o) { var s = r.options.group.name && i.options.group.name && r.options.group.name === i.options.group.name; @@ -1368,31 +3381,31 @@ var G, K, q, J, Ot, kt, Y, At, jt, Mt, Nt, Pt, Ft, X, It = !1, Lt = !1, Rt = [], }; } var n = {}, r = e.group; - (!r || Be(r) != "object") && (r = { name: r }), n.name = r.name, n.checkPull = t(r.pull, !0), n.checkPut = t(r.put), n.revertClone = r.revertClone, e.group = n; -}, un = function() { - !an && q && V(q, "display", "none"); -}, dn = function() { - !an && q && V(q, "display", ""); + (!r || ci(r) != "object") && (r = { name: r }), n.name = r.name, n.checkPull = t(r.pull, !0), n.checkPut = t(r.put), n.revertClone = r.revertClone, e.group = n; +}, Fa = function() { + !Aa && X && K(X, "display", "none"); +}, Ia = function() { + !Aa && X && K(X, "display", ""); }; -en && !Je && document.addEventListener("click", function(e) { - if (Lt) return e.preventDefault(), e.stopPropagation && e.stopPropagation(), e.stopImmediatePropagation && e.stopImmediatePropagation(), Lt = !1, !1; +Ea && !gi && document.addEventListener("click", function(e) { + if (ca) return e.preventDefault(), e.stopPropagation && e.stopPropagation(), e.stopImmediatePropagation && e.stopImmediatePropagation(), ca = !1, !1; }, !0); -var fn = function(e) { - if (G) { +var La = function(e) { + if (J) { e = e.touches ? e.touches[0] : e; - var t = cn(e.clientX, e.clientY); + var t = Na(e.clientX, e.clientY); if (t) { var n = {}; for (var r in e) e.hasOwnProperty(r) && (n[r] = e[r]); - n.target = n.rootEl = t, n.preventDefault = void 0, n.stopPropagation = void 0, t[U]._onDragOver(n); + n.target = n.rootEl = t, n.preventDefault = void 0, n.stopPropagation = void 0, t[Vi]._onDragOver(n); } } -}, pn = function(e) { - G && G.parentNode[U]._isOutsideThisEl(e.target); +}, Ra = function(e) { + J && J.parentNode[Vi]._isOutsideThisEl(e.target); }; -function Q(e, t) { +function $(e, t) { if (!(e && e.nodeType && e.nodeType === 1)) throw `Sortable: \`el\` must be an HTMLElement, not ${{}.toString.call(e)}`; - this.el = e, this.options = t = Ne({}, t), e[U] = this; + this.el = e, this.options = t = ti({}, t), e[Vi] = this; var n = { group: null, sort: !0, @@ -1405,7 +3418,7 @@ function Q(e, t) { invertedSwapThreshold: null, removeCloneOnHide: !0, direction: function() { - return on(e, this.options); + return ja(e, this.options); }, ghostClass: "sortable-ghost", chosenClass: "sortable-chosen", @@ -1432,80 +3445,80 @@ function Q(e, t) { x: 0, y: 0 }, - supportPointer: Q.supportPointer !== !1 && "PointerEvent" in window && (!Ke || qe), + supportPointer: $.supportPointer !== !1 && "PointerEvent" in window && (!mi || hi), emptyInsertThreshold: 5 }; - for (var r in wt.initializePlugins(this, e, n), n) !(r in t) && (t[r] = n[r]); - for (var i in ln(t), this) i.charAt(0) === "_" && typeof this[i] == "function" && (this[i] = this[i].bind(this)); - this.nativeDraggable = !t.forceFallback && rn, this.nativeDraggable && (this.options.touchStartThreshold = 1), t.supportPointer ? z(e, "pointerdown", this._onTapStart) : (z(e, "mousedown", this._onTapStart), z(e, "touchstart", this._onTapStart)), this.nativeDraggable && (z(e, "dragover", this), z(e, "dragenter", this)), Rt.push(this.el), t.store && t.store.get && this.sort(t.store.get(this) || []), Ne(this, yt()); + for (var r in qi.initializePlugins(this, e, n), n) !(r in t) && (t[r] = n[r]); + for (var i in Pa(t), this) i.charAt(0) === "_" && typeof this[i] == "function" && (this[i] = this[i].bind(this)); + this.nativeDraggable = !t.forceFallback && ka, this.nativeDraggable && (this.options.touchStartThreshold = 1), t.supportPointer ? W(e, "pointerdown", this._onTapStart) : (W(e, "mousedown", this._onTapStart), W(e, "touchstart", this._onTapStart)), this.nativeDraggable && (W(e, "dragover", this), W(e, "dragenter", this)), la.push(this.el), t.store && t.store.get && this.sort(t.store.get(this) || []), ti(this, Hi()); } -Q.prototype = { - constructor: Q, +$.prototype = { + constructor: $, _isOutsideThisEl: function(e) { - !this.el.contains(e) && e !== this.el && (Kt = null); + !this.el.contains(e) && e !== this.el && (_a = null); }, _getDirection: function(e, t) { - return typeof this.options.direction == "function" ? this.options.direction.call(this, e, t, G) : this.options.direction; + return typeof this.options.direction == "function" ? this.options.direction.call(this, e, t, J) : this.options.direction; }, _onTapStart: function(e) { if (e.cancelable) { var t = this, n = this.el, r = this.options, i = r.preventOnFilter, a = e.type, o = e.touches && e.touches[0] || e.pointerType && e.pointerType === "touch" && e, s = (o || e).target, c = e.target.shadowRoot && (e.path && e.path[0] || e.composedPath && e.composedPath()[0]) || s, l = r.filter; - if (Cn(n), !G && !(/mousedown|pointerdown/.test(a) && e.button !== 0 || r.disabled) && !c.isContentEditable && !(!this.nativeDraggable && Ke && s && s.tagName.toUpperCase() === "SELECT") && (s = Qe(s, r.draggable, n, !1), !(s && s.animated) && kt !== s)) { - if (jt = st(s), Nt = st(s, r.draggable), typeof l == "function") { + if (Ja(n), !J && !(/mousedown|pointerdown/.test(a) && e.button !== 0 || r.disabled) && !c.isContentEditable && !(!this.nativeDraggable && mi && s && s.tagName.toUpperCase() === "SELECT") && (s = bi(s, r.draggable, n, !1), !(s && s.animated) && $i !== s)) { + if (ta = ki(s), ra = ki(s, r.draggable), typeof l == "function") { if (l.call(this, e, s, this)) { - W({ + Zi({ sortable: t, rootEl: c, name: "filter", targetEl: s, toEl: n, fromEl: n - }), Dt("filter", t, { evt: e }), i && e.preventDefault(); + }), Xi("filter", t, { evt: e }), i && e.preventDefault(); return; } } else if (l && (l = l.split(",").some(function(r) { - if (r = Qe(c, r.trim(), n, !1), r) return W({ + if (r = bi(c, r.trim(), n, !1), r) return Zi({ sortable: t, rootEl: r, name: "filter", targetEl: s, fromEl: n, toEl: n - }), Dt("filter", t, { evt: e }), !0; + }), Xi("filter", t, { evt: e }), !0; }), l)) { i && e.preventDefault(); return; } - (!r.handle || Qe(c, r.handle, n, !1)) && this._prepareDragStart(e, o, s); + (!r.handle || bi(c, r.handle, n, !1)) && this._prepareDragStart(e, o, s); } } }, _prepareDragStart: function(e, t, n) { var r = this, i = r.el, a = r.options, o = i.ownerDocument, s; - if (n && !G && n.parentNode === i) { - var c = H(n); - if (J = i, G = n, K = G.parentNode, Ot = G.nextSibling, kt = n, Ft = a.group, Q.dragged = G, zt = { - target: G, + if (n && !J && n.parentNode === i) { + var c = q(n); + if (Z = i, J = n, Y = J.parentNode, Qi = J.nextSibling, $i = n, aa = a.group, $.dragged = J, ua = { + target: J, clientX: (t || e).clientX, clientY: (t || e).clientY - }, Ut = zt.clientX - c.left, Wt = zt.clientY - c.top, this._lastX = (t || e).clientX, this._lastY = (t || e).clientY, G.style["will-change"] = "all", s = function() { - if (Dt("delayEnded", r, { evt: e }), Q.eventCanceled) { + }, ma = ua.clientX - c.left, ha = ua.clientY - c.top, this._lastX = (t || e).clientX, this._lastY = (t || e).clientY, J.style["will-change"] = "all", s = function() { + if (Xi("delayEnded", r, { evt: e }), $.eventCanceled) { r._onDrop(); return; } - r._disableDelayedDragEvents(), !Ge && r.nativeDraggable && (G.draggable = !0), r._triggerDragStart(e, t), W({ + r._disableDelayedDragEvents(), !pi && r.nativeDraggable && (J.draggable = !0), r._triggerDragStart(e, t), Zi({ sortable: r, name: "choose", originalEvent: e - }), et(G, a.chosenClass, !0); + }), Si(J, a.chosenClass, !0); }, a.ignore.split(",").forEach(function(e) { - nt(G, e.trim(), gn); - }), z(o, "dragover", fn), z(o, "mousemove", fn), z(o, "touchmove", fn), a.supportPointer ? (z(o, "pointerup", r._onDrop), !this.nativeDraggable && z(o, "pointercancel", r._onDrop)) : (z(o, "mouseup", r._onDrop), z(o, "touchend", r._onDrop), z(o, "touchcancel", r._onDrop)), Ge && this.nativeDraggable && (this.options.touchStartThreshold = 4, G.draggable = !0), Dt("delayStart", this, { evt: e }), a.delay && (!a.delayOnTouchOnly || t) && (!this.nativeDraggable || !(We || Ue))) { - if (Q.eventCanceled) { + wi(J, e.trim(), Va); + }), W(o, "dragover", La), W(o, "mousemove", La), W(o, "touchmove", La), a.supportPointer ? (W(o, "pointerup", r._onDrop), !this.nativeDraggable && W(o, "pointercancel", r._onDrop)) : (W(o, "mouseup", r._onDrop), W(o, "touchend", r._onDrop), W(o, "touchcancel", r._onDrop)), pi && this.nativeDraggable && (this.options.touchStartThreshold = 4, J.draggable = !0), Xi("delayStart", this, { evt: e }), a.delay && (!a.delayOnTouchOnly || t) && (!this.nativeDraggable || !(fi || di))) { + if ($.eventCanceled) { this._onDrop(); return; } - a.supportPointer ? (z(o, "pointerup", r._disableDelayedDrag), z(o, "pointercancel", r._disableDelayedDrag)) : (z(o, "mouseup", r._disableDelayedDrag), z(o, "touchend", r._disableDelayedDrag), z(o, "touchcancel", r._disableDelayedDrag)), z(o, "mousemove", r._delayedDragTouchMoveHandler), z(o, "touchmove", r._delayedDragTouchMoveHandler), a.supportPointer && z(o, "pointermove", r._delayedDragTouchMoveHandler), r._dragStartTimer = setTimeout(s, a.delay); + a.supportPointer ? (W(o, "pointerup", r._disableDelayedDrag), W(o, "pointercancel", r._disableDelayedDrag)) : (W(o, "mouseup", r._disableDelayedDrag), W(o, "touchend", r._disableDelayedDrag), W(o, "touchcancel", r._disableDelayedDrag)), W(o, "mousemove", r._delayedDragTouchMoveHandler), W(o, "touchmove", r._delayedDragTouchMoveHandler), a.supportPointer && W(o, "pointermove", r._delayedDragTouchMoveHandler), r._dragStartTimer = setTimeout(s, a.delay); } else s(); } }, @@ -1514,25 +3527,25 @@ Q.prototype = { Math.max(Math.abs(t.clientX - this._lastX), Math.abs(t.clientY - this._lastY)) >= Math.floor(this.options.touchStartThreshold / (this.nativeDraggable && window.devicePixelRatio || 1)) && this._disableDelayedDrag(); }, _disableDelayedDrag: function() { - G && gn(G), clearTimeout(this._dragStartTimer), this._disableDelayedDragEvents(); + J && Va(J), clearTimeout(this._dragStartTimer), this._disableDelayedDragEvents(); }, _disableDelayedDragEvents: function() { var e = this.el.ownerDocument; - B(e, "mouseup", this._disableDelayedDrag), B(e, "touchend", this._disableDelayedDrag), B(e, "touchcancel", this._disableDelayedDrag), B(e, "pointerup", this._disableDelayedDrag), B(e, "pointercancel", this._disableDelayedDrag), B(e, "mousemove", this._delayedDragTouchMoveHandler), B(e, "touchmove", this._delayedDragTouchMoveHandler), B(e, "pointermove", this._delayedDragTouchMoveHandler); + G(e, "mouseup", this._disableDelayedDrag), G(e, "touchend", this._disableDelayedDrag), G(e, "touchcancel", this._disableDelayedDrag), G(e, "pointerup", this._disableDelayedDrag), G(e, "pointercancel", this._disableDelayedDrag), G(e, "mousemove", this._delayedDragTouchMoveHandler), G(e, "touchmove", this._delayedDragTouchMoveHandler), G(e, "pointermove", this._delayedDragTouchMoveHandler); }, _triggerDragStart: function(e, t) { - t ||= e.pointerType == "touch" && e, !this.nativeDraggable || t ? this.options.supportPointer ? z(document, "pointermove", this._onTouchMove) : t ? z(document, "touchmove", this._onTouchMove) : z(document, "mousemove", this._onTouchMove) : (z(G, "dragend", this), z(J, "dragstart", this._onDragStart)); + t ||= e.pointerType == "touch" && e, !this.nativeDraggable || t ? this.options.supportPointer ? W(document, "pointermove", this._onTouchMove) : t ? W(document, "touchmove", this._onTouchMove) : W(document, "mousemove", this._onTouchMove) : (W(J, "dragend", this), W(Z, "dragstart", this._onDragStart)); try { - document.selection ? wn(function() { + document.selection ? Ya(function() { document.selection.empty(); }) : window.getSelection().removeAllRanges(); } catch {} }, _dragStarted: function(e, t) { - if (It = !1, J && G) { - Dt("dragStarted", this, { evt: t }), this.nativeDraggable && z(document, "dragover", pn); + if (sa = !1, Z && J) { + Xi("dragStarted", this, { evt: t }), this.nativeDraggable && W(document, "dragover", Ra); var n = this.options; - !e && et(G, n.dragClass, !1), et(G, n.ghostClass, !0), Q.active = this, e && this._appendGhost(), W({ + !e && Si(J, n.dragClass, !1), Si(J, n.ghostClass, !0), $.active = this, e && this._appendGhost(), Zi({ sortable: this, name: "start", originalEvent: t @@ -1540,33 +3553,33 @@ Q.prototype = { } else this._nulling(); }, _emulateDragOver: function() { - if (Bt) { - this._lastX = Bt.clientX, this._lastY = Bt.clientY, un(); - for (var e = document.elementFromPoint(Bt.clientX, Bt.clientY), t = e; e && e.shadowRoot && (e = e.shadowRoot.elementFromPoint(Bt.clientX, Bt.clientY), e !== t);) t = e; - if (G.parentNode[U]._isOutsideThisEl(e), t) do { - if (t[U]) { + if (da) { + this._lastX = da.clientX, this._lastY = da.clientY, Fa(); + for (var e = document.elementFromPoint(da.clientX, da.clientY), t = e; e && e.shadowRoot && (e = e.shadowRoot.elementFromPoint(da.clientX, da.clientY), e !== t);) t = e; + if (J.parentNode[Vi]._isOutsideThisEl(e), t) do { + if (t[Vi]) { var n = void 0; - if (n = t[U]._onDragOver({ - clientX: Bt.clientX, - clientY: Bt.clientY, + if (n = t[Vi]._onDragOver({ + clientX: da.clientX, + clientY: da.clientY, target: e, rootEl: t }), n && !this.options.dragoverBubble) break; } e = t; - } while (t = Ze(t)); - dn(); + } while (t = yi(t)); + Ia(); } }, _onTouchMove: function(e) { - if (zt) { - var t = this.options, n = t.fallbackTolerance, r = t.fallbackOffset, i = e.touches ? e.touches[0] : e, a = q && tt(q, !0), o = q && a && a.a, s = q && a && a.d, c = tn && Z && ct(Z), l = (i.clientX - zt.clientX + r.x) / (o || 1) + (c ? c[0] - Zt[0] : 0) / (o || 1), u = (i.clientY - zt.clientY + r.y) / (s || 1) + (c ? c[1] - Zt[1] : 0) / (s || 1); - if (!Q.active && !It) { + if (ua) { + var t = this.options, n = t.fallbackTolerance, r = t.fallbackOffset, i = e.touches ? e.touches[0] : e, a = X && Ci(X, !0), o = X && a && a.a, s = X && a && a.d, c = Da && Sa && Ai(Sa), l = (i.clientX - ua.clientX + r.x) / (o || 1) + (c ? c[0] - Ca[0] : 0) / (o || 1), u = (i.clientY - ua.clientY + r.y) / (s || 1) + (c ? c[1] - Ca[1] : 0) / (s || 1); + if (!$.active && !sa) { if (n && Math.max(Math.abs(i.clientX - this._lastX), Math.abs(i.clientY - this._lastY)) < n) return; this._onDragStart(e, !0); } - if (q) { - a ? (a.e += l - (Vt || 0), a.f += u - (Ht || 0)) : a = { + if (X) { + a ? (a.e += l - (fa || 0), a.f += u - (pa || 0)) : a = { a: 1, b: 0, c: 0, @@ -1575,39 +3588,39 @@ Q.prototype = { f: u }; var d = `matrix(${a.a},${a.b},${a.c},${a.d},${a.e},${a.f})`; - V(q, "webkitTransform", d), V(q, "mozTransform", d), V(q, "msTransform", d), V(q, "transform", d), Vt = l, Ht = u, Bt = i; + K(X, "webkitTransform", d), K(X, "mozTransform", d), K(X, "msTransform", d), K(X, "transform", d), fa = l, pa = u, da = i; } e.cancelable && e.preventDefault(); } }, _appendGhost: function() { - if (!q) { - var e = this.options.fallbackOnBody ? document.body : J, t = H(G, !0, tn, !0, e), n = this.options; - if (tn) { - for (Z = e; V(Z, "position") === "static" && V(Z, "transform") === "none" && Z !== document;) Z = Z.parentNode; - Z !== document.body && Z !== document.documentElement ? (Z === document && (Z = rt()), t.top += Z.scrollTop, t.left += Z.scrollLeft) : Z = rt(), Zt = ct(Z); + if (!X) { + var e = this.options.fallbackOnBody ? document.body : Z, t = q(J, !0, Da, !0, e), n = this.options; + if (Da) { + for (Sa = e; K(Sa, "position") === "static" && K(Sa, "transform") === "none" && Sa !== document;) Sa = Sa.parentNode; + Sa !== document.body && Sa !== document.documentElement ? (Sa === document && (Sa = Ti()), t.top += Sa.scrollTop, t.left += Sa.scrollLeft) : Sa = Ti(), Ca = Ai(Sa); } - q = G.cloneNode(!0), et(q, n.ghostClass, !1), et(q, n.fallbackClass, !0), et(q, n.dragClass, !0), V(q, "transition", ""), V(q, "transform", ""), V(q, "box-sizing", "border-box"), V(q, "margin", 0), V(q, "top", t.top), V(q, "left", t.left), V(q, "width", t.width), V(q, "height", t.height), V(q, "opacity", "0.8"), V(q, "position", tn ? "absolute" : "fixed"), V(q, "zIndex", "100000"), V(q, "pointerEvents", "none"), Q.ghost = q, e.appendChild(q), V(q, "transform-origin", Ut / parseInt(q.style.width) * 100 + "% " + Wt / parseInt(q.style.height) * 100 + "%"); + X = J.cloneNode(!0), Si(X, n.ghostClass, !1), Si(X, n.fallbackClass, !0), Si(X, n.dragClass, !0), K(X, "transition", ""), K(X, "transform", ""), K(X, "box-sizing", "border-box"), K(X, "margin", 0), K(X, "top", t.top), K(X, "left", t.left), K(X, "width", t.width), K(X, "height", t.height), K(X, "opacity", "0.8"), K(X, "position", Da ? "absolute" : "fixed"), K(X, "zIndex", "100000"), K(X, "pointerEvents", "none"), $.ghost = X, e.appendChild(X), K(X, "transform-origin", ma / parseInt(X.style.width) * 100 + "% " + ha / parseInt(X.style.height) * 100 + "%"); } }, _onDragStart: function(e, t) { var n = this, r = e.dataTransfer, i = n.options; - if (Dt("dragStart", this, { evt: e }), Q.eventCanceled) { + if (Xi("dragStart", this, { evt: e }), $.eventCanceled) { this._onDrop(); return; } - Dt("setupClone", this), Q.eventCanceled || (Y = _t(G), Y.removeAttribute("id"), Y.draggable = !1, Y.style["will-change"] = "", this._hideClone(), et(Y, this.options.chosenClass, !1), Q.clone = Y), n.cloneId = wn(function() { - Dt("clone", n), !Q.eventCanceled && (n.options.removeCloneOnHide || J.insertBefore(Y, G), n._hideClone(), W({ + Xi("setupClone", this), $.eventCanceled || (Q = zi(J), Q.removeAttribute("id"), Q.draggable = !1, Q.style["will-change"] = "", this._hideClone(), Si(Q, this.options.chosenClass, !1), $.clone = Q), n.cloneId = Ya(function() { + Xi("clone", n), !$.eventCanceled && (n.options.removeCloneOnHide || Z.insertBefore(Q, J), n._hideClone(), Zi({ sortable: n, name: "clone" })); - }), !t && et(G, i.dragClass, !0), t ? (Lt = !0, n._loopId = setInterval(n._emulateDragOver, 50)) : (B(document, "mouseup", n._onDrop), B(document, "touchend", n._onDrop), B(document, "touchcancel", n._onDrop), r && (r.effectAllowed = "move", i.setData && i.setData.call(n, r, G)), z(document, "drop", n), V(G, "transform", "translateZ(0)")), It = !0, n._dragStartId = wn(n._dragStarted.bind(n, t, e)), z(document, "selectstart", n), Gt = !0, window.getSelection().removeAllRanges(), Ke && V(document.body, "user-select", "none"); + }), !t && Si(J, i.dragClass, !0), t ? (ca = !0, n._loopId = setInterval(n._emulateDragOver, 50)) : (G(document, "mouseup", n._onDrop), G(document, "touchend", n._onDrop), G(document, "touchcancel", n._onDrop), r && (r.effectAllowed = "move", i.setData && i.setData.call(n, r, J)), W(document, "drop", n), K(J, "transform", "translateZ(0)")), sa = !0, n._dragStartId = Ya(n._dragStarted.bind(n, t, e)), W(document, "selectstart", n), ga = !0, window.getSelection().removeAllRanges(), mi && K(document.body, "user-select", "none"); }, _onDragOver: function(e) { - var t = this.el, n = e.target, r, i, a, o = this.options, s = o.group, c = Q.active, l = Ft === s, u = o.sort, d = X || c, f, p = this, m = !1; - if (Qt) return; + var t = this.el, n = e.target, r, i, a, o = this.options, s = o.group, c = $.active, l = aa === s, u = o.sort, d = oa || c, f, p = this, m = !1; + if (wa) return; function h(o, s) { - Dt(o, p, Fe({ + Xi(o, p, ri({ evt: e, isOwner: l, axis: f ? "vertical" : "horizontal", @@ -1619,7 +3632,7 @@ Q.prototype = { target: n, completed: _, onMove: function(n, i) { - return hn(J, t, G, r, n, H(n), e, i); + return Ba(Z, t, J, r, n, q(n), e, i); }, changed: v }, s)); @@ -1628,121 +3641,121 @@ Q.prototype = { h("dragOverAnimationCapture"), p.captureAnimationState(), p !== d && d.captureAnimationState(); } function _(r) { - return h("dragOverCompleted", { insertion: r }), r && (l ? c._hideClone() : c._showClone(p), p !== d && (et(G, X ? X.options.ghostClass : c.options.ghostClass, !1), et(G, o.ghostClass, !0)), X !== p && p !== Q.active ? X = p : p === Q.active && X && (X = null), d === p && (p._ignoreWhileAnimating = n), p.animateAll(function() { + return h("dragOverCompleted", { insertion: r }), r && (l ? c._hideClone() : c._showClone(p), p !== d && (Si(J, oa ? oa.options.ghostClass : c.options.ghostClass, !1), Si(J, o.ghostClass, !0)), oa !== p && p !== $.active ? oa = p : p === $.active && oa && (oa = null), d === p && (p._ignoreWhileAnimating = n), p.animateAll(function() { h("dragOverAnimationComplete"), p._ignoreWhileAnimating = null; - }), p !== d && (d.animateAll(), d._ignoreWhileAnimating = null)), (n === G && !G.animated || n === t && !n.animated) && (Kt = null), !o.dragoverBubble && !e.rootEl && n !== document && (G.parentNode[U]._isOutsideThisEl(e.target), !r && fn(e)), !o.dragoverBubble && e.stopPropagation && e.stopPropagation(), m = !0; + }), p !== d && (d.animateAll(), d._ignoreWhileAnimating = null)), (n === J && !J.animated || n === t && !n.animated) && (_a = null), !o.dragoverBubble && !e.rootEl && n !== document && (J.parentNode[Vi]._isOutsideThisEl(e.target), !r && La(e)), !o.dragoverBubble && e.stopPropagation && e.stopPropagation(), m = !0; } function v() { - Mt = st(G), Pt = st(G, o.draggable), W({ + na = ki(J), ia = ki(J, o.draggable), Zi({ sortable: p, name: "change", toEl: t, - newIndex: Mt, - newDraggableIndex: Pt, + newIndex: na, + newDraggableIndex: ia, originalEvent: e }); } - if (e.preventDefault !== void 0 && e.cancelable && e.preventDefault(), n = Qe(n, o.draggable, t, !0), h("dragOver"), Q.eventCanceled) return m; - if (G.contains(e.target) || n.animated && n.animatingX && n.animatingY || p._ignoreWhileAnimating === n) return _(!1); - if (Lt = !1, c && !o.disabled && (l ? u || (a = K !== J) : X === this || (this.lastPutMode = Ft.checkPull(this, c, G, e)) && s.checkPut(this, c, G, e))) { - if (f = this._getDirection(e, n) === "vertical", r = H(G), h("dragOverValid"), Q.eventCanceled) return m; - if (a) return K = J, g(), this._hideClone(), h("revert"), Q.eventCanceled || (Ot ? J.insertBefore(G, Ot) : J.appendChild(G)), _(!0); - var y = ot(t, o.draggable); - if (!y || yn(e, f, this) && !y.animated) { - if (y === G) return _(!1); - if (y && t === e.target && (n = y), n && (i = H(n)), hn(J, t, G, r, n, i, e, !!n) !== !1) return g(), y && y.nextSibling ? t.insertBefore(G, y.nextSibling) : t.appendChild(G), K = t, v(), _(!0); - } else if (y && vn(e, f, this)) { - var b = at(t, 0, o, !0); - if (b === G) return _(!1); - if (n = b, i = H(n), hn(J, t, G, r, n, i, e, !1) !== !1) return g(), t.insertBefore(G, b), K = t, v(), _(!0); + if (e.preventDefault !== void 0 && e.cancelable && e.preventDefault(), n = bi(n, o.draggable, t, !0), h("dragOver"), $.eventCanceled) return m; + if (J.contains(e.target) || n.animated && n.animatingX && n.animatingY || p._ignoreWhileAnimating === n) return _(!1); + if (ca = !1, c && !o.disabled && (l ? u || (a = Y !== Z) : oa === this || (this.lastPutMode = aa.checkPull(this, c, J, e)) && s.checkPut(this, c, J, e))) { + if (f = this._getDirection(e, n) === "vertical", r = q(J), h("dragOverValid"), $.eventCanceled) return m; + if (a) return Y = Z, g(), this._hideClone(), h("revert"), $.eventCanceled || (Qi ? Z.insertBefore(J, Qi) : Z.appendChild(J)), _(!0); + var y = Oi(t, o.draggable); + if (!y || Wa(e, f, this) && !y.animated) { + if (y === J) return _(!1); + if (y && t === e.target && (n = y), n && (i = q(n)), Ba(Z, t, J, r, n, i, e, !!n) !== !1) return g(), y && y.nextSibling ? t.insertBefore(J, y.nextSibling) : t.appendChild(J), Y = t, v(), _(!0); + } else if (y && Ua(e, f, this)) { + var b = Di(t, 0, o, !0); + if (b === J) return _(!1); + if (n = b, i = q(n), Ba(Z, t, J, r, n, i, e, !1) !== !1) return g(), t.insertBefore(J, b), Y = t, v(), _(!0); } else if (n.parentNode === t) { - i = H(n); - var x = 0, S, C = G.parentNode !== t, w = !sn(G.animated && G.toRect || r, n.animated && n.toRect || i, f), T = f ? "top" : "left", E = it(n, "top", "top") || it(G, "top", "top"), D = E ? E.scrollTop : void 0; - Kt !== n && (S = i[T], Jt = !1, Yt = !w && o.invertSwap || C), x = bn(e, n, i, f, w ? 1 : o.swapThreshold, o.invertedSwapThreshold == null ? o.swapThreshold : o.invertedSwapThreshold, Yt, Kt === n); + i = q(n); + var x = 0, S, C = J.parentNode !== t, w = !Ma(J.animated && J.toRect || r, n.animated && n.toRect || i, f), T = f ? "top" : "left", E = Ei(n, "top", "top") || Ei(J, "top", "top"), D = E ? E.scrollTop : void 0; + _a !== n && (S = i[T], ya = !1, ba = !w && o.invertSwap || C), x = Ga(e, n, i, f, w ? 1 : o.swapThreshold, o.invertedSwapThreshold == null ? o.swapThreshold : o.invertedSwapThreshold, ba, _a === n); var O; if (x !== 0) { - var k = st(G); + var k = ki(J); do - k -= x, O = K.children[k]; - while (O && (V(O, "display") === "none" || O === q)); + k -= x, O = Y.children[k]; + while (O && (K(O, "display") === "none" || O === X)); } if (x === 0 || O === n) return _(!1); - Kt = n, qt = x; + _a = n, va = x; var A = n.nextElementSibling, j = !1; j = x === 1; - var M = hn(J, t, G, r, n, i, e, j); - if (M !== !1) return (M === 1 || M === -1) && (j = M === 1), Qt = !0, setTimeout(_n, 30), g(), j && !A ? t.appendChild(G) : n.parentNode.insertBefore(G, j ? A : n), E && gt(E, 0, D - E.scrollTop), K = G.parentNode, S !== void 0 && !Yt && (Xt = Math.abs(S - H(n)[T])), v(), _(!0); + var M = Ba(Z, t, J, r, n, i, e, j); + if (M !== !1) return (M === 1 || M === -1) && (j = M === 1), wa = !0, setTimeout(Ha, 30), g(), j && !A ? t.appendChild(J) : n.parentNode.insertBefore(J, j ? A : n), E && Ri(E, 0, D - E.scrollTop), Y = J.parentNode, S !== void 0 && !ba && (xa = Math.abs(S - q(n)[T])), v(), _(!0); } - if (t.contains(G)) return _(!1); + if (t.contains(J)) return _(!1); } return !1; }, _ignoreWhileAnimating: null, _offMoveEvents: function() { - B(document, "mousemove", this._onTouchMove), B(document, "touchmove", this._onTouchMove), B(document, "pointermove", this._onTouchMove), B(document, "dragover", fn), B(document, "mousemove", fn), B(document, "touchmove", fn); + G(document, "mousemove", this._onTouchMove), G(document, "touchmove", this._onTouchMove), G(document, "pointermove", this._onTouchMove), G(document, "dragover", La), G(document, "mousemove", La), G(document, "touchmove", La); }, _offUpEvents: function() { var e = this.el.ownerDocument; - B(e, "mouseup", this._onDrop), B(e, "touchend", this._onDrop), B(e, "pointerup", this._onDrop), B(e, "pointercancel", this._onDrop), B(e, "touchcancel", this._onDrop), B(document, "selectstart", this); + G(e, "mouseup", this._onDrop), G(e, "touchend", this._onDrop), G(e, "pointerup", this._onDrop), G(e, "pointercancel", this._onDrop), G(e, "touchcancel", this._onDrop), G(document, "selectstart", this); }, _onDrop: function(e) { var t = this.el, n = this.options; - if (Mt = st(G), Pt = st(G, n.draggable), Dt("drop", this, { evt: e }), K = G && G.parentNode, Mt = st(G), Pt = st(G, n.draggable), Q.eventCanceled) { + if (na = ki(J), ia = ki(J, n.draggable), Xi("drop", this, { evt: e }), Y = J && J.parentNode, na = ki(J), ia = ki(J, n.draggable), $.eventCanceled) { this._nulling(); return; } - It = !1, Yt = !1, Jt = !1, clearInterval(this._loopId), clearTimeout(this._dragStartTimer), Tn(this.cloneId), Tn(this._dragStartId), this.nativeDraggable && (B(document, "drop", this), B(t, "dragstart", this._onDragStart)), this._offMoveEvents(), this._offUpEvents(), Ke && V(document.body, "user-select", ""), V(G, "transform", ""), e && (Gt && (e.cancelable && e.preventDefault(), !n.dropBubble && e.stopPropagation()), q && q.parentNode && q.parentNode.removeChild(q), (J === K || X && X.lastPutMode !== "clone") && Y && Y.parentNode && Y.parentNode.removeChild(Y), G && (this.nativeDraggable && B(G, "dragend", this), gn(G), G.style["will-change"] = "", Gt && !It && et(G, X ? X.options.ghostClass : this.options.ghostClass, !1), et(G, this.options.chosenClass, !1), W({ + sa = !1, ba = !1, ya = !1, clearInterval(this._loopId), clearTimeout(this._dragStartTimer), Xa(this.cloneId), Xa(this._dragStartId), this.nativeDraggable && (G(document, "drop", this), G(t, "dragstart", this._onDragStart)), this._offMoveEvents(), this._offUpEvents(), mi && K(document.body, "user-select", ""), K(J, "transform", ""), e && (ga && (e.cancelable && e.preventDefault(), !n.dropBubble && e.stopPropagation()), X && X.parentNode && X.parentNode.removeChild(X), (Z === Y || oa && oa.lastPutMode !== "clone") && Q && Q.parentNode && Q.parentNode.removeChild(Q), J && (this.nativeDraggable && G(J, "dragend", this), Va(J), J.style["will-change"] = "", ga && !sa && Si(J, oa ? oa.options.ghostClass : this.options.ghostClass, !1), Si(J, this.options.chosenClass, !1), Zi({ sortable: this, name: "unchoose", - toEl: K, + toEl: Y, newIndex: null, newDraggableIndex: null, originalEvent: e - }), J === K ? Mt !== jt && Mt >= 0 && (W({ + }), Z === Y ? na !== ta && na >= 0 && (Zi({ sortable: this, name: "update", - toEl: K, + toEl: Y, originalEvent: e - }), W({ + }), Zi({ sortable: this, name: "sort", - toEl: K, + toEl: Y, originalEvent: e - })) : (Mt >= 0 && (W({ - rootEl: K, + })) : (na >= 0 && (Zi({ + rootEl: Y, name: "add", - toEl: K, - fromEl: J, + toEl: Y, + fromEl: Z, originalEvent: e - }), W({ + }), Zi({ sortable: this, name: "remove", - toEl: K, + toEl: Y, originalEvent: e - }), W({ - rootEl: K, + }), Zi({ + rootEl: Y, name: "sort", - toEl: K, - fromEl: J, + toEl: Y, + fromEl: Z, originalEvent: e - }), W({ + }), Zi({ sortable: this, name: "sort", - toEl: K, + toEl: Y, originalEvent: e - })), X && X.save()), Q.active && ((Mt == null || Mt === -1) && (Mt = jt, Pt = Nt), W({ + })), oa && oa.save()), $.active && ((na == null || na === -1) && (na = ta, ia = ra), Zi({ sortable: this, name: "end", - toEl: K, + toEl: Y, originalEvent: e }), this.save()))), this._nulling(); }, _nulling: function() { - Dt("nulling", this), J = G = K = q = Ot = Y = kt = At = zt = Bt = Gt = Mt = Pt = jt = Nt = Kt = qt = X = Ft = Q.dragged = Q.ghost = Q.clone = Q.active = null; + Xi("nulling", this), Z = J = Y = X = Qi = Q = $i = ea = ua = da = ga = na = ia = ta = ra = _a = va = oa = aa = $.dragged = $.ghost = $.clone = $.active = null; var e = this.el; - $t.forEach(function(t) { + Ta.forEach(function(t) { e.contains(t) && (t.checked = !0); - }), $t.length = Vt = Ht = 0; + }), Ta.length = fa = pa = 0; }, handleEvent: function(e) { switch (e.type) { @@ -1752,20 +3765,20 @@ Q.prototype = { break; case "dragenter": case "dragover": - G && (this._onDragOver(e), mn(e)); + J && (this._onDragOver(e), za(e)); break; case "selectstart": e.preventDefault(); } }, toArray: function() { - for (var e = [], t, n = this.el.children, r = 0, i = n.length, a = this.options; r < i; r++) t = n[r], Qe(t, a.draggable, this.el, !1) && e.push(t.getAttribute(a.dataIdAttr) || Sn(t)); + for (var e = [], t, n = this.el.children, r = 0, i = n.length, a = this.options; r < i; r++) t = n[r], bi(t, a.draggable, this.el, !1) && e.push(t.getAttribute(a.dataIdAttr) || qa(t)); return e; }, sort: function(e, t) { var n = {}, r = this.el; this.toArray().forEach(function(e, t) { var i = r.children[t]; - Qe(i, this.options.draggable, r, !1) && (n[e] = i); + bi(i, this.options.draggable, r, !1) && (n[e] = i); }, this), t && this.captureAnimationState(), e.forEach(function(e) { n[e] && (r.removeChild(n[e]), r.appendChild(n[e])); }), t && this.animateAll(); @@ -1775,25 +3788,25 @@ Q.prototype = { e && e.set && e.set(this); }, closest: function(e, t) { - return Qe(e, t || this.options.draggable, this.el, !1); + return bi(e, t || this.options.draggable, this.el, !1); }, option: function(e, t) { var n = this.options; if (t === void 0) return n[e]; - var r = wt.modifyOption(this, e, t); - n[e] = r === void 0 ? t : r, e === "group" && ln(n); + var r = qi.modifyOption(this, e, t); + n[e] = r === void 0 ? t : r, e === "group" && Pa(n); }, destroy: function() { - Dt("destroy", this); + Xi("destroy", this); var e = this.el; - e[U] = null, B(e, "mousedown", this._onTapStart), B(e, "touchstart", this._onTapStart), B(e, "pointerdown", this._onTapStart), this.nativeDraggable && (B(e, "dragover", this), B(e, "dragenter", this)), Array.prototype.forEach.call(e.querySelectorAll("[draggable]"), function(e) { + e[Vi] = null, G(e, "mousedown", this._onTapStart), G(e, "touchstart", this._onTapStart), G(e, "pointerdown", this._onTapStart), this.nativeDraggable && (G(e, "dragover", this), G(e, "dragenter", this)), Array.prototype.forEach.call(e.querySelectorAll("[draggable]"), function(e) { e.removeAttribute("draggable"); - }), this._onDrop(), this._disableDelayedDragEvents(), Rt.splice(Rt.indexOf(this.el), 1), this.el = e = null; + }), this._onDrop(), this._disableDelayedDragEvents(), la.splice(la.indexOf(this.el), 1), this.el = e = null; }, _hideClone: function() { - if (!At) { - if (Dt("hideClone", this), Q.eventCanceled) return; - V(Y, "display", "none"), this.options.removeCloneOnHide && Y.parentNode && Y.parentNode.removeChild(Y), At = !0; + if (!ea) { + if (Xi("hideClone", this), $.eventCanceled) return; + K(Q, "display", "none"), this.options.removeCloneOnHide && Q.parentNode && Q.parentNode.removeChild(Q), ea = !0; } }, _showClone: function(e) { @@ -1801,100 +3814,100 @@ Q.prototype = { this._hideClone(); return; } - if (At) { - if (Dt("showClone", this), Q.eventCanceled) return; - G.parentNode == J && !this.options.group.revertClone ? J.insertBefore(Y, G) : Ot ? J.insertBefore(Y, Ot) : J.appendChild(Y), this.options.group.revertClone && this.animate(G, Y), V(Y, "display", ""), At = !1; + if (ea) { + if (Xi("showClone", this), $.eventCanceled) return; + J.parentNode == Z && !this.options.group.revertClone ? Z.insertBefore(Q, J) : Qi ? Z.insertBefore(Q, Qi) : Z.appendChild(Q), this.options.group.revertClone && this.animate(J, Q), K(Q, "display", ""), ea = !1; } } }; -function mn(e) { +function za(e) { e.dataTransfer && (e.dataTransfer.dropEffect = "move"), e.cancelable && e.preventDefault(); } -function hn(e, t, n, r, i, a, o, s) { - var c, l = e[U], u = l.options.onMove, d; - return window.CustomEvent && !Ue && !We ? c = new CustomEvent("move", { +function Ba(e, t, n, r, i, a, o, s) { + var c, l = e[Vi], u = l.options.onMove, d; + return window.CustomEvent && !di && !fi ? c = new CustomEvent("move", { bubbles: !0, cancelable: !0 - }) : (c = document.createEvent("Event"), c.initEvent("move", !0, !0)), c.to = t, c.from = e, c.dragged = n, c.draggedRect = r, c.related = i || t, c.relatedRect = a || H(t), c.willInsertAfter = s, c.originalEvent = o, e.dispatchEvent(c), u && (d = u.call(l, c, o)), d; + }) : (c = document.createEvent("Event"), c.initEvent("move", !0, !0)), c.to = t, c.from = e, c.dragged = n, c.draggedRect = r, c.related = i || t, c.relatedRect = a || q(t), c.willInsertAfter = s, c.originalEvent = o, e.dispatchEvent(c), u && (d = u.call(l, c, o)), d; } -function gn(e) { +function Va(e) { e.draggable = !1; } -function _n() { - Qt = !1; +function Ha() { + wa = !1; } -function vn(e, t, n) { - var r = H(at(n.el, 0, n.options, !0)), i = vt(n.el, n.options, q), a = 10; +function Ua(e, t, n) { + var r = q(Di(n.el, 0, n.options, !0)), i = Bi(n.el, n.options, X), a = 10; return t ? e.clientX < i.left - a || e.clientY < r.top && e.clientX < r.right : e.clientY < i.top - a || e.clientY < r.bottom && e.clientX < r.left; } -function yn(e, t, n) { - var r = H(ot(n.el, n.options.draggable)), i = vt(n.el, n.options, q), a = 10; +function Wa(e, t, n) { + var r = q(Oi(n.el, n.options.draggable)), i = Bi(n.el, n.options, X), a = 10; return t ? e.clientX > i.right + a || e.clientY > r.bottom && e.clientX > r.left : e.clientY > i.bottom + a || e.clientX > r.right && e.clientY > r.top; } -function bn(e, t, n, r, i, a, o, s) { +function Ga(e, t, n, r, i, a, o, s) { var c = r ? e.clientY : e.clientX, l = r ? n.height : n.width, u = r ? n.top : n.left, d = r ? n.bottom : n.right, f = !1; if (!o) { - if (s && Xt < l * i) { - if (!Jt && (qt === 1 ? c > u + l * a / 2 : c < d - l * a / 2) && (Jt = !0), Jt) f = !0; - else if (qt === 1 ? c < u + Xt : c > d - Xt) return -qt; - } else if (c > u + l * (1 - i) / 2 && c < d - l * (1 - i) / 2) return xn(t); + if (s && xa < l * i) { + if (!ya && (va === 1 ? c > u + l * a / 2 : c < d - l * a / 2) && (ya = !0), ya) f = !0; + else if (va === 1 ? c < u + xa : c > d - xa) return -va; + } else if (c > u + l * (1 - i) / 2 && c < d - l * (1 - i) / 2) return Ka(t); } return f ||= o, f && (c < u + l * a / 2 || c > d - l * a / 2) ? c > u + l / 2 ? 1 : -1 : 0; } -function xn(e) { - return st(G) < st(e) ? 1 : -1; +function Ka(e) { + return ki(J) < ki(e) ? 1 : -1; } -function Sn(e) { +function qa(e) { for (var t = e.tagName + e.className + e.src + e.href + e.textContent, n = t.length, r = 0; n--;) r += t.charCodeAt(n); return r.toString(36); } -function Cn(e) { - $t.length = 0; +function Ja(e) { + Ta.length = 0; for (var t = e.getElementsByTagName("input"), n = t.length; n--;) { var r = t[n]; - r.checked && $t.push(r); + r.checked && Ta.push(r); } } -function wn(e) { +function Ya(e) { return setTimeout(e, 0); } -function Tn(e) { +function Xa(e) { return clearTimeout(e); } -en && z(document, "touchmove", function(e) { - (Q.active || It) && e.cancelable && e.preventDefault(); -}), Q.utils = { - on: z, - off: B, - css: V, - find: nt, +Ea && W(document, "touchmove", function(e) { + ($.active || sa) && e.cancelable && e.preventDefault(); +}), $.utils = { + on: W, + off: G, + css: K, + find: wi, is: function(e, t) { - return !!Qe(e, t, e, !1); - }, - extend: dt, - throttle: mt, - closest: Qe, - toggleClass: et, - clone: _t, - index: st, - nextTick: wn, - cancelNextTick: Tn, - detectDirection: on, - getChild: at, - expando: U -}, Q.get = function(e) { - return e[U]; -}, Q.mount = function() { + return !!bi(e, t, e, !1); + }, + extend: Ni, + throttle: Ii, + closest: bi, + toggleClass: Si, + clone: zi, + index: ki, + nextTick: Ya, + cancelNextTick: Xa, + detectDirection: ja, + getChild: Di, + expando: Vi +}, $.get = function(e) { + return e[Vi]; +}, $.mount = function() { var e = [...arguments]; e[0].constructor === Array && (e = e[0]), e.forEach(function(e) { if (!e.prototype || !e.prototype.constructor) throw `Sortable: Mounted plugin must be a constructor function, not ${{}.toString.call(e)}`; - e.utils && (Q.utils = Fe(Fe({}, Q.utils), e.utils)), wt.mount(e); + e.utils && ($.utils = ri(ri({}, $.utils), e.utils)), qi.mount(e); }); -}, Q.create = function(e, t) { - return new Q(e, t); -}, Q.version = Ve; -var $ = [], En, Dn, On = !1, kn, An, jn, Mn; -function Nn() { +}, $.create = function(e, t) { + return new $(e, t); +}, $.version = li; +var Za = [], Qa, $a, eo = !1, to, no, ro, io; +function ao() { function e() { for (var e in this.defaults = { scroll: !0, @@ -1907,70 +3920,70 @@ function Nn() { return e.prototype = { dragStarted: function(e) { var t = e.originalEvent; - this.sortable.nativeDraggable ? z(document, "dragover", this._handleAutoScroll) : this.options.supportPointer ? z(document, "pointermove", this._handleFallbackAutoScroll) : t.touches ? z(document, "touchmove", this._handleFallbackAutoScroll) : z(document, "mousemove", this._handleFallbackAutoScroll); + this.sortable.nativeDraggable ? W(document, "dragover", this._handleAutoScroll) : this.options.supportPointer ? W(document, "pointermove", this._handleFallbackAutoScroll) : t.touches ? W(document, "touchmove", this._handleFallbackAutoScroll) : W(document, "mousemove", this._handleFallbackAutoScroll); }, dragOverCompleted: function(e) { var t = e.originalEvent; !this.options.dragOverBubble && !t.rootEl && this._handleAutoScroll(t); }, drop: function() { - this.sortable.nativeDraggable ? B(document, "dragover", this._handleAutoScroll) : (B(document, "pointermove", this._handleFallbackAutoScroll), B(document, "touchmove", this._handleFallbackAutoScroll), B(document, "mousemove", this._handleFallbackAutoScroll)), Fn(), Pn(), ht(); + this.sortable.nativeDraggable ? G(document, "dragover", this._handleAutoScroll) : (G(document, "pointermove", this._handleFallbackAutoScroll), G(document, "touchmove", this._handleFallbackAutoScroll), G(document, "mousemove", this._handleFallbackAutoScroll)), so(), oo(), Li(); }, nulling: function() { - jn = Dn = En = On = Mn = kn = An = null, $.length = 0; + ro = $a = Qa = eo = io = to = no = null, Za.length = 0; }, _handleFallbackAutoScroll: function(e) { this._handleAutoScroll(e, !0); }, _handleAutoScroll: function(e, t) { var n = this, r = (e.touches ? e.touches[0] : e).clientX, i = (e.touches ? e.touches[0] : e).clientY, a = document.elementFromPoint(r, i); - if (jn = e, t || this.options.forceAutoScrollFallback || We || Ue || Ke) { - In(e, this.options, a, t); - var o = ut(a, !0); - On && (!Mn || r !== kn || i !== An) && (Mn && Fn(), Mn = setInterval(function() { - var a = ut(document.elementFromPoint(r, i), !0); - a !== o && (o = a, Pn()), In(e, n.options, a, t); - }, 10), kn = r, An = i); + if (ro = e, t || this.options.forceAutoScrollFallback || fi || di || mi) { + co(e, this.options, a, t); + var o = Mi(a, !0); + eo && (!io || r !== to || i !== no) && (io && so(), io = setInterval(function() { + var a = Mi(document.elementFromPoint(r, i), !0); + a !== o && (o = a, oo()), co(e, n.options, a, t); + }, 10), to = r, no = i); } else { - if (!this.options.bubbleScroll || ut(a, !0) === rt()) { - Pn(); + if (!this.options.bubbleScroll || Mi(a, !0) === Ti()) { + oo(); return; } - In(e, this.options, ut(a, !1), !1); + co(e, this.options, Mi(a, !1), !1); } } - }, Ne(e, { + }, ti(e, { pluginName: "scroll", initializeByDefault: !0 }); } -function Pn() { - $.forEach(function(e) { +function oo() { + Za.forEach(function(e) { clearInterval(e.pid); - }), $ = []; + }), Za = []; } -function Fn() { - clearInterval(Mn); +function so() { + clearInterval(io); } -var In = mt(function(e, t, n, r) { +var co = Ii(function(e, t, n, r) { if (t.scroll) { - var i = (e.touches ? e.touches[0] : e).clientX, a = (e.touches ? e.touches[0] : e).clientY, o = t.scrollSensitivity, s = t.scrollSpeed, c = rt(), l = !1, u; - Dn !== n && (Dn = n, Pn(), En = t.scroll, u = t.scrollFn, En === !0 && (En = ut(n, !0))); - var d = 0, f = En; + var i = (e.touches ? e.touches[0] : e).clientX, a = (e.touches ? e.touches[0] : e).clientY, o = t.scrollSensitivity, s = t.scrollSpeed, c = Ti(), l = !1, u; + $a !== n && ($a = n, oo(), Qa = t.scroll, u = t.scrollFn, Qa === !0 && (Qa = Mi(n, !0))); + var d = 0, f = Qa; do { - var p = f, m = H(p), h = m.top, g = m.bottom, _ = m.left, v = m.right, y = m.width, b = m.height, x = void 0, S = void 0, C = p.scrollWidth, w = p.scrollHeight, T = V(p), E = p.scrollLeft, D = p.scrollTop; + var p = f, m = q(p), h = m.top, g = m.bottom, _ = m.left, v = m.right, y = m.width, b = m.height, x = void 0, S = void 0, C = p.scrollWidth, w = p.scrollHeight, T = K(p), E = p.scrollLeft, D = p.scrollTop; p === c ? (x = y < C && (T.overflowX === "auto" || T.overflowX === "scroll" || T.overflowX === "visible"), S = b < w && (T.overflowY === "auto" || T.overflowY === "scroll" || T.overflowY === "visible")) : (x = y < C && (T.overflowX === "auto" || T.overflowX === "scroll"), S = b < w && (T.overflowY === "auto" || T.overflowY === "scroll")); var O = x && (Math.abs(v - i) <= o && E + y < C) - (Math.abs(_ - i) <= o && !!E), k = S && (Math.abs(g - a) <= o && D + b < w) - (Math.abs(h - a) <= o && !!D); - if (!$[d]) for (var A = 0; A <= d; A++) $[A] || ($[A] = {}); - ($[d].vx != O || $[d].vy != k || $[d].el !== p) && ($[d].el = p, $[d].vx = O, $[d].vy = k, clearInterval($[d].pid), (O != 0 || k != 0) && (l = !0, $[d].pid = setInterval(function() { - r && this.layer === 0 && Q.active._onTouchMove(jn); - var t = $[this.layer].vy ? $[this.layer].vy * s : 0, n = $[this.layer].vx ? $[this.layer].vx * s : 0; - (typeof u != "function" || u.call(Q.dragged.parentNode[U], n, t, e, jn, $[this.layer].el) === "continue") && gt($[this.layer].el, n, t); + if (!Za[d]) for (var A = 0; A <= d; A++) Za[A] || (Za[A] = {}); + (Za[d].vx != O || Za[d].vy != k || Za[d].el !== p) && (Za[d].el = p, Za[d].vx = O, Za[d].vy = k, clearInterval(Za[d].pid), (O != 0 || k != 0) && (l = !0, Za[d].pid = setInterval(function() { + r && this.layer === 0 && $.active._onTouchMove(ro); + var t = Za[this.layer].vy ? Za[this.layer].vy * s : 0, n = Za[this.layer].vx ? Za[this.layer].vx * s : 0; + (typeof u != "function" || u.call($.dragged.parentNode[Vi], n, t, e, ro, Za[this.layer].el) === "continue") && Ri(Za[this.layer].el, n, t); }.bind({ layer: d }), 24))), d++; - } while (t.bubbleScroll && f !== c && (f = ut(f, !1))); - On = l; + } while (t.bubbleScroll && f !== c && (f = Mi(f, !1))); + eo = l; } -}, 30), Ln = function(e) { +}, 30), lo = function(e) { var t = e.originalEvent, n = e.putSortable, r = e.dragEl, i = e.activeSortable, a = e.dispatchSortableEvent, o = e.hideGhostForTarget, s = e.unhideGhostForTarget; if (t) { var c = n || i; @@ -1982,8 +3995,8 @@ var In = mt(function(e, t, n, r) { })); } }; -function Rn() {} -Rn.prototype = { +function uo() {} +uo.prototype = { startIndex: null, dragStart: function(e) { var t = e.oldDraggableIndex; @@ -1992,92 +4005,92 @@ Rn.prototype = { onSpill: function(e) { var t = e.dragEl, n = e.putSortable; this.sortable.captureAnimationState(), n && n.captureAnimationState(); - var r = at(this.sortable.el, this.startIndex, this.options); + var r = Di(this.sortable.el, this.startIndex, this.options); r ? this.sortable.el.insertBefore(t, r) : this.sortable.el.appendChild(t), this.sortable.animateAll(), n && n.animateAll(); }, - drop: Ln -}, Ne(Rn, { pluginName: "revertOnSpill" }); -function zn() {} -zn.prototype = { + drop: lo +}, ti(uo, { pluginName: "revertOnSpill" }); +function fo() {} +fo.prototype = { onSpill: function(e) { var t = e.dragEl, n = e.putSortable || this.sortable; n.captureAnimationState(), t.parentNode && t.parentNode.removeChild(t), n.animateAll(); }, - drop: Ln -}, Ne(zn, { pluginName: "removeOnSpill" }), Q.mount(new Nn()), Q.mount(zn, Rn); + drop: lo +}, ti(fo, { pluginName: "removeOnSpill" }), $.mount(new ao()), $.mount(fo, uo); //#endregion //#region resources/js/api/board.ts -var Bn = { - board: `${O}/board`, - tasks: `${O}/tasks`, - task: (e) => `${O}/tasks/${e}`, - moveTask: (e) => `${O}/tasks/${e}/move`, - taskStatuses: `${O}/task-statuses`, - timeEntries: `${O}/time-entries`, - projectMembers: (e) => `${O}/projects/${e}/members`, - projectMember: (e, t) => `${O}/projects/${e}/members/${t}` +var po = { + board: `${j}/board`, + tasks: `${j}/tasks`, + task: (e) => `${j}/tasks/${e}`, + moveTask: (e) => `${j}/tasks/${e}/move`, + taskStatuses: `${j}/task-statuses`, + timeEntries: `${j}/time-entries`, + projectMembers: (e) => `${j}/projects/${e}/members`, + projectMember: (e, t) => `${j}/projects/${e}/members/${t}` }; -async function Vn(e, t) { - let { data: n } = await e.get(Bn.board, { params: t }); +async function mo(e, t) { + let { data: n } = await e.get(po.board, { params: t }); return n.data; } -async function Hn(e) { - let { data: t } = await e.get(Bn.taskStatuses); +async function ho(e) { + let { data: t } = await e.get(po.taskStatuses); return t.data; } -async function Un(e, t) { - let { data: n } = await e.get(Bn.tasks, { params: t }); +async function go(e, t) { + let { data: n } = await e.get(po.tasks, { params: t }); return n; } -async function Wn(e, t) { - let { data: n } = await e.post(Bn.tasks, t); +async function _o(e, t) { + let { data: n } = await e.post(po.tasks, t); return n.data; } -async function Gn(e, t, n) { - let { data: r } = await e.put(Bn.task(t), n); +async function vo(e, t, n) { + let { data: r } = await e.put(po.task(t), n); return r.data; } -async function Kn(e, t) { - await e.delete(Bn.task(t)); +async function yo(e, t) { + await e.delete(po.task(t)); } -async function qn(e, t, n) { - let { data: r } = await e.post(Bn.moveTask(t), n); +async function bo(e, t, n) { + let { data: r } = await e.post(po.moveTask(t), n); return r.data; } -async function Jn(e, t) { - let { data: n } = await e.get(k.project(t)); +async function xo(e, t) { + let { data: n } = await e.get(M.project(t)); return n.data; } -async function Yn(e, t) { - let { data: n } = await e.get(Bn.projectMembers(t)); +async function So(e, t) { + let { data: n } = await e.get(po.projectMembers(t)); return n.data; } -async function Xn(e, t, n) { - let { data: r } = await e.post(Bn.projectMembers(t), n); +async function Co(e, t, n) { + let { data: r } = await e.post(po.projectMembers(t), n); return r.data; } -async function Zn(e, t, n) { - await e.delete(Bn.projectMember(t, n)); +async function wo(e, t, n) { + await e.delete(po.projectMember(t, n)); } -async function Qn(e, t) { - let { data: n } = await e.get(Bn.timeEntries, { params: t }); +async function To(e, t) { + let { data: n } = await e.get(po.timeEntries, { params: t }); return n; } -async function $n(e, t) { - let { data: n } = await e.get(`${A.customers}/${t}`); +async function Eo(e, t) { + let { data: n } = await e.get(`${N.customers}/${t}`); return n.data; } //#endregion //#region resources/js/types/task.ts -var er = [ +var Do = [ "LOW", "NORMAL", "HIGH", "URGENT" -], tr = { class: "flex w-full items-center justify-between" }, nr = { +], Oo = { class: "flex w-full items-center justify-between" }, ko = { key: 0, class: "ml-2 text-sm font-normal text-muted" -}, rr = { class: "space-y-5 px-6 py-6" }, ir = { class: "flex items-center justify-between border-t border-line-default px-6 py-4" }, ar = { key: 1 }, or = { class: "flex space-x-3" }, sr = /* @__PURE__ */ c({ +}, Ao = { class: "space-y-5 px-6 py-6" }, jo = { class: "flex items-center justify-between border-t border-line-default px-6 py-4" }, Mo = { key: 1 }, No = { class: "flex space-x-3" }, Po = /* @__PURE__ */ l({ __name: "TaskDrawer", props: { show: { type: Boolean }, @@ -2095,55 +4108,55 @@ var er = [ "saved", "deleted" ], - setup(e, { emit: c }) { - let l = e, u = c, d = fe(), f = g({ + setup(e, { emit: t }) { + let l = e, u = t, d = V(), f = _({ name: "", description: "", estimateHours: "", rate: "", dueDate: "", billable: !0 - }), p = _(null), m = _(null), v = _(null), S = _(null), T = _(null), D = _({}), O = _(!1), k = _(!1), A = t({ - get: () => F(l.projects, m.value), + }), p = v(null), m = v(null), h = v(null), y = v(null), C = v(null), w = v({}), D = v(!1), O = v(!1), A = n({ + get: () => I(l.projects, m.value), set: (e) => { m.value = e?.id ?? null; } - }), j = t(() => l.task !== null), M = t(() => j.value ? d("tasks_projects.tasks.edit_task") : d("tasks_projects.tasks.new_task")), ee = t(() => l.statuses.map((e) => ({ + }), j = n(() => l.task !== null), M = n(() => j.value ? d("tasks_projects.tasks.edit_task") : d("tasks_projects.tasks.new_task")), N = n(() => l.statuses.map((e) => ({ id: e.id, label: e.name - }))), N = t(() => l.members.map((e) => ({ + }))), P = n(() => l.members.map((e) => ({ id: e.id, label: e.name - }))), P = t(() => er.map((e, t) => ({ + }))), F = n(() => Do.map((e, t) => ({ id: t, label: d(`tasks_projects.tasks.priority.${e.toLowerCase()}`) }))); - C(() => l.show, (e) => { - e && te(); + T(() => l.show, (e) => { + e && L(); }, { immediate: !0 }); - function F(e, t) { + function I(e, t) { return t === null ? null : e.find((e) => e.id === t) ?? null; } - function te() { + function L() { let e = l.task; f.name = e?.name ?? "", f.description = e?.description ?? "", f.estimateHours = oe(e?.estimated_minutes ?? null), f.rate = ie(e?.rate ?? null), f.dueDate = e?.due_date ?? "", f.billable = e?.billable ?? !0; let t = l.statuses.find((e) => e.is_default) ?? l.statuses[0], n = e?.task_status_id ?? l.defaults?.task_status_id ?? t?.id ?? null; - p.value = F(ee.value, n), m.value = e?.project_id ?? l.defaults?.project_id ?? null, v.value = F(N.value, e?.assignee_id ?? null), S.value = e?.priority ? P.value[er.indexOf(e.priority)] ?? null : null, T.value = e?.customer_id ?? null, D.value = {}; + p.value = I(N.value, n), m.value = e?.project_id ?? l.defaults?.project_id ?? null, h.value = I(P.value, e?.assignee_id ?? null), y.value = e?.priority ? F.value[Do.indexOf(e.priority)] ?? null : null, C.value = e?.customer_id ?? null, w.value = {}; } - function ne(e) { + function R(e) { f.dueDate = e ? le(e) : ""; } - function I() { + function z() { let e = p.value?.id ?? null; if (e === null) return null; - let t = S.value === null ? null : er[S.value.id]; + let t = y.value === null ? null : Do[y.value.id]; return { name: f.name.trim(), task_status_id: e, project_id: m.value, - customer_id: m.value === null ? T.value : null, + customer_id: m.value === null ? C.value : null, description: f.description.trim() || null, - assignee_id: v.value?.id ?? null, + assignee_id: h.value?.id ?? null, priority: t, due_date: f.dueDate || null, estimated_minutes: se(f.estimateHours), @@ -2151,78 +4164,78 @@ var er = [ rate: ae(f.rate) }; } - async function ce() { - if (O.value) return; + async function ee() { + if (D.value) return; if (f.name.trim() === "") { - D.value = { name: d("tasks_projects.tasks.name_required") }; + w.value = { name: d("tasks_projects.tasks.name_required") }; return; } - let e = I(); + let e = z(); if (e === null) { l.notify("error", d("tasks_projects.task_statuses.none")); return; } - O.value = !0, D.value = {}; + D.value = !0, w.value = {}; try { - let t = l.task, n = t ? await Gn(l.client, t.id, e) : await Wn(l.client, e); + let t = l.task, n = t ? await vo(l.client, t.id, e) : await _o(l.client, e); u("saved", n); } catch (e) { - D.value = re(e), l.notify("error", L(e, d("tasks_projects.tasks.save_failed"))); + w.value = re(e), l.notify("error", B(e, d("tasks_projects.tasks.save_failed"))); } finally { - O.value = !1; + D.value = !1; } } - async function R() { + async function te() { let e = l.task; - if (!(e === null || k.value) && window.confirm(d("tasks_projects.tasks.delete_confirm", { name: e.name }))) { - k.value = !0; + if (!(e === null || O.value) && window.confirm(d("tasks_projects.tasks.delete_confirm", { name: e.name }))) { + O.value = !0; try { - await Kn(l.client, e.id), u("deleted", e); + await yo(l.client, e.id), u("deleted", e); } catch (e) { - l.notify("error", L(e, d("tasks_projects.tasks.delete_failed"))); + l.notify("error", B(e, d("tasks_projects.tasks.delete_failed"))); } finally { - k.value = !1; + O.value = !1; } } } - return (t, c) => { - let l = y("BaseIcon"), m = y("BaseInput"), g = y("BaseInputGroup"), _ = y("BaseSelectInput"), C = y("BaseDatePicker"), F = y("BaseInputGrid"), te = y("BaseSwitch"), I = y("BaseTextarea"), L = y("BaseButton"), re = y("BaseModal"); - return h(), n(re, { + return (t, n) => { + let l = b("BaseIcon"), m = b("BaseInput"), _ = b("BaseInputGroup"), v = b("BaseSelectInput"), T = b("BaseDatePicker"), I = b("BaseInputGrid"), L = b("BaseSwitch"), z = b("BaseTextarea"), ne = b("BaseButton"), B = b("BaseModal"); + return g(), r(B, { show: e.show, - onClose: c[11] ||= (e) => u("close") + onClose: n[11] ||= (e) => u("close") }, { - header: w(() => [a("div", tr, [a("span", null, [o(b(M.value) + " ", 1), e.task ? (h(), i("span", nr, "#" + b(e.task.number), 1)) : r("", !0)]), s(l, { + header: E(() => [o("div", Oo, [o("span", null, [s(x(M.value) + " ", 1), e.task ? (g(), a("span", ko, "#" + x(e.task.number), 1)) : i("", !0)]), c(l, { name: "XMarkIcon", class: "h-6 w-6 cursor-pointer text-subtle hover:text-body", - onClick: c[0] ||= (e) => u("close") + onClick: n[0] ||= (e) => u("close") })])]), - default: w(() => [a("form", { onSubmit: E(ce, ["prevent"]) }, [a("div", rr, [ - s(g, { - label: x(d)("tasks_projects.tasks.fields.name"), - error: D.value.name, + default: E(() => [o("form", { onSubmit: k(ee, ["prevent"]) }, [o("div", Ao, [ + c(_, { + label: S(d)("tasks_projects.tasks.fields.name"), + error: w.value.name, required: "" }, { - default: w(() => [s(m, { + default: E(() => [c(m, { modelValue: f.name, - "onUpdate:modelValue": c[1] ||= (e) => f.name = e, - invalid: !!D.value.name, + "onUpdate:modelValue": n[1] ||= (e) => f.name = e, + invalid: !!w.value.name, type: "text" }, null, 8, ["modelValue", "invalid"])]), _: 1 }, 8, ["label", "error"]), - s(F, null, { - default: w(() => [ - e.lockProject ? r("", !0) : (h(), n(g, { + c(I, null, { + default: E(() => [ + e.lockProject ? i("", !0) : (g(), r(_, { key: 0, - label: x(d)("tasks_projects.tasks.fields.project"), - error: D.value.project_id, - "help-text": x(d)("tasks_projects.tasks.fields.project_help") + label: S(d)("tasks_projects.tasks.fields.project"), + error: w.value.project_id, + "help-text": S(d)("tasks_projects.tasks.fields.project_help") }, { - default: w(() => [s(_, { + default: E(() => [c(v, { modelValue: A.value, - "onUpdate:modelValue": c[2] ||= (e) => A.value = e, + "onUpdate:modelValue": n[2] ||= (e) => A.value = e, options: e.projects, - placeholder: x(d)("tasks_projects.tasks.fields.project_placeholder"), + placeholder: S(d)("tasks_projects.tasks.fields.project_placeholder"), "label-key": "label" }, null, 8, [ "modelValue", @@ -2235,39 +4248,39 @@ var er = [ "error", "help-text" ])), - T.value === null ? r("", !0) : (h(), n(g, { + C.value === null ? i("", !0) : (g(), r(_, { key: 1, - label: x(d)("tasks_projects.tasks.fields.customer"), - "help-text": x(d)("tasks_projects.tasks.fields.customer_help") + label: S(d)("tasks_projects.tasks.fields.customer"), + "help-text": S(d)("tasks_projects.tasks.fields.customer_help") }, { - default: w(() => [s(m, { - "model-value": `#${T.value}`, + default: E(() => [c(m, { + "model-value": `#${C.value}`, type: "text", disabled: "" }, null, 8, ["model-value"])]), _: 1 }, 8, ["label", "help-text"])), - s(g, { - label: x(d)("tasks_projects.tasks.fields.status"), - error: D.value.task_status_id + c(_, { + label: S(d)("tasks_projects.tasks.fields.status"), + error: w.value.task_status_id }, { - default: w(() => [s(_, { + default: E(() => [c(v, { modelValue: p.value, - "onUpdate:modelValue": c[3] ||= (e) => p.value = e, - options: ee.value, + "onUpdate:modelValue": n[3] ||= (e) => p.value = e, + options: N.value, "label-key": "label" }, null, 8, ["modelValue", "options"])]), _: 1 }, 8, ["label", "error"]), - s(g, { - label: x(d)("tasks_projects.tasks.fields.assignee"), - error: D.value.assignee_id + c(_, { + label: S(d)("tasks_projects.tasks.fields.assignee"), + error: w.value.assignee_id }, { - default: w(() => [s(_, { - modelValue: v.value, - "onUpdate:modelValue": c[4] ||= (e) => v.value = e, - options: N.value, - placeholder: x(d)("tasks_projects.tasks.fields.assignee_placeholder"), + default: E(() => [c(v, { + modelValue: h.value, + "onUpdate:modelValue": n[4] ||= (e) => h.value = e, + options: P.value, + placeholder: S(d)("tasks_projects.tasks.fields.assignee_placeholder"), "label-key": "label" }, null, 8, [ "modelValue", @@ -2276,15 +4289,15 @@ var er = [ ])]), _: 1 }, 8, ["label", "error"]), - s(g, { - label: x(d)("tasks_projects.tasks.fields.priority"), - error: D.value.priority + c(_, { + label: S(d)("tasks_projects.tasks.fields.priority"), + error: w.value.priority }, { - default: w(() => [s(_, { - modelValue: S.value, - "onUpdate:modelValue": c[5] ||= (e) => S.value = e, - options: P.value, - placeholder: x(d)("tasks_projects.tasks.fields.priority_placeholder"), + default: E(() => [c(v, { + modelValue: y.value, + "onUpdate:modelValue": n[5] ||= (e) => y.value = e, + options: F.value, + placeholder: S(d)("tasks_projects.tasks.fields.priority_placeholder"), "label-key": "label" }, null, 8, [ "modelValue", @@ -2293,39 +4306,39 @@ var er = [ ])]), _: 1 }, 8, ["label", "error"]), - s(g, { - label: x(d)("tasks_projects.tasks.fields.due_date"), - error: D.value.due_date + c(_, { + label: S(d)("tasks_projects.tasks.fields.due_date"), + error: w.value.due_date }, { - default: w(() => [s(C, { + default: E(() => [c(T, { "model-value": f.dueDate, - "onUpdate:modelValue": ne + "onUpdate:modelValue": R }, null, 8, ["model-value"])]), _: 1 }, 8, ["label", "error"]), - s(g, { - label: x(d)("tasks_projects.tasks.fields.estimate_hours"), - error: D.value.estimated_minutes + c(_, { + label: S(d)("tasks_projects.tasks.fields.estimate_hours"), + error: w.value.estimated_minutes }, { - default: w(() => [s(m, { + default: E(() => [c(m, { modelValue: f.estimateHours, - "onUpdate:modelValue": c[6] ||= (e) => f.estimateHours = e, - invalid: !!D.value.estimated_minutes, + "onUpdate:modelValue": n[6] ||= (e) => f.estimateHours = e, + invalid: !!w.value.estimated_minutes, type: "number", step: "0.25", min: "0" }, null, 8, ["modelValue", "invalid"])]), _: 1 }, 8, ["label", "error"]), - s(g, { - label: x(d)("tasks_projects.tasks.fields.rate"), - error: D.value.rate, - "help-text": x(d)("tasks_projects.tasks.fields.rate_help") + c(_, { + label: S(d)("tasks_projects.tasks.fields.rate"), + error: w.value.rate, + "help-text": S(d)("tasks_projects.tasks.fields.rate_help") }, { - default: w(() => [s(m, { + default: E(() => [c(m, { modelValue: f.rate, - "onUpdate:modelValue": c[7] ||= (e) => f.rate = e, - invalid: !!D.value.rate, + "onUpdate:modelValue": n[7] ||= (e) => f.rate = e, + invalid: !!w.value.rate, type: "number", step: "0.01", min: "0" @@ -2339,163 +4352,163 @@ var er = [ ]), _: 1 }), - s(g, { - label: x(d)("tasks_projects.tasks.fields.billable"), - error: D.value.billable + c(_, { + label: S(d)("tasks_projects.tasks.fields.billable"), + error: w.value.billable }, { - default: w(() => [s(te, { + default: E(() => [c(L, { modelValue: f.billable, - "onUpdate:modelValue": c[8] ||= (e) => f.billable = e, + "onUpdate:modelValue": n[8] ||= (e) => f.billable = e, class: "mt-1" }, null, 8, ["modelValue"])]), _: 1 }, 8, ["label", "error"]), - s(g, { - label: x(d)("tasks_projects.tasks.fields.description"), - error: D.value.description + c(_, { + label: S(d)("tasks_projects.tasks.fields.description"), + error: w.value.description }, { - default: w(() => [s(I, { + default: E(() => [c(z, { modelValue: f.description, - "onUpdate:modelValue": c[9] ||= (e) => f.description = e, + "onUpdate:modelValue": n[9] ||= (e) => f.description = e, row: 3, - invalid: !!D.value.description + invalid: !!w.value.description }, null, 8, ["modelValue", "invalid"])]), _: 1 }, 8, ["label", "error"]) - ]), a("div", ir, [j.value ? (h(), n(L, { + ]), o("div", jo, [j.value ? (g(), r(ne, { key: 0, type: "button", variant: "danger", - loading: k.value, - disabled: k.value, - onClick: R + loading: O.value, + disabled: O.value, + onClick: te }, { - default: w(() => [o(b(x(d)("tasks_projects.general.delete")), 1)]), + default: E(() => [s(x(S(d)("tasks_projects.general.delete")), 1)]), _: 1 - }, 8, ["loading", "disabled"])) : (h(), i("span", ar)), a("div", or, [s(L, { + }, 8, ["loading", "disabled"])) : (g(), a("span", Mo)), o("div", No, [c(ne, { type: "button", variant: "primary-outline", - onClick: c[10] ||= (e) => u("close") + onClick: n[10] ||= (e) => u("close") }, { - default: w(() => [o(b(x(d)("tasks_projects.general.cancel")), 1)]), + default: E(() => [s(x(S(d)("tasks_projects.general.cancel")), 1)]), _: 1 - }), s(L, { + }), c(ne, { type: "submit", variant: "primary", - loading: O.value, - disabled: O.value + loading: D.value, + disabled: D.value }, { - default: w(() => [o(b(j.value ? x(d)("tasks_projects.general.update") : x(d)("tasks_projects.general.save")), 1)]), + default: E(() => [s(x(j.value ? S(d)("tasks_projects.general.update") : S(d)("tasks_projects.general.save")), 1)]), _: 1 }, 8, ["loading", "disabled"])])])], 32)]), _: 1 }, 8, ["show"]); }; } -}), cr = { class: "flex items-center justify-end space-x-5" }, lr = { +}), Fo = { class: "flex items-center justify-end space-x-5" }, Io = { key: 0, class: "flex justify-center py-16" -}, ur = { +}, Lo = { key: 2, class: "flex items-start gap-4 overflow-x-auto pb-4" -}, dr = { class: "flex items-center justify-between border-b border-line-light px-3 py-2.5" }, fr = { class: "flex items-center" }, pr = { class: "text-sm font-semibold text-heading" }, mr = { class: "ml-2 text-xs text-muted" }, hr = [ +}, Ro = { class: "flex items-center justify-between border-b border-line-light px-3 py-2.5" }, zo = { class: "flex items-center" }, Bo = { class: "text-sm font-semibold text-heading" }, Vo = { class: "ml-2 text-xs text-muted" }, Ho = [ "aria-label", "title", "onClick" -], gr = ["data-status-id"], _r = ["data-task-id", "onClick"], vr = { class: "flex items-start justify-between gap-2" }, yr = { class: "text-sm font-medium text-heading" }, br = { class: "mt-2 flex flex-wrap items-center gap-2 text-xs text-muted" }, xr = { +], Uo = ["data-status-id"], Wo = ["data-task-id", "onClick"], Go = { class: "flex items-start justify-between gap-2" }, Ko = { class: "text-sm font-medium text-heading" }, qo = { class: "mt-2 flex flex-wrap items-center gap-2 text-xs text-muted" }, Jo = { key: 0, class: "rounded-sm bg-surface-tertiary px-1.5 py-0.5 text-[11px] text-body" -}, Sr = { +}, Yo = { key: 1, class: "inline-flex items-center text-body" -}, Cr = { class: "mt-3 flex items-center justify-between" }, wr = { +}, Xo = { class: "mt-3 flex items-center justify-between" }, Zo = { key: 1, class: "text-xs text-subtle" -}, Tr = ["title"], Er = { +}, Qo = ["title"], $o = { key: 0, class: "px-3 pt-2 text-xs text-subtle" -}, Dr = { class: "px-3 pt-2 pb-3" }, Or = ["onClick"], kr = /* @__PURE__ */ c({ +}, es = { class: "px-3 pt-2 pb-3" }, ts = ["onClick"], ns = /* @__PURE__ */ l({ __name: "BoardPage", props: { client: { type: [Function, Object] }, notify: { type: Function } }, - setup(c) { - let l = c, u = fe(), g = _([]), S = _([]), T = _([]), E = _(!0), D = _(null), O = _(null), k = _(!1), A = _(null), M = _({}), ee = { + setup(t) { + let l = t, u = V(), d = v([]), _ = v([]), C = v([]), w = v(!0), D = v(null), O = v(null), k = v(!1), A = v(null), j = v({}), M = { LOW: "bg-surface-tertiary text-muted", NORMAL: "bg-primary-50 text-primary-500", HIGH: "bg-alert-warning-bg text-alert-warning-text", URGENT: "bg-alert-error-bg text-alert-error-text" - }, N = /* @__PURE__ */ new Map(), P = /* @__PURE__ */ new Map(), F = !1, ne = t(() => T.value.map((e) => ({ + }, N = /* @__PURE__ */ new Map(), F = /* @__PURE__ */ new Map(), I = !1, L = n(() => C.value.map((e) => ({ id: e.id, label: e.name - }))), I = t(() => S.value.map((e) => ({ + }))), R = n(() => _.value.map((e) => ({ id: e.id, label: e.name - }))), re = t(() => g.value.map((e) => e.status)), ie = t(() => !E.value && g.value.length === 0); - C([D, O], () => { - oe(); + }))), z = n(() => d.value.map((e) => e.status)), te = n(() => !w.value && d.value.length === 0); + T([D, O], () => { + re(); + }), h(() => { + ne(), re(); }), m(() => { - ae(), oe(); - }), p(() => { for (let e of N.values()) e.destroy(); - N.clear(), P.clear(); + N.clear(), F.clear(); }); - async function ae() { + async function ne() { try { - let e = await j(l.client, { + let e = await P(l.client, { limit: 100, status: "ACTIVE" }); - T.value = e.data; + C.value = e.data; } catch (e) { - l.notify("error", L(e, u("tasks_projects.tasks.projects_failed"))); + l.notify("error", B(e, u("tasks_projects.tasks.projects_failed"))); } try { - S.value = await te(l.client); + _.value = await ee(l.client); } catch (e) { - l.notify("error", L(e, u("tasks_projects.tasks.members_failed"))); + l.notify("error", B(e, u("tasks_projects.tasks.members_failed"))); } } - async function oe() { + async function re() { let e = {}; - D.value && (e.project_id = D.value.id), O.value && (e.assignee_id = O.value.id), E.value = !0; + D.value && (e.project_id = D.value.id), O.value && (e.assignee_id = O.value.id), w.value = !0; try { - g.value = await Vn(l.client, e); + d.value = await mo(l.client, e); } catch (e) { - l.notify("error", L(e, u("tasks_projects.board.load_failed"))); + l.notify("error", B(e, u("tasks_projects.board.load_failed"))); } finally { - E.value = !1; + w.value = !1; } } - function se(e, t) { + function ie(e, t) { let n = t instanceof HTMLElement ? t : null; - P.get(e) !== n && (N.get(e)?.destroy(), N.delete(e), P.delete(e), n !== null && (P.set(e, n), N.set(e, Q.create(n, { + F.get(e) !== n && (N.get(e)?.destroy(), N.delete(e), F.delete(e), n !== null && (F.set(e, n), N.set(e, $.create(n, { group: "tasks", animation: 150, draggable: "[data-task-id]", ghostClass: "opacity-40", onStart: () => { - F = !0; + I = !0; }, onEnd: (e) => { - pe(e), setTimeout(() => { - F = !1; + se(e), setTimeout(() => { + I = !1; }); } })))); } - function le(e) { + function ae(e) { let t = e.item, n = e.oldIndex ?? 0; t.parentNode?.removeChild(t), e.from.insertBefore(t, e.from.children[n] ?? null); } - function R(e) { - return g.value.find((t) => t.status.id === e); + function oe(e) { + return d.value.find((t) => t.status.id === e); } - async function pe(e) { + async function se(e) { let t = Number(e.from.dataset.statusId), n = Number(e.to.dataset.statusId), r = e.oldIndex ?? 0, i = e.newIndex ?? 0; - if (le(e), Number.isNaN(t) || Number.isNaN(n) || t === n && r === i) return; - let a = R(t), o = R(n); + if (ae(e), Number.isNaN(t) || Number.isNaN(n) || t === n && r === i) return; + let a = oe(t), o = oe(n); if (!a || !o) return; let s = { from: [...a.tasks], @@ -2508,7 +4521,7 @@ var er = [ o.tasks.splice(i, 0, c); let d = o.tasks[i - 1] ?? null, f = o.tasks[i + 1] ?? null; try { - let e = await qn(l.client, c.id, { + let e = await bo(l.client, c.id, { task_status_id: n, before_id: d?.id ?? null, after_id: f?.id ?? null @@ -2518,75 +4531,75 @@ var er = [ status: o.status.name })); } catch (e) { - a.tasks = s.from, o.tasks = s.to, l.notify("error", L(e, u("tasks_projects.board.move_failed"))); + a.tasks = s.from, o.tasks = s.to, l.notify("error", B(e, u("tasks_projects.board.move_failed"))); } } - function me(e) { - A.value = null, M.value = { + function le(e) { + A.value = null, j.value = { task_status_id: e.id, project_id: D.value?.id ?? null }, k.value = !0; } - function he(e) { - F || (A.value = e, M.value = {}, k.value = !0); + function ue(e) { + I || (A.value = e, j.value = {}, k.value = !0); } - function ge(e) { + function pe(e) { let t = A.value ? u("tasks_projects.tasks.updated", { name: e.name }) : u("tasks_projects.tasks.created", { name: e.name }); - k.value = !1, A.value = null, l.notify("success", t), oe(); + k.value = !1, A.value = null, l.notify("success", t), re(); } - function _e(e) { - k.value = !1, A.value = null, l.notify("success", u("tasks_projects.tasks.deleted", { name: e.name })), oe(); + function me(e) { + k.value = !1, A.value = null, l.notify("success", u("tasks_projects.tasks.deleted", { name: e.name })), re(); } - function ve() { + function he() { D.value = null, O.value = null; } - function ye(e) { + function ge(e) { if (e.project_id === null) return null; - let t = T.value.find((t) => t.id === e.project_id); + let t = C.value.find((t) => t.id === e.project_id); return t?.identifier || t?.name || null; } - function be(e) { + function _e(e) { if (e.assignee_id === null) return null; - let t = S.value.find((t) => t.id === e.assignee_id); - return t ? ue(t.name) : `#${e.assignee_id}`; + let t = _.value.find((t) => t.id === e.assignee_id); + return t ? de(t.name) : `#${e.assignee_id}`; } - function xe(e) { - return e.assignee_id === null ? u("tasks_projects.tasks.unassigned") : S.value.find((t) => t.id === e.assignee_id)?.name ?? `#${e.assignee_id}`; + function ve(e) { + return e.assignee_id === null ? u("tasks_projects.tasks.unassigned") : _.value.find((t) => t.id === e.assignee_id)?.name ?? `#${e.assignee_id}`; } - function Se(e) { + function ye(e) { return u(`tasks_projects.tasks.priority.${e.toLowerCase()}`); } - function Ce(e) { - return ee[e]; - } - return (t, l) => { - let p = y("BaseBreadcrumbItem"), m = y("BaseBreadcrumb"), _ = y("BaseIcon"), C = y("BaseButton"), T = y("router-link"), j = y("BasePageHeader"), ee = y("BaseSelectInput"), N = y("BaseInputGroup"), P = y("BaseFilterWrapper"), F = y("BaseSpinner"), te = y("BaseEmptyPlaceholder"), L = y("BasePage"); - return h(), n(L, null, { - default: w(() => [ - s(j, { title: x(u)("tasks_projects.board.title") }, { - actions: w(() => [a("div", cr, [s(T, { to: "/admin/modules/tasks-projects" }, { - default: w(() => [s(C, { variant: "white" }, { - left: w((e) => [s(_, { + function be(e) { + return M[e]; + } + return (n, l) => { + let m = b("BaseBreadcrumbItem"), h = b("BaseBreadcrumb"), v = b("BaseIcon"), C = b("BaseButton"), T = b("router-link"), M = b("BasePageHeader"), N = b("BaseSelectInput"), P = b("BaseInputGroup"), F = b("BaseFilterWrapper"), I = b("BaseSpinner"), ee = b("BaseEmptyPlaceholder"), ne = b("BasePage"); + return g(), r(ne, null, { + default: E(() => [ + c(M, { title: S(u)("tasks_projects.board.title") }, { + actions: E(() => [o("div", Fo, [c(T, { to: "/admin/modules/tasks-projects" }, { + default: E(() => [c(C, { variant: "white" }, { + left: E((e) => [c(v, { name: "FolderIcon", - class: d(e.class) + class: f(e.class) }, null, 8, ["class"])]), - default: w(() => [o(" " + b(x(u)("tasks_projects.projects.title")), 1)]), + default: E(() => [s(" " + x(S(u)("tasks_projects.projects.title")), 1)]), _: 1 })]), _: 1 })])]), - default: w(() => [s(m, null, { - default: w(() => [ - s(p, { - title: x(u)("tasks_projects.general.home"), + default: E(() => [c(h, null, { + default: E(() => [ + c(m, { + title: S(u)("tasks_projects.general.home"), to: "/admin/dashboard" }, null, 8, ["title"]), - s(p, { - title: x(u)("tasks_projects.projects.title"), + c(m, { + title: S(u)("tasks_projects.projects.title"), to: "/admin/modules/tasks-projects" }, null, 8, ["title"]), - s(p, { - title: x(u)("tasks_projects.board.title"), + c(m, { + title: S(u)("tasks_projects.board.title"), to: "#", active: "" }, null, 8, ["title"]) @@ -2595,20 +4608,20 @@ var er = [ })]), _: 1 }, 8, ["title"]), - s(P, { + c(F, { show: !0, class: "mt-4", - onClear: ve + onClear: he }, { - default: w(() => [s(N, { - label: x(u)("tasks_projects.board.filters.project"), + default: E(() => [c(P, { + label: S(u)("tasks_projects.board.filters.project"), class: "mt-2 flex-1" }, { - default: w(() => [s(ee, { + default: E(() => [c(N, { modelValue: D.value, "onUpdate:modelValue": l[0] ||= (e) => D.value = e, - options: ne.value, - placeholder: x(u)("tasks_projects.board.filters.all_projects"), + options: L.value, + placeholder: S(u)("tasks_projects.board.filters.all_projects"), "label-key": "label" }, null, 8, [ "modelValue", @@ -2616,15 +4629,15 @@ var er = [ "placeholder" ])]), _: 1 - }, 8, ["label"]), s(N, { - label: x(u)("tasks_projects.board.filters.assignee"), + }, 8, ["label"]), c(P, { + label: S(u)("tasks_projects.board.filters.assignee"), class: "mt-2 flex-1" }, { - default: w(() => [s(ee, { + default: E(() => [c(N, { modelValue: O.value, "onUpdate:modelValue": l[1] ||= (e) => O.value = e, - options: I.value, - placeholder: x(u)("tasks_projects.board.filters.all_assignees"), + options: R.value, + placeholder: S(u)("tasks_projects.board.filters.all_assignees"), "label-key": "label" }, null, 8, [ "modelValue", @@ -2635,88 +4648,88 @@ var er = [ }, 8, ["label"])]), _: 1 }), - E.value && g.value.length === 0 ? (h(), i("div", lr, [s(F, { class: "h-8 w-8 text-primary-500" })])) : ie.value ? (h(), n(te, { + w.value && d.value.length === 0 ? (g(), a("div", Io, [c(I, { class: "h-8 w-8 text-primary-500" })])) : te.value ? (g(), r(ee, { key: 1, - title: x(u)("tasks_projects.task_statuses.none"), - description: x(u)("tasks_projects.tasks.empty_description") + title: S(u)("tasks_projects.task_statuses.none"), + description: S(u)("tasks_projects.tasks.empty_description") }, { - default: w(() => [s(_, { + default: E(() => [c(v, { name: "ViewColumnsIcon", class: "mt-5 mb-4 h-16 w-16 text-subtle" })]), _: 1 - }, 8, ["title", "description"])) : (h(), i("div", ur, [(h(!0), i(e, null, v(g.value, (t) => (h(), i("section", { + }, 8, ["title", "description"])) : (g(), a("div", Lo, [(g(!0), a(e, null, y(d.value, (t) => (g(), a("section", { key: t.status.id, class: "w-72 shrink-0 rounded-xl border border-line-default bg-surface-secondary" }, [ - a("header", dr, [a("div", fr, [ - a("span", { - class: d(["mr-2 inline-block h-2.5 w-2.5 shrink-0 rounded-full", t.status.colour ? "" : "bg-line-default"]), - style: f(t.status.colour ? { backgroundColor: t.status.colour } : void 0) + o("header", Ro, [o("div", zo, [ + o("span", { + class: f(["mr-2 inline-block h-2.5 w-2.5 shrink-0 rounded-full", t.status.colour ? "" : "bg-line-default"]), + style: p(t.status.colour ? { backgroundColor: t.status.colour } : void 0) }, null, 6), - a("h3", pr, b(t.status.name), 1), - a("span", mr, b(t.tasks.length), 1) - ]), a("button", { + o("h3", Bo, x(t.status.name), 1), + o("span", Vo, x(t.tasks.length), 1) + ]), o("button", { type: "button", class: "rounded-md p-1 text-subtle hover:bg-hover hover:text-body", - "aria-label": x(u)("tasks_projects.tasks.new_task"), - title: x(u)("tasks_projects.tasks.new_task"), - onClick: (e) => me(t.status) - }, [s(_, { + "aria-label": S(u)("tasks_projects.tasks.new_task"), + title: S(u)("tasks_projects.tasks.new_task"), + onClick: (e) => le(t.status) + }, [c(v, { name: "PlusIcon", class: "h-4 w-4" - })], 8, hr)]), - a("div", { + })], 8, Ho)]), + o("div", { ref_for: !0, - ref: (e) => se(t.status.id, e), + ref: (e) => ie(t.status.id, e), "data-status-id": t.status.id, class: "min-h-[80px] space-y-2 px-3 pt-3" - }, [(h(!0), i(e, null, v(t.tasks, (e) => (h(), i("article", { + }, [(g(!0), a(e, null, y(t.tasks, (e) => (g(), a("article", { key: e.id, "data-task-id": e.id, class: "cursor-pointer rounded-lg border border-line-default bg-surface p-3 shadow-sm hover:bg-hover", - onClick: (t) => he(e) + onClick: (t) => ue(e) }, [ - a("div", vr, [a("p", yr, b(e.name), 1), e.priority ? (h(), i("span", { + o("div", Go, [o("p", Ko, x(e.name), 1), e.priority ? (g(), a("span", { key: 0, - class: d(["shrink-0 rounded-full px-2 py-0.5 text-[11px] font-medium", Ce(e.priority)]) - }, b(Se(e.priority)), 3)) : r("", !0)]), - a("div", br, [ - a("span", null, "#" + b(e.number), 1), - ye(e) ? (h(), i("span", xr, b(ye(e)), 1)) : r("", !0), - e.billable ? (h(), i("span", Sr, [s(_, { + class: f(["shrink-0 rounded-full px-2 py-0.5 text-[11px] font-medium", be(e.priority)]) + }, x(ye(e.priority)), 3)) : i("", !0)]), + o("div", qo, [ + o("span", null, "#" + x(e.number), 1), + ge(e) ? (g(), a("span", Jo, x(ge(e)), 1)) : i("", !0), + e.billable ? (g(), a("span", Yo, [c(v, { name: "CurrencyDollarIcon", class: "mr-0.5 h-3.5 w-3.5" - }), o(" " + b(x(u)("tasks_projects.tasks.billable")), 1)])) : r("", !0) + }), s(" " + x(S(u)("tasks_projects.tasks.billable")), 1)])) : i("", !0) ]), - a("div", Cr, [e.due_date ? (h(), i("span", { + o("div", Xo, [e.due_date ? (g(), a("span", { key: 0, - class: d(["text-xs", x(de)(e.due_date) && !e.closed_at ? "font-medium text-status-red" : "text-muted"]) - }, b(x(ce)(e.due_date)), 3)) : (h(), i("span", wr, "-")), be(e) ? (h(), i("span", { + class: f(["text-xs", S(fe)(e.due_date) && !e.closed_at ? "font-medium text-status-red" : "text-muted"]) + }, x(S(ce)(e.due_date)), 3)) : (g(), a("span", Zo, "-")), _e(e) ? (g(), a("span", { key: 2, class: "flex h-6 w-6 items-center justify-center rounded-full bg-primary-50 text-[11px] font-semibold text-primary-500", - title: xe(e) - }, b(be(e)), 9, Tr)) : r("", !0)]) - ], 8, _r))), 128))], 8, gr), - t.tasks.length === 0 ? (h(), i("p", Er, b(x(u)("tasks_projects.board.empty_column")), 1)) : r("", !0), - a("div", Dr, [a("button", { + title: ve(e) + }, x(_e(e)), 9, Qo)) : i("", !0)]) + ], 8, Wo))), 128))], 8, Uo), + t.tasks.length === 0 ? (g(), a("p", $o, x(S(u)("tasks_projects.board.empty_column")), 1)) : i("", !0), + o("div", es, [o("button", { type: "button", class: "w-full rounded-md border border-dashed border-line-default py-1.5 text-xs text-muted hover:bg-hover hover:text-body", - onClick: (e) => me(t.status) - }, " + " + b(x(u)("tasks_projects.tasks.new_task")), 9, Or)]) + onClick: (e) => le(t.status) + }, " + " + x(S(u)("tasks_projects.tasks.new_task")), 9, ts)]) ]))), 128))])), - s(sr, { + c(Po, { show: k.value, - client: c.client, - notify: c.notify, + client: t.client, + notify: t.notify, task: A.value, - statuses: re.value, - members: S.value, - projects: ne.value, - defaults: M.value, + statuses: z.value, + members: _.value, + projects: L.value, + defaults: j.value, onClose: l[2] ||= (e) => k.value = !1, - onSaved: ge, - onDeleted: _e + onSaved: pe, + onDeleted: me }, null, 8, [ "show", "client", @@ -2732,23 +4745,23 @@ var er = [ }); }; } -}), Ar = { +}), rs = { key: 0, class: "mt-2 flex flex-wrap items-center gap-3 text-sm text-muted" -}, jr = { +}, is = { key: 0, class: "rounded-sm bg-surface-tertiary px-2 py-0.5 text-body" -}, Mr = { key: 1 }, Nr = { class: "text-body" }, Pr = { +}, as = { key: 1 }, os = { class: "text-body" }, ss = { key: 2, class: "text-subtle" -}, Fr = { key: 3 }, Ir = { class: "text-body" }, Lr = { class: "flex items-center justify-end space-x-5" }, Rr = { class: "mt-6 flex overflow-x-auto border-b border-line-default" }, zr = [ +}, cs = { key: 3 }, ls = { class: "text-body" }, us = { class: "flex items-center justify-end space-x-5" }, ds = { class: "mt-6 flex overflow-x-auto border-b border-line-default" }, fs = [ "href", "aria-current", "onClick" -], Br = { +], ps = { key: 0, class: "flex justify-center py-16" -}, Vr = "extension.page.tasks-projects.project", Hr = /* @__PURE__ */ c({ +}, ms = "extension.page.tasks-projects.project", hs = /* @__PURE__ */ l({ __name: "ProjectDetailPage", props: { id: {}, @@ -2756,187 +4769,187 @@ var er = [ notify: { type: Function }, router: {} }, - setup(c) { - let l = c, u = fe(), f = _(null), p = _(null), g = _(!0), S = _(!1), T = _(!1), E = t(() => Number(l.id)), D = t(() => [ + setup(t) { + let l = t, u = V(), d = v(null), p = v(null), m = v(!0), _ = v(!1), C = v(!1), w = n(() => Number(l.id)), D = n(() => [ { id: "overview", label: u("tasks_projects.project.tabs.overview"), - name: `${Vr}.overview` + name: `${ms}.overview` }, { id: "tasks", label: u("tasks_projects.project.tabs.tasks"), - name: `${Vr}.tasks` + name: `${ms}.tasks` }, { id: "time", label: u("tasks_projects.project.tabs.time"), - name: `${Vr}.time` + name: `${ms}.time` }, { id: "members", label: u("tasks_projects.project.tabs.members"), - name: `${Vr}.members` + name: `${ms}.members` } - ]), O = t(() => String(l.router.currentRoute.value.name ?? "")), k = t(() => f.value?.name ?? u("tasks_projects.projects.title")); - C(E, () => { + ]), O = n(() => String(l.router.currentRoute.value.name ?? "")), k = n(() => d.value?.name ?? u("tasks_projects.projects.title")); + T(w, () => { j(); - }), C(O, (e) => A(e)), m(() => { + }), T(O, (e) => A(e)), h(() => { A(O.value), j(); }); function A(e) { - e === Vr && l.router.replace({ - name: `${Vr}.overview`, + e === ms && l.router.replace({ + name: `${ms}.overview`, params: { id: l.id } }); } async function j() { - g.value = !0; + m.value = !0; try { - f.value = await Jn(l.client, E.value), await M(); + d.value = await xo(l.client, w.value), await M(); } catch (e) { - l.notify("error", L(e, u("tasks_projects.project.load_failed"))); + l.notify("error", B(e, u("tasks_projects.project.load_failed"))); } finally { - g.value = !1; + m.value = !1; } } async function M() { - let e = f.value?.customer_id ?? null; + let e = d.value?.customer_id ?? null; if (e === null) { p.value = null; return; } try { - let t = await $n(l.client, e); + let t = await Eo(l.client, e); p.value = t.display_name || t.name || `#${e}`; } catch { p.value = `#${e}`; } } - function ee(e) { + function N(e) { return { name: e.name, params: { id: l.id } }; } - function F(e) { + function P(e) { return O.value === e.name; } - function te(e) { - T.value = !1, l.notify("success", u("tasks_projects.projects.updated", { name: e.name })), j(); + function F(e) { + C.value = !1, l.notify("success", u("tasks_projects.projects.updated", { name: e.name })), j(); } - async function ne() { - let e = f.value; - if (!(e === null || S.value)) { - S.value = !0; + async function I() { + let e = d.value; + if (!(e === null || _.value)) { + _.value = !0; try { - e.status === "ARCHIVED" ? (await P(l.client, e.id), l.notify("success", u("tasks_projects.projects.unarchived", { name: e.name }))) : (await N(l.client, e.id), l.notify("success", u("tasks_projects.projects.archived", { name: e.name }))), await j(); + e.status === "ARCHIVED" ? (await R(l.client, e.id), l.notify("success", u("tasks_projects.projects.unarchived", { name: e.name }))) : (await L(l.client, e.id), l.notify("success", u("tasks_projects.projects.archived", { name: e.name }))), await j(); } catch (e) { - l.notify("error", L(e, u("tasks_projects.projects.save_failed"))); + l.notify("error", B(e, u("tasks_projects.projects.save_failed"))); } finally { - S.value = !1; + _.value = !1; } } } - function I(e) { + function z(e) { return e === "ACTIVE" ? "bg-primary-50! text-primary-500!" : "bg-surface-tertiary! text-muted!"; } - function re(e) { + function ee(e) { return u(e === "ACTIVE" ? "tasks_projects.projects.status.active" : "tasks_projects.projects.status.archived"); } - return (t, l) => { - let m = y("BaseBreadcrumbItem"), _ = y("BaseBreadcrumb"), C = y("BaseBadge"), E = y("BaseIcon"), O = y("BaseButton"), A = y("router-link"), M = y("BasePageHeader"), N = y("BaseSpinner"), P = y("router-view"), L = y("BasePage"); - return h(), n(L, null, { - default: w(() => [ - s(M, { title: k.value }, { - actions: w(() => [a("div", Lr, [ - s(A, { to: "/admin/modules/tasks-projects/board" }, { - default: w(() => [s(O, { variant: "white" }, { - left: w((e) => [s(E, { + return (n, l) => { + let h = b("BaseBreadcrumbItem"), v = b("BaseBreadcrumb"), w = b("BaseBadge"), T = b("BaseIcon"), O = b("BaseButton"), A = b("router-link"), M = b("BasePageHeader"), L = b("BaseSpinner"), R = b("router-view"), te = b("BasePage"); + return g(), r(te, null, { + default: E(() => [ + c(M, { title: k.value }, { + actions: E(() => [o("div", us, [ + c(A, { to: "/admin/modules/tasks-projects/board" }, { + default: E(() => [c(O, { variant: "white" }, { + left: E((e) => [c(T, { name: "ViewColumnsIcon", - class: d(e.class) + class: f(e.class) }, null, 8, ["class"])]), - default: w(() => [o(" " + b(x(u)("tasks_projects.project.board")), 1)]), + default: E(() => [s(" " + x(S(u)("tasks_projects.project.board")), 1)]), _: 1 })]), _: 1 }), - f.value ? (h(), n(O, { + d.value ? (g(), r(O, { key: 0, variant: "primary-outline", - loading: S.value, - disabled: S.value, - onClick: ne + loading: _.value, + disabled: _.value, + onClick: I }, { - default: w(() => [o(b(f.value.status === "ARCHIVED" ? x(u)("tasks_projects.projects.unarchive") : x(u)("tasks_projects.projects.archive")), 1)]), + default: E(() => [s(x(d.value.status === "ARCHIVED" ? S(u)("tasks_projects.projects.unarchive") : S(u)("tasks_projects.projects.archive")), 1)]), _: 1 - }, 8, ["loading", "disabled"])) : r("", !0), - f.value ? (h(), n(O, { + }, 8, ["loading", "disabled"])) : i("", !0), + d.value ? (g(), r(O, { key: 1, variant: "primary", - onClick: l[0] ||= (e) => T.value = !0 + onClick: l[0] ||= (e) => C.value = !0 }, { - left: w((e) => [s(E, { + left: E((e) => [c(T, { name: "PencilIcon", - class: d(e.class) + class: f(e.class) }, null, 8, ["class"])]), - default: w(() => [o(" " + b(x(u)("tasks_projects.general.edit")), 1)]), + default: E(() => [s(" " + x(S(u)("tasks_projects.general.edit")), 1)]), _: 1 - })) : r("", !0) + })) : i("", !0) ])]), - default: w(() => [s(_, null, { - default: w(() => [ - s(m, { - title: x(u)("tasks_projects.general.home"), + default: E(() => [c(v, null, { + default: E(() => [ + c(h, { + title: S(u)("tasks_projects.general.home"), to: "/admin/dashboard" }, null, 8, ["title"]), - s(m, { - title: x(u)("tasks_projects.projects.title"), + c(h, { + title: S(u)("tasks_projects.projects.title"), to: "/admin/modules/tasks-projects" }, null, 8, ["title"]), - s(m, { + c(h, { title: k.value, to: "#", active: "" }, null, 8, ["title"]) ]), _: 1 - }), f.value ? (h(), i("div", Ar, [ - s(C, { class: d(["rounded-full", I(f.value.status)]) }, { - default: w(() => [o(b(re(f.value.status)), 1)]), + }), d.value ? (g(), a("div", rs, [ + c(w, { class: f(["rounded-full", z(d.value.status)]) }, { + default: E(() => [s(x(ee(d.value.status)), 1)]), _: 1 }, 8, ["class"]), - f.value.identifier ? (h(), i("span", jr, b(f.value.identifier), 1)) : r("", !0), - f.value.customer_id ? (h(), i("span", Mr, [o(b(x(u)("tasks_projects.project.customer")) + ": ", 1), a("span", Nr, b(p.value ?? `#${f.value.customer_id}`), 1)])) : (h(), i("span", Pr, b(x(u)("tasks_projects.projects.internal")), 1)), - f.value.due_date ? (h(), i("span", Fr, [o(b(x(u)("tasks_projects.project.due_date")) + ": ", 1), a("span", Ir, b(x(ce)(f.value.due_date)), 1)])) : r("", !0) - ])) : r("", !0)]), + d.value.identifier ? (g(), a("span", is, x(d.value.identifier), 1)) : i("", !0), + d.value.customer_id ? (g(), a("span", as, [s(x(S(u)("tasks_projects.project.customer")) + ": ", 1), o("span", os, x(p.value ?? `#${d.value.customer_id}`), 1)])) : (g(), a("span", ss, x(S(u)("tasks_projects.projects.internal")), 1)), + d.value.due_date ? (g(), a("span", cs, [s(x(S(u)("tasks_projects.project.due_date")) + ": ", 1), o("span", ls, x(S(ce)(d.value.due_date)), 1)])) : i("", !0) + ])) : i("", !0)]), _: 1 }, 8, ["title"]), - a("nav", Rr, [(h(!0), i(e, null, v(D.value, (e) => (h(), n(A, { + o("nav", ds, [(g(!0), a(e, null, y(D.value, (e) => (g(), r(A, { key: e.id, - to: ee(e), + to: N(e), custom: "" }, { - default: w(({ href: t, navigate: n }) => [a("a", { + default: E(({ href: t, navigate: n }) => [o("a", { href: t, - "aria-current": F(e) ? "page" : void 0, - class: d(["relative -mb-px flex items-center border-b-2 px-5 py-2.5 text-sm leading-5 font-medium whitespace-nowrap transition-colors focus:outline-hidden", F(e) ? "border-primary-400 text-heading" : "border-transparent text-muted hover:border-line-strong hover:text-body"]), + "aria-current": P(e) ? "page" : void 0, + class: f(["relative -mb-px flex items-center border-b-2 px-5 py-2.5 text-sm leading-5 font-medium whitespace-nowrap transition-colors focus:outline-hidden", P(e) ? "border-primary-400 text-heading" : "border-transparent text-muted hover:border-line-strong hover:text-body"]), onClick: n - }, b(e.label), 11, zr)]), + }, x(e.label), 11, fs)]), _: 2 }, 1032, ["to"]))), 128))]), - g.value && f.value === null ? (h(), i("div", Br, [s(N, { class: "h-8 w-8 text-primary-500" })])) : (h(), n(P, { + m.value && d.value === null ? (g(), a("div", ps, [c(L, { class: "h-8 w-8 text-primary-500" })])) : (g(), r(R, { key: 1, - project: f.value, + project: d.value, onRefresh: j }, null, 8, ["project"])), - s(ve, { - show: T.value, - client: c.client, - notify: c.notify, - project: f.value, - onClose: l[1] ||= (e) => T.value = !1, - onSaved: te + c(ve, { + show: C.value, + client: t.client, + notify: t.notify, + project: d.value, + onClose: l[1] ||= (e) => C.value = !1, + onSaved: F }, null, 8, [ "show", "client", @@ -2948,22 +4961,22 @@ var er = [ }); }; } -}), Ur = { class: "py-4" }, Wr = { class: "rounded-xl border border-line-default bg-surface p-5" }, Gr = { class: "flex flex-col gap-4 lg:flex-row lg:items-end" }, Kr = { +}), gs = { class: "py-4" }, _s = { class: "rounded-xl border border-line-default bg-surface p-5" }, vs = { class: "flex flex-col gap-4 lg:flex-row lg:items-end" }, ys = { key: 0, class: "mt-3 text-xs text-subtle" -}, qr = { class: "mt-4 overflow-hidden rounded-xl border border-line-default bg-surface" }, Jr = { +}, bs = { class: "mt-4 overflow-hidden rounded-xl border border-line-default bg-surface" }, xs = { key: 0, class: "flex justify-center py-10" -}, Yr = { +}, Ss = { key: 1, class: "px-5 py-8 text-center text-sm text-muted" -}, Xr = { +}, Cs = { key: 2, class: "divide-y divide-line-light" -}, Zr = { class: "text-sm font-medium text-heading" }, Qr = { class: "text-xs text-muted" }, $r = { +}, ws = { class: "text-sm font-medium text-heading" }, Ts = { class: "text-xs text-muted" }, Es = { key: 1, class: "text-subtle" -}, ei = /* @__PURE__ */ c({ +}, Ds = /* @__PURE__ */ l({ __name: "ProjectMembersTab", props: { id: {}, @@ -2971,74 +4984,74 @@ var er = [ notify: { type: Function }, project: {} }, - setup(c) { - let l = c, u = fe(), f = _([]), p = _([]), g = _(!0), S = _(!1), C = _(null), T = _(null), E = _(""), D = _({}), O = t(() => l.project?.id ?? Number(l.id)), k = t(() => p.value.filter((e) => !f.value.some((t) => t.user_id === e.id)).map((e) => ({ + setup(t) { + let l = t, u = V(), d = v([]), p = v([]), m = v(!0), _ = v(!1), C = v(null), w = v(null), T = v(""), D = v({}), O = n(() => l.project?.id ?? Number(l.id)), k = n(() => p.value.filter((e) => !d.value.some((t) => t.user_id === e.id)).map((e) => ({ id: e.id, label: e.name }))); - m(() => { + h(() => { A(); }); async function A() { - g.value = !0; + m.value = !0; try { - p.value = await te(l.client); + p.value = await ee(l.client); } catch (e) { - l.notify("error", L(e, u("tasks_projects.tasks.members_failed"))); + l.notify("error", B(e, u("tasks_projects.tasks.members_failed"))); } try { - f.value = await Yn(l.client, O.value); + d.value = await So(l.client, O.value); } catch (e) { - l.notify("error", L(e, u("tasks_projects.project.members.load_failed"))); + l.notify("error", B(e, u("tasks_projects.project.members.load_failed"))); } finally { - g.value = !1; + m.value = !1; } } function j(e) { return p.value.find((t) => t.id === e)?.name ?? u("tasks_projects.project.time.removed_member"); } async function M() { - let e = T.value; - if (!(e === null || S.value)) { - S.value = !0, D.value = {}; + let e = w.value; + if (!(e === null || _.value)) { + _.value = !0, D.value = {}; try { - await Xn(l.client, O.value, { + await Co(l.client, O.value, { user_id: e.id, - rate: ae(E.value) - }), l.notify("success", u("tasks_projects.project.members.attached", { name: e.label })), T.value = null, E.value = "", await A(); + rate: ae(T.value) + }), l.notify("success", u("tasks_projects.project.members.attached", { name: e.label })), w.value = null, T.value = "", await A(); } catch (e) { - D.value = re(e), l.notify("error", L(e, u("tasks_projects.project.members.attach_failed"))); + D.value = re(e), l.notify("error", B(e, u("tasks_projects.project.members.attach_failed"))); } finally { - S.value = !1; + _.value = !1; } } } - async function ee(e) { + async function N(e) { let t = j(e.user_id); if (window.confirm(u("tasks_projects.project.members.detach_confirm", { name: t }))) { C.value = e.user_id; try { - await Zn(l.client, O.value, e.user_id), l.notify("success", u("tasks_projects.project.members.detached", { name: t })), await A(); + await wo(l.client, O.value, e.user_id), l.notify("success", u("tasks_projects.project.members.detached", { name: t })), await A(); } catch (e) { - l.notify("error", L(e, u("tasks_projects.project.members.detach_failed"))); + l.notify("error", B(e, u("tasks_projects.project.members.detach_failed"))); } finally { C.value = null; } } } - return (t, c) => { - let l = y("BaseSelectInput"), p = y("BaseInputGroup"), m = y("BaseInput"), _ = y("BaseIcon"), O = y("BaseButton"), A = y("BaseSpinner"), N = y("BaseFormatMoney"); - return h(), i("div", Ur, [a("div", Wr, [a("div", Gr, [ - s(p, { - label: x(u)("tasks_projects.project.members.member"), + return (t, n) => { + let l = b("BaseSelectInput"), p = b("BaseInputGroup"), h = b("BaseInput"), v = b("BaseIcon"), O = b("BaseButton"), A = b("BaseSpinner"), P = b("BaseFormatMoney"); + return g(), a("div", gs, [o("div", _s, [o("div", vs, [ + c(p, { + label: S(u)("tasks_projects.project.members.member"), error: D.value.user_id, class: "flex-1" }, { - default: w(() => [s(l, { - modelValue: T.value, - "onUpdate:modelValue": c[0] ||= (e) => T.value = e, + default: E(() => [c(l, { + modelValue: w.value, + "onUpdate:modelValue": n[0] ||= (e) => w.value = e, options: k.value, - placeholder: x(u)("tasks_projects.project.members.attach_placeholder"), + placeholder: S(u)("tasks_projects.project.members.attach_placeholder"), "label-key": "label" }, null, 8, [ "modelValue", @@ -3047,15 +5060,15 @@ var er = [ ])]), _: 1 }, 8, ["label", "error"]), - s(p, { - label: x(u)("tasks_projects.project.members.rate"), + c(p, { + label: S(u)("tasks_projects.project.members.rate"), error: D.value.rate, - "help-text": x(u)("tasks_projects.project.members.rate_help"), + "help-text": S(u)("tasks_projects.project.members.rate_help"), class: "flex-1" }, { - default: w(() => [s(m, { - modelValue: E.value, - "onUpdate:modelValue": c[1] ||= (e) => E.value = e, + default: E(() => [c(h, { + modelValue: T.value, + "onUpdate:modelValue": n[1] ||= (e) => T.value = e, type: "number", step: "0.01", min: "0" @@ -3066,34 +5079,34 @@ var er = [ "error", "help-text" ]), - s(O, { + c(O, { variant: "primary", class: "lg:mb-1", - loading: S.value, - disabled: S.value || T.value === null, + loading: _.value, + disabled: _.value || w.value === null, onClick: M }, { - left: w((e) => [s(_, { + left: E((e) => [c(v, { name: "PlusIcon", - class: d(e.class) + class: f(e.class) }, null, 8, ["class"])]), - default: w(() => [o(" " + b(x(u)("tasks_projects.project.members.attach")), 1)]), + default: E(() => [s(" " + x(S(u)("tasks_projects.project.members.attach")), 1)]), _: 1 }, 8, ["loading", "disabled"]) - ]), k.value.length === 0 && !g.value ? (h(), i("p", Kr, b(x(u)("tasks_projects.project.members.all_attached")), 1)) : r("", !0)]), a("div", qr, [g.value ? (h(), i("div", Jr, [s(A, { class: "h-6 w-6 text-primary-500" })])) : f.value.length === 0 ? (h(), i("p", Yr, b(x(u)("tasks_projects.project.members.empty")), 1)) : (h(), i("ul", Xr, [(h(!0), i(e, null, v(f.value, (e) => (h(), i("li", { + ]), k.value.length === 0 && !m.value ? (g(), a("p", ys, x(S(u)("tasks_projects.project.members.all_attached")), 1)) : i("", !0)]), o("div", bs, [m.value ? (g(), a("div", xs, [c(A, { class: "h-6 w-6 text-primary-500" })])) : d.value.length === 0 ? (g(), a("p", Ss, x(S(u)("tasks_projects.project.members.empty")), 1)) : (g(), a("ul", Cs, [(g(!0), a(e, null, y(d.value, (e) => (g(), a("li", { key: e.id, class: "flex items-center justify-between px-5 py-4" - }, [a("div", null, [a("p", Zr, b(j(e.user_id)), 1), a("p", Qr, [o(b(x(u)("tasks_projects.project.members.rate")) + ": ", 1), e.rate === null ? (h(), i("span", $r, b(x(u)("tasks_projects.tasks.none")), 1)) : (h(), n(N, { + }, [o("div", null, [o("p", ws, x(j(e.user_id)), 1), o("p", Ts, [s(x(S(u)("tasks_projects.project.members.rate")) + ": ", 1), e.rate === null ? (g(), a("span", Es, x(S(u)("tasks_projects.tasks.none")), 1)) : (g(), r(P, { key: 0, amount: e.rate - }, null, 8, ["amount"]))])]), s(O, { + }, null, 8, ["amount"]))])]), c(O, { variant: "danger", size: "sm", loading: C.value === e.user_id, disabled: C.value === e.user_id, - onClick: (t) => ee(e) + onClick: (t) => N(e) }, { - default: w(() => [o(b(x(u)("tasks_projects.general.delete")), 1)]), + default: E(() => [s(x(S(u)("tasks_projects.general.delete")), 1)]), _: 1 }, 8, [ "loading", @@ -3102,74 +5115,74 @@ var er = [ ])]))), 128))]))])]); }; } -}), ti = { +}), Os = { key: 0, class: "py-6" -}, ni = { class: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4" }, ri = { class: "rounded-xl border border-line-default bg-surface p-5" }, ii = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, ai = { class: "mt-2 text-2xl font-semibold text-heading" }, oi = { class: "mt-1 text-xs text-muted" }, si = { class: "rounded-xl border border-line-default bg-surface p-5" }, ci = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, li = { class: "mt-2 text-2xl font-semibold text-heading" }, ui = { class: "mt-1 text-xs text-muted" }, di = { class: "rounded-xl border border-line-default bg-surface p-5" }, fi = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, pi = { class: "mt-2 text-2xl font-semibold text-heading" }, mi = { class: "rounded-xl border border-line-default bg-surface p-5" }, hi = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, gi = { class: "mt-2 text-2xl font-semibold text-heading" }, _i = { class: "mt-4 rounded-xl border border-line-default bg-surface p-5" }, vi = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, yi = { class: "mt-2 text-sm text-body" }, bi = { class: "mt-3 h-2 w-full overflow-hidden rounded-full bg-surface-tertiary" }, xi = { +}, ks = { class: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4" }, As = { class: "rounded-xl border border-line-default bg-surface p-5" }, js = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, Ms = { class: "mt-2 text-2xl font-semibold text-heading" }, Ns = { class: "mt-1 text-xs text-muted" }, Ps = { class: "rounded-xl border border-line-default bg-surface p-5" }, Fs = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, Is = { class: "mt-2 text-2xl font-semibold text-heading" }, Ls = { class: "mt-1 text-xs text-muted" }, Rs = { class: "rounded-xl border border-line-default bg-surface p-5" }, zs = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, Bs = { class: "mt-2 text-2xl font-semibold text-heading" }, Vs = { class: "rounded-xl border border-line-default bg-surface p-5" }, Hs = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, Us = { class: "mt-2 text-2xl font-semibold text-heading" }, Ws = { class: "mt-4 rounded-xl border border-line-default bg-surface p-5" }, Gs = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, Ks = { class: "mt-2 text-sm text-body" }, qs = { class: "mt-3 h-2 w-full overflow-hidden rounded-full bg-surface-tertiary" }, Js = { key: 0, class: "mt-2 text-xs font-medium text-status-red" -}, Si = { +}, Ys = { key: 1, class: "mt-2 text-sm text-subtle" -}, Ci = { class: "mt-4 rounded-xl border border-line-default bg-surface p-5" }, wi = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, Ti = { +}, Xs = { class: "mt-4 rounded-xl border border-line-default bg-surface p-5" }, Zs = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, Qs = { key: 0, class: "mt-2 text-sm whitespace-pre-line text-body" -}, Ei = { +}, $s = { key: 1, class: "mt-2 text-sm text-subtle" -}, Di = { +}, ec = { key: 1, class: "flex justify-center py-16" -}, Oi = /* @__PURE__ */ c({ +}, tc = /* @__PURE__ */ l({ __name: "ProjectOverviewTab", props: { project: {} }, - setup(n) { - let o = n, c = fe(), l = t(() => o.project?.totals ?? null), u = t(() => o.project?.budget_minutes ?? null), p = t(() => { + setup(t) { + let r = t, s = V(), l = n(() => r.project?.totals ?? null), u = n(() => r.project?.budget_minutes ?? null), d = n(() => { let e = u.value, t = l.value?.logged_minutes ?? 0; return e ? Math.min(100, Math.round(t / e * 100)) : 0; - }), m = t(() => { + }), m = n(() => { let e = u.value, t = l.value?.logged_minutes ?? 0; return e && t > e ? t - e : 0; }); - return (t, o) => { - let g = y("BaseFormatMoney"), _ = y("BaseSpinner"); - return n.project && l.value ? (h(), i("div", ti, [ - a("div", ni, [ - a("div", ri, [ - a("p", ii, b(x(c)("tasks_projects.project.overview.tasks")), 1), - a("p", ai, b(l.value.tasks.total), 1), - a("p", oi, b(x(c)("tasks_projects.project.overview.open_tasks", { count: l.value.tasks.open })) + " · " + b(x(c)("tasks_projects.project.overview.closed_tasks", { count: l.value.tasks.closed })), 1) + return (n, r) => { + let h = b("BaseFormatMoney"), _ = b("BaseSpinner"); + return t.project && l.value ? (g(), a("div", Os, [ + o("div", ks, [ + o("div", As, [ + o("p", js, x(S(s)("tasks_projects.project.overview.tasks")), 1), + o("p", Ms, x(l.value.tasks.total), 1), + o("p", Ns, x(S(s)("tasks_projects.project.overview.open_tasks", { count: l.value.tasks.open })) + " · " + x(S(s)("tasks_projects.project.overview.closed_tasks", { count: l.value.tasks.closed })), 1) ]), - a("div", si, [ - a("p", ci, b(x(c)("tasks_projects.project.overview.logged")), 1), - a("p", li, b(x(R)(l.value.logged_minutes)), 1), - a("p", ui, b(x(c)("tasks_projects.project.overview.billable")) + ": " + b(x(R)(l.value.billable_minutes)), 1) + o("div", Ps, [ + o("p", Fs, x(S(s)("tasks_projects.project.overview.logged")), 1), + o("p", Is, x(S(ue)(l.value.logged_minutes)), 1), + o("p", Ls, x(S(s)("tasks_projects.project.overview.billable")) + ": " + x(S(ue)(l.value.billable_minutes)), 1) ]), - a("div", di, [a("p", fi, b(x(c)("tasks_projects.project.overview.billable_amount")), 1), a("p", pi, [s(g, { amount: l.value.billable_amount }, null, 8, ["amount"])])]), - a("div", mi, [a("p", hi, b(x(c)("tasks_projects.project.overview.unbilled_amount")), 1), a("p", gi, [s(g, { amount: l.value.unbilled_amount }, null, 8, ["amount"])])]) + o("div", Rs, [o("p", zs, x(S(s)("tasks_projects.project.overview.billable_amount")), 1), o("p", Bs, [c(h, { amount: l.value.billable_amount }, null, 8, ["amount"])])]), + o("div", Vs, [o("p", Hs, x(S(s)("tasks_projects.project.overview.unbilled_amount")), 1), o("p", Us, [c(h, { amount: l.value.unbilled_amount }, null, 8, ["amount"])])]) ]), - a("div", _i, [a("p", vi, b(x(c)("tasks_projects.project.overview.budget")), 1), u.value ? (h(), i(e, { key: 0 }, [ - a("p", yi, b(x(c)("tasks_projects.project.overview.budget_used", { - used: x(R)(l.value.logged_minutes), - total: x(R)(u.value) + o("div", Ws, [o("p", Gs, x(S(s)("tasks_projects.project.overview.budget")), 1), u.value ? (g(), a(e, { key: 0 }, [ + o("p", Ks, x(S(s)("tasks_projects.project.overview.budget_used", { + used: S(ue)(l.value.logged_minutes), + total: S(ue)(u.value) })), 1), - a("div", bi, [a("div", { - class: d(["h-2 rounded-full", m.value > 0 ? "bg-status-red" : "bg-primary-500"]), - style: f({ width: `${p.value}%` }) + o("div", qs, [o("div", { + class: f(["h-2 rounded-full", m.value > 0 ? "bg-status-red" : "bg-primary-500"]), + style: p({ width: `${d.value}%` }) }, null, 6)]), - m.value > 0 ? (h(), i("p", xi, b(x(c)("tasks_projects.project.overview.budget_over", { amount: x(R)(m.value) })), 1)) : r("", !0) - ], 64)) : (h(), i("p", Si, b(x(c)("tasks_projects.project.overview.no_budget")), 1))]), - a("div", Ci, [a("p", wi, b(x(c)("tasks_projects.project.overview.description")), 1), n.project.description ? (h(), i("p", Ti, b(n.project.description), 1)) : (h(), i("p", Ei, b(x(c)("tasks_projects.project.overview.no_description")), 1))]) - ])) : (h(), i("div", Di, [s(_, { class: "h-8 w-8 text-primary-500" })])); + m.value > 0 ? (g(), a("p", Js, x(S(s)("tasks_projects.project.overview.budget_over", { amount: S(ue)(m.value) })), 1)) : i("", !0) + ], 64)) : (g(), a("p", Ys, x(S(s)("tasks_projects.project.overview.no_budget")), 1))]), + o("div", Xs, [o("p", Zs, x(S(s)("tasks_projects.project.overview.description")), 1), t.project.description ? (g(), a("p", Qs, x(t.project.description), 1)) : (g(), a("p", $s, x(S(s)("tasks_projects.project.overview.no_description")), 1))]) + ])) : (g(), a("div", ec, [c(_, { class: "h-8 w-8 text-primary-500" })])); }; } -}), ki = { class: "relative table-container" }, Ai = ["onClick"], ji = { class: "inline-flex items-center" }, Mi = { +}), nc = { class: "relative table-container" }, rc = ["onClick"], ic = { class: "inline-flex items-center" }, ac = { key: 1, class: "text-subtle" -}, Ni = { +}, oc = { key: 1, class: "text-subtle" -}, Pi = 10, Fi = 350, Ii = /* @__PURE__ */ c({ +}, sc = 10, cc = 350, lc = /* @__PURE__ */ l({ __name: "TaskList", props: { client: { type: [Function, Object] }, @@ -3178,104 +5191,104 @@ var er = [ filterable: { type: Boolean } }, emits: ["changed"], - setup(e, { expose: c, emit: l }) { - let u = e, v = l, E = fe(), D = _(null), O = _(!0), k = _(0), A = _([]), M = _([]), ee = _([]), N = _(!1), P = _(null), F = _({}), ne = _(null), I = g({ + setup(e, { expose: t, emit: l }) { + let u = e, d = l, y = V(), C = v(null), O = v(!0), k = v(0), A = v([]), j = v([]), M = v([]), N = v(!1), F = v(null), I = v({}), L = v(null), R = _({ search: "", status: null, assignee: null - }), re = t(() => A.value.map((e) => ({ + }), z = n(() => A.value.map((e) => ({ id: e.id, label: e.name - }))), ie = t(() => M.value.map((e) => ({ + }))), te = n(() => j.value.map((e) => ({ id: e.id, label: e.name - }))), ae = t(() => I.search.trim() !== "" || I.status !== null || I.assignee !== null), oe = t(() => !O.value && k.value === 0 && !ae.value), se = t(() => [ + }))), ne = n(() => R.search.trim() !== "" || R.status !== null || R.assignee !== null), re = n(() => !O.value && k.value === 0 && !ne.value), ie = n(() => [ { key: "number", - label: E("tasks_projects.tasks.columns.number"), + label: y("tasks_projects.tasks.columns.number"), sortable: !1, tdClass: "text-muted" }, { key: "name", - label: E("tasks_projects.tasks.columns.name"), + label: y("tasks_projects.tasks.columns.name"), sortable: !1, thClass: "extra", tdClass: "font-medium text-heading" }, { key: "status", - label: E("tasks_projects.tasks.columns.status"), + label: y("tasks_projects.tasks.columns.status"), sortable: !1 }, { key: "assignee", - label: E("tasks_projects.tasks.columns.assignee"), + label: y("tasks_projects.tasks.columns.assignee"), sortable: !1 }, { key: "priority", - label: E("tasks_projects.tasks.columns.priority"), + label: y("tasks_projects.tasks.columns.priority"), sortable: !1 }, { key: "due_date", - label: E("tasks_projects.tasks.columns.due_date"), + label: y("tasks_projects.tasks.columns.due_date"), sortable: !1 }, { key: "actions", - label: E("tasks_projects.general.actions"), + label: y("tasks_projects.general.actions"), sortable: !1, tdClass: "text-right text-sm font-medium" } - ]), le = { + ]), ae = { LOW: "bg-surface-tertiary text-muted", NORMAL: "bg-primary-50 text-primary-500", HIGH: "bg-alert-warning-bg text-alert-warning-text", URGENT: "bg-alert-error-bg text-alert-error-text" - }, R; - C(() => I.search, () => { - clearTimeout(R), R = setTimeout(() => me(), Fi); - }), C([ - () => I.status, - () => I.assignee, + }, oe; + T(() => R.search, () => { + clearTimeout(oe), oe = setTimeout(() => ue(), cc); + }), T([ + () => R.status, + () => R.assignee, () => u.projectId - ], () => me()), m(() => { - ue(); - }), p(() => clearTimeout(R)); - async function ue() { + ], () => ue()), h(() => { + se(); + }), m(() => clearTimeout(oe)); + async function se() { try { - A.value = await Hn(u.client); + A.value = await ho(u.client); } catch (e) { - u.notify("error", L(e, E("tasks_projects.task_statuses.load_failed"))); + u.notify("error", B(e, y("tasks_projects.task_statuses.load_failed"))); } try { - M.value = await te(u.client); + j.value = await ee(u.client); } catch (e) { - u.notify("error", L(e, E("tasks_projects.tasks.members_failed"))); + u.notify("error", B(e, y("tasks_projects.tasks.members_failed"))); } if (!u.projectId) try { - let e = await j(u.client, { + let e = await P(u.client, { limit: 100, status: "ACTIVE" }); - ee.value = e.data.map((e) => ({ + M.value = e.data.map((e) => ({ id: e.id, label: e.name })); } catch (e) { - u.notify("error", L(e, E("tasks_projects.tasks.projects_failed"))); + u.notify("error", B(e, y("tasks_projects.tasks.projects_failed"))); } } - async function pe({ page: e }) { + async function le({ page: e }) { let t = { page: e, - limit: Pi + limit: sc }; - u.projectId && (t.project_id = u.projectId), I.status && (t.task_status_id = I.status.id), I.assignee && (t.assignee_id = I.assignee.id), I.search.trim() !== "" && (t.search = I.search.trim()), O.value = !0; + u.projectId && (t.project_id = u.projectId), R.status && (t.task_status_id = R.status.id), R.assignee && (t.assignee_id = R.assignee.id), R.search.trim() !== "" && (t.search = R.search.trim()), O.value = !0; try { - let e = await Un(u.client, t); + let e = await go(u.client, t); return k.value = e.meta.total, { data: e.data, pagination: { @@ -3286,98 +5299,98 @@ var er = [ } }; } catch (e) { - return u.notify("error", L(e, E("tasks_projects.tasks.load_failed"))), { + return u.notify("error", B(e, y("tasks_projects.tasks.load_failed"))), { data: [], pagination: { totalPages: 1, currentPage: 1, totalCount: 0, - limit: Pi + limit: sc } }; } finally { O.value = !1; } } - function me(e = !1) { - D.value?.refresh(e); + function ue(e = !1) { + C.value?.refresh(e); } - function he() { - I.search = "", I.status = null, I.assignee = null; + function de() { + R.search = "", R.status = null, R.assignee = null; } - function ge() { - P.value = null, F.value = { project_id: u.projectId ?? null }, N.value = !0; + function pe() { + F.value = null, I.value = { project_id: u.projectId ?? null }, N.value = !0; } - function _e(e) { - P.value = e, F.value = {}, N.value = !0; + function me(e) { + F.value = e, I.value = {}, N.value = !0; } - function ve(e) { - let t = P.value ? E("tasks_projects.tasks.updated", { name: e.name }) : E("tasks_projects.tasks.created", { name: e.name }); - N.value = !1, P.value = null, u.notify("success", t), me(!0), v("changed"); + function he(e) { + let t = F.value ? y("tasks_projects.tasks.updated", { name: e.name }) : y("tasks_projects.tasks.created", { name: e.name }); + N.value = !1, F.value = null, u.notify("success", t), ue(!0), d("changed"); } - function ye(e) { - N.value = !1, P.value = null, u.notify("success", E("tasks_projects.tasks.deleted", { name: e.name })), me(!0), v("changed"); + function ge(e) { + N.value = !1, F.value = null, u.notify("success", y("tasks_projects.tasks.deleted", { name: e.name })), ue(!0), d("changed"); } - function be(e) { + function _e(e) { return A.value.find((t) => t.id === e.task_status_id) ?? null; } - function xe(e) { - return e.assignee_id === null ? E("tasks_projects.tasks.unassigned") : M.value.find((t) => t.id === e.assignee_id)?.name ?? `#${e.assignee_id}`; + function ve(e) { + return e.assignee_id === null ? y("tasks_projects.tasks.unassigned") : j.value.find((t) => t.id === e.assignee_id)?.name ?? `#${e.assignee_id}`; } - function Se(e) { - return E(`tasks_projects.tasks.priority.${e.toLowerCase()}`); + function ye(e) { + return y(`tasks_projects.tasks.priority.${e.toLowerCase()}`); } - function Ce(e) { - return le[e]; + function be(e) { + return ae[e]; } - async function we(e) { - if (window.confirm(E("tasks_projects.tasks.delete_confirm", { name: e.name }))) { - ne.value = e.id; + async function xe(e) { + if (window.confirm(y("tasks_projects.tasks.delete_confirm", { name: e.name }))) { + L.value = e.id; try { - await Kn(u.client, e.id), u.notify("success", E("tasks_projects.tasks.deleted", { name: e.name })), me(!0), v("changed"); + await yo(u.client, e.id), u.notify("success", y("tasks_projects.tasks.deleted", { name: e.name })), ue(!0), d("changed"); } catch (e) { - u.notify("error", L(e, E("tasks_projects.tasks.delete_failed"))); + u.notify("error", B(e, y("tasks_projects.tasks.delete_failed"))); } finally { - ne.value = null; + L.value = null; } } } - return c({ - openCreate: ge, - refresh: me - }), (t, c) => { - let l = y("BaseInput"), u = y("BaseInputGroup"), p = y("BaseSelectInput"), m = y("BaseFilterWrapper"), g = y("BaseIcon"), _ = y("BaseButton"), v = y("BaseEmptyPlaceholder"), C = y("BaseDropdownItem"), O = y("BaseDropdown"), k = y("BaseTable"); - return h(), i("div", null, [ - e.filterable ? (h(), n(m, { + return t({ + openCreate: pe, + refresh: ue + }), (t, n) => { + let l = b("BaseInput"), u = b("BaseInputGroup"), d = b("BaseSelectInput"), m = b("BaseFilterWrapper"), h = b("BaseIcon"), _ = b("BaseButton"), v = b("BaseEmptyPlaceholder"), T = b("BaseDropdownItem"), O = b("BaseDropdown"), k = b("BaseTable"); + return g(), a("div", null, [ + e.filterable ? (g(), r(m, { key: 0, show: !0, class: "mt-3", - onClear: he + onClear: de }, { - default: w(() => [ - s(u, { - label: x(E)("tasks_projects.general.search"), + default: E(() => [ + c(u, { + label: S(y)("tasks_projects.general.search"), class: "mt-2 flex-1" }, { - default: w(() => [s(l, { - modelValue: I.search, - "onUpdate:modelValue": c[0] ||= (e) => I.search = e, + default: E(() => [c(l, { + modelValue: R.search, + "onUpdate:modelValue": n[0] ||= (e) => R.search = e, type: "text", name: "search", autocomplete: "off", - placeholder: x(E)("tasks_projects.tasks.search_placeholder") + placeholder: S(y)("tasks_projects.tasks.search_placeholder") }, null, 8, ["modelValue", "placeholder"])]), _: 1 }, 8, ["label"]), - s(u, { - label: x(E)("tasks_projects.tasks.columns.status"), + c(u, { + label: S(y)("tasks_projects.tasks.columns.status"), class: "mt-2 flex-1" }, { - default: w(() => [s(p, { - modelValue: I.status, - "onUpdate:modelValue": c[1] ||= (e) => I.status = e, - options: re.value, - placeholder: x(E)("tasks_projects.tasks.all_tasks"), + default: E(() => [c(d, { + modelValue: R.status, + "onUpdate:modelValue": n[1] ||= (e) => R.status = e, + options: z.value, + placeholder: S(y)("tasks_projects.tasks.all_tasks"), "label-key": "label" }, null, 8, [ "modelValue", @@ -3386,15 +5399,15 @@ var er = [ ])]), _: 1 }, 8, ["label"]), - s(u, { - label: x(E)("tasks_projects.tasks.columns.assignee"), + c(u, { + label: S(y)("tasks_projects.tasks.columns.assignee"), class: "mt-2 flex-1" }, { - default: w(() => [s(p, { - modelValue: I.assignee, - "onUpdate:modelValue": c[2] ||= (e) => I.assignee = e, - options: ie.value, - placeholder: x(E)("tasks_projects.board.filters.all_assignees"), + default: E(() => [c(d, { + modelValue: R.assignee, + "onUpdate:modelValue": n[2] ||= (e) => R.assignee = e, + options: te.value, + placeholder: S(y)("tasks_projects.board.filters.all_assignees"), "label-key": "label" }, null, 8, [ "modelValue", @@ -3405,89 +5418,89 @@ var er = [ }, 8, ["label"]) ]), _: 1 - })) : r("", !0), - T(s(v, { - title: x(E)("tasks_projects.tasks.empty_title"), - description: x(E)("tasks_projects.tasks.empty_description") + })) : i("", !0), + D(c(v, { + title: S(y)("tasks_projects.tasks.empty_title"), + description: S(y)("tasks_projects.tasks.empty_description") }, { - actions: w(() => [s(_, { + actions: E(() => [c(_, { variant: "primary", - onClick: ge + onClick: pe }, { - left: w((e) => [s(g, { + left: E((e) => [c(h, { name: "PlusIcon", - class: d(e.class) + class: f(e.class) }, null, 8, ["class"])]), - default: w(() => [o(" " + b(x(E)("tasks_projects.tasks.new_task")), 1)]), + default: E(() => [s(" " + x(S(y)("tasks_projects.tasks.new_task")), 1)]), _: 1 })]), - default: w(() => [s(g, { + default: E(() => [c(h, { name: "ClipboardDocumentListIcon", class: "mt-5 mb-4 h-16 w-16 text-subtle" })]), _: 1 - }, 8, ["title", "description"]), [[S, oe.value]]), - T(a("div", ki, [s(k, { + }, 8, ["title", "description"]), [[w, re.value]]), + D(o("div", nc, [c(k, { ref_key: "tableRef", - ref: D, - data: pe, - columns: se.value, + ref: C, + data: le, + columns: ie.value, class: "mt-3" }, { - "cell-number": w(({ row: e }) => [o("#" + b(e.data.number), 1)]), - "cell-name": w(({ row: e }) => [a("button", { + "cell-number": E(({ row: e }) => [s("#" + x(e.data.number), 1)]), + "cell-name": E(({ row: e }) => [o("button", { type: "button", class: "text-left hover:text-primary-500", - onClick: (t) => _e(e.data) - }, b(e.data.name), 9, Ai)]), - "cell-status": w(({ row: e }) => [a("span", ji, [a("span", { - class: d(["mr-2 inline-block h-2.5 w-2.5 shrink-0 rounded-full", be(e.data)?.colour ? "" : "bg-line-default"]), - style: f(be(e.data)?.colour ? { backgroundColor: be(e.data)?.colour } : void 0) - }, null, 6), o(" " + b(be(e.data)?.name ?? "-"), 1)])]), - "cell-assignee": w(({ row: e }) => [a("span", { class: d(e.data.assignee_id === null ? "text-subtle" : "") }, b(xe(e.data)), 3)]), - "cell-priority": w(({ row: e }) => [e.data.priority ? (h(), i("span", { + onClick: (t) => me(e.data) + }, x(e.data.name), 9, rc)]), + "cell-status": E(({ row: e }) => [o("span", ic, [o("span", { + class: f(["mr-2 inline-block h-2.5 w-2.5 shrink-0 rounded-full", _e(e.data)?.colour ? "" : "bg-line-default"]), + style: p(_e(e.data)?.colour ? { backgroundColor: _e(e.data)?.colour } : void 0) + }, null, 6), s(" " + x(_e(e.data)?.name ?? "-"), 1)])]), + "cell-assignee": E(({ row: e }) => [o("span", { class: f(e.data.assignee_id === null ? "text-subtle" : "") }, x(ve(e.data)), 3)]), + "cell-priority": E(({ row: e }) => [e.data.priority ? (g(), a("span", { key: 0, - class: d(["rounded-full px-2 py-0.5 text-xs font-medium", Ce(e.data.priority)]) - }, b(Se(e.data.priority)), 3)) : (h(), i("span", Mi, "-"))]), - "cell-due_date": w(({ row: e }) => [e.data.due_date ? (h(), i("span", { + class: f(["rounded-full px-2 py-0.5 text-xs font-medium", be(e.data.priority)]) + }, x(ye(e.data.priority)), 3)) : (g(), a("span", ac, "-"))]), + "cell-due_date": E(({ row: e }) => [e.data.due_date ? (g(), a("span", { key: 0, - class: d(x(de)(e.data.due_date) && !e.data.closed_at ? "font-medium text-status-red" : "") - }, b(x(ce)(e.data.due_date)), 3)) : (h(), i("span", Ni, "-"))]), - "cell-actions": w(({ row: e }) => [s(O, { "content-loading": ne.value === e.data.id }, { - activator: w(() => [s(g, { + class: f(S(fe)(e.data.due_date) && !e.data.closed_at ? "font-medium text-status-red" : "") + }, x(S(ce)(e.data.due_date)), 3)) : (g(), a("span", oc, "-"))]), + "cell-actions": E(({ row: e }) => [c(O, { "content-loading": L.value === e.data.id }, { + activator: E(() => [c(h, { name: "EllipsisHorizontalIcon", class: "h-5 text-muted" })]), - default: w(() => [s(C, { onClick: (t) => _e(e.data) }, { - default: w(() => [s(g, { + default: E(() => [c(T, { onClick: (t) => me(e.data) }, { + default: E(() => [c(h, { name: "PencilIcon", class: "mr-3 h-5 w-5 text-subtle group-hover:text-muted" - }), o(" " + b(x(E)("tasks_projects.general.edit")), 1)]), + }), s(" " + x(S(y)("tasks_projects.general.edit")), 1)]), _: 1 - }, 8, ["onClick"]), s(C, { onClick: (t) => we(e.data) }, { - default: w(() => [s(g, { + }, 8, ["onClick"]), c(T, { onClick: (t) => xe(e.data) }, { + default: E(() => [c(h, { name: "TrashIcon", class: "mr-3 h-5 w-5 text-subtle group-hover:text-muted" - }), o(" " + b(x(E)("tasks_projects.general.delete")), 1)]), + }), s(" " + x(S(y)("tasks_projects.general.delete")), 1)]), _: 1 }, 8, ["onClick"])]), _: 2 }, 1032, ["content-loading"])]), _: 1 - }, 8, ["columns"])], 512), [[S, !oe.value]]), - s(sr, { + }, 8, ["columns"])], 512), [[w, !re.value]]), + c(Po, { show: N.value, client: e.client, notify: e.notify, - task: P.value, + task: F.value, statuses: A.value, - members: M.value, - projects: ee.value, - defaults: F.value, + members: j.value, + projects: M.value, + defaults: I.value, "lock-project": !!e.projectId, - onClose: c[3] ||= (e) => N.value = !1, - onSaved: ve, - onDeleted: ye + onClose: n[3] ||= (e) => N.value = !1, + onSaved: he, + onDeleted: ge }, null, 8, [ "show", "client", @@ -3502,7 +5515,7 @@ var er = [ ]); }; } -}), Li = { class: "py-4" }, Ri = { class: "flex justify-end" }, zi = /* @__PURE__ */ c({ +}), uc = { class: "py-4" }, dc = { class: "flex justify-end" }, fc = /* @__PURE__ */ l({ __name: "ProjectTasksTab", props: { id: {}, @@ -3511,29 +5524,29 @@ var er = [ project: {} }, emits: ["refresh"], - setup(e, { emit: n }) { - let r = e, c = n, l = fe(), u = _(null), f = t(() => r.project?.id ?? Number(r.id)); + setup(e, { emit: t }) { + let r = e, i = t, l = V(), u = v(null), d = n(() => r.project?.id ?? Number(r.id)); function p() { - c("refresh"); + i("refresh"); } return (t, n) => { - let r = y("BaseIcon"), c = y("BaseButton"); - return h(), i("div", Li, [a("div", Ri, [s(c, { + let r = b("BaseIcon"), i = b("BaseButton"); + return g(), a("div", uc, [o("div", dc, [c(i, { variant: "primary", onClick: n[0] ||= (e) => u.value?.openCreate() }, { - left: w((e) => [s(r, { + left: E((e) => [c(r, { name: "PlusIcon", - class: d(e.class) + class: f(e.class) }, null, 8, ["class"])]), - default: w(() => [o(" " + b(x(l)("tasks_projects.tasks.new_task")), 1)]), + default: E(() => [s(" " + x(S(l)("tasks_projects.tasks.new_task")), 1)]), _: 1 - })]), s(Ii, { + })]), c(lc, { ref_key: "listRef", ref: u, client: e.client, notify: e.notify, - "project-id": f.value, + "project-id": d.value, filterable: "", onChanged: p }, null, 8, [ @@ -3543,16 +5556,16 @@ var er = [ ])]); }; } -}), Bi = { class: "relative py-4 table-container" }, Vi = { class: "font-medium text-heading" }, Hi = { +}), pc = { class: "relative py-4 table-container" }, mc = { class: "font-medium text-heading" }, hc = { key: 0, class: "block text-xs text-muted" -}, Ui = { +}, gc = { key: 0, class: "text-primary-500" -}, Wi = { key: 1 }, Gi = { +}, _c = { key: 1 }, vc = { key: 1, class: "text-subtle" -}, Ki = 15, qi = /* @__PURE__ */ c({ +}, yc = 15, bc = /* @__PURE__ */ l({ __name: "ProjectTimeTab", props: { id: {}, @@ -3561,7 +5574,7 @@ var er = [ project: {} }, setup(e) { - let c = e, l = fe(), u = _([]), d = _([]), f = t(() => c.project?.id ?? Number(c.id)), p = t(() => [ + let t = e, l = V(), u = v([]), d = v([]), f = n(() => t.project?.id ?? Number(t.id)), p = n(() => [ { key: "started_at", label: l("tasks_projects.project.time.columns.date"), @@ -3595,33 +5608,33 @@ var er = [ tdClass: "text-right" } ]); - m(() => { - g(); + h(() => { + m(); }); - async function g() { + async function m() { try { - u.value = await te(c.client); + u.value = await ee(t.client); } catch (e) { - c.notify("error", L(e, l("tasks_projects.tasks.members_failed"))); + t.notify("error", B(e, l("tasks_projects.tasks.members_failed"))); } try { - let e = await Un(c.client, { + let e = await go(t.client, { project_id: f.value, limit: 100 }); d.value = e.data; } catch (e) { - c.notify("error", L(e, l("tasks_projects.tasks.load_failed"))); + t.notify("error", B(e, l("tasks_projects.tasks.load_failed"))); } } - async function v({ page: e }) { - let t = { + async function _({ page: e }) { + let n = { page: e, - limit: Ki, + limit: yc, project_id: f.value }; try { - let e = await Qn(c.client, t); + let e = await To(t.client, n); return { data: e.data, pagination: { @@ -3632,88 +5645,88 @@ var er = [ } }; } catch (e) { - return c.notify("error", L(e, l("tasks_projects.project.time.load_failed"))), { + return t.notify("error", B(e, l("tasks_projects.project.time.load_failed"))), { data: [], pagination: { totalPages: 1, currentPage: 1, totalCount: 0, - limit: Ki + limit: yc } }; } } - function S(e) { + function y(e) { return u.value.find((t) => t.id === e)?.name ?? l("tasks_projects.project.time.removed_member"); } function C(e) { return d.value.find((t) => t.id === e)?.name ?? `#${e}`; } return (e, t) => { - let c = y("BaseIcon"), u = y("BaseFormatMoney"), d = y("BaseTable"); - return h(), i("div", Bi, [s(d, { - data: v, + let n = b("BaseIcon"), u = b("BaseFormatMoney"), d = b("BaseTable"); + return g(), a("div", pc, [c(d, { + data: _, columns: p.value }, { - "cell-started_at": w(({ row: e }) => [o(b(e.data.started_at ? x(ce)(e.data.started_at) : "-"), 1)]), - "cell-user": w(({ row: e }) => [o(b(S(e.data.user_id)), 1)]), - "cell-task": w(({ row: e }) => [a("span", Vi, b(C(e.data.task_id)), 1), e.data.description ? (h(), i("span", Hi, b(e.data.description), 1)) : r("", !0)]), - "cell-duration_minutes": w(({ row: e }) => [e.data.is_running ? (h(), i("span", Ui, b(x(l)("tasks_projects.project.time.running")), 1)) : (h(), i("span", Wi, b(x(R)(e.data.duration_minutes)), 1))]), - "cell-billable": w(({ row: e }) => [e.data.billable ? (h(), n(c, { + "cell-started_at": E(({ row: e }) => [s(x(e.data.started_at ? S(ce)(e.data.started_at) : "-"), 1)]), + "cell-user": E(({ row: e }) => [s(x(y(e.data.user_id)), 1)]), + "cell-task": E(({ row: e }) => [o("span", mc, x(C(e.data.task_id)), 1), e.data.description ? (g(), a("span", hc, x(e.data.description), 1)) : i("", !0)]), + "cell-duration_minutes": E(({ row: e }) => [e.data.is_running ? (g(), a("span", gc, x(S(l)("tasks_projects.project.time.running")), 1)) : (g(), a("span", _c, x(S(ue)(e.data.duration_minutes)), 1))]), + "cell-billable": E(({ row: e }) => [e.data.billable ? (g(), r(n, { key: 0, name: "CheckCircleIcon", class: "h-5 w-5 text-status-green" - })) : (h(), i("span", Gi, "-"))]), - "cell-amount": w(({ row: e }) => [s(u, { amount: e.data.amount }, null, 8, ["amount"])]), + })) : (g(), a("span", vc, "-"))]), + "cell-amount": E(({ row: e }) => [c(u, { amount: e.data.amount }, null, 8, ["amount"])]), _: 1 }, 8, ["columns"])]); }; } -}), Ji = { class: "flex items-center justify-end space-x-5" }, Yi = /* @__PURE__ */ c({ +}), xc = { class: "flex items-center justify-end space-x-5" }, Sc = /* @__PURE__ */ l({ __name: "TasksPage", props: { client: { type: [Function, Object] }, notify: { type: Function } }, setup(e) { - let t = fe(), r = _(null); - return (i, c) => { - let l = y("BaseBreadcrumbItem"), u = y("BaseBreadcrumb"), f = y("BaseIcon"), p = y("BaseButton"), m = y("router-link"), g = y("BasePageHeader"), _ = y("BasePage"); - return h(), n(_, null, { - default: w(() => [s(g, { title: x(t)("tasks_projects.tasks.title") }, { - actions: w(() => [a("div", Ji, [s(m, { to: "/admin/modules/tasks-projects/board" }, { - default: w(() => [s(p, { variant: "white" }, { - left: w((e) => [s(f, { + let t = V(), n = v(null); + return (i, a) => { + let l = b("BaseBreadcrumbItem"), u = b("BaseBreadcrumb"), d = b("BaseIcon"), p = b("BaseButton"), m = b("router-link"), h = b("BasePageHeader"), _ = b("BasePage"); + return g(), r(_, null, { + default: E(() => [c(h, { title: S(t)("tasks_projects.tasks.title") }, { + actions: E(() => [o("div", xc, [c(m, { to: "/admin/modules/tasks-projects/board" }, { + default: E(() => [c(p, { variant: "white" }, { + left: E((e) => [c(d, { name: "ViewColumnsIcon", - class: d(e.class) + class: f(e.class) }, null, 8, ["class"])]), - default: w(() => [o(" " + b(x(t)("tasks_projects.board.title")), 1)]), + default: E(() => [s(" " + x(S(t)("tasks_projects.board.title")), 1)]), _: 1 })]), _: 1 - }), s(p, { + }), c(p, { variant: "primary", - onClick: c[0] ||= (e) => r.value?.openCreate() + onClick: a[0] ||= (e) => n.value?.openCreate() }, { - left: w((e) => [s(f, { + left: E((e) => [c(d, { name: "PlusIcon", - class: d(e.class) + class: f(e.class) }, null, 8, ["class"])]), - default: w(() => [o(" " + b(x(t)("tasks_projects.tasks.new_task")), 1)]), + default: E(() => [s(" " + x(S(t)("tasks_projects.tasks.new_task")), 1)]), _: 1 })])]), - default: w(() => [s(u, null, { - default: w(() => [ - s(l, { - title: x(t)("tasks_projects.general.home"), + default: E(() => [c(u, null, { + default: E(() => [ + c(l, { + title: S(t)("tasks_projects.general.home"), to: "/admin/dashboard" }, null, 8, ["title"]), - s(l, { - title: x(t)("tasks_projects.projects.title"), + c(l, { + title: S(t)("tasks_projects.projects.title"), to: "/admin/modules/tasks-projects" }, null, 8, ["title"]), - s(l, { - title: x(t)("tasks_projects.tasks.title"), + c(l, { + title: S(t)("tasks_projects.tasks.title"), to: "#", active: "" }, null, 8, ["title"]) @@ -3721,9 +5734,9 @@ var er = [ _: 1 })]), _: 1 - }, 8, ["title"]), s(Ii, { + }, 8, ["title"]), c(lc, { ref_key: "listRef", - ref: r, + ref: n, client: e.client, notify: e.notify, filterable: "" @@ -3735,8 +5748,8 @@ var er = [ }); //#endregion //#region resources/js/support/page.ts -function Xi(e, t) { - return c({ setup: (n, { attrs: r }) => () => u(t, { +function Cc(e, t) { + return l({ setup: (n, { attrs: r }) => () => d(t, { ...r, client: e.client, notify: (t, n) => { @@ -3747,74 +5760,74 @@ function Xi(e, t) { } //#endregion //#region resources/js/registrations/board.ts -var Zi = "tasks-projects", Qi = { - viewProject: `${Zi}:view-project`, - editProject: `${Zi}:edit-project`, - viewTask: `${Zi}:view-task`, - viewOwnTime: `${Zi}:view-own-time` +var wc = "tasks-projects", Tc = { + viewProject: `${wc}:view-project`, + editProject: `${wc}:edit-project`, + viewTask: `${wc}:view-task`, + viewOwnTime: `${wc}:view-own-time` }; -function $i(e) { - e.addMessages(je), e.registerPage({ +function Ec(e) { + e.addMessages($r), e.registerPage({ id: "board", - module: Zi, + module: wc, path: "board", - component: Xi(e, kr), + component: Cc(e, ns), meta: { - ability: Qi.viewTask, + ability: Tc.viewTask, title: "tasks_projects.board.title" } }), e.registerPage({ id: "tasks", - module: Zi, + module: wc, path: "tasks", - component: Xi(e, Yi), + component: Cc(e, Sc), meta: { - ability: Qi.viewTask, + ability: Tc.viewTask, title: "tasks_projects.tasks.title" } }), e.registerPage({ id: "project", - module: Zi, + module: wc, path: "projects/:id", - component: Xi(e, Hr), + component: Cc(e, hs), meta: { - ability: Qi.viewProject, + ability: Tc.viewProject, title: "tasks_projects.projects.title" }, children: [ { id: "overview", path: "", - component: Xi(e, Oi), + component: Cc(e, tc), meta: { - ability: Qi.viewProject, + ability: Tc.viewProject, title: "tasks_projects.project.tabs.overview" } }, { id: "tasks", path: "tasks", - component: Xi(e, zi), + component: Cc(e, fc), meta: { - ability: Qi.viewTask, + ability: Tc.viewTask, title: "tasks_projects.project.tabs.tasks" } }, { id: "time", path: "time", - component: Xi(e, qi), + component: Cc(e, bc), meta: { - ability: Qi.viewOwnTime, + ability: Tc.viewOwnTime, title: "tasks_projects.project.tabs.time" } }, { id: "members", path: "members", - component: Xi(e, ei), + component: Cc(e, Ds), meta: { - ability: Qi.editProject, + ability: Tc.editProject, title: "tasks_projects.project.tabs.members" } } @@ -3823,21 +5836,21 @@ function $i(e) { } //#endregion //#region resources/js/init.ts -var ea = "tasks-projects"; +var Dc = "tasks-projects"; window.InvoiceShelf.booting((e, t, n) => { - n.addMessages(D), n.registerPage({ + n.addMessages(A), n.registerPage({ id: "projects", - module: ea, + module: Dc, path: "", - component: ta(n, Ae), + component: Oc(n, Ae), meta: { - ability: `${ea}:view-project`, + ability: `${Dc}:view-project`, title: "tasks_projects.projects.title" } - }), $i(n); + }), Yr(n), Ec(n); }); -function ta(e, t) { - return c({ setup: (n, { attrs: r }) => () => u(t, { +function Oc(e, t) { + return l({ setup: (n, { attrs: r }) => () => d(t, { ...r, client: e.client, notify: (t, n) => { diff --git a/dist/style.css b/dist/style.css index a5c5422..7abfcce 100644 --- a/dist/style.css +++ b/dist/style.css @@ -1,3 +1,3 @@ /*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer utilities{.fixed{position:fixed}.relative{position:relative}.mt-1{margin-top:var(--spacing,.25rem)}.mt-2{margin-top:calc(var(--spacing,.25rem) * 2)}.mt-3{margin-top:calc(var(--spacing,.25rem) * 3)}.mt-4{margin-top:calc(var(--spacing,.25rem) * 4)}.mt-5{margin-top:calc(var(--spacing,.25rem) * 5)}.mt-6{margin-top:calc(var(--spacing,.25rem) * 6)}.mr-0\.5{margin-right:calc(var(--spacing,.25rem) * .5)}.mr-2{margin-right:calc(var(--spacing,.25rem) * 2)}.mr-3{margin-right:calc(var(--spacing,.25rem) * 3)}.-mb-px{margin-bottom:-1px}.mb-4{margin-bottom:calc(var(--spacing,.25rem) * 4)}.ml-2{margin-left:calc(var(--spacing,.25rem) * 2)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-2{height:calc(var(--spacing,.25rem) * 2)}.h-2\.5{height:calc(var(--spacing,.25rem) * 2.5)}.h-3\.5{height:calc(var(--spacing,.25rem) * 3.5)}.h-4{height:calc(var(--spacing,.25rem) * 4)}.h-5{height:calc(var(--spacing,.25rem) * 5)}.h-6{height:calc(var(--spacing,.25rem) * 6)}.h-7{height:calc(var(--spacing,.25rem) * 7)}.h-8{height:calc(var(--spacing,.25rem) * 8)}.h-16{height:calc(var(--spacing,.25rem) * 16)}.min-h-\[80px\]{min-height:80px}.w-2\.5{width:calc(var(--spacing,.25rem) * 2.5)}.w-3\.5{width:calc(var(--spacing,.25rem) * 3.5)}.w-4{width:calc(var(--spacing,.25rem) * 4)}.w-5{width:calc(var(--spacing,.25rem) * 5)}.w-6{width:calc(var(--spacing,.25rem) * 6)}.w-7{width:calc(var(--spacing,.25rem) * 7)}.w-8{width:calc(var(--spacing,.25rem) * 8)}.w-16{width:calc(var(--spacing,.25rem) * 16)}.w-72{width:calc(var(--spacing,.25rem) * 72)}.w-full{width:100%}.flex-1{flex:1}.shrink-0{flex-shrink:0}.cursor-pointer{cursor:pointer}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-2{gap:calc(var(--spacing,.25rem) * 2)}.gap-3{gap:calc(var(--spacing,.25rem) * 3)}.gap-4{gap:calc(var(--spacing,.25rem) * 4)}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing,.25rem) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing,.25rem) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing,.25rem) * 5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing,.25rem) * 5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing,.25rem) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing,.25rem) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-5>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing,.25rem) * 5) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing,.25rem) * 5) * calc(1 - var(--tw-space-x-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-line-light>:not(:last-child)){border-color:var(--color-line-light)}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.rounded-full{border-radius:2147483647px}.rounded-lg{border-radius:var(--radius-lg,.5rem)}.rounded-md{border-radius:var(--radius-md,.375rem)}.rounded-sm{border-radius:var(--radius-sm,.25rem)}.rounded-xl{border-radius:var(--radius-xl,.75rem)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-heading{border-color:var(--color-heading)}.border-line-default{border-color:var(--color-line-default)}.border-line-light{border-color:var(--color-line-light)}.border-primary-400{border-color:var(--color-primary-400)}.border-transparent{border-color:#0000}.bg-alert-error-bg{background-color:var(--color-alert-error-bg)}.bg-alert-warning-bg{background-color:var(--color-alert-warning-bg)}.bg-line-default{background-color:var(--color-line-default)}.bg-primary-50{background-color:var(--color-primary-50)}.bg-primary-50\!{background-color:var(--color-primary-50)!important}.bg-primary-500{background-color:var(--color-primary-500)}.bg-status-red{background-color:var(--color-status-red)}.bg-surface{background-color:var(--color-surface)}.bg-surface-secondary{background-color:var(--color-surface-secondary)}.bg-surface-tertiary{background-color:var(--color-surface-tertiary)}.bg-surface-tertiary\!{background-color:var(--color-surface-tertiary)!important}.p-1{padding:var(--spacing,.25rem)}.p-3{padding:calc(var(--spacing,.25rem) * 3)}.p-5{padding:calc(var(--spacing,.25rem) * 5)}.px-1\.5{padding-inline:calc(var(--spacing,.25rem) * 1.5)}.px-2{padding-inline:calc(var(--spacing,.25rem) * 2)}.px-3{padding-inline:calc(var(--spacing,.25rem) * 3)}.px-5{padding-inline:calc(var(--spacing,.25rem) * 5)}.px-6{padding-inline:calc(var(--spacing,.25rem) * 6)}.py-0\.5{padding-block:calc(var(--spacing,.25rem) * .5)}.py-1{padding-block:var(--spacing,.25rem)}.py-1\.5{padding-block:calc(var(--spacing,.25rem) * 1.5)}.py-2\.5{padding-block:calc(var(--spacing,.25rem) * 2.5)}.py-4{padding-block:calc(var(--spacing,.25rem) * 4)}.py-6{padding-block:calc(var(--spacing,.25rem) * 6)}.py-8{padding-block:calc(var(--spacing,.25rem) * 8)}.py-10{padding-block:calc(var(--spacing,.25rem) * 10)}.py-16{padding-block:calc(var(--spacing,.25rem) * 16)}.pt-2{padding-top:calc(var(--spacing,.25rem) * 2)}.pt-3{padding-top:calc(var(--spacing,.25rem) * 3)}.pb-3{padding-bottom:calc(var(--spacing,.25rem) * 3)}.pb-4{padding-bottom:calc(var(--spacing,.25rem) * 4)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.text-2xl{font-size:var(--text-2xl,1.5rem);line-height:var(--tw-leading,var(--text-2xl--line-height,calc(2 / 1.5)))}.text-sm{font-size:var(--text-sm,.875rem);line-height:var(--tw-leading,var(--text-sm--line-height,calc(1.25 / .875)))}.text-xs{font-size:var(--text-xs,.75rem);line-height:var(--tw-leading,var(--text-xs--line-height,calc(1 / .75)))}.text-\[11px\]{font-size:11px}.leading-5{--tw-leading:calc(var(--spacing,.25rem) * 5);line-height:calc(var(--spacing,.25rem) * 5)}.font-medium{--tw-font-weight:var(--font-weight-medium,500);font-weight:var(--font-weight-medium,500)}.font-normal{--tw-font-weight:var(--font-weight-normal,400);font-weight:var(--font-weight-normal,400)}.font-semibold{--tw-font-weight:var(--font-weight-semibold,600);font-weight:var(--font-weight-semibold,600)}.tracking-wider{--tw-tracking:var(--tracking-wider,.05em);letter-spacing:var(--tracking-wider,.05em)}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-line{white-space:pre-line}.text-alert-error-text{color:var(--color-alert-error-text)}.text-alert-warning-text{color:var(--color-alert-warning-text)}.text-body{color:var(--color-body)}.text-heading{color:var(--color-heading)}.text-muted{color:var(--color-muted)}.text-muted\!{color:var(--color-muted)!important}.text-primary-500{color:var(--color-primary-500)}.text-primary-500\!{color:var(--color-primary-500)!important}.text-status-green{color:var(--color-status-green)}.text-status-red{color:var(--color-status-red)}.text-subtle{color:var(--color-subtle)}.uppercase{text-transform:uppercase}.opacity-40{opacity:.4}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function,cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration,var(--default-transition-duration,.15s))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function,cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration,var(--default-transition-duration,.15s))}@media (hover:hover){.group-hover\:text-muted:is(:where(.group):hover *){color:var(--color-muted)}.hover\:border-line-strong:hover{border-color:var(--color-line-strong)}.hover\:bg-hover:hover{background-color:var(--color-hover)}.hover\:text-body:hover{color:var(--color-body)}.hover\:text-primary-500:hover{color:var(--color-primary-500)}}.focus\:outline-hidden:focus{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.focus\:outline-hidden:focus{outline-offset:2px;outline:2px solid #0000}}@media (width>=40rem){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (width>=64rem){.lg\:mb-1{margin-bottom:var(--spacing,.25rem)}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:items-end{align-items:flex-end}}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false} +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer utilities{.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.fixed{position:fixed}.relative{position:relative}.right-6{right:calc(var(--spacing,.25rem) * 6)}.bottom-20{bottom:calc(var(--spacing,.25rem) * 20)}.z-40{z-index:40}.float-left{float:left}.m-0{margin:0}.mt-1{margin-top:var(--spacing,.25rem)}.mt-2{margin-top:calc(var(--spacing,.25rem) * 2)}.mt-3{margin-top:calc(var(--spacing,.25rem) * 3)}.mt-4{margin-top:calc(var(--spacing,.25rem) * 4)}.mt-5{margin-top:calc(var(--spacing,.25rem) * 5)}.mt-6{margin-top:calc(var(--spacing,.25rem) * 6)}.mr-0\.5{margin-right:calc(var(--spacing,.25rem) * .5)}.mr-2{margin-right:calc(var(--spacing,.25rem) * 2)}.mr-3{margin-right:calc(var(--spacing,.25rem) * 3)}.-mb-px{margin-bottom:-1px}.mb-4{margin-bottom:calc(var(--spacing,.25rem) * 4)}.ml-1{margin-left:var(--spacing,.25rem)}.ml-2{margin-left:calc(var(--spacing,.25rem) * 2)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-1\.5{height:calc(var(--spacing,.25rem) * 1.5)}.h-2{height:calc(var(--spacing,.25rem) * 2)}.h-2\.5{height:calc(var(--spacing,.25rem) * 2.5)}.h-3{height:calc(var(--spacing,.25rem) * 3)}.h-3\.5{height:calc(var(--spacing,.25rem) * 3.5)}.h-4{height:calc(var(--spacing,.25rem) * 4)}.h-5{height:calc(var(--spacing,.25rem) * 5)}.h-6{height:calc(var(--spacing,.25rem) * 6)}.h-7{height:calc(var(--spacing,.25rem) * 7)}.h-8{height:calc(var(--spacing,.25rem) * 8)}.h-16{height:calc(var(--spacing,.25rem) * 16)}.max-h-48{max-height:calc(var(--spacing,.25rem) * 48)}.min-h-40{min-height:calc(var(--spacing,.25rem) * 40)}.min-h-\[80px\]{min-height:80px}.w-1\.5{width:calc(var(--spacing,.25rem) * 1.5)}.w-2{width:calc(var(--spacing,.25rem) * 2)}.w-2\.5{width:calc(var(--spacing,.25rem) * 2.5)}.w-3{width:calc(var(--spacing,.25rem) * 3)}.w-3\.5{width:calc(var(--spacing,.25rem) * 3.5)}.w-4{width:calc(var(--spacing,.25rem) * 4)}.w-5{width:calc(var(--spacing,.25rem) * 5)}.w-6{width:calc(var(--spacing,.25rem) * 6)}.w-7{width:calc(var(--spacing,.25rem) * 7)}.w-8{width:calc(var(--spacing,.25rem) * 8)}.w-16{width:calc(var(--spacing,.25rem) * 16)}.w-72{width:calc(var(--spacing,.25rem) * 72)}.w-80{width:calc(var(--spacing,.25rem) * 80)}.w-full{width:100%}.max-w-32{max-width:calc(var(--spacing,.25rem) * 32)}.max-w-64{max-width:calc(var(--spacing,.25rem) * 64)}.max-w-\[calc\(100vw-3rem\)\]{max-width:calc(100vw - 3rem)}.min-w-0{min-width:0}.flex-1{flex:1}.shrink-0{flex-shrink:0}.animate-pulse{animation:var(--animate-pulse,pulse 2s cubic-bezier(.4, 0, .6, 1) infinite)}.cursor-pointer{cursor:pointer}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:var(--spacing,.25rem)}.gap-2{gap:calc(var(--spacing,.25rem) * 2)}.gap-3{gap:calc(var(--spacing,.25rem) * 3)}.gap-4{gap:calc(var(--spacing,.25rem) * 4)}.gap-6{gap:calc(var(--spacing,.25rem) * 6)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing,.25rem) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing,.25rem) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing,.25rem) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing,.25rem) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing,.25rem) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing,.25rem) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing,.25rem) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing,.25rem) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing,.25rem) * 5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing,.25rem) * 5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing,.25rem) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing,.25rem) * 6) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing,.25rem) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing,.25rem) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-5>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing,.25rem) * 5) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing,.25rem) * 5) * calc(1 - var(--tw-space-x-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-line-light>:not(:last-child)){border-color:var(--color-line-light)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:2147483647px}.rounded-lg{border-radius:var(--radius-lg,.5rem)}.rounded-md{border-radius:var(--radius-md,.375rem)}.rounded-sm{border-radius:var(--radius-sm,.25rem)}.rounded-xl{border-radius:var(--radius-xl,.75rem)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-heading{border-color:var(--color-heading)}.border-line-default{border-color:var(--color-line-default)}.border-line-light{border-color:var(--color-line-light)}.border-primary-400{border-color:var(--color-primary-400)}.border-primary-500{border-color:var(--color-primary-500)}.border-transparent{border-color:#0000}.bg-alert-error-bg{background-color:var(--color-alert-error-bg)}.bg-alert-warning-bg{background-color:var(--color-alert-warning-bg)}.bg-btn-primary{background-color:var(--color-btn-primary)}.bg-hover-strong{background-color:var(--color-hover-strong)}.bg-line-default{background-color:var(--color-line-default)}.bg-line-strong{background-color:var(--color-line-strong)}.bg-primary-50{background-color:var(--color-primary-50)}.bg-primary-50\!{background-color:var(--color-primary-50)!important}.bg-primary-500{background-color:var(--color-primary-500)}.bg-status-green{background-color:var(--color-status-green)}.bg-status-red{background-color:var(--color-status-red)}.bg-surface{background-color:var(--color-surface)}.bg-surface-secondary{background-color:var(--color-surface-secondary)}.bg-surface-tertiary{background-color:var(--color-surface-tertiary)}.bg-surface-tertiary\!{background-color:var(--color-surface-tertiary)!important}.bg-white{background-color:var(--color-white,#fff)}.bg-white\/20{background-color:#fff3}@supports (color:color-mix(in lab, red, red)){.bg-white\/20{background-color:color-mix(in oklab, var(--color-white,#fff) 20%, transparent)}}.p-1{padding:var(--spacing,.25rem)}.p-3{padding:calc(var(--spacing,.25rem) * 3)}.p-5{padding:calc(var(--spacing,.25rem) * 5)}.px-1{padding-inline:var(--spacing,.25rem)}.px-1\.5{padding-inline:calc(var(--spacing,.25rem) * 1.5)}.px-2{padding-inline:calc(var(--spacing,.25rem) * 2)}.px-3{padding-inline:calc(var(--spacing,.25rem) * 3)}.px-4{padding-inline:calc(var(--spacing,.25rem) * 4)}.px-5{padding-inline:calc(var(--spacing,.25rem) * 5)}.px-6{padding-inline:calc(var(--spacing,.25rem) * 6)}.py-0\.5{padding-block:calc(var(--spacing,.25rem) * .5)}.py-1{padding-block:var(--spacing,.25rem)}.py-1\.5{padding-block:calc(var(--spacing,.25rem) * 1.5)}.py-2{padding-block:calc(var(--spacing,.25rem) * 2)}.py-2\.5{padding-block:calc(var(--spacing,.25rem) * 2.5)}.py-3{padding-block:calc(var(--spacing,.25rem) * 3)}.py-4{padding-block:calc(var(--spacing,.25rem) * 4)}.py-6{padding-block:calc(var(--spacing,.25rem) * 6)}.py-8{padding-block:calc(var(--spacing,.25rem) * 8)}.py-10{padding-block:calc(var(--spacing,.25rem) * 10)}.py-16{padding-block:calc(var(--spacing,.25rem) * 16)}.pt-2{padding-top:calc(var(--spacing,.25rem) * 2)}.pt-3{padding-top:calc(var(--spacing,.25rem) * 3)}.pb-3{padding-bottom:calc(var(--spacing,.25rem) * 3)}.pb-4{padding-bottom:calc(var(--spacing,.25rem) * 4)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.text-2xl{font-size:var(--text-2xl,1.5rem);line-height:var(--tw-leading,var(--text-2xl--line-height,calc(2 / 1.5)))}.text-lg{font-size:var(--text-lg,1.125rem);line-height:var(--tw-leading,var(--text-lg--line-height,calc(1.75 / 1.125)))}.text-sm{font-size:var(--text-sm,.875rem);line-height:var(--tw-leading,var(--text-sm--line-height,calc(1.25 / .875)))}.text-xs{font-size:var(--text-xs,.75rem);line-height:var(--tw-leading,var(--text-xs--line-height,calc(1 / .75)))}.text-\[11px\]{font-size:11px}.leading-5{--tw-leading:calc(var(--spacing,.25rem) * 5);line-height:calc(var(--spacing,.25rem) * 5)}.font-medium{--tw-font-weight:var(--font-weight-medium,500);font-weight:var(--font-weight-medium,500)}.font-normal{--tw-font-weight:var(--font-weight-normal,400);font-weight:var(--font-weight-normal,400)}.font-semibold{--tw-font-weight:var(--font-weight-semibold,600);font-weight:var(--font-weight-semibold,600)}.tracking-wide{--tw-tracking:var(--tracking-wide,.025em);letter-spacing:var(--tracking-wide,.025em)}.tracking-wider{--tw-tracking:var(--tracking-wider,.05em);letter-spacing:var(--tracking-wider,.05em)}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-line{white-space:pre-line}.text-alert-error-text{color:var(--color-alert-error-text)}.text-alert-warning-text{color:var(--color-alert-warning-text)}.text-body{color:var(--color-body)}.text-heading{color:var(--color-heading)}.text-muted{color:var(--color-muted)}.text-muted\!{color:var(--color-muted)!important}.text-primary-500{color:var(--color-primary-500)}.text-primary-500\!{color:var(--color-primary-500)!important}.text-status-green{color:var(--color-status-green)}.text-status-red{color:var(--color-status-red)}.text-subtle{color:var(--color-subtle)}.text-white{color:var(--color-white,#fff)}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.opacity-40{opacity:.4}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline-hidden{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.outline-hidden{outline-offset:2px;outline:2px solid #0000}}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function,cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration,var(--default-transition-duration,.15s))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function,cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration,var(--default-transition-duration,.15s))}@media (hover:hover){.group-hover\:text-muted:is(:where(.group):hover *){color:var(--color-muted)}.hover\:border-line-strong:hover{border-color:var(--color-line-strong)}.hover\:bg-btn-primary-hover:hover{background-color:var(--color-btn-primary-hover)}.hover\:bg-hover:hover{background-color:var(--color-hover)}.hover\:bg-white\/20:hover{background-color:#fff3}@supports (color:color-mix(in lab, red, red)){.hover\:bg-white\/20:hover{background-color:color-mix(in oklab, var(--color-white,#fff) 20%, transparent)}}.hover\:text-alert-error-text:hover{color:var(--color-alert-error-text)}.hover\:text-body:hover{color:var(--color-body)}.hover\:text-heading:hover{color:var(--color-heading)}.hover\:text-primary-500:hover{color:var(--color-primary-500)}.hover\:underline:hover{text-decoration-line:underline}}.focus\:border-primary-400:focus{border-color:var(--color-primary-400)}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-primary-400:focus{--tw-ring-color:var(--color-primary-400)}.focus\:outline-hidden:focus{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.focus\:outline-hidden:focus{outline-offset:2px;outline:2px solid #0000}}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}@media (width>=40rem){.sm\:flex{display:flex}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (width>=48rem){.md\:h-9{height:calc(var(--spacing,.25rem) * 9)}.md\:px-3{padding-inline:calc(var(--spacing,.25rem) * 3)}}@media (width>=64rem){.lg\:mb-1{margin-bottom:var(--spacing,.25rem)}.lg\:block{display:block}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:items-end{align-items:flex-end}}@media (width>=80rem){.xl\:grid-cols-7{grid-template-columns:repeat(7,minmax(0,1fr))}}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@keyframes pulse{50%{opacity:.5}} /*$vite$:1*/ \ No newline at end of file diff --git a/resources/js/api/time.ts b/resources/js/api/time.ts new file mode 100644 index 0000000..69e678b --- /dev/null +++ b/resources/js/api/time.ts @@ -0,0 +1,212 @@ +import type { AxiosInstance } from 'axios' +import type { Paginated, Wrapped } from '@/types/api' +import type { CompanyMember } from '@/types/member' +import type { ModuleSettings } from '@/types/settings' +import type { TaskStatus, TaskStatusInput } from '@/types/task-status' +import type { TaskSummary } from '@/types/task-summary' +import type { TimeEntry, TimeEntryInput, TimeEntryListParams } from '@/types/time-entry' +import type { RunningTimer, StartTimerInput } from '@/types/timer' + +const BASE = '/api/v1/tasks-projects' + +/** The endpoints the time screens talk to. */ +export const TIME_API = { + timeEntries: `${BASE}/time-entries`, + timeEntry: (id: number): string => `${BASE}/time-entries/${id}`, + timer: `${BASE}/timer`, + timerStart: `${BASE}/timer/start`, + timerStop: `${BASE}/timer/stop`, + taskStatuses: `${BASE}/task-statuses`, + taskStatus: (id: number): string => `${BASE}/task-statuses/${id}`, + reorderTaskStatuses: `${BASE}/task-statuses/reorder`, + tasks: `${BASE}/tasks`, + task: (id: number): string => `${BASE}/tasks/${id}`, + members: `${BASE}/members`, + settings: `${BASE}/settings`, +} as const + +/** Host endpoints the module reads through the same session client. */ +export const HOST_TIME_API = { + bootstrap: '/api/v1/bootstrap', +} as const + +/** How many rows one list request asks for, and how many it may ever ask for. */ +export const TIME_PAGE_SIZE = 25 + +const WEEK_PAGE_SIZE = 100 +const MAX_WEEK_PAGES = 5 +const TASK_SEARCH_LIMIT = 10 + +export async function listTimeEntries( + client: AxiosInstance, + params: TimeEntryListParams, +): Promise> { + const { data } = await client.get>(TIME_API.timeEntries, { params }) + + return data +} + +/** + * Every entry of one range, rather than one page of them. + * + * The week grid has to show whole days, so it follows the paginator instead of + * cutting the last day in half. The page walk is bounded: a week with more + * than five hundred entries is a data problem, not a view to render. + */ +export async function listAllTimeEntries( + client: AxiosInstance, + params: TimeEntryListParams, +): Promise { + const entries: TimeEntry[] = [] + + for (let page = 1; page <= MAX_WEEK_PAGES; page += 1) { + const response = await listTimeEntries(client, { ...params, page, limit: WEEK_PAGE_SIZE }) + + entries.push(...(response.data ?? [])) + + if (!response.meta || page >= response.meta.last_page) { + break + } + } + + return entries +} + +export async function createTimeEntry( + client: AxiosInstance, + input: TimeEntryInput, +): Promise { + const { data } = await client.post>(TIME_API.timeEntries, input) + + return data.data +} + +export async function updateTimeEntry( + client: AxiosInstance, + id: number, + input: TimeEntryInput, +): Promise { + const { data } = await client.put>(TIME_API.timeEntry(id), input) + + return data.data +} + +export async function deleteTimeEntry(client: AxiosInstance, id: number): Promise { + await client.delete(TIME_API.timeEntry(id)) +} + +/** The caller's running entry, or null when the clock is not running. */ +export async function fetchTimer(client: AxiosInstance): Promise { + const { data } = await client.get(TIME_API.timer) + + return data?.data ?? null +} + +export async function startTimer( + client: AxiosInstance, + input: StartTimerInput, +): Promise { + const { data } = await client.post>(TIME_API.timerStart, input) + + return data.data +} + +export async function stopTimer(client: AxiosInstance): Promise { + const { data } = await client.post>(TIME_API.timerStop) + + return data.data +} + +export async function discardTimer(client: AxiosInstance): Promise { + await client.delete(TIME_API.timer) +} + +export async function listTaskStatuses(client: AxiosInstance): Promise { + const { data } = await client.get>(TIME_API.taskStatuses) + + return data.data ?? [] +} + +export async function createTaskStatus( + client: AxiosInstance, + input: TaskStatusInput, +): Promise { + const { data } = await client.post>(TIME_API.taskStatuses, input) + + return data.data +} + +export async function updateTaskStatus( + client: AxiosInstance, + id: number, + input: TaskStatusInput, +): Promise { + const { data } = await client.put>(TIME_API.taskStatus(id), input) + + return data.data +} + +export async function deleteTaskStatus(client: AxiosInstance, id: number): Promise { + await client.delete(TIME_API.taskStatus(id)) +} + +/** Apply the wanted column order; the endpoint answers with the new list. */ +export async function reorderTaskStatuses( + client: AxiosInstance, + ids: number[], +): Promise { + const { data } = await client.post>(TIME_API.reorderTaskStatuses, { ids }) + + return data.data ?? [] +} + +/** Tasks matching what the picker has typed so far. */ +export async function searchTasks( + client: AxiosInstance, + search: string, + limit = TASK_SEARCH_LIMIT, +): Promise { + const params: Record = { limit } + + if (search.trim() !== '') { + params.search = search.trim() + } + + const { data } = await client.get>(TIME_API.tasks, { params }) + + return data.data ?? [] +} + +export async function fetchTask(client: AxiosInstance, id: number): Promise { + const { data } = await client.get>(TIME_API.task(id)) + + return data.data +} + +/** The company's members, for the member filter and the "who logged it" column. */ +export async function listTimeMembers(client: AxiosInstance): Promise { + const { data } = await client.get>(TIME_API.members) + + return data.data ?? [] +} + +export async function fetchTimeSettings(client: AxiosInstance): Promise { + const { data } = await client.get>(TIME_API.settings) + + return data.data +} + +/** + * The signed-in user's id, read from the host bootstrap payload. + * + * A module bundle has no access to the host's user store, and the time + * endpoints answer "my time" only when they are asked for a specific + * `user_id`, so the id is fetched once per company session from the same + * round trip the shell itself uses. + */ +export async function fetchCurrentUserId(client: AxiosInstance): Promise { + const { data } = await client.get<{ current_user?: { id?: unknown } }>(HOST_TIME_API.bootstrap) + const id = data?.current_user?.id + + return typeof id === 'number' ? id : null +} diff --git a/resources/js/components/AllTimeTable.vue b/resources/js/components/AllTimeTable.vue new file mode 100644 index 0000000..1c1b3ef --- /dev/null +++ b/resources/js/components/AllTimeTable.vue @@ -0,0 +1,308 @@ + + + diff --git a/resources/js/components/QuickStartOverlay.vue b/resources/js/components/QuickStartOverlay.vue new file mode 100644 index 0000000..5087a1a --- /dev/null +++ b/resources/js/components/QuickStartOverlay.vue @@ -0,0 +1,264 @@ + + + diff --git a/resources/js/components/TaskStatusEditor.vue b/resources/js/components/TaskStatusEditor.vue new file mode 100644 index 0000000..26dbb01 --- /dev/null +++ b/resources/js/components/TaskStatusEditor.vue @@ -0,0 +1,364 @@ + + + diff --git a/resources/js/components/TimeEntryModal.vue b/resources/js/components/TimeEntryModal.vue new file mode 100644 index 0000000..e702966 --- /dev/null +++ b/resources/js/components/TimeEntryModal.vue @@ -0,0 +1,456 @@ + + + diff --git a/resources/js/components/TimerChip.vue b/resources/js/components/TimerChip.vue new file mode 100644 index 0000000..b4d8a3c --- /dev/null +++ b/resources/js/components/TimerChip.vue @@ -0,0 +1,73 @@ + + + diff --git a/resources/js/components/WeekTimesheet.vue b/resources/js/components/WeekTimesheet.vue new file mode 100644 index 0000000..028cf13 --- /dev/null +++ b/resources/js/components/WeekTimesheet.vue @@ -0,0 +1,257 @@ + + + diff --git a/resources/js/init.ts b/resources/js/init.ts index e3da209..6ae8eed 100644 --- a/resources/js/init.ts +++ b/resources/js/init.ts @@ -4,6 +4,7 @@ import type { InvoiceShelfExtensionApi } from '@invoiceshelf/modules/frontend' import '../css/module.css' import { messages } from './messages' import ProjectsIndexPage from './pages/ProjectsIndexPage.vue' +import { registerTimeTracking } from './registrations/time' import { registerBoardPages } from './registrations/board' const MODULE = 'tasks-projects' @@ -22,6 +23,7 @@ window.InvoiceShelf.booting((_app, _router, extensions) => { }, }) + registerTimeTracking(extensions) registerBoardPages(extensions) }) diff --git a/resources/js/messages/time.ts b/resources/js/messages/time.ts new file mode 100644 index 0000000..2a65515 --- /dev/null +++ b/resources/js/messages/time.ts @@ -0,0 +1,140 @@ +/** + * Every string the time screens render. + * + * Kept apart from the projects bundle so the two slices never edit the same + * file; the host merges both into one catalogue, so a template still reaches + * these with `$t('tasks_projects.time....')`. + */ +export const timeMessages = { + en: { + tasks_projects: { + time: { + title: 'Time', + my_time: 'My time', + all_time: 'All time', + this_week: 'This week', + previous_week: 'Previous week', + next_week: 'Next week', + week_total: 'Week total', + day_total: 'Total', + add_entry: 'Add entry', + new_entry: 'New time entry', + edit_entry: 'Edit time entry', + view_entry: 'Time entry', + no_entries: 'Nothing logged.', + empty_title: 'No time logged yet', + empty_description: 'Log an entry by hand, or start the timer on a task.', + unknown_member: 'Removed member', + unknown_user: 'Unable to identify the signed-in user. Reload the page and try again.', + billable: 'Billable', + non_billable: 'Not billable', + billed: 'Billed', + unbilled: 'Unbilled', + stamped_notice: + 'This entry is already on an invoice. Invoiced time is history and cannot be changed.', + created: 'The time entry was saved.', + updated: 'The time entry was updated.', + deleted: 'The time entry was deleted.', + delete_confirm: 'Delete this time entry?', + load_failed: 'Unable to load the time entries.', + save_failed: 'Unable to save the time entry.', + delete_failed: 'Unable to delete the time entry.', + members_failed: 'Unable to load the members.', + projects_failed: 'Unable to load the projects.', + tasks_failed: 'Unable to load the tasks.', + columns: { + date: 'Date', + member: 'Member', + task: 'Task', + description: 'Description', + duration: 'Duration', + billable: 'Billable', + amount: 'Amount', + }, + filters: { + member: 'Member', + project: 'Project', + from: 'From', + to: 'To', + billing: 'Billing', + all: 'All', + any_member: 'Everyone', + any_project: 'Any project', + }, + fields: { + task: 'Task', + task_placeholder: 'Search by task name or number', + date: 'Date', + mode: 'Entry', + duration: 'Duration', + duration_help: 'Hours and minutes, as 1:30 or 1.5.', + start: 'Start', + end: 'End', + description: 'Description', + billable: 'Billable', + }, + mode: { + duration: 'Duration', + range: 'Start and end', + }, + task_required: 'Pick a task.', + date_required: 'Pick a date.', + duration_invalid: 'Enter a duration like 1:30 or 1.5.', + range_invalid: 'Enter a start and an end time, with the end after the start.', + }, + timer: { + running: 'Timer running', + quick_start: 'Start a timer', + panel_title: 'Quick start', + start: 'Start', + stop: 'Stop', + discard: 'Discard', + close: 'Close', + open_timesheet: 'Open my time', + elapsed: 'Elapsed', + search_tasks: 'Search tasks', + no_tasks: 'No tasks match that search.', + description_placeholder: 'What are you working on? (optional)', + started: 'The timer is running on {name}.', + stopped: 'Logged {duration} on {name}.', + discarded: 'The running timer was discarded.', + discard_confirm: 'Discard the running timer? The elapsed time is not saved.', + already_running: 'A timer is already running. It has been reloaded.', + start_failed: 'Unable to start the timer.', + stop_failed: 'Unable to stop the timer.', + discard_failed: 'Unable to discard the timer.', + }, + settings: { + title: 'Tasks and Projects', + general_title: 'General', + general_description: + 'The default hourly rate, the rounding increment, the first day of the week and who may see other members time.', + open_module_settings: 'Open module settings', + statuses_title: 'Task statuses', + statuses_description: + 'The columns of the board. One status is the default, where new tasks land; a closed status counts as done.', + status_name: 'Name', + colour: 'Colour', + colour_none: 'None', + is_default: 'Default', + is_closed: 'Closed', + add_status: 'Add status', + new_status: 'New status', + move_up: 'Move up', + move_down: 'Move down', + no_statuses: 'No statuses yet.', + status_created: '{name} was added.', + status_updated: '{name} was updated.', + status_deleted: '{name} was deleted.', + status_reordered: 'The order was saved.', + status_delete_confirm: 'Delete {name}?', + status_name_required: 'Enter a status name.', + load_failed: 'Unable to load the task statuses.', + save_failed: 'Unable to save the task status.', + delete_failed: 'Unable to delete the task status.', + reorder_failed: 'Unable to save the new order.', + forbidden: 'Your role does not allow managing the board columns.', + }, + }, + }, +} diff --git a/resources/js/pages/TimePage.vue b/resources/js/pages/TimePage.vue new file mode 100644 index 0000000..f2415d0 --- /dev/null +++ b/resources/js/pages/TimePage.vue @@ -0,0 +1,233 @@ + + + diff --git a/resources/js/pages/TimeSettingsPage.vue b/resources/js/pages/TimeSettingsPage.vue new file mode 100644 index 0000000..e16c9b9 --- /dev/null +++ b/resources/js/pages/TimeSettingsPage.vue @@ -0,0 +1,57 @@ + + + diff --git a/resources/js/registrations/time.ts b/resources/js/registrations/time.ts new file mode 100644 index 0000000..faeedc1 --- /dev/null +++ b/resources/js/registrations/time.ts @@ -0,0 +1,147 @@ +import { defineComponent, h } from 'vue' +import type { Component } from 'vue' +import type { InvoiceShelfExtensionApi } from '@invoiceshelf/modules/frontend' +import QuickStartOverlay from '@/components/QuickStartOverlay.vue' +import TimerChip from '@/components/TimerChip.vue' +import { timeMessages } from '@/messages/time' +import TimePage from '@/pages/TimePage.vue' +import TimeSettingsPage from '@/pages/TimeSettingsPage.vue' +import { refreshSession, resetSession, session, setAdminMode } from '@/stores/session' +import { resetTaskNames } from '@/stores/tasks' +import { timerStore } from '@/stores/timer' + +type NotifyType = 'success' | 'error' | 'warning' | 'info' + +const MODULE = 'tasks-projects' + +/** Where `registerPage` mounts the timesheet, for the links that lead to it. */ +const TIME_PATH = `/admin/modules/${MODULE}/time` + +/** + * Everything the time-tracking slice contributes to the host. + * + * Kept in one file so `init.ts` only ever gains a line per slice: the page, the + * header chip, the quick-start launcher, the settings page and the lifecycle + * wiring all start here. + * + * Nothing in this function talks to the network. Pinia is not installed when + * the boot callback runs, so the first read waits for `bootstrap:completed`, + * and every later company switch clears the previous company's answers before + * asking again. + */ +export function registerTimeTracking(extensions: InvoiceShelfExtensionApi): void { + extensions.addMessages(timeMessages) + + const notify = (type: NotifyType, message: string): void => { + extensions.notify(type, message) + } + + const openTimesheet = (): void => { + void extensions.router.push(TIME_PATH) + } + + extensions.registerPage({ + id: 'time', + module: MODULE, + path: 'time', + component: injected(extensions, TimePage), + meta: { + ability: `${MODULE}:view-own-time`, + title: 'tasks_projects.time.title', + }, + }) + + extensions.registerHeaderAction({ + id: `${MODULE}.timer-chip`, + priority: 30, + visible: (): boolean => timerStore.running !== null, + component: defineComponent({ + setup: () => () => + h(TimerChip, { + client: extensions.client, + notify, + onOpen: openTimesheet, + }), + }), + }) + + extensions.registerCompanyLayoutOverlay({ + id: `${MODULE}.quick-start`, + component: defineComponent({ + setup: () => () => + h(QuickStartOverlay, { + // A company switch starts the launcher clean rather than carrying a + // half-typed search from the workspace the user just left. + key: session.companySession, + client: extensions.client, + notify, + enabled: !session.adminMode, + onOpenTimesheet: openTimesheet, + }), + }), + }) + + extensions.registerCompanySettingsPage({ + id: `${MODULE}.settings`, + title: 'tasks_projects.settings.title', + icon: 'ClockIcon', + path: MODULE, + priority: 70, + component: injected(extensions, TimeSettingsPage), + }) + + extensions.on('bootstrap:completed', ({ adminMode }) => { + void enter(extensions, adminMode) + }) + + extensions.on('company:changing', () => { + leave() + }) + + extensions.on('company:changed', ({ companyId }) => { + void enter(extensions, companyId === null) + }) +} + +/** Read the company's settings and the caller's running timer. */ +async function enter(extensions: InvoiceShelfExtensionApi, adminMode: boolean): Promise { + setAdminMode(adminMode) + + if (adminMode) { + leave() + + return + } + + await refreshSession(extensions.client) + await timerStore.refresh(extensions.client) +} + +/** Forget the previous company: its timer, its task names and its settings. */ +function leave(): void { + timerStore.reset() + resetTaskNames() + resetSession() +} + +/** + * Hand a page the host services it cannot reach on its own. + * + * The same wrapper `init.ts` uses for the projects page: a module bundle runs + * on the host's Vue instance but not on its Pinia or router injections, so the + * client, the notifier and the router arrive as props, and route params arrive + * as attrs because the host registers module pages with `props: true`. + */ +function injected(extensions: InvoiceShelfExtensionApi, page: Component): Component { + return defineComponent({ + setup: (_props, { attrs }) => () => + h(page, { + ...attrs, + client: extensions.client, + notify: (type: NotifyType, message: string): void => { + extensions.notify(type, message) + }, + router: extensions.router, + }), + }) +} diff --git a/resources/js/stores/session.ts b/resources/js/stores/session.ts new file mode 100644 index 0000000..4f153b9 --- /dev/null +++ b/resources/js/stores/session.ts @@ -0,0 +1,109 @@ +import { reactive } from 'vue' +import type { AxiosInstance } from 'axios' +import { fetchCurrentUserId, fetchTimeSettings } from '@/api/time' +import type { ModuleSettings } from '@/types/settings' + +/** + * What the time screens need to know about the current session. + * + * A module bundle runs on the host's Vue instance but not on its Pinia, so it + * cannot read the user or company stores. The two facts the timesheet cannot + * work without, the signed-in user's id and the company's module settings, are + * fetched once per company and kept here, where the page, the overlay and the + * settings editor all reach them. + * + * Everything degrades rather than throws: a member who may not read the module + * settings still gets the defaults, and a missing user id only means the "My + * time" view asks the caller to reload. + */ + +export const DEFAULT_SETTINGS: ModuleSettings = { + default_rate: 0, + rounding_minutes: 1, + week_start: 1, + members_see_all_time: false, + rounding_increments: [1, 6, 15, 30], +} + +interface SessionState { + /** True while the shell is in platform administration, where no company is active. */ + adminMode: boolean + userId: number | null + settings: ModuleSettings + /** Bumped on every company change so views can key off it and start clean. */ + companySession: number + loading: boolean +} + +export const session = reactive({ + adminMode: false, + userId: null, + settings: { ...DEFAULT_SETTINGS }, + companySession: 0, + loading: false, +}) + +/** Read the user id and the company settings for the active company. */ +export async function refreshSession(client: AxiosInstance): Promise { + if (session.adminMode) { + return + } + + session.loading = true + + const [userId, settings] = await Promise.all([ + fetchCurrentUserId(client).catch((): null => null), + fetchTimeSettings(client).catch((): null => null), + ]) + + session.userId = userId + session.settings = normaliseSettings(settings) + session.loading = false +} + +/** Forget the previous company's answers before the next one loads. */ +export function resetSession(): void { + session.userId = null + session.settings = { ...DEFAULT_SETTINGS } + session.companySession += 1 + session.loading = false +} + +export function setAdminMode(adminMode: boolean): void { + session.adminMode = adminMode +} + +/** + * Settings as the module promises them, whatever the endpoint answered. + * + * The payload is host data crossing a module boundary, so every field is + * checked rather than trusted: a missing settings endpoint, an older module + * version or a 403 all end up as the documented defaults. + */ +function normaliseSettings(settings: ModuleSettings | null): ModuleSettings { + if (settings === null || typeof settings !== 'object') { + return { ...DEFAULT_SETTINGS } + } + + const increments = Array.isArray(settings.rounding_increments) + ? settings.rounding_increments.filter((value): value is number => typeof value === 'number') + : DEFAULT_SETTINGS.rounding_increments + + return { + default_rate: numberOr(settings.default_rate, DEFAULT_SETTINGS.default_rate), + rounding_minutes: numberOr(settings.rounding_minutes, DEFAULT_SETTINGS.rounding_minutes), + week_start: weekStartOr(settings.week_start), + members_see_all_time: settings.members_see_all_time === true, + rounding_increments: increments.length > 0 ? increments : DEFAULT_SETTINGS.rounding_increments, + } +} + +function numberOr(value: unknown, fallback: number): number { + return typeof value === 'number' && Number.isFinite(value) ? value : fallback +} + +function weekStartOr(value: unknown): number { + return typeof value === 'number' && Number.isInteger(value) && value >= 0 && value <= 6 + ? value + : DEFAULT_SETTINGS.week_start +} diff --git a/resources/js/stores/tasks.ts b/resources/js/stores/tasks.ts new file mode 100644 index 0000000..70bb972 --- /dev/null +++ b/resources/js/stores/tasks.ts @@ -0,0 +1,74 @@ +import { reactive } from 'vue' +import type { AxiosInstance } from 'axios' +import { fetchTask } from '@/api/time' +import type { TaskSummary } from '@/types/task-summary' + +/** + * A name for every task id the time screens display. + * + * `TimeEntryResource` carries `task_id` and nothing else, so a timesheet row, + * the header chip and the entry editor would all show a bare number. The cache + * fills in the names, once per task per company session, and a task that + * cannot be read keeps its id as the label rather than blanking the row. + */ + +const names = reactive>({}) +const pending = new Set() + +/** How many name lookups may be in flight at once. */ +const BATCH_SIZE = 5 + +/** The cached name, or a stable `#id` placeholder to render meanwhile. */ +export function taskLabel(id: number | null): string { + if (id === null) { + return '' + } + + return names[id] ?? `#${id}` +} + +/** Remember a task the caller already holds, so no lookup is needed. */ +export function rememberTask(task: TaskSummary | null | undefined): void { + if (task && typeof task.id === 'number' && typeof task.name === 'string') { + names[task.id] = task.name + } +} + +/** + * Make sure every id given has a name, fetching the ones that do not. + * + * Failures are swallowed on purpose: a missing name is cosmetic, and the + * timesheet must render even when one task has been deleted under it. + */ +export async function ensureTaskNames(client: AxiosInstance, ids: number[]): Promise { + const wanted = [...new Set(ids)].filter( + (id) => typeof id === 'number' && names[id] === undefined && !pending.has(id), + ) + + for (const id of wanted) { + pending.add(id) + } + + for (let index = 0; index < wanted.length; index += BATCH_SIZE) { + await Promise.all( + wanted.slice(index, index + BATCH_SIZE).map(async (id) => { + try { + rememberTask(await fetchTask(client, id)) + } catch { + // A task that cannot be read keeps its id as its label. + } finally { + pending.delete(id) + } + }), + ) + } +} + +/** Drop everything: task ids belong to one company. */ +export function resetTaskNames(): void { + for (const key of Object.keys(names)) { + delete names[Number(key)] + } + + pending.clear() +} diff --git a/resources/js/stores/timer.ts b/resources/js/stores/timer.ts new file mode 100644 index 0000000..80fc19c --- /dev/null +++ b/resources/js/stores/timer.ts @@ -0,0 +1,213 @@ +import { reactive } from 'vue' +import type { AxiosInstance } from 'axios' +import { + discardTimer, + fetchTimer, + startTimer, + stopTimer, +} from '@/api/time' +import { errorMessage } from '@/support/errors' +import { isConflict } from '@/support/http' +import { secondsSince } from '@/support/time' +import type { Translate } from '@/support/i18n' +import type { TimeEntry } from '@/types/time-entry' +import { ensureTaskNames } from './tasks' + +/** + * The running timer, shared by the header chip, the quick-start launcher and + * the timesheet. + * + * A module bundle has no Pinia, so this is a plain reactive singleton. The + * elapsed time is recomputed from `started_at` on every tick rather than + * counted up, so a throttled background tab, a sleeping laptop and a clock + * correction all land on the right number at the next tick. + * + * Nothing here throws at a caller: a failed refresh leaves the chip hidden + * rather than breaking the header it renders in. + */ + +type NotifyType = 'success' | 'error' | 'warning' | 'info' + +/** + * How a caller wants failures reported. The translator comes from the calling + * component, because a store cannot reach the host's i18n on its own. + */ +export interface TimerFeedback { + notify: (type: NotifyType, message: string) => void + t: Translate +} + +interface TimerState { + running: TimeEntry | null + elapsedSeconds: number + /** True while a start, stop or discard is in flight, to disable the buttons. */ + busy: boolean +} + +const state = reactive({ + running: null, + elapsedSeconds: 0, + busy: false, +}) + +let ticker: ReturnType | undefined + +function tick(): void { + state.elapsedSeconds = state.running === null ? 0 : secondsSince(state.running.started_at) +} + +function startTicking(): void { + tick() + + if (ticker === undefined) { + ticker = setInterval(tick, 1000) + } +} + +function stopTicking(): void { + if (ticker !== undefined) { + clearInterval(ticker) + ticker = undefined + } + + state.elapsedSeconds = 0 +} + +/** Adopt a payload as the running entry, or clear the clock when it is null. */ +function adopt(entry: TimeEntry | null, client?: AxiosInstance): void { + state.running = entry && typeof entry.id === 'number' ? entry : null + + if (state.running === null) { + stopTicking() + + return + } + + startTicking() + + if (client && typeof state.running.task_id === 'number') { + void ensureTaskNames(client, [state.running.task_id]) + } +} + +function report(feedback: TimerFeedback | undefined, error: unknown, key: string): void { + feedback?.notify('error', errorMessage(error, feedback.t(key))) +} + +export const timerStore = { + /** The running entry, or null when the clock is not running. */ + get running(): TimeEntry | null { + return state.running + }, + + /** Seconds since the running entry started, recomputed every second. */ + get elapsedSeconds(): number { + return state.elapsedSeconds + }, + + /** True while a timer request is in flight. */ + get busy(): boolean { + return state.busy + }, + + /** Read the caller's running entry from the server. */ + async refresh(client: AxiosInstance): Promise { + try { + adopt(await fetchTimer(client), client) + } catch { + // The header chip stays hidden rather than reporting a background read. + adopt(null) + } + }, + + /** + * Start the clock on a task. + * + * A 409 means another tab got there first, which is not an error the user + * caused: it is reported and the real running entry is read back. + */ + async start( + client: AxiosInstance, + taskId: number, + description: string | null = null, + feedback?: TimerFeedback, + ): Promise { + if (state.busy) { + return null + } + + state.busy = true + + try { + const entry = await startTimer(client, { task_id: taskId, description }) + + adopt(entry, client) + + return entry + } catch (error: unknown) { + if (isConflict(error)) { + feedback?.notify('warning', feedback.t('tasks_projects.timer.already_running')) + await this.refresh(client) + } else { + report(feedback, error, 'tasks_projects.timer.start_failed') + } + + return null + } finally { + state.busy = false + } + }, + + /** Close the running entry and answer the completed one. */ + async stop(client: AxiosInstance, feedback?: TimerFeedback): Promise { + if (state.busy || state.running === null) { + return null + } + + state.busy = true + + try { + const entry = await stopTimer(client) + + adopt(null) + + return entry + } catch (error: unknown) { + report(feedback, error, 'tasks_projects.timer.stop_failed') + await this.refresh(client) + + return null + } finally { + state.busy = false + } + }, + + /** Throw the running entry away without recording any time. */ + async discard(client: AxiosInstance, feedback?: TimerFeedback): Promise { + if (state.busy || state.running === null) { + return false + } + + state.busy = true + + try { + await discardTimer(client) + adopt(null) + + return true + } catch (error: unknown) { + report(feedback, error, 'tasks_projects.timer.discard_failed') + await this.refresh(client) + + return false + } finally { + state.busy = false + } + }, + + /** Forget the clock, for a company switch or a sign-out. */ + reset(): void { + state.busy = false + adopt(null) + }, +} diff --git a/resources/js/support/http.ts b/resources/js/support/http.ts new file mode 100644 index 0000000..3c9ee01 --- /dev/null +++ b/resources/js/support/http.ts @@ -0,0 +1,27 @@ +/** + * The status of a failed request, read structurally. + * + * The module bundle runs on the host's axios instance and never imports axios + * at runtime, so the error is inspected rather than narrowed with + * `axios.isAxiosError`. A missing or unparseable response answers null, which + * callers treat as "some other failure". + */ +export function errorStatus(error: unknown): number | null { + if (typeof error !== 'object' || error === null) { + return null + } + + const status = (error as { response?: { status?: unknown } }).response?.status + + return typeof status === 'number' ? status : null +} + +/** A 409 from `timer/start`: someone else's tab already started the clock. */ +export function isConflict(error: unknown): boolean { + return errorStatus(error) === 409 +} + +/** A 403: the caller lacks the ability the endpoint asks for. */ +export function isForbidden(error: unknown): boolean { + return errorStatus(error) === 403 +} diff --git a/resources/js/support/time.ts b/resources/js/support/time.ts new file mode 100644 index 0000000..72f73e4 --- /dev/null +++ b/resources/js/support/time.ts @@ -0,0 +1,201 @@ +/** + * Clocks, durations and weeks. + * + * The API stores instants in UTC and durations in whole minutes; the timesheet + * talks in the viewer's own day, so every conversion here goes through the + * browser's local time zone and never through a string comparison of two + * differently offset timestamps. + */ + +const MINUTES_PER_HOUR = 60 +const SECONDS_PER_MINUTE = 60 +const DAYS_PER_WEEK = 7 + +/** Seconds as `h:mm:ss`, which is what a running timer shows. */ +export function formatClock(seconds: number): string { + const total = Number.isFinite(seconds) && seconds > 0 ? Math.floor(seconds) : 0 + const hours = Math.floor(total / (SECONDS_PER_MINUTE * MINUTES_PER_HOUR)) + const minutes = Math.floor((total % (SECONDS_PER_MINUTE * MINUTES_PER_HOUR)) / SECONDS_PER_MINUTE) + const rest = total % SECONDS_PER_MINUTE + + return `${hours}:${pad(minutes)}:${pad(rest)}` +} + +/** Minutes as `h:mm`, which is how a logged duration is written and typed. */ +export function formatDuration(minutes: number | null): string { + const total = minutes !== null && Number.isFinite(minutes) && minutes > 0 ? Math.round(minutes) : 0 + + return `${Math.floor(total / MINUTES_PER_HOUR)}:${pad(total % MINUTES_PER_HOUR)}` +} + +/** + * A typed duration back to minutes. + * + * Both notations people actually use are accepted: `1:30` and the decimal + * `1.5`. Anything else answers null so the caller can mark the field invalid + * rather than silently logging zero. + */ +export function parseDuration(value: string): number | null { + const text = value.trim() + + if (text === '') { + return null + } + + const clock = /^(\d+):([0-5]?\d)$/.exec(text) + + if (clock) { + return Number(clock[1]) * MINUTES_PER_HOUR + Number(clock[2]) + } + + if (!/^\d+([.,]\d+)?$/.test(text)) { + return null + } + + const hours = Number(text.replace(',', '.')) + + return Number.isNaN(hours) ? null : Math.round(hours * MINUTES_PER_HOUR) +} + +/** The local calendar date of an instant, as the `Y-m-d` the API takes. */ +export function localDateOf(instant: string | null): string { + const date = parseInstant(instant) + + return date === null ? '' : formatLocalDate(date) +} + +/** The local wall-clock time of an instant, as the `HH:MM` an input shows. */ +export function localTimeOf(instant: string | null): string { + const date = parseInstant(instant) + + return date === null ? '' : `${pad(date.getHours())}:${pad(date.getMinutes())}` +} + +/** + * A local date and an optional `HH:MM` back to the instant the API stores. + * + * The pair is read as local wall-clock time, so an entry typed as "the 3rd, + * 09:00" stays on the 3rd at 09:00 for the person who typed it whatever their + * offset is. A duration-only entry gets a default hour rather than midnight, + * which would land on the previous day for anyone east of UTC. + */ +export function localInstant(date: string, time = '09:00'): string | null { + const day = parseDateString(date) + const clock = /^(\d{1,2}):([0-5]\d)$/.exec(time.trim()) + + if (day === null || clock === null) { + return null + } + + const hours = Number(clock[1]) + + if (hours > 23) { + return null + } + + day.setHours(hours, Number(clock[2]), 0, 0) + + return day.toISOString() +} + +/** The same instant moved by whole minutes, for the end of a typed duration. */ +export function addMinutes(instant: string, minutes: number): string { + const date = new Date(instant) + + date.setTime(date.getTime() + minutes * SECONDS_PER_MINUTE * 1000) + + return date.toISOString() +} + +/** + * The first day of the week `date` falls in. + * + * `weekStart` is the company setting, 0 for Sunday through 6 for Saturday; an + * out-of-range value falls back to Monday rather than shifting the grid. + */ +export function startOfWeek(date: Date, weekStart: number): Date { + const first = Number.isInteger(weekStart) && weekStart >= 0 && weekStart <= 6 ? weekStart : 1 + const start = startOfDay(date) + const shift = (start.getDay() - first + DAYS_PER_WEEK) % DAYS_PER_WEEK + + start.setDate(start.getDate() - shift) + + return start +} + +/** The seven days of the week beginning at `start`. */ +export function weekDays(start: Date): Date[] { + return Array.from({ length: DAYS_PER_WEEK }, (_unused, index) => addDays(start, index)) +} + +export function addDays(date: Date, days: number): Date { + const shifted = startOfDay(date) + + shifted.setDate(shifted.getDate() + days) + + return shifted +} + +/** A `Date` as the `Y-m-d` the API takes, in local time. */ +export function formatLocalDate(date: Date): string { + return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}` +} + +/** The weekday and day-of-month a column of the week grid is labelled with. */ +export function dayLabel(date: Date): { weekday: string; day: string } { + return { + weekday: date.toLocaleDateString(undefined, { weekday: 'short' }), + day: date.toLocaleDateString(undefined, { day: 'numeric', month: 'short' }), + } +} + +/** Whether a date is today, so the grid can mark the column. */ +export function isToday(date: Date): boolean { + return formatLocalDate(date) === formatLocalDate(new Date()) +} + +/** Seconds elapsed since an instant, never negative and never NaN. */ +export function secondsSince(instant: string | null): number { + const start = parseInstant(instant) + + if (start === null) { + return 0 + } + + return Math.max(0, Math.floor((Date.now() - start.getTime()) / 1000)) +} + +function parseInstant(instant: string | null): Date | null { + if (!instant) { + return null + } + + const date = new Date(instant) + + return Number.isNaN(date.getTime()) ? null : date +} + +/** A `Y-m-d` at local midnight. Anything else answers null. */ +function parseDateString(value: string): Date | null { + const match = /^(\d{4})-(\d{2})-(\d{2})/.exec(value.trim()) + + if (match === null) { + return null + } + + const date = new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]), 0, 0, 0, 0) + + return Number.isNaN(date.getTime()) ? null : date +} + +function startOfDay(date: Date): Date { + const copy = new Date(date.getTime()) + + copy.setHours(0, 0, 0, 0) + + return copy +} + +function pad(value: number): string { + return String(value).padStart(2, '0') +} diff --git a/resources/js/types/task-status.ts b/resources/js/types/task-status.ts index bdc7b55..55130af 100644 --- a/resources/js/types/task-status.ts +++ b/resources/js/types/task-status.ts @@ -13,3 +13,11 @@ export interface TaskStatus { created_at: string | null updated_at: string | null } + +/** What the create and update endpoints accept. */ +export interface TaskStatusInput { + name?: string + colour?: string | null + is_default?: boolean + is_closed?: boolean +} diff --git a/resources/js/types/task-summary.ts b/resources/js/types/task-summary.ts new file mode 100644 index 0000000..7b4e224 --- /dev/null +++ b/resources/js/types/task-summary.ts @@ -0,0 +1,11 @@ +/** + * The few fields of a task the time screens need: enough to search for one, + * label an entry and know whether logging against it is billable by default. + */ +export interface TaskSummary { + id: number + name: string + number: number | null + project_id: number | null + billable: boolean +} diff --git a/resources/js/types/time-entry.ts b/resources/js/types/time-entry.ts index 550f2d8..ea1c01b 100644 --- a/resources/js/types/time-entry.ts +++ b/resources/js/types/time-entry.ts @@ -1,7 +1,10 @@ /** - * Logged time, as `TimeEntryResource` renders it. Durations are minutes, - * `rate` is minor units per hour and `amount` is the frozen money on the - * entry, also in minor units. + * Logged time, as `TimeEntryResource` renders it. + * + * Durations are minutes, `rate` is minor units per hour and `amount` is the + * money frozen on the entry when it was saved, also in minor units. An entry + * carrying an `invoice_id` is stamped: it belongs to an invoice and the API + * refuses to delete it. */ export interface TimeEntry { id: number @@ -25,12 +28,27 @@ export interface TimeEntry { updated_at: string | null } +/** What the create and update endpoints accept. */ +export interface TimeEntryInput { + task_id: number + user_id?: number | null + started_at?: string | null + ended_at?: string | null + duration_minutes?: number | null + description?: string | null + billable?: boolean +} + export interface TimeEntryListParams { page?: number limit?: number + user_id?: number project_id?: number task_id?: number - user_id?: number + /** `Y-m-d`, inclusive. */ from?: string + /** `Y-m-d`, inclusive. */ to?: string + billable?: boolean + billed?: boolean } diff --git a/resources/js/types/timer.ts b/resources/js/types/timer.ts new file mode 100644 index 0000000..a17fff8 --- /dev/null +++ b/resources/js/types/timer.ts @@ -0,0 +1,15 @@ +import type { TimeEntry } from './time-entry' + +/** + * The timer endpoint answers with the caller's running entry or with null, so + * the payload is wrapped rather than a bare resource. + */ +export interface RunningTimer { + data: TimeEntry | null +} + +/** What `timer/start` accepts. */ +export interface StartTimerInput { + task_id: number + description?: string | null +} diff --git a/tests/Feature/ModuleRegistrationTest.php b/tests/Feature/ModuleRegistrationTest.php index b20de87..6244183 100644 --- a/tests/Feature/ModuleRegistrationTest.php +++ b/tests/Feature/ModuleRegistrationTest.php @@ -25,7 +25,7 @@ public function test_it_registers_a_local_script_style_sidebar_entry_and_setting self::assertSame([ 'group' => 'modules', 'group_label' => 'navigation.modules', - 'priority' => 100, + 'priority' => 10, 'title' => 'tasksprojects::menu.title', 'link' => '/admin/modules/tasks-projects', 'icon' => 'ClipboardDocumentListIcon',