diff --git a/dist/init.js b/dist/init.js index e943ed0..9209649 100644 --- a/dist/init.js +++ b/dist/init.js @@ -1,6 +1,6 @@ -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; +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, onScopeDispose: g, openBlock: _, reactive: v, ref: y, renderList: b, resolveComponent: x, toDisplayString: S, unref: C, vModelText: w, vShow: T, watch: E, withCtx: D, withDirectives: O, withKeys: k, withModifiers: A } = window.__invoiceshelf_vue; //#region resources/js/messages.ts -var A = { en: { tasks_projects: { +var j = { en: { tasks_projects: { general: { home: "Home", filter: "Filter", @@ -61,87 +61,222 @@ var A = { en: { tasks_projects: { delete_failed: "Unable to delete the project.", customers_failed: "Unable to load the customers." } -} } }, 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" }; -function P(e, t) { - if (e === void 0 || e.order === "") return {}; - let n = t[e.fieldName]; - return n === void 0 ? {} : { - sort_by: n, - sort_order: e.order - }; +} } }, M = { en: { tasks_projects: { billing: { + title: "Invoice time", + subtitle: "Turn unbilled hours into a draft invoice.", + invoice_time: "Invoice time", + unbilled: "Unbilled", + view_unbilled: "Invoice this time", + steps: { + customer: "Customer", + entries: "Entries", + preview: "Preview", + create: "Create" + }, + back: "Back", + next: "Continue", + start_over: "Start over", + customer: { + title: "Who are you invoicing?", + description: "Customers with billable time that has not reached an invoice yet.", + entries: "{count} entries", + empty_title: "Nothing to invoice", + empty_description: "Billable time appears here once it has been logged against a task that belongs to a customer.", + load_failed: "Unable to load the customers with unbilled time.", + names_failed: "Unable to load the customer names; ids are shown instead.", + unnamed: "Customer #{id}", + from: "From", + to: "To", + clear_range: "Clear dates" + }, + entries: { + title: "Which time goes on the invoice?", + grouping: "Group lines by", + group_by: { + task: "Task", + project: "Project", + member: "Member", + summary: "One summary line" + }, + select_all: "Select all", + selected: "{count} of {total} entries selected", + selected_total: "Selected: {hours}", + no_description: "No description", + columns: { + date: "Date", + task: "Task", + project: "Project", + member: "Member", + duration: "Duration", + amount: "Amount" + }, + empty_title: "No unbilled time", + empty_description: "This customer has nothing waiting to be invoiced in this range.", + load_failed: "Unable to load the unbilled time.", + none_selected: "Select at least one entry." + }, + preview: { + title: "Check the invoice", + lines: "Invoice lines", + columns: { + description: "Description", + quantity: "Hours", + price: "Rate", + total: "Amount" + }, + sub_total: "Subtotal", + total: "Total", + invoice_date: "Invoice date", + due_date: "Due date", + invoice_number: "Invoice number", + invoice_number_auto: "Generated by the company number format.", + template: "Template", + exchange_rate: "Exchange rate", + exchange_rate_help: "1 {currency} in the company currency.", + prepare_failed: "Unable to prepare the invoice.", + templates_failed: "Unable to load the invoice templates.", + number_failed: "Unable to read the next invoice number. Type one in.", + rate_failed: "Unable to read the exchange rate. Type one in.", + create: "Create invoice", + invalid: "The invoice was refused. Fix the fields below and try again." + }, + create: { + creating: "Creating the invoice", + stamping: "Marking the time as invoiced", + created_title: "Invoice {number} created", + created_description: "{count} entries were marked as invoiced.", + view_invoice: "Open the invoice", + invoice_more: "Invoice more time", + failed: "Unable to create the invoice.", + stamp_failed_title: "The invoice was created, but the time is not marked yet", + stamp_failed_description: "Invoice {number} exists. The time entries still count as unbilled until they are stamped, which is safe to run again.", + retry_stamp: "Retry stamping", + stamped: "The time entries were marked as invoiced." + } +} } } }, N = "/api/v1/tasks-projects", P = { + customers: `${N}/billing/customers`, + unbilled: `${N}/billing/unbilled`, + prepare: `${N}/billing/prepare`, + confirm: `${N}/billing/confirm` +}, F = { + bootstrap: "/api/v1/bootstrap", + customers: "/api/v1/customers", + invoices: "/api/v1/invoices", + invoiceTemplates: "/api/v1/invoices/templates", + nextNumber: "/api/v1/next-number", + exchangeRate: (e) => `/api/v1/currencies/${e}/exchange-rate` +}; +async function ee(e, t = {}) { + let { data: n } = await e.get(P.customers, { params: t }); + return n.data ?? []; } -async function F(e, t) { - let { data: n } = await e.get(M.projects, { params: t }); - return n; +async function I(e, t, n = {}) { + let { data: r } = await e.get(P.unbilled, { params: { + customer_id: t, + ...n + } }); + return r.data; } -async function I(e, t) { - let { data: n } = await e.post(M.projects, t); - return n.data; +async function te(e, t, n) { + let { data: r } = await e.post(P.prepare, { + entry_ids: t, + grouping: n + }); + return r.data; } async function L(e, t, n) { - let { data: r } = await e.put(M.project(t), n); - return r.data; + let { data: r } = await e.post(P.confirm, { + invoice_id: t, + items: n + }); + return r?.stamped ?? 0; } -async function R(e, t) { - let { data: n } = await e.post(M.archiveProject(t)); - return n.data; +async function ne(e, t = 200) { + let { data: n } = await e.get(F.customers, { params: { limit: t } }); + return n.data ?? []; } -async function z(e, t) { - let { data: n } = await e.post(M.unarchiveProject(t)); +async function re(e, t) { + let { data: n } = await e.post(F.invoices, t); return n.data; } -async function B(e, t) { - await e.delete(M.project(t)); +async function ie(e) { + let { data: t } = await e.get(F.invoiceTemplates); + return t?.invoiceTemplates ?? []; } -async function V(e) { - let { data: t } = await e.get(M.members); - return t.data; +async function ae(e, t) { + let n = { key: "invoice" }; + t !== void 0 && (n.userId = t); + let { data: r } = await e.get(F.nextNumber, { params: n }); + return r?.success && typeof r.nextNumber == "string" ? r.nextNumber : null; } -async function H(e, t = 100) { - let { data: n } = await e.get(N.customers, { params: { limit: t } }); - return n.data; +async function oe(e, t) { + let { data: n } = await e.get(F.exchangeRate(t)), r = Array.isArray(n?.exchangeRate) ? n.exchangeRate[0] : n?.exchangeRate, i = Number(r); + return Number.isFinite(i) && i > 0 ? i : null; +} +async function se(e) { + let { data: t } = await e.get(F.bootstrap), n = t?.current_company_settings ?? {}, r = t?.current_user_settings ?? {}, i = Number(n.invoice_due_date_days), a = r.default_invoice_template; + return { + currency: t?.current_company_currency ?? null, + dueDateDays: Number.isFinite(i) && i >= 0 ? i : 0, + setDueDateAutomatically: n.invoice_set_due_date_automatically === "YES", + autoGenerateNumber: n.invoice_auto_generate !== "NO", + defaultTemplate: typeof a == "string" && a !== "" ? a : null + }; } //#endregion //#region resources/js/support/errors.ts -function ee(e) { +function ce(e) { if (typeof e != "object" || !e) return null; let t = e.response; return typeof t?.data != "object" || t.data === null ? null : t.data; } -function U(e, t) { - let n = ee(e)?.message; +function R(e, t) { + let n = ce(e)?.message; return typeof n == "string" && n !== "" ? n : t; } -function te(e) { - let t = ee(e)?.errors, n = {}; +function le(e) { + let t = ce(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; } //#endregion +//#region resources/js/support/http.ts +function ue(e) { + if (typeof e != "object" || !e) return null; + let t = e.response?.status; + return typeof t == "number" ? t : null; +} +function z(e) { + if (typeof e != "object" || !e) return null; + let t = e.response?.data; + if (typeof t != "object" || !t) return null; + let n = t.error; + return typeof n == "string" && n !== "" ? n : null; +} +function de(e) { + return ue(e) === 409; +} +function fe(e) { + return ue(e) === 403; +} +//#endregion //#region resources/js/support/format.ts -function ne(e) { +function pe(e) { return e === null ? "" : String(e / 100); } -function re(e) { +function me(e) { let t = Number(e); return e.trim() === "" || Number.isNaN(t) ? null : Math.round(t * 100); } -function ie(e) { +function he(e) { return e === null ? "" : String(e / 60); } -function ae(e) { +function ge(e) { let t = Number(e); return e.trim() === "" || Number.isNaN(t) ? null : Math.round(t * 60); } -function oe(e) { +function _e(e) { if (!e) return ""; let [t, n, r] = e.slice(0, 10).split("-").map(Number); return !t || !n || !r ? e : new Date(Date.UTC(t, n - 1, r)).toLocaleDateString(void 0, { @@ -151,666 +286,1402 @@ function oe(e) { timeZone: "UTC" }); } -function se(e) { +function ve(e) { if (typeof e == "string") return e.slice(0, 10); let t = String(e.getMonth() + 1).padStart(2, "0"), n = String(e.getDate()).padStart(2, "0"); return `${e.getFullYear()}-${t}-${n}`; } -function ce(e) { +function ye(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 le(e) { +function be(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 ue(e) { +function xe(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 de() { +function B() { return u()?.appContext.config.globalProperties.$t ?? ((e) => e); } //#endregion -//#region resources/js/components/ProjectFormModal.vue?vue&type=script&setup=true&lang.ts -var fe = { class: "flex w-full items-center justify-between" }, pe = { class: "space-y-5 px-6 py-6" }, me = { class: "flex flex-wrap items-center gap-2" }, he = ["aria-label", "onClick"], ge = { class: "flex justify-end space-x-3 border-t border-line-default px-6 py-4" }, _e = /* @__PURE__ */ l({ - __name: "ProjectFormModal", +//#region resources/js/support/page.ts +var V = "tasks-projects", Se = `/admin/modules/${V}`, H = { + tasks: Se, + board: `${Se}/board`, + week: `${Se}/week`, + task: (e) => `${Se}/tasks/${e}`, + projects: `${Se}/projects`, + project: (e) => `${Se}/projects/${e}`, + reports: `${Se}/reports`, + settings: "/admin/settings/modules", + customer: (e) => `/admin/customers/${e}/view` +}, Ce = { + tasks: `extension.page.${V}.tasks`, + list: `extension.page.${V}.tasks.list`, + board: `extension.page.${V}.tasks.board`, + week: `extension.page.${V}.tasks.week`, + task: `extension.page.${V}.task`, + projects: `extension.page.${V}.projects`, + project: `extension.page.${V}.project` +}; +function we(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/pages/BillingPage.vue?vue&type=script&setup=true&lang.ts +var Te = { class: "flex items-center justify-end space-x-5" }, Ee = { class: "mt-6 flex flex-wrap items-center gap-x-6 gap-y-3" }, De = { + key: 0, + class: "flex justify-center py-16" +}, Oe = { + key: 1, + class: "mt-6" +}, ke = { class: "text-base font-semibold text-heading" }, Ae = { class: "mt-1 text-sm text-muted" }, je = { class: "mt-4 flex flex-wrap items-end gap-4" }, Me = { + key: 0, + class: "mt-5 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3" +}, Ne = ["onClick"], Pe = { class: "text-sm font-semibold text-heading" }, Fe = { class: "mt-1 text-xs text-muted" }, Ie = { class: "mt-3 text-xl font-semibold text-heading" }, Le = { + key: 2, + class: "mt-6" +}, Re = { class: "flex flex-wrap items-end justify-between gap-4" }, ze = { class: "text-base font-semibold text-heading" }, Be = { class: "mt-1 text-sm text-muted" }, Ve = { + key: 0, + class: "flex justify-center py-16" +}, He = { class: "mt-4 flex flex-wrap items-center justify-between gap-3 rounded-lg border border-line-default bg-surface-secondary px-4 py-3" }, Ue = { class: "flex cursor-pointer items-center gap-2 text-sm font-medium text-heading" }, We = ["checked"], Ge = { class: "text-sm text-muted" }, Ke = { class: "flex flex-wrap items-center justify-between gap-3 bg-surface-secondary px-4 py-3" }, qe = { class: "flex cursor-pointer items-center gap-2 text-sm font-semibold text-heading" }, Je = ["checked", "onChange"], Ye = { class: "text-sm text-muted" }, Xe = { class: "overflow-x-auto" }, Ze = { class: "w-full table-auto" }, Qe = { class: "bg-surface text-xs tracking-wider text-muted uppercase" }, $e = { class: "px-4 py-2 text-left font-medium" }, et = { class: "px-4 py-2 text-left font-medium" }, tt = { class: "px-4 py-2 text-left font-medium" }, nt = { class: "px-4 py-2 text-left font-medium" }, rt = { class: "px-4 py-2 text-right font-medium" }, it = { class: "px-4 py-2 text-right font-medium" }, at = { class: "divide-y divide-line-default bg-surface text-sm" }, ot = { class: "pl-4" }, st = ["checked", "onChange"], ct = { class: "px-4 py-2 whitespace-nowrap text-muted" }, lt = { class: "px-4 py-2" }, ut = { class: "text-heading" }, dt = { class: "block text-xs text-subtle" }, ft = { class: "px-4 py-2 text-muted" }, pt = { class: "px-4 py-2 text-muted" }, mt = { class: "px-4 py-2 text-right whitespace-nowrap text-muted" }, ht = { class: "px-4 py-2 text-right whitespace-nowrap text-heading" }, gt = { class: "mt-5 flex flex-wrap items-center justify-between gap-4" }, _t = { class: "text-sm font-medium text-heading" }, vt = { class: "flex items-center gap-3" }, yt = { + key: 3, + class: "mt-6" +}, bt = { class: "text-base font-semibold text-heading" }, xt = { + key: 0, + class: "flex justify-center py-16" +}, St = { + key: 0, + class: "mt-4 rounded-lg border border-status-red bg-surface px-4 py-3 text-sm text-status-red" +}, Ct = { class: "mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4" }, wt = { + key: 0, + class: "mt-1 block text-xs text-subtle" +}, Tt = { class: "mt-1 block text-xs text-subtle" }, Et = { class: "mt-5 overflow-hidden rounded-xl border border-line-default" }, Dt = { class: "overflow-x-auto" }, Ot = { class: "w-full table-auto" }, kt = { class: "bg-surface-secondary text-xs tracking-wider text-muted uppercase" }, At = { class: "px-4 py-2 text-left font-medium" }, jt = { class: "px-4 py-2 text-right font-medium" }, Mt = { class: "px-4 py-2 text-right font-medium" }, Nt = { class: "px-4 py-2 text-right font-medium" }, Pt = { class: "divide-y divide-line-default bg-surface text-sm" }, Ft = { class: "px-4 py-3" }, It = { class: "font-medium text-heading" }, Lt = { + key: 0, + class: "mt-1 block text-xs whitespace-pre-line text-subtle" +}, Rt = { class: "px-4 py-3 text-right whitespace-nowrap text-muted" }, zt = { class: "px-4 py-3 text-right whitespace-nowrap text-muted" }, Bt = { class: "px-4 py-3 text-right whitespace-nowrap font-medium text-heading" }, Vt = { class: "bg-surface-secondary text-sm" }, Ht = { + class: "px-4 py-2 text-right text-muted", + colspan: "3" +}, Ut = { class: "px-4 py-2 text-right whitespace-nowrap text-heading" }, Wt = { + class: "px-4 py-2 text-right font-semibold text-heading", + colspan: "3" +}, Gt = { class: "px-4 py-2 text-right whitespace-nowrap font-semibold text-heading" }, Kt = { class: "mt-5 flex items-center justify-end gap-3" }, qt = { + key: 4, + class: "mt-6" +}, Jt = { + key: 0, + class: "flex flex-col items-center gap-3 py-16" +}, Yt = { class: "text-sm text-muted" }, Xt = { + key: 0, + class: "rounded-xl border border-status-yellow bg-surface p-5" +}, Zt = { class: "text-sm font-semibold text-heading" }, Qt = { class: "mt-1 text-sm text-muted" }, $t = { class: "mt-4 flex flex-wrap items-center gap-3" }, en = { + key: 1, + class: "rounded-xl border border-line-default bg-surface p-6 text-center" +}, tn = { class: "mt-3 text-base font-semibold text-heading" }, nn = { class: "mt-1 text-sm text-muted" }, rn = { class: "mt-3 text-2xl font-semibold text-heading" }, an = { class: "mt-5 flex flex-wrap items-center justify-center gap-3" }, on = { + key: 2, + class: "flex justify-center py-16" +}, sn = "/admin/invoices", cn = /* @__PURE__ */ l({ + __name: "BillingPage", props: { - show: { type: Boolean }, client: { type: [Function, Object] }, notify: { type: Function }, - project: {} + router: {} }, - emits: ["close", "saved"], - setup(t, { emit: i }) { - let l = t, u = i, d = [ - "#2563eb", - "#0891b2", - "#059669", - "#ca8a04", - "#ea580c", - "#dc2626", - "#7c3aed", - "#64748b" - ], m = de(), h = _({ - name: "", - identifier: "", - description: "", - colour: "", - defaultRate: "", - budgetHours: "", - dueDate: "" - }), 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 N() { - let e = l.project; - h.name = e?.name ?? "", h.identifier = e?.identifier ?? "", h.description = e?.description ?? "", h.colour = e?.colour ?? "", h.defaultRate = ne(e?.default_rate ?? null), h.budgetHours = ie(e?.budget_minutes ?? null), h.dueDate = e?.due_date ?? "", O.value = {}, C.value = P(e?.customer_id ?? null); - } - function P(e) { - return e === null ? null : w.value.find((t) => t.id === e) ?? null; - } - function F(e) { - return e.display_name || e.name || `#${e.id}`; - } - async function R() { - if (!D.value) try { - let e = await H(l.client); - w.value = e.map((e) => ({ - id: e.id, - label: F(e) - })), D.value = !0, C.value = P(l.project?.customer_id ?? null); - } catch (e) { - l.notify("error", U(e, m("tasks_projects.projects.customers_failed"))); - } - } - function z() { - return { - 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: re(h.defaultRate), - budget_minutes: ae(h.budgetHours), - due_date: h.dueDate || null - }; - } - function B(e) { - h.dueDate = e ? se(e) : ""; - } - async function V() { - if (!A.value) { - if (h.name.trim() === "") { - O.value = { name: m("tasks_projects.projects.name_required") }; - return; - } - A.value = !0, O.value = {}; - try { - let e = l.project, t = e ? await L(l.client, e.id, z()) : await I(l.client, z()); - u("saved", t); + setup(t) { + let l = t, u = [ + "task", + "project", + "member", + "summary" + ], d = B(), p = y(1), m = y(!0), g = y([]), w = y({}), T = v({ + from: "", + to: "" + }), O = y(null), k = y(null), A = y("task"), j = y([]), M = y(!1), N = y(null), P = y([]), F = y(null), ce = y(!1), z = v({ + invoiceDate: "", + dueDate: "", + invoiceNumber: "", + templateName: "", + exchangeRate: "" + }), de = y({}), fe = y(!1), pe = y(!1), me = y(null), he = y(null), ge = y(!1), be = y(!1), xe = n(() => [ + d("tasks_projects.billing.steps.customer"), + d("tasks_projects.billing.steps.entries"), + d("tasks_projects.billing.steps.preview"), + d("tasks_projects.billing.steps.create") + ]), V = n(() => u.map((e) => ({ + id: e, + label: d(`tasks_projects.billing.entries.group_by.${e}`) + }))), Se = n({ + get: () => V.value.find((e) => e.id === A.value) ?? V.value[0], + set: (e) => { + A.value = e.id; + } + }), Ce = n(() => P.value.map((e) => ({ name: e.name }))), we = n({ + get: () => Ce.value.find((e) => e.name === z.templateName) ?? { name: z.templateName }, + set: (e) => { + z.templateName = e.name; + } + }), cn = n(() => (k.value?.entries ?? []).filter((e) => e.currency_id === (O.value?.currency_id ?? null))), ln = n(() => { + let e = {}; + for (let t of cn.value) e[t.id] = t; + return e; + }), un = n(() => (k.value?.groups[A.value] ?? []).filter((e) => e.currency_id === (O.value?.currency_id ?? null))), dn = n(() => cn.value.length), fn = n(() => dn.value > 0 && j.value.length === dn.value), pn = n(() => j.value.reduce((e, t) => e + (ln.value[t]?.minutes ?? 0), 0)), mn = n(() => j.value.reduce((e, t) => e + (ln.value[t]?.amount ?? 0), 0)), hn = n(() => O.value === null ? null : Dn(O.value.customer_id, O.value.currency_id)), gn = n(() => { + let e = N.value?.currency?.id ?? null; + return e === null || O.value === null ? !1 : En(O.value.customer_id) !== e; + }), _n = n(() => N.value?.autoGenerateNumber !== !0 || de.value.invoice_number !== void 0), vn = n(() => { + let e = {}; + return T.from !== "" && (e.from = T.from), T.to !== "" && (e.to = T.to), e; + }); + E(() => [T.from, T.to], () => void Sn()), h(() => void yn()); + async function yn() { + m.value = !0, await Promise.all([ + xn(), + Sn(), + Cn(), + wn() + ]), m.value = !1; + let e = bn(); + if (e !== null) { + let t = g.value.find((t) => t.customer_id === e); + t && await jn(t); + } + } + function bn() { + let e = l.router.currentRoute.value.query.customer_id, t = Array.isArray(e) ? e[0] : e ?? new URLSearchParams(window.location.search).get("customer_id"), n = Number(t); + return Number.isInteger(n) && n > 0 ? n : null; + } + async function xn() { + try { + N.value = await se(l.client); + } catch { + N.value = null; + } + } + async function Sn() { + try { + g.value = await ee(l.client, vn.value); + } catch (e) { + g.value = [], l.notify("error", R(e, d("tasks_projects.billing.customer.load_failed"))); + } + } + async function Cn() { + try { + let e = {}; + for (let t of await ne(l.client)) e[t.id] = t; + w.value = e; + } catch { + w.value = {}; + } + } + async function wn() { + try { + P.value = await ie(l.client); + } catch (e) { + P.value = [], l.notify("error", R(e, d("tasks_projects.billing.preview.templates_failed"))); + } + } + function Tn(e) { + let t = w.value[e], n = t?.display_name ?? t?.name; + return n && n !== "" ? n : d("tasks_projects.billing.customer.unnamed", { id: e }); + } + function En(e) { + let t = w.value[e]; + return t?.currency_id ?? t?.currency?.id ?? null; + } + function Dn(e, t) { + if (t === null) return null; + let n = w.value[e]; + if (n?.currency && n.currency.id === t) return n.currency; + let r = N.value?.currency ?? null; + return r !== null && r.id === t ? r : null; + } + function On() { + T.from = "", T.to = ""; + } + function kn(e) { + T.from = e ? ve(e) : ""; + } + function An(e) { + T.to = e ? ve(e) : ""; + } + async function jn(e) { + O.value = e, p.value = 2, M.value = !0, k.value = null, j.value = []; + try { + k.value = await I(l.client, e.customer_id, vn.value), j.value = cn.value.map((e) => e.id); + } catch (e) { + l.notify("error", R(e, d("tasks_projects.billing.entries.load_failed"))); + } finally { + M.value = !1; + } + } + function Mn(e) { + return j.value.includes(e); + } + function Nn(e) { + j.value = Mn(e) ? j.value.filter((t) => t !== e) : [...j.value, e]; + } + function Pn() { + j.value = fn.value ? [] : cn.value.map((e) => e.id); + } + function Fn(e) { + return e.entry_ids.length > 0 && e.entry_ids.every((e) => Mn(e)); + } + function In(e) { + if (Fn(e)) { + j.value = j.value.filter((t) => !e.entry_ids.includes(t)); + return; + } + let t = e.entry_ids.filter((e) => !Mn(e)); + j.value = [...j.value, ...t]; + } + function Ln(e) { + return e.entry_ids.map((e) => ln.value[e]).filter((e) => e !== void 0); + } + async function Rn() { + if (j.value.length === 0) { + l.notify("warning", d("tasks_projects.billing.entries.none_selected")); + return; + } + p.value = 3, ce.value = !0, F.value = null, de.value = {}, fe.value = !1; + try { + let e = await te(l.client, j.value, A.value); + F.value = e, await zn(e); + } catch (e) { + l.notify("error", R(e, d("tasks_projects.billing.preview.prepare_failed"))), p.value = 2; + } finally { + ce.value = !1; + } + } + async function zn(e) { + z.invoiceDate = e.invoice_date, z.dueDate = Bn(e.invoice_date), z.templateName = N.value?.defaultTemplate ?? P.value[0]?.name ?? ""; + let [t, n] = await Promise.all([ae(l.client, e.customer_id).catch(() => null), gn.value && O.value?.currency_id ? oe(l.client, O.value.currency_id).catch(() => null) : Promise.resolve(null)]); + z.invoiceNumber = t ?? "", t === null && l.notify("warning", d("tasks_projects.billing.preview.number_failed")), gn.value ? (z.exchangeRate = n === null ? "" : String(n), n === null && l.notify("warning", d("tasks_projects.billing.preview.rate_failed"))) : z.exchangeRate = ""; + } + function Bn(e) { + let t = N.value; + if (t === null || !t.setDueDateAutomatically) return ""; + let n = /* @__PURE__ */ new Date(`${e}T00:00:00`); + return Number.isNaN(n.getTime()) ? "" : (n.setDate(n.getDate() + t.dueDateDays), ve(n)); + } + function Vn(e) { + z.invoiceDate = e ? ve(e) : "", z.dueDate = Bn(z.invoiceDate); + } + function Hn(e) { + z.dueDate = e ? ve(e) : ""; + } + function Un(e) { + return { + invoice_date: z.invoiceDate, + due_date: z.dueDate === "" ? null : z.dueDate, + customer_id: e.customer_id, + invoice_number: z.invoiceNumber, + currency_id: En(e.customer_id) ?? e.currency_id, + exchange_rate: gn.value && z.exchangeRate !== "" ? Number(z.exchangeRate) : null, + discount: e.discount, + discount_type: e.discount_type, + discount_val: e.discount_val, + tax: e.tax, + sub_total: e.sub_total, + total: e.total, + tax_included: !1, + notes: e.notes, + template_name: z.templateName, + items: e.items.map((e) => ({ ...e })), + taxes: [] + }; + } + async function Wn() { + let e = F.value; + if (!(e === null || pe.value)) { + p.value = 4, pe.value = !0, de.value = {}, fe.value = !1, me.value = null, he.value = null, ge.value = !1; + try { + let t = await re(l.client, Un(e)); + me.value = t, await Gn(t, e); } catch (e) { - O.value = te(e), l.notify("error", U(e, m("tasks_projects.projects.save_failed"))); + ue(e) === 422 ? (de.value = le(e), fe.value = !0, p.value = 3, l.notify("error", R(e, d("tasks_projects.billing.preview.invalid")))) : (p.value = 3, l.notify("error", R(e, d("tasks_projects.billing.create.failed")))); } finally { - A.value = !1; + pe.value = !1; } } } - 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: E(() => [o("div", fe, [o("span", null, x(M.value), 1), c(l, { - name: "XMarkIcon", - class: "h-6 w-6 cursor-pointer text-subtle hover:text-body", - onClick: i[0] ||= (e) => u("close") - })])]), - default: E(() => [o("form", { onSubmit: k(V, ["prevent"]) }, [o("div", pe, [ - c(N, null, { - default: E(() => [ - c(v, { - label: S(m)("tasks_projects.projects.fields.name"), - error: O.value.name, - required: "" - }, { - 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"]), - 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: 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"])]), - _: 1 - }, 8, [ - "label", - "error", - "help-text" - ]), - 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: 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", - "options", - "placeholder" - ])]), + async function Gn(e, t) { + let n = e.items ?? [], r = []; + if (t.groups.forEach((e, t) => { + let i = n[t]; + i && r.push({ + invoice_item_id: i.id, + entry_ids: e.entry_ids + }); + }), r.length === 0) { + ge.value = !0; + return; + } + be.value = !0; + try { + he.value = await L(l.client, e.id, r), ge.value = !1; + } catch (e) { + ge.value = !0, l.notify("error", R(e, d("tasks_projects.billing.create.stamp_failed_title"))); + } finally { + be.value = !1; + } + } + async function Kn() { + let e = me.value, t = F.value; + e === null || t === null || be.value || (await Gn(e, t), ge.value || l.notify("success", d("tasks_projects.billing.create.stamped"))); + } + async function qn() { + p.value = 1, O.value = null, k.value = null, F.value = null, me.value = null, he.value = null, ge.value = !1, j.value = [], de.value = {}, fe.value = !1, await Sn(); + } + function Jn(e) { + return p.value > e ? "border-primary-500 bg-primary-500 text-white" : p.value === e ? "border-primary-500 text-primary-500" : "border-line-default text-subtle"; + } + return (t, n) => { + let l = x("BaseBreadcrumbItem"), u = x("BaseBreadcrumb"), h = x("BaseIcon"), v = x("BaseButton"), y = x("router-link"), w = x("BasePageHeader"), E = x("BaseSpinner"), k = x("BaseDatePicker"), A = x("BaseInputGroup"), N = x("BaseFormatMoney"), P = x("BaseEmptyPlaceholder"), ee = x("BaseSelectInput"), I = x("BaseInput"), te = x("BasePage"); + return _(), r(te, null, { + default: D(() => [ + c(w, { title: C(d)("tasks_projects.billing.title") }, { + actions: D(() => [o("div", Te, [c(y, { to: C(H).week }, { + default: D(() => [c(v, { variant: "white" }, { + left: D((e) => [c(h, { + name: "ClockIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: D(() => [s(" " + S(C(d)("tasks_projects.time.title")), 1)]), _: 1 - }, 8, [ - "label", - "error", - "help-text" + })]), + _: 1 + }, 8, ["to"])])]), + default: D(() => [c(u, null, { + default: D(() => [ + c(l, { + title: C(d)("tasks_projects.general.home"), + to: "/admin/dashboard" + }, null, 8, ["title"]), + c(l, { + title: C(d)("tasks_projects.tasks.title"), + to: C(H).tasks + }, null, 8, ["title", "to"]), + c(l, { + title: C(d)("tasks_projects.billing.title"), + to: "#", + active: "" + }, null, 8, ["title"]) ]), - c(v, { - label: S(m)("tasks_projects.projects.fields.due_date"), - error: O.value.due_date + _: 1 + })]), + _: 1 + }, 8, ["title"]), + o("ol", Ee, [(_(!0), a(e, null, b(xe.value, (e, t) => (_(), a("li", { + key: e, + class: "flex items-center gap-2" + }, [o("span", { class: f(["flex h-7 w-7 items-center justify-center rounded-full border text-xs font-semibold", Jn(t + 1)]) }, S(t + 1), 3), o("span", { class: f(["text-sm font-medium", p.value === t + 1 ? "text-heading" : "text-muted"]) }, S(e), 3)]))), 128))]), + m.value ? (_(), a("div", De, [c(E, { class: "h-8 w-8 text-primary-500" })])) : p.value === 1 ? (_(), a("section", Oe, [ + o("h2", ke, S(C(d)("tasks_projects.billing.customer.title")), 1), + o("p", Ae, S(C(d)("tasks_projects.billing.customer.description")), 1), + o("div", je, [ + c(A, { + label: C(d)("tasks_projects.billing.customer.from"), + class: "w-full sm:w-48" }, { - default: E(() => [c(D, { - "model-value": h.dueDate, - "onUpdate:modelValue": B + default: D(() => [c(k, { + "model-value": T.from, + "onUpdate:modelValue": kn }, null, 8, ["model-value"])]), _: 1 - }, 8, ["label", "error"]), - 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") + }, 8, ["label"]), + c(A, { + label: C(d)("tasks_projects.billing.customer.to"), + class: "w-full sm:w-48" }, { - 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" - }, null, 8, ["modelValue", "invalid"])]), + default: D(() => [c(k, { + "model-value": T.to, + "onUpdate:modelValue": An + }, null, 8, ["model-value"])]), _: 1 - }, 8, [ - "label", - "error", - "help-text" - ]), - c(v, { - label: S(m)("tasks_projects.projects.fields.budget_hours"), - error: O.value.budget_minutes + }, 8, ["label"]), + T.from !== "" || T.to !== "" ? (_(), r(v, { + key: 0, + variant: "primary-outline", + onClick: On }, { - 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" - }, null, 8, ["modelValue", "invalid"])]), + default: D(() => [s(S(C(d)("tasks_projects.billing.customer.clear_range")), 1)]), _: 1 - }, 8, ["label", "error"]) + })) : i("", !0) ]), - _: 1 - }), - c(v, { - label: S(m)("tasks_projects.projects.fields.colour"), - error: O.value.colour - }, { - default: E(() => [o("div", me, [(g(), a(e, null, y(d, (e) => o("button", { - key: e, + g.value.length > 0 ? (_(), a("div", Me, [(_(!0), a(e, null, b(g.value, (e) => (_(), a("button", { + key: `${e.customer_id}-${e.currency_id ?? "none"}`, type: "button", - 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) => h.colour = h.colour === e ? "" : e - }, null, 14, he)), 64)), o("button", { - type: "button", - class: "rounded-md border border-line-default px-2 py-1 text-xs text-muted hover:bg-hover", - onClick: i[6] ||= (e) => h.colour = "" - }, x(S(m)("tasks_projects.projects.fields.colour_none")), 1)])]), + class: "rounded-xl border border-line-default bg-surface p-5 text-left transition hover:border-primary-500", + onClick: (t) => jn(e) + }, [ + o("p", Pe, S(Tn(e.customer_id)), 1), + o("p", Fe, S(C(d)("tasks_projects.billing.customer.entries", { count: e.entries })) + " · " + S(C(ye)(e.minutes)), 1), + o("p", Ie, [c(N, { + amount: e.amount, + currency: Dn(e.customer_id, e.currency_id) + }, null, 8, ["amount", "currency"])]) + ], 8, Ne))), 128))])) : (_(), r(P, { + key: 1, + title: C(d)("tasks_projects.billing.customer.empty_title"), + description: C(d)("tasks_projects.billing.customer.empty_description") + }, { + default: D(() => [c(h, { + name: "BanknotesIcon", + class: "mt-5 mb-4 h-16 w-16 text-subtle" + })]), + _: 1 + }, 8, ["title", "description"])) + ])) : p.value === 2 ? (_(), a("section", Le, [o("div", Re, [o("div", null, [o("h2", ze, S(C(d)("tasks_projects.billing.entries.title")), 1), o("p", Be, S(O.value ? Tn(O.value.customer_id) : ""), 1)]), c(A, { + label: C(d)("tasks_projects.billing.entries.grouping"), + class: "w-full sm:w-56" + }, { + default: D(() => [c(ee, { + modelValue: Se.value, + "onUpdate:modelValue": n[0] ||= (e) => Se.value = e, + options: V.value, + "label-key": "label" + }, null, 8, ["modelValue", "options"])]), _: 1 - }, 8, ["label", "error"]), - c(v, { - label: S(m)("tasks_projects.projects.fields.description"), - error: O.value.description + }, 8, ["label"])]), M.value ? (_(), a("div", Ve, [c(E, { class: "h-8 w-8 text-primary-500" })])) : dn.value > 0 ? (_(), a(e, { key: 1 }, [ + o("div", He, [o("label", Ue, [o("input", { + type: "checkbox", + class: "h-4 w-4 cursor-pointer rounded border-line-strong", + checked: fn.value, + onChange: Pn + }, null, 40, We), s(" " + S(C(d)("tasks_projects.billing.entries.select_all")), 1)]), o("p", Ge, S(C(d)("tasks_projects.billing.entries.selected", { + count: j.value.length, + total: dn.value + })), 1)]), + (_(!0), a(e, null, b(un.value, (t) => (_(), a("div", { + key: `${t.label}-${t.key ?? "none"}-${t.currency_id ?? "none"}`, + class: "mt-4 overflow-hidden rounded-xl border border-line-default" + }, [o("div", Ke, [o("label", qe, [o("input", { + type: "checkbox", + class: "h-4 w-4 cursor-pointer rounded border-line-strong", + checked: Fn(t), + onChange: (e) => In(t) + }, null, 40, Je), s(" " + S(t.label), 1)]), o("p", Ye, [s(S(C(ye)(t.minutes)) + " · ", 1), c(N, { + amount: t.amount, + currency: hn.value + }, null, 8, ["amount", "currency"])])]), o("div", Xe, [o("table", Ze, [o("thead", Qe, [o("tr", null, [ + n[6] ||= o("th", { class: "w-10" }, null, -1), + o("th", $e, S(C(d)("tasks_projects.billing.entries.columns.date")), 1), + o("th", et, S(C(d)("tasks_projects.billing.entries.columns.task")), 1), + o("th", tt, S(C(d)("tasks_projects.billing.entries.columns.project")), 1), + o("th", nt, S(C(d)("tasks_projects.billing.entries.columns.member")), 1), + o("th", rt, S(C(d)("tasks_projects.billing.entries.columns.duration")), 1), + o("th", it, S(C(d)("tasks_projects.billing.entries.columns.amount")), 1) + ])]), o("tbody", at, [(_(!0), a(e, null, b(Ln(t), (e) => (_(), a("tr", { key: e.id }, [ + o("td", ot, [o("input", { + type: "checkbox", + class: "h-4 w-4 cursor-pointer rounded border-line-strong", + checked: Mn(e.id), + onChange: (t) => Nn(e.id) + }, null, 40, st)]), + o("td", ct, S(C(_e)(e.date)), 1), + o("td", lt, [o("span", ut, S(e.task_name), 1), o("span", dt, S(e.description || C(d)("tasks_projects.billing.entries.no_description")), 1)]), + o("td", ft, S(e.project_name ?? "-"), 1), + o("td", pt, S(e.user_name), 1), + o("td", mt, S(C(ye)(e.minutes)), 1), + o("td", ht, [c(N, { + amount: e.amount, + currency: hn.value + }, null, 8, ["amount", "currency"])]) + ]))), 128))])])])]))), 128)), + o("div", gt, [o("p", _t, [s(S(C(d)("tasks_projects.billing.entries.selected_total", { hours: C(ye)(pn.value) })) + " · ", 1), c(N, { + amount: mn.value, + currency: hn.value + }, null, 8, ["amount", "currency"])]), o("div", vt, [c(v, { + variant: "primary-outline", + onClick: qn + }, { + default: D(() => [s(S(C(d)("tasks_projects.billing.back")), 1)]), + _: 1 + }), c(v, { + variant: "primary", + disabled: j.value.length === 0, + onClick: Rn + }, { + default: D(() => [s(S(C(d)("tasks_projects.billing.next")), 1)]), + _: 1 + }, 8, ["disabled"])])]) + ], 64)) : (_(), r(P, { + key: 2, + title: C(d)("tasks_projects.billing.entries.empty_title"), + description: C(d)("tasks_projects.billing.entries.empty_description") }, { - default: E(() => [c(P, { - modelValue: h.description, - "onUpdate:modelValue": i[7] ||= (e) => h.description = e, - row: 3, - invalid: !!O.value.description - }, null, 8, ["modelValue", "invalid"])]), + actions: D(() => [c(v, { + variant: "primary", + onClick: qn + }, { + default: D(() => [s(S(C(d)("tasks_projects.billing.back")), 1)]), + _: 1 + })]), + default: D(() => [c(h, { + name: "ClockIcon", + class: "mt-5 mb-4 h-16 w-16 text-subtle" + })]), _: 1 - }, 8, ["label", "error"]) - ]), o("div", ge, [c(F, { - type: "button", - variant: "primary-outline", - onClick: i[8] ||= (e) => u("close") - }, { - default: E(() => [s(x(S(m)("tasks_projects.general.cancel")), 1)]), - _: 1 - }), c(F, { - type: "submit", - variant: "primary", - loading: A.value, - disabled: A.value - }, { - default: E(() => [s(x(j.value ? S(m)("tasks_projects.general.update") : S(m)("tasks_projects.general.save")), 1)]), - _: 1 - }, 8, ["loading", "disabled"])])], 32)]), + }, 8, ["title", "description"]))])) : p.value === 3 ? (_(), a("section", yt, [o("h2", bt, S(C(d)("tasks_projects.billing.preview.title")), 1), ce.value || F.value === null ? (_(), a("div", xt, [c(E, { class: "h-8 w-8 text-primary-500" })])) : (_(), a(e, { key: 1 }, [ + fe.value ? (_(), a("div", St, S(C(d)("tasks_projects.billing.preview.invalid")), 1)) : i("", !0), + o("div", Ct, [ + c(A, { + label: C(d)("tasks_projects.billing.preview.invoice_date"), + error: de.value.invoice_date, + required: "" + }, { + default: D(() => [c(k, { + "model-value": z.invoiceDate, + "onUpdate:modelValue": Vn + }, null, 8, ["model-value"])]), + _: 1 + }, 8, ["label", "error"]), + c(A, { + label: C(d)("tasks_projects.billing.preview.due_date"), + error: de.value.due_date + }, { + default: D(() => [c(k, { + "model-value": z.dueDate, + "onUpdate:modelValue": Hn + }, null, 8, ["model-value"])]), + _: 1 + }, 8, ["label", "error"]), + c(A, { + label: C(d)("tasks_projects.billing.preview.invoice_number"), + error: de.value.invoice_number, + required: "" + }, { + default: D(() => [c(I, { + modelValue: z.invoiceNumber, + "onUpdate:modelValue": n[1] ||= (e) => z.invoiceNumber = e, + type: "text", + name: "invoice_number", + disabled: !_n.value + }, null, 8, ["modelValue", "disabled"]), _n.value ? i("", !0) : (_(), a("span", wt, S(C(d)("tasks_projects.billing.preview.invoice_number_auto")), 1))]), + _: 1 + }, 8, ["label", "error"]), + c(A, { + label: C(d)("tasks_projects.billing.preview.template"), + error: de.value.template_name, + required: "" + }, { + default: D(() => [c(ee, { + modelValue: we.value, + "onUpdate:modelValue": n[2] ||= (e) => we.value = e, + options: Ce.value, + "label-key": "name" + }, null, 8, ["modelValue", "options"])]), + _: 1 + }, 8, ["label", "error"]), + gn.value ? (_(), r(A, { + key: 0, + label: C(d)("tasks_projects.billing.preview.exchange_rate"), + error: de.value.exchange_rate, + required: "" + }, { + default: D(() => [c(I, { + modelValue: z.exchangeRate, + "onUpdate:modelValue": n[3] ||= (e) => z.exchangeRate = e, + type: "text", + name: "exchange_rate" + }, null, 8, ["modelValue"]), o("span", Tt, S(C(d)("tasks_projects.billing.preview.exchange_rate_help", { currency: hn.value?.code ?? "" })), 1)]), + _: 1 + }, 8, ["label", "error"])) : i("", !0) + ]), + o("div", Et, [o("div", Dt, [o("table", Ot, [ + o("thead", kt, [o("tr", null, [ + o("th", At, S(C(d)("tasks_projects.billing.preview.columns.description")), 1), + o("th", jt, S(C(d)("tasks_projects.billing.preview.columns.quantity")), 1), + o("th", Mt, S(C(d)("tasks_projects.billing.preview.columns.price")), 1), + o("th", Nt, S(C(d)("tasks_projects.billing.preview.columns.total")), 1) + ])]), + o("tbody", Pt, [(_(!0), a(e, null, b(F.value.items, (e, t) => (_(), a("tr", { key: `${e.name}-${t}` }, [ + o("td", Ft, [o("span", It, S(e.name), 1), e.description ? (_(), a("span", Lt, S(e.description), 1)) : i("", !0)]), + o("td", Rt, S(e.quantity), 1), + o("td", zt, [c(N, { + amount: e.price, + currency: hn.value + }, null, 8, ["amount", "currency"])]), + o("td", Bt, [c(N, { + amount: e.total, + currency: hn.value + }, null, 8, ["amount", "currency"])]) + ]))), 128))]), + o("tfoot", Vt, [o("tr", null, [o("td", Ht, S(C(d)("tasks_projects.billing.preview.sub_total")), 1), o("td", Ut, [c(N, { + amount: F.value.sub_total, + currency: hn.value + }, null, 8, ["amount", "currency"])])]), o("tr", null, [o("td", Wt, S(C(d)("tasks_projects.billing.preview.total")), 1), o("td", Gt, [c(N, { + amount: F.value.total, + currency: hn.value + }, null, 8, ["amount", "currency"])])])]) + ])])]), + o("div", Kt, [c(v, { + variant: "primary-outline", + onClick: n[4] ||= (e) => p.value = 2 + }, { + default: D(() => [s(S(C(d)("tasks_projects.billing.back")), 1)]), + _: 1 + }), c(v, { + variant: "primary", + loading: pe.value, + disabled: pe.value, + onClick: Wn + }, { + left: D((e) => [pe.value ? i("", !0) : (_(), r(h, { + key: 0, + name: "DocumentPlusIcon", + class: f(e.class) + }, null, 8, ["class"]))]), + default: D(() => [s(" " + S(C(d)("tasks_projects.billing.preview.create")), 1)]), + _: 1 + }, 8, ["loading", "disabled"])]) + ], 64))])) : (_(), a("section", qt, [pe.value || be.value ? (_(), a("div", Jt, [c(E, { class: "h-8 w-8 text-primary-500" }), o("p", Yt, S(pe.value ? C(d)("tasks_projects.billing.create.creating") : C(d)("tasks_projects.billing.create.stamping")), 1)])) : me.value ? (_(), a(e, { key: 1 }, [ge.value ? (_(), a("div", Xt, [ + o("p", Zt, S(C(d)("tasks_projects.billing.create.stamp_failed_title")), 1), + o("p", Qt, S(C(d)("tasks_projects.billing.create.stamp_failed_description", { number: me.value.invoice_number })), 1), + o("div", $t, [c(v, { + variant: "primary", + loading: be.value, + onClick: Kn + }, { + default: D(() => [s(S(C(d)("tasks_projects.billing.create.retry_stamp")), 1)]), + _: 1 + }, 8, ["loading"]), c(y, { to: `${sn}/${me.value.id}/view` }, { + default: D(() => [c(v, { variant: "white" }, { + default: D(() => [s(S(C(d)("tasks_projects.billing.create.view_invoice")), 1)]), + _: 1 + })]), + _: 1 + }, 8, ["to"])]) + ])) : (_(), a("div", en, [ + c(h, { + name: "CheckCircleIcon", + class: "mx-auto h-12 w-12 text-primary-500" + }), + o("p", tn, S(C(d)("tasks_projects.billing.create.created_title", { number: me.value.invoice_number })), 1), + o("p", nn, S(C(d)("tasks_projects.billing.create.created_description", { count: he.value ?? 0 })), 1), + o("p", rn, [c(N, { + amount: me.value.total, + currency: hn.value + }, null, 8, ["amount", "currency"])]), + o("div", an, [c(y, { to: `${sn}/${me.value.id}/view` }, { + default: D(() => [c(v, { variant: "primary" }, { + default: D(() => [s(S(C(d)("tasks_projects.billing.create.view_invoice")), 1)]), + _: 1 + })]), + _: 1 + }, 8, ["to"]), c(v, { + variant: "white", + onClick: qn + }, { + default: D(() => [s(S(C(d)("tasks_projects.billing.create.invoice_more")), 1)]), + _: 1 + })]) + ]))], 64)) : (_(), a("div", on, [c(v, { + variant: "primary-outline", + onClick: n[5] ||= (e) => p.value = 3 + }, { + default: D(() => [s(S(C(d)("tasks_projects.billing.back")), 1)]), + _: 1 + })]))])) + ]), _: 1 - }, 8, ["show"]); + }); }; } -}), ve = _({}), ye = !1, be = null; -function xe(e) { - return e === null ? "" : ve[e] ?? `#${e}`; +}), ln = "tasks-projects"; +function un(e) { + e.addMessages(M), e.registerPage({ + id: "billing", + module: ln, + path: "billing", + component: we(e, cn), + meta: { + ability: `${ln}:invoice-tasks`, + title: "tasks_projects.billing.title" + } + }); } -async function Se(e) { - ye || (be ??= we(e), await be); +//#endregion +//#region resources/js/messages/projects.ts +var dn = { en: { tasks_projects: { project: { + load_failed: "Unable to load the project.", + customer: "Customer", + identifier: "Identifier", + due_date: "Due date", + board: "Board", + tasks: "Tasks", + invoice_project: "Invoice project", + invoice_soon: "Coming with invoicing", + tabs: { + overview: "Overview", + tasks: "Tasks", + time: "Time", + members: "Members" + }, + overview: { + tasks: "Tasks", + open_tasks: "{count} open", + closed_tasks: "{count} done", + logged: "Logged", + billable: "Billable", + billable_amount: "Billable value", + unbilled_amount: "Unbilled", + budget: "Budget", + budget_used: "{used} of {total}", + budget_over: "Over budget by {amount}", + no_budget: "No budget set.", + description: "Description", + no_description: "No description yet." + }, + time: { + title: "Time log", + add_entry: "Add entry", + columns: { + date: "Date", + member: "Member", + task: "Task", + minutes: "Duration", + billable: "Billable", + amount: "Amount" + }, + running: "Running", + removed_member: "Removed member", + load_failed: "Unable to load the time entries." + }, + members: { + title: "Members", + member: "Member", + rate: "Rate / hour", + rate_help: "Per hour on this project. Leave empty to use the project default.", + attach: "Add member", + attach_placeholder: "Choose a member", + attached: "{name} was added to the project.", + detached: "{name} was removed from the project.", + detach_confirm: "Remove {name} from this project? Their time entries stay.", + empty: "Nobody is on this project yet.", + all_attached: "Every company member is already on this project.", + load_failed: "Unable to load the project members.", + attach_failed: "Unable to add the member.", + detach_failed: "Unable to remove the member." + } +} } } }, fn = "/api/v1/tasks-projects", pn = { + projects: `${fn}/projects`, + project: (e) => `${fn}/projects/${e}`, + archiveProject: (e) => `${fn}/projects/${e}/archive`, + unarchiveProject: (e) => `${fn}/projects/${e}/unarchive`, + members: `${fn}/members`, + settings: `${fn}/settings` +}, mn = { customers: "/api/v1/customers" }; +function hn(e, t) { + if (e === void 0 || e.order === "") return {}; + let n = t[e.fieldName]; + return n === void 0 ? {} : { + sort_by: n, + sort_order: e.order + }; } -function Ce() { - for (let e of Object.keys(ve)) delete ve[Number(e)]; - ye = !1, be = null; +async function gn(e, t) { + let { data: n } = await e.get(pn.projects, { params: t }); + return n; } -async function we(e) { - try { - for (let t of await H(e, 200)) { - let e = t?.id; - typeof e == "number" && (ve[e] = Te(t)); - } - ye = !0; - } catch {} finally { - be = null; - } +async function _n(e, t) { + let { data: n } = await e.post(pn.projects, t); + return n.data; } -function Te(e) { - let t = typeof e.display_name == "string" ? e.display_name.trim() : ""; - if (t !== "") return t; - let n = typeof e.name == "string" ? e.name.trim() : ""; - return n === "" ? `#${e.id}` : n; +async function vn(e, t, n) { + let { data: r } = await e.put(pn.project(t), n); + return r.data; +} +async function yn(e, t) { + let { data: n } = await e.post(pn.archiveProject(t)); + return n.data; +} +async function bn(e, t) { + let { data: n } = await e.post(pn.unarchiveProject(t)); + return n.data; +} +async function xn(e, t) { + await e.delete(pn.project(t)); +} +async function Sn(e) { + let { data: t } = await e.get(pn.members); + return t.data; +} +async function Cn(e, t = 100) { + let { data: n } = await e.get(mn.customers, { params: { limit: t } }); + return n.data; } //#endregion -//#region resources/js/pages/ProjectsIndexPage.vue?vue&type=script&setup=true&lang.ts -var Ee = { class: "flex items-center justify-end space-x-5" }, De = { class: "relative table-container" }, Oe = { class: "flex items-center" }, ke = { - key: 0, - class: "block text-xs font-normal text-muted" -}, Ae = { key: 0 }, je = { - key: 1, - class: "text-subtle" -}, Me = { - key: 1, - class: "text-subtle" -}, Ne = { key: 0 }, Pe = { - key: 1, - class: "text-subtle" -}, Fe = 10, Ie = 350, Le = /* @__PURE__ */ l({ - __name: "ProjectsIndexPage", +//#region resources/js/api/board.ts +var wn = { + board: `${fn}/board`, + tasks: `${fn}/tasks`, + task: (e) => `${fn}/tasks/${e}`, + moveTask: (e) => `${fn}/tasks/${e}/move`, + startTask: (e) => `${fn}/tasks/${e}/start`, + stopTask: (e) => `${fn}/tasks/${e}/stop`, + taskTimeLog: (e) => `${fn}/tasks/${e}/time-log`, + bulkTasks: `${fn}/tasks/bulk`, + taskStatuses: `${fn}/task-statuses`, + timeEntries: `${fn}/time-entries`, + projectMembers: (e) => `${fn}/projects/${e}/members`, + projectMember: (e, t) => `${fn}/projects/${e}/members/${t}` +}; +async function Tn(e, t) { + let { data: n } = await e.get(wn.board, { params: t }); + return n.data; +} +async function En(e) { + let { data: t } = await e.get(wn.taskStatuses); + return t.data; +} +async function Dn(e, t) { + let { data: n } = await e.get(wn.tasks, { params: t }); + return n; +} +async function On(e, t) { + let { data: n } = await e.post(wn.tasks, t); + return n.data; +} +async function kn(e, t, n) { + let { data: r } = await e.put(wn.task(t), n); + return r.data; +} +async function An(e, t) { + await e.delete(wn.task(t)); +} +async function jn(e, t) { + let { data: n } = await e.get(wn.task(t)); + return n.data; +} +async function Mn(e, t, n = null) { + let r = n === null ? {} : { description: n }, { data: i } = await e.post(wn.startTask(t), r); + return i.data; +} +async function Nn(e, t) { + let { data: n } = await e.post(wn.stopTask(t)); + return n.data; +} +async function Pn(e, t) { + let { data: n } = await e.get(wn.taskTimeLog(t)); + return n.data ?? []; +} +async function Fn(e, t) { + let { data: n } = await e.post(wn.bulkTasks, t); + return { + updated: n?.updated ?? 0, + failed: n?.failed ?? [] + }; +} +async function In(e, t, n) { + let { data: r } = await e.post(wn.moveTask(t), n); + return r.data; +} +async function Ln(e, t) { + let { data: n } = await e.get(pn.project(t)); + return n.data; +} +async function Rn(e, t) { + let { data: n } = await e.get(wn.projectMembers(t)); + return n.data; +} +async function zn(e, t, n) { + let { data: r } = await e.post(wn.projectMembers(t), n); + return r.data; +} +async function Bn(e, t, n) { + await e.delete(wn.projectMember(t, n)); +} +async function Vn(e, t) { + let { data: n } = await e.get(wn.timeEntries, { params: t }); + return n; +} +//#endregion +//#region resources/js/components/ProjectFormModal.vue?vue&type=script&setup=true&lang.ts +var Hn = { class: "flex w-full items-center justify-between" }, Un = { class: "space-y-5 px-6 py-6" }, Wn = { class: "flex flex-wrap items-center gap-2" }, Gn = ["aria-label", "onClick"], Kn = { class: "flex justify-end space-x-3 border-t border-line-default px-6 py-4" }, qn = /* @__PURE__ */ l({ + __name: "ProjectFormModal", props: { + show: { type: Boolean }, client: { type: [Function, Object] }, notify: { type: Function }, - router: {} + project: {} }, - setup(e) { - let t = e, l = { - name: "name", - status: "status", - default_rate: "default_rate", - due_date: "due_date" - }, u = de(), d = v(null), h = v(!1), y = v(!0), C = v(0), O = v(!1), k = v(null), A = v(null), j = _({ - search: "", - status: "ACTIVE" - }), M = n(() => [ - { - id: "ACTIVE", - label: u("tasks_projects.projects.status.active") - }, - { - id: "ARCHIVED", - label: u("tasks_projects.projects.status.archived") - }, - { - id: "ALL", - label: u("tasks_projects.projects.status.all") - } - ]), N = n({ - get: () => M.value.find((e) => e.id === j.status) ?? M.value[0], - set: (e) => { - j.status = e.id; - } - }), I = n(() => [ - { - key: "name", - label: u("tasks_projects.projects.columns.name"), - sortable: !0, - sortBy: "name", - thClass: "extra", - tdClass: "font-medium text-heading" - }, - { - key: "status", - label: u("tasks_projects.projects.columns.status"), - sortable: !0, - sortBy: "status" - }, - { - key: "customer", - label: u("tasks_projects.projects.columns.customer"), - sortable: !1 - }, - { - key: "default_rate", - label: u("tasks_projects.projects.columns.default_rate"), - sortable: !0, - sortBy: "default_rate" - }, - { - key: "due_date", - label: u("tasks_projects.projects.columns.due_date"), - sortable: !0, - sortBy: "due_date" - }, - { - key: "actions", - label: u("tasks_projects.general.actions"), - sortable: !1, - tdClass: "text-right text-sm font-medium" - } - ]), L = n(() => j.search.trim() !== "" || j.status !== "ACTIVE"), V = n(() => !y.value && C.value === 0 && !L.value), H; - T(() => j.search, () => { - clearTimeout(H), H = setTimeout(() => te(), Ie); - }), T(() => j.status, () => te()), m(() => clearTimeout(H)); - async function ee({ page: e, sort: n }) { - let r = { - page: e, - limit: Fe, - ...P(n, l) - }; - j.status !== "ALL" && (r.status = j.status), j.search.trim() !== "" && (r.search = j.search.trim()), y.value = !0; - try { - let e = await F(t.client, r); - return C.value = e.meta.total, e.data.some((e) => e.customer_id !== null) && Se(t.client), { - data: e.data, - pagination: { - totalPages: e.meta.last_page, - currentPage: e.meta.current_page, - totalCount: e.meta.total, - limit: e.meta.per_page - } - }; - } catch (e) { - return t.notify("error", U(e, u("tasks_projects.projects.load_failed"))), { - data: [], - pagination: { - totalPages: 1, - currentPage: 1, - totalCount: 0, - limit: Fe - } - }; - } finally { - y.value = !1; - } - } - function te(e = !1) { - d.value?.refresh(e); - } - function ne() { - h.value && re(), h.value = !h.value; - } - function re() { - j.search = "", j.status = "ACTIVE"; - } - function ie() { - k.value = null, O.value = !0; + emits: ["close", "saved"], + setup(t, { emit: i }) { + let l = t, u = i, d = [ + "#2563eb", + "#0891b2", + "#059669", + "#ca8a04", + "#ea580c", + "#dc2626", + "#7c3aed", + "#64748b" + ], m = B(), h = v({ + name: "", + identifier: "", + description: "", + colour: "", + defaultRate: "", + budgetHours: "", + dueDate: "" + }), g = y(null), w = y([]), T = y(!1), O = y({}), k = y(!1), j = n(() => l.project !== null), M = n(() => j.value ? m("tasks_projects.projects.edit_project") : m("tasks_projects.projects.new_project")); + E(() => l.show, (e) => { + e && (N(), ee()); + }, { immediate: !0 }); + function N() { + let e = l.project; + h.name = e?.name ?? "", h.identifier = e?.identifier ?? "", h.description = e?.description ?? "", h.colour = e?.colour ?? "", h.defaultRate = pe(e?.default_rate ?? null), h.budgetHours = he(e?.budget_minutes ?? null), h.dueDate = e?.due_date ?? "", O.value = {}, g.value = P(e?.customer_id ?? null); } - function ae(e) { - k.value = e, O.value = !0; + function P(e) { + return e === null ? null : w.value.find((t) => t.id === e) ?? null; } - function se(e) { - let n = k.value ? u("tasks_projects.projects.updated", { name: e.name }) : u("tasks_projects.projects.created", { name: e.name }); - O.value = !1, k.value = null, t.notify("success", n), te(); + function F(e) { + return e.display_name || e.name || `#${e.id}`; } - async function ce(e) { - A.value = e.id; - try { - e.status === "ARCHIVED" ? (await z(t.client, e.id), t.notify("success", u("tasks_projects.projects.unarchived", { name: e.name }))) : (await R(t.client, e.id), t.notify("success", u("tasks_projects.projects.archived", { name: e.name }))), te(!0); + async function ee() { + if (!T.value) try { + let e = await Cn(l.client); + w.value = e.map((e) => ({ + id: e.id, + label: F(e) + })), T.value = !0, g.value = P(l.project?.customer_id ?? null); } catch (e) { - t.notify("error", U(e, u("tasks_projects.projects.save_failed"))); - } finally { - A.value = null; + l.notify("error", R(e, m("tasks_projects.projects.customers_failed"))); } } - async function le(e) { - if (window.confirm(u("tasks_projects.projects.delete_confirm", { name: e.name }))) { - A.value = e.id; + function I() { + return { + name: h.name.trim(), + customer_id: g.value?.id ?? null, + identifier: h.identifier.trim() || null, + description: h.description.trim() || null, + colour: h.colour || null, + default_rate: me(h.defaultRate), + budget_minutes: ge(h.budgetHours), + due_date: h.dueDate || null + }; + } + function te(e) { + h.dueDate = e ? ve(e) : ""; + } + async function L() { + if (!k.value) { + if (h.name.trim() === "") { + O.value = { name: m("tasks_projects.projects.name_required") }; + return; + } + k.value = !0, O.value = {}; try { - await B(t.client, e.id), t.notify("success", u("tasks_projects.projects.deleted", { name: e.name })), te(!0); + let e = l.project, t = e ? await vn(l.client, e.id, I()) : await _n(l.client, I()); + u("saved", t); } catch (e) { - t.notify("error", U(e, u("tasks_projects.projects.delete_failed"))); + O.value = le(e), l.notify("error", R(e, m("tasks_projects.projects.save_failed"))); } finally { - A.value = null; + k.value = !1; } } } - function ue(e) { - return e === "ACTIVE" ? "bg-primary-50! text-primary-500!" : "bg-surface-tertiary! text-muted!"; - } - function fe(e) { - return u(e === "ACTIVE" ? "tasks_projects.projects.status.active" : "tasks_projects.projects.status.archived"); - } - return (t, n) => { - let l = b("BaseBreadcrumbItem"), m = b("BaseBreadcrumb"), _ = b("BaseIcon"), v = b("BaseButton"), y = b("router-link"), C = b("BasePageHeader"), T = b("BaseInput"), P = b("BaseInputGroup"), F = b("BaseSelectInput"), L = b("BaseFilterWrapper"), R = b("BaseEmptyPlaceholder"), z = b("BaseBadge"), B = b("BaseFormatMoney"), H = b("BaseDropdownItem"), U = b("BaseDropdown"), te = b("BaseTable"), de = b("BasePage"); - return g(), r(de, null, { - default: E(() => [ - c(C, { title: S(u)("tasks_projects.projects.title") }, { - actions: E(() => [o("div", Ee, [ - 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(u)("tasks_projects.board.title")), 1)]), - _: 1 - })]), + return (n, i) => { + let l = x("BaseIcon"), v = x("BaseInput"), y = x("BaseInputGroup"), T = x("BaseSelectInput"), E = x("BaseDatePicker"), N = x("BaseInputGrid"), P = x("BaseTextarea"), F = x("BaseButton"), ee = x("BaseModal"); + return _(), r(ee, { + show: t.show, + onClose: i[9] ||= (e) => u("close") + }, { + header: D(() => [o("div", Hn, [o("span", null, S(M.value), 1), c(l, { + name: "XMarkIcon", + class: "h-6 w-6 cursor-pointer text-subtle hover:text-body", + onClick: i[0] ||= (e) => u("close") + })])]), + default: D(() => [o("form", { onSubmit: A(L, ["prevent"]) }, [o("div", Un, [ + c(N, null, { + default: D(() => [ + c(y, { + label: C(m)("tasks_projects.projects.fields.name"), + error: O.value.name, + required: "" + }, { + default: D(() => [c(v, { + modelValue: h.name, + "onUpdate:modelValue": i[1] ||= (e) => h.name = e, + invalid: !!O.value.name, + type: "text" + }, null, 8, ["modelValue", "invalid"])]), _: 1 - }), - c(y, { to: "/admin/modules/tasks-projects/reports" }, { - default: E(() => [c(v, { variant: "white" }, { - left: E((e) => [c(_, { - name: "ChartBarIcon", - class: f(e.class) - }, null, 8, ["class"])]), - default: E(() => [s(" " + x(S(u)("tasks_projects.reports.title")), 1)]), - _: 1 - })]), + }, 8, ["label", "error"]), + c(y, { + label: C(m)("tasks_projects.projects.fields.identifier"), + error: O.value.identifier, + "help-text": C(m)("tasks_projects.projects.fields.identifier_help") + }, { + default: D(() => [c(v, { + modelValue: h.identifier, + "onUpdate:modelValue": i[2] ||= (e) => h.identifier = e, + invalid: !!O.value.identifier, + type: "text", + maxlength: "32" + }, null, 8, ["modelValue", "invalid"])]), _: 1 - }), - c(v, { - variant: "primary-outline", - onClick: ne + }, 8, [ + "label", + "error", + "help-text" + ]), + c(y, { + label: C(m)("tasks_projects.projects.fields.customer"), + error: O.value.customer_id, + "help-text": C(m)("tasks_projects.projects.fields.customer_help") }, { - right: E((e) => [h.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(u)("tasks_projects.general.filter")) + " ", 1)]), + default: D(() => [c(T, { + modelValue: g.value, + "onUpdate:modelValue": i[3] ||= (e) => g.value = e, + options: w.value, + placeholder: C(m)("tasks_projects.projects.fields.customer_placeholder"), + "label-key": "label" + }, null, 8, [ + "modelValue", + "options", + "placeholder" + ])]), _: 1 - }), - c(v, { - variant: "primary", - onClick: ie + }, 8, [ + "label", + "error", + "help-text" + ]), + c(y, { + label: C(m)("tasks_projects.projects.fields.due_date"), + error: O.value.due_date }, { - left: E((e) => [c(_, { - name: "PlusIcon", - class: f(e.class) - }, null, 8, ["class"])]), - default: E(() => [s(" " + x(S(u)("tasks_projects.projects.new_project")), 1)]), + default: D(() => [c(E, { + "model-value": h.dueDate, + "onUpdate:modelValue": te + }, null, 8, ["model-value"])]), _: 1 - }) - ])]), - default: E(() => [c(m, null, { - default: E(() => [c(l, { - title: S(u)("tasks_projects.general.home"), - to: "/admin/dashboard" - }, null, 8, ["title"]), c(l, { - title: S(u)("tasks_projects.projects.title"), - to: "#", - active: "" - }, null, 8, ["title"])]), - _: 1 - })]), - _: 1 - }, 8, ["title"]), - c(L, { - show: h.value, - class: "mt-3", - onClear: re - }, { - default: E(() => [c(P, { - label: S(u)("tasks_projects.general.search"), - class: "mt-2 flex-1" - }, { - default: E(() => [c(T, { - modelValue: j.search, - "onUpdate:modelValue": n[0] ||= (e) => j.search = e, - type: "text", - name: "search", - autocomplete: "off", - placeholder: S(u)("tasks_projects.projects.search_placeholder") - }, null, 8, ["modelValue", "placeholder"])]), - _: 1 - }, 8, ["label"]), c(P, { - label: S(u)("tasks_projects.projects.columns.status"), - class: "mt-2 flex-1" - }, { - default: E(() => [c(F, { - modelValue: N.value, - "onUpdate:modelValue": n[1] ||= (e) => N.value = e, - options: M.value, - "label-key": "label" - }, null, 8, ["modelValue", "options"])]), - _: 1 - }, 8, ["label"])]), + }, 8, ["label", "error"]), + c(y, { + label: C(m)("tasks_projects.projects.fields.default_rate"), + error: O.value.default_rate, + "help-text": C(m)("tasks_projects.projects.fields.default_rate_help") + }, { + default: D(() => [c(v, { + modelValue: h.defaultRate, + "onUpdate:modelValue": i[4] ||= (e) => h.defaultRate = e, + invalid: !!O.value.default_rate, + type: "number", + step: "0.01", + min: "0" + }, null, 8, ["modelValue", "invalid"])]), + _: 1 + }, 8, [ + "label", + "error", + "help-text" + ]), + c(y, { + label: C(m)("tasks_projects.projects.fields.budget_hours"), + error: O.value.budget_minutes + }, { + default: D(() => [c(v, { + modelValue: h.budgetHours, + "onUpdate:modelValue": i[5] ||= (e) => h.budgetHours = e, + invalid: !!O.value.budget_minutes, + type: "number", + step: "0.25", + min: "0" + }, null, 8, ["modelValue", "invalid"])]), + _: 1 + }, 8, ["label", "error"]) + ]), _: 1 - }, 8, ["show"]), - D(c(R, { - title: S(u)("tasks_projects.projects.empty_title"), - description: S(u)("tasks_projects.projects.empty_description") + }), + c(y, { + label: C(m)("tasks_projects.projects.fields.colour"), + error: O.value.colour }, { - actions: E(() => [c(v, { - variant: "primary", - onClick: ie - }, { - left: E((e) => [c(_, { - name: "PlusIcon", - class: f(e.class) - }, null, 8, ["class"])]), - default: E(() => [s(" " + x(S(u)("tasks_projects.projects.new_project")), 1)]), - _: 1 - })]), - default: E(() => [c(_, { - name: "FolderIcon", - class: "mt-5 mb-4 h-16 w-16 text-subtle" - })]), + default: D(() => [o("div", Wn, [(_(), a(e, null, b(d, (e) => o("button", { + key: e, + type: "button", + 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) => h.colour = h.colour === e ? "" : e + }, null, 14, Gn)), 64)), o("button", { + type: "button", + class: "rounded-md border border-line-default px-2 py-1 text-xs text-muted hover:bg-hover", + onClick: i[6] ||= (e) => h.colour = "" + }, S(C(m)("tasks_projects.projects.fields.colour_none")), 1)])]), _: 1 - }, 8, ["title", "description"]), [[w, V.value]]), - D(o("div", De, [c(te, { - ref_key: "tableRef", - ref: d, - data: ee, - columns: I.value, - class: "mt-3" + }, 8, ["label", "error"]), + c(y, { + label: C(m)("tasks_projects.projects.fields.description"), + error: O.value.description }, { - "cell-name": E(({ row: e }) => [o("div", Oe, [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", ke, x(e.data.identifier), 1)) : i("", !0)])])]), - "cell-status": E(({ row: e }) => [c(z, { class: f(["rounded-full", ue(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", Ae, x(S(xe)(e.data.customer_id)), 1)) : (g(), a("span", je, x(S(u)("tasks_projects.projects.internal")), 1))]), - "cell-default_rate": E(({ row: e }) => [e.data.default_rate === null ? (g(), a("span", Me, "-")) : (g(), r(B, { - key: 0, - amount: e.data.default_rate - }, null, 8, ["amount"]))]), - "cell-due_date": E(({ row: e }) => [e.data.due_date ? (g(), a("span", Ne, x(S(oe)(e.data.due_date)), 1)) : (g(), a("span", Pe, "-"))]), - "cell-actions": E(({ row: e }) => [c(U, { "content-loading": A.value === e.data.id }, { - activator: E(() => [c(_, { - name: "EllipsisHorizontalIcon", - class: "h-5 text-muted" - })]), - default: E(() => [ - c(H, { onClick: (t) => ae(e.data) }, { - default: E(() => [c(_, { - name: "PencilIcon", - class: "mr-3 h-5 w-5 text-subtle group-hover:text-muted" - }), s(" " + x(S(u)("tasks_projects.general.edit")), 1)]), - _: 1 - }, 8, ["onClick"]), - c(H, { onClick: (t) => ce(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(u)("tasks_projects.projects.unarchive") : S(u)("tasks_projects.projects.archive")), 1)]), - _: 2 - }, 1032, ["onClick"]), - c(H, { onClick: (t) => le(e.data) }, { - default: E(() => [c(_, { - name: "TrashIcon", - class: "mr-3 h-5 w-5 text-subtle group-hover:text-muted" - }), s(" " + x(S(u)("tasks_projects.general.delete")), 1)]), + default: D(() => [c(P, { + modelValue: h.description, + "onUpdate:modelValue": i[7] ||= (e) => h.description = e, + row: 3, + invalid: !!O.value.description + }, null, 8, ["modelValue", "invalid"])]), + _: 1 + }, 8, ["label", "error"]) + ]), o("div", Kn, [c(F, { + type: "button", + variant: "primary-outline", + onClick: i[8] ||= (e) => u("close") + }, { + default: D(() => [s(S(C(m)("tasks_projects.general.cancel")), 1)]), + _: 1 + }), c(F, { + type: "submit", + variant: "primary", + loading: k.value, + disabled: k.value + }, { + default: D(() => [s(S(j.value ? C(m)("tasks_projects.general.update") : C(m)("tasks_projects.general.save")), 1)]), + _: 1 + }, 8, ["loading", "disabled"])])], 32)]), + _: 1 + }, 8, ["show"]); + }; + } +}), Jn = v({}), Yn = !1, Xn = null; +function Zn(e) { + return e === null ? "" : Jn[e] ?? `#${e}`; +} +async function Qn(e) { + Yn || (Xn ??= er(e), await Xn); +} +function $n() { + for (let e of Object.keys(Jn)) delete Jn[Number(e)]; + Yn = !1, Xn = null; +} +async function er(e) { + try { + for (let t of await Cn(e, 200)) { + let e = t?.id; + typeof e == "number" && (Jn[e] = tr(t)); + } + Yn = !0; + } catch {} finally { + Xn = null; + } +} +function tr(e) { + let t = typeof e.display_name == "string" ? e.display_name.trim() : ""; + if (t !== "") return t; + let n = typeof e.name == "string" ? e.name.trim() : ""; + return n === "" ? `#${e.id}` : n; +} +//#endregion +//#region resources/js/pages/ProjectDetailPage.vue?vue&type=script&setup=true&lang.ts +var nr = { + key: 0, + class: "mt-2 flex flex-wrap items-center gap-3 text-sm text-muted" +}, rr = { + key: 0, + class: "rounded-sm bg-surface-tertiary px-2 py-0.5 text-body" +}, ir = { key: 1 }, ar = { class: "text-body" }, or = { + key: 2, + class: "text-subtle" +}, sr = { key: 3 }, cr = { class: "text-body" }, lr = { class: "flex items-center justify-end space-x-5" }, ur = { class: "mt-6 flex overflow-x-auto border-b border-line-default" }, dr = [ + "href", + "aria-current", + "onClick" +], fr = { + key: 0, + class: "flex justify-center py-16" +}, pr = /* @__PURE__ */ l({ + __name: "ProjectDetailPage", + props: { + id: {}, + client: { type: [Function, Object] }, + notify: { type: Function }, + router: {} + }, + setup(t) { + let l = t, u = Ce.project, d = B(), p = y(null), m = y(!0), g = y(!1), v = y(!1), w = n(() => Number(l.id)), T = n(() => [ + { + id: "overview", + label: d("tasks_projects.project.tabs.overview"), + name: `${u}.overview` + }, + { + id: "tasks", + label: d("tasks_projects.project.tabs.tasks"), + name: `${u}.tasks` + }, + { + id: "time", + label: d("tasks_projects.project.tabs.time"), + name: `${u}.time` + }, + { + id: "members", + label: d("tasks_projects.project.tabs.members"), + name: `${u}.members` + } + ]), O = n(() => String(l.router.currentRoute.value.name ?? "")), k = n(() => p.value?.name ?? d("tasks_projects.projects.title")), A = n(() => ({ + path: H.board, + query: { project: String(w.value) } + })), j = n(() => Zn(p.value?.customer_id ?? null)); + E(w, () => { + N(); + }), E(O, (e) => M(e)), h(() => { + M(O.value), N(); + }); + function M(e) { + e === u && l.router.replace({ + name: `${u}.overview`, + params: { id: l.id } + }); + } + async function N() { + m.value = !0; + try { + p.value = await Ln(l.client, w.value), typeof p.value?.customer_id == "number" && await Qn(l.client); + } catch (e) { + l.notify("error", R(e, d("tasks_projects.project.load_failed"))); + } finally { + m.value = !1; + } + } + function P(e) { + return { + name: e.name, + params: { id: l.id } + }; + } + function F(e) { + return O.value === e.name; + } + function ee(e) { + v.value = !1, l.notify("success", d("tasks_projects.projects.updated", { name: e.name })), N(); + } + async function I() { + let e = p.value; + if (!(e === null || g.value)) { + g.value = !0; + try { + e.status === "ARCHIVED" ? (await bn(l.client, e.id), l.notify("success", d("tasks_projects.projects.unarchived", { name: e.name }))) : (await yn(l.client, e.id), l.notify("success", d("tasks_projects.projects.archived", { name: e.name }))), await N(); + } catch (e) { + l.notify("error", R(e, d("tasks_projects.projects.save_failed"))); + } finally { + g.value = !1; + } + } + } + function te(e) { + return e === "ACTIVE" ? "bg-primary-50! text-primary-500!" : "bg-surface-tertiary! text-muted!"; + } + function L(e) { + return d(e === "ACTIVE" ? "tasks_projects.projects.status.active" : "tasks_projects.projects.status.archived"); + } + return (n, l) => { + let u = x("BaseBreadcrumbItem"), h = x("BaseBreadcrumb"), y = x("BaseBadge"), w = x("BaseIcon"), E = x("BaseButton"), O = x("router-link"), M = x("BasePageHeader"), ne = x("BaseSpinner"), re = x("router-view"), ie = x("BasePage"); + return _(), r(ie, null, { + default: D(() => [ + c(M, { title: k.value }, { + actions: D(() => [o("div", lr, [ + c(O, { to: A.value }, { + default: D(() => [c(E, { variant: "white" }, { + left: D((e) => [c(w, { + name: "ViewColumnsIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: D(() => [s(" " + S(C(d)("tasks_projects.project.board")), 1)]), _: 1 - }, 8, ["onClick"]) + })]), + _: 1 + }, 8, ["to"]), + p.value ? (_(), r(E, { + key: 0, + variant: "primary-outline", + loading: g.value, + disabled: g.value, + onClick: I + }, { + default: D(() => [s(S(p.value.status === "ARCHIVED" ? C(d)("tasks_projects.projects.unarchive") : C(d)("tasks_projects.projects.archive")), 1)]), + _: 1 + }, 8, ["loading", "disabled"])) : i("", !0), + p.value ? (_(), r(E, { + key: 1, + variant: "primary", + onClick: l[0] ||= (e) => v.value = !0 + }, { + left: D((e) => [c(w, { + name: "PencilIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: D(() => [s(" " + S(C(d)("tasks_projects.general.edit")), 1)]), + _: 1 + })) : i("", !0) + ])]), + default: D(() => [c(h, null, { + default: D(() => [ + c(u, { + title: C(d)("tasks_projects.general.home"), + to: "/admin/dashboard" + }, null, 8, ["title"]), + c(u, { + title: C(d)("tasks_projects.projects.title"), + to: C(H).projects + }, null, 8, ["title", "to"]), + c(u, { + title: k.value, + to: "#", + active: "" + }, null, 8, ["title"]) ]), - _: 2 - }, 1032, ["content-loading"])]), + _: 1 + }), p.value ? (_(), a("div", nr, [ + c(y, { class: f(["rounded-full", te(p.value.status)]) }, { + default: D(() => [s(S(L(p.value.status)), 1)]), + _: 1 + }, 8, ["class"]), + p.value.identifier ? (_(), a("span", rr, S(p.value.identifier), 1)) : i("", !0), + p.value.customer_id ? (_(), a("span", ir, [s(S(C(d)("tasks_projects.project.customer")) + ": ", 1), o("span", ar, S(j.value), 1)])) : (_(), a("span", or, S(C(d)("tasks_projects.projects.internal")), 1)), + p.value.due_date ? (_(), a("span", sr, [s(S(C(d)("tasks_projects.project.due_date")) + ": ", 1), o("span", cr, S(C(_e)(p.value.due_date)), 1)])) : i("", !0) + ])) : i("", !0)]), _: 1 - }, 8, ["columns"])], 512), [[w, !V.value]]), - c(_e, { - show: O.value, - client: e.client, - notify: e.notify, - project: k.value, - onClose: n[2] ||= (e) => O.value = !1, - onSaved: se + }, 8, ["title"]), + o("nav", ur, [(_(!0), a(e, null, b(T.value, (e) => (_(), r(O, { + key: e.id, + to: P(e), + custom: "" + }, { + default: D(({ href: t, navigate: n }) => [o("a", { + href: t, + "aria-current": F(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", F(e) ? "border-primary-400 text-heading" : "border-transparent text-muted hover:border-line-strong hover:text-body"]), + onClick: n + }, S(e.label), 11, dr)]), + _: 2 + }, 1032, ["to"]))), 128))]), + m.value && p.value === null ? (_(), a("div", fr, [c(ne, { class: "h-8 w-8 text-primary-500" })])) : (_(), r(re, { + key: 1, + project: p.value, + onRefresh: N + }, null, 8, ["project"])), + c(qn, { + show: v.value, + client: t.client, + notify: t.notify, + project: p.value, + onClose: l[1] ||= (e) => v.value = !1, + onSaved: ee }, null, 8, [ "show", "client", @@ -822,4024 +1693,3572 @@ var Ee = { class: "flex items-center justify-end space-x-5" }, De = { class: "re }); }; } -}), Re = "/api/v1/tasks-projects", ze = { - timeEntries: `${Re}/time-entries`, - timeEntry: (e) => `${Re}/time-entries/${e}`, - timer: `${Re}/timer`, - timerStart: `${Re}/timer/start`, - timerStop: `${Re}/timer/stop`, - taskStatuses: `${Re}/task-statuses`, - taskStatus: (e) => `${Re}/task-statuses/${e}`, - reorderTaskStatuses: `${Re}/task-statuses/reorder`, - tasks: `${Re}/tasks`, - task: (e) => `${Re}/tasks/${e}`, - members: `${Re}/members`, - settings: `${Re}/settings` -}, Be = { bootstrap: "/api/v1/bootstrap" }, Ve = 100, He = 5, Ue = 10; -async function We(e, t) { - let { data: n } = await e.get(ze.timeEntries, { params: t }); - return n; -} -async function Ge(e, t) { - let n = []; - for (let r = 1; r <= He; r += 1) { - let i = await We(e, { - ...t, - page: r, - limit: Ve - }); - if (n.push(...i.data ?? []), !i.meta || r >= i.meta.last_page) break; - } - return n; -} -async function Ke(e, t) { - let { data: n } = await e.post(ze.timeEntries, t); - return n.data; -} -async function qe(e, t, n) { - let { data: r } = await e.put(ze.timeEntry(t), n); - return r.data; -} -async function Je(e, t) { - await e.delete(ze.timeEntry(t)); -} -async function Ye(e) { - let { data: t } = await e.get(ze.timer); - return t?.data ?? null; -} -async function Xe(e, t) { - let { data: n } = await e.post(ze.timerStart, t); - return n.data; -} -async function Ze(e) { - let { data: t } = await e.post(ze.timerStop); - return t.data; -} -async function Qe(e) { - await e.delete(ze.timer); -} -async function $e(e) { - let { data: t } = await e.get(ze.taskStatuses); - return t.data ?? []; -} -async function et(e, t) { - let { data: n } = await e.post(ze.taskStatuses, t); - return n.data; -} -async function tt(e, t, n) { - let { data: r } = await e.put(ze.taskStatus(t), n); - return r.data; -} -async function nt(e, t) { - await e.delete(ze.taskStatus(t)); -} -async function rt(e, t) { - let { data: n } = await e.post(ze.reorderTaskStatuses, { ids: t }); - return n.data ?? []; -} -async function it(e, t, n = Ue) { - let r = { limit: n }; - t.trim() !== "" && (r.search = t.trim()); - let { data: i } = await e.get(ze.tasks, { params: r }); - return i.data ?? []; -} -async function at(e, t) { - let { data: n } = await e.get(ze.task(t)); - return n.data; -} -async function ot(e) { - let { data: t } = await e.get(ze.members); - return t.data ?? []; -} -async function st(e) { - let { data: t } = await e.get(ze.settings); - return t.data; -} -async function ct(e) { - let { data: t } = await e.get(Be.bootstrap), n = t?.current_user?.id; - return typeof n == "number" ? n : null; -} -//#endregion -//#region resources/js/stores/tasks.ts -var lt = _({}), ut = /* @__PURE__ */ new Set(), dt = 5; -function ft(e) { - return e === null ? "" : lt[e] ?? `#${e}`; -} -function pt(e) { - e && typeof e.id == "number" && typeof e.name == "string" && (lt[e.id] = e.name); -} -async function mt(e, t) { - let n = [...new Set(t)].filter((e) => typeof e == "number" && lt[e] === void 0 && !ut.has(e)); - for (let e of n) ut.add(e); - for (let t = 0; t < n.length; t += dt) await Promise.all(n.slice(t, t + dt).map(async (t) => { - try { - pt(await at(e, t)); - } catch {} finally { - ut.delete(t); - } - })); -} -function ht() { - for (let e of Object.keys(lt)) delete lt[Number(e)]; - ut.clear(); -} -//#endregion -//#region resources/js/support/http.ts -function gt(e) { - if (typeof e != "object" || !e) return null; - let t = e.response?.status; - return typeof t == "number" ? t : null; -} -function _t(e) { - return gt(e) === 409; -} -function vt(e) { - return gt(e) === 403; -} -//#endregion -//#region resources/js/support/time.ts -var yt = 60, bt = 60, xt = 7; -function St(e) { - let t = Number.isFinite(e) && e > 0 ? Math.floor(e) : 0, n = Math.floor(t / 3600), r = Math.floor(t % 3600 / bt), i = t % bt; - return `${n}:${zt(r)}:${zt(i)}`; -} -function Ct(e) { - let t = e !== null && Number.isFinite(e) && e > 0 ? Math.round(e) : 0; - return `${Math.floor(t / yt)}:${zt(t % yt)}`; -} -function wt(e) { - let t = e.trim(); - if (t === "") return null; - let n = /^(\d+):([0-5]?\d)$/.exec(t); - if (n) return Number(n[1]) * yt + Number(n[2]); - if (!/^\d+([.,]\d+)?$/.test(t)) return null; - let r = Number(t.replace(",", ".")); - return Number.isNaN(r) ? null : Math.round(r * yt); -} -function Tt(e) { - let t = It(e); - return t === null ? "" : Mt(t); -} -function Et(e) { - let t = It(e); - return t === null ? "" : `${zt(t.getHours())}:${zt(t.getMinutes())}`; -} -function Dt(e, t = "09:00") { - let n = Lt(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 Ot(e, t) { - let n = new Date(e); - return n.setTime(n.getTime() + t * bt * 1e3), n.toISOString(); -} -function kt(e, t) { - let n = Number.isInteger(t) && t >= 0 && t <= 6 ? t : 1, r = Rt(e), i = (r.getDay() - n + xt) % xt; - return r.setDate(r.getDate() - i), r; -} -function At(e) { - return Array.from({ length: xt }, (t, n) => jt(e, n)); -} -function jt(e, t) { - let n = Rt(e); - return n.setDate(n.getDate() + t), n; -} -function Mt(e) { - return `${e.getFullYear()}-${zt(e.getMonth() + 1)}-${zt(e.getDate())}`; -} -function Nt(e) { - return { - weekday: e.toLocaleDateString(void 0, { weekday: "short" }), - day: e.toLocaleDateString(void 0, { - day: "numeric", - month: "short" - }) - }; -} -function Pt(e) { - return Mt(e) === Mt(/* @__PURE__ */ new Date()); -} -function Ft(e) { - let t = It(e); - return t === null ? 0 : Math.max(0, Math.floor((Date.now() - t.getTime()) / 1e3)); -} -function It(e) { - if (!e) return null; - let t = new Date(e); - return Number.isNaN(t.getTime()) ? null : t; -} -function Lt(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 Rt(e) { - let t = new Date(e.getTime()); - return t.setHours(0, 0, 0, 0), t; -} -function zt(e) { - return String(e).padStart(2, "0"); -} -//#endregion -//#region resources/js/stores/timer.ts -var W = _({ - running: null, - elapsedSeconds: 0, - busy: !1 -}), Bt; -function Vt() { - W.elapsedSeconds = W.running === null ? 0 : Ft(W.running.started_at); -} -function Ht() { - Vt(), Bt === void 0 && (Bt = setInterval(Vt, 1e3)); -} -function Ut() { - Bt !== void 0 && (clearInterval(Bt), Bt = void 0), W.elapsedSeconds = 0; -} -function Wt(e, t) { - if (W.running = e && typeof e.id == "number" ? e : null, W.running === null) { - Ut(); - return; - } - Ht(), t && typeof W.running.task_id == "number" && mt(t, [W.running.task_id]); -} -function Gt(e, t, n) { - e?.notify("error", U(t, e.t(n))); -} -var G = { - get running() { - return W.running; - }, - get elapsedSeconds() { - return W.elapsedSeconds; - }, - get busy() { - return W.busy; - }, - async refresh(e) { - try { - Wt(await Ye(e), e); - } catch { - Wt(null); - } - }, - async start(e, t, n = null, r) { - if (W.busy) return null; - W.busy = !0; - try { - let r = await Xe(e, { - task_id: t, - description: n - }); - return Wt(r, e), r; - } catch (t) { - return _t(t) ? (r?.notify("warning", r.t("tasks_projects.timer.already_running")), await this.refresh(e)) : Gt(r, t, "tasks_projects.timer.start_failed"), null; - } finally { - W.busy = !1; - } - }, - async stop(e, t) { - if (W.busy || W.running === null) return null; - W.busy = !0; - try { - let t = await Ze(e); - return Wt(null), t; - } catch (n) { - return Gt(t, n, "tasks_projects.timer.stop_failed"), await this.refresh(e), null; - } finally { - W.busy = !1; - } - }, - async discard(e, t) { - if (W.busy || W.running === null) return !1; - W.busy = !0; - try { - return await Qe(e), Wt(null), !0; - } catch (n) { - return Gt(t, n, "tasks_projects.timer.discard_failed"), await this.refresh(e), !1; - } finally { - W.busy = !1; - } - }, - reset() { - W.busy = !1, Wt(null); - } -}, Kt = { - key: 0, - class: "fixed right-6 bottom-20 z-40 flex flex-col items-end gap-3" -}, qt = ["aria-label"], Jt = { class: "flex items-center justify-between border-b border-line-default px-4 py-3" }, Yt = { class: "text-sm font-semibold text-heading" }, Xt = ["aria-label"], Zt = { +}), mr = { class: "flex items-center justify-end space-x-5" }, hr = { class: "relative table-container" }, gr = { class: "flex items-center" }, _r = { key: 0, - class: "space-y-4 px-4 py-4" -}, Qt = { class: "truncate text-sm font-medium text-heading" }, $t = { class: "mt-1 text-2xl font-semibold tabular-nums text-primary-500" }, en = { - key: 0, - class: "mt-1 text-xs text-muted" -}, tn = { class: "flex items-center gap-2" }, nn = { + class: "block text-xs font-normal text-muted" +}, vr = { key: 0 }, yr = { key: 1, - class: "space-y-3 px-4 py-4" -}, rn = { class: "block" }, an = { class: "sr-only" }, on = ["placeholder"], sn = { - key: 0, - class: "text-xs text-muted" -}, cn = { + class: "text-subtle" +}, br = { key: 1, - class: "max-h-48 space-y-1 overflow-y-auto" -}, ln = ["onClick"], un = { - key: 2, - class: "text-xs text-muted" -}, dn = ["placeholder", "aria-label"], fn = { class: "flex items-center justify-between" }, pn = ["title", "aria-label"], mn = { - key: 0, - class: "tabular-nums" -}, hn = 300, gn = /* @__PURE__ */ l({ - __name: "QuickStartOverlay", + class: "text-subtle" +}, xr = { key: 0 }, Sr = { + key: 1, + class: "text-subtle" +}, Cr = 10, wr = 350, Tr = /* @__PURE__ */ l({ + __name: "ProjectsIndexPage", props: { client: { type: [Function, Object] }, notify: { type: Function }, - enabled: { type: Boolean } + router: {} }, - emits: ["open-timesheet"], - setup(l, { emit: u }) { - let d = l, p = u, h = de(), _ = v(!1), w = v(""), k = v([]), A = v(!1), j = v(null), M = v(""), N, P = n(() => ({ - notify: d.notify, - t: h - })), F = n(() => ft(G.running?.task_id ?? null)), I = n(() => St(G.elapsedSeconds)); - T(() => d.enabled, (e) => { - e || z(); - }), T(_, (e) => { - e && G.running === null && L(); - }), T(w, () => { - clearTimeout(N), N = setTimeout(() => void L(), hn); - }), m(() => clearTimeout(N)); - async function L() { - A.value = !0; + setup(e) { + let t = e, l = { + name: "name", + status: "status", + default_rate: "default_rate", + due_date: "due_date" + }, u = B(), d = y(null), h = y(!1), g = y(!0), b = y(0), w = y(!1), k = y(null), A = y(null), j = v({ + search: "", + status: "ACTIVE" + }), M = n(() => [ + { + id: "ACTIVE", + label: u("tasks_projects.projects.status.active") + }, + { + id: "ARCHIVED", + label: u("tasks_projects.projects.status.archived") + }, + { + id: "ALL", + label: u("tasks_projects.projects.status.all") + } + ]), N = n({ + get: () => M.value.find((e) => e.id === j.status) ?? M.value[0], + set: (e) => { + j.status = e.id; + } + }), P = n(() => [ + { + key: "name", + label: u("tasks_projects.projects.columns.name"), + sortable: !0, + sortBy: "name", + thClass: "extra", + tdClass: "font-medium text-heading" + }, + { + key: "status", + label: u("tasks_projects.projects.columns.status"), + sortable: !0, + sortBy: "status" + }, + { + key: "customer", + label: u("tasks_projects.projects.columns.customer"), + sortable: !1 + }, + { + key: "default_rate", + label: u("tasks_projects.projects.columns.default_rate"), + sortable: !0, + sortBy: "default_rate" + }, + { + key: "due_date", + label: u("tasks_projects.projects.columns.due_date"), + sortable: !0, + sortBy: "due_date" + }, + { + key: "actions", + label: u("tasks_projects.general.actions"), + sortable: !1, + tdClass: "text-right text-sm font-medium" + } + ]), F = n(() => j.search.trim() !== "" || j.status !== "ACTIVE"), ee = n(() => !g.value && b.value === 0 && !F.value), I; + E(() => j.search, () => { + clearTimeout(I), I = setTimeout(() => L(), wr); + }), E(() => j.status, () => L()), m(() => clearTimeout(I)); + async function te({ page: e, sort: n }) { + let r = { + page: e, + limit: Cr, + ...hn(n, l) + }; + j.status !== "ALL" && (r.status = j.status), j.search.trim() !== "" && (r.search = j.search.trim()), g.value = !0; try { - let e = await it(d.client, w.value); - k.value = e, e.forEach(pt); + let e = await gn(t.client, r); + return b.value = e.meta.total, e.data.some((e) => e.customer_id !== null) && Qn(t.client), { + data: e.data, + pagination: { + totalPages: e.meta.last_page, + currentPage: e.meta.current_page, + totalCount: e.meta.total, + limit: e.meta.per_page + } + }; } catch (e) { - k.value = [], d.notify("error", U(e, h("tasks_projects.time.tasks_failed"))); - } finally { - A.value = !1; - } - } - function R(e) { - j.value = e, pt(e); + return t.notify("error", R(e, u("tasks_projects.projects.load_failed"))), { + data: [], + pagination: { + totalPages: 1, + currentPage: 1, + totalCount: 0, + limit: Cr + } + }; + } finally { + g.value = !1; + } } - function z() { - _.value = !1, w.value = "", k.value = [], j.value = null, M.value = ""; + function L(e = !1) { + d.value?.refresh(e); } - async function B() { - let e = j.value; - e !== null && await G.start(d.client, e.id, M.value.trim() || null, P.value) !== null && (d.notify("success", h("tasks_projects.timer.started", { name: e.name })), z()); + function ne() { + h.value && re(), h.value = !h.value; } - async function V() { - let e = F.value, t = await G.stop(d.client, P.value); - t !== null && (d.notify("success", h("tasks_projects.timer.stopped", { - name: e, - duration: Ct(t.duration_minutes) - })), z()); + function re() { + j.search = "", j.status = "ACTIVE"; } - async function H() { - window.confirm(h("tasks_projects.timer.discard_confirm")) && await G.discard(d.client, P.value) && (d.notify("success", h("tasks_projects.timer.discarded")), z()); + function ie() { + k.value = null, w.value = !0; } - return (n, u) => { - let d = b("BaseIcon"), m = b("BaseButton"); - return g(), r(t, { to: "body" }, [l.enabled ? (g(), a("div", Kt, [_.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", Jt, [o("h2", Yt, 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, Xt)]), S(G).running === null ? (g(), a("div", nn, [ - o("label", rn, [o("span", an, 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, on), [[C, w.value]])]), - A.value ? (g(), a("p", sn, x(S(h)("tasks_projects.general.search")), 1)) : k.value.length > 0 ? (g(), a("ul", cn, [(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, ln)]))), 128))])) : (g(), a("p", un, 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, dn), [[C, M.value]]), - o("div", fn, [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(G).busy, - onClick: B - }, { - 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", Zt, [o("div", null, [ - o("p", Qt, x(F.value), 1), - o("p", $t, x(I.value), 1), - S(G).running.description ? (g(), a("p", en, x(S(G).running.description), 1)) : i("", !0) - ]), o("div", tn, [c(m, { - variant: "primary", - disabled: S(G).busy, - onClick: V - }, { - 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(G).busy, - onClick: H - }, { - default: E(() => [s(x(S(h)("tasks_projects.timer.discard")), 1)]), + function ae(e) { + k.value = e, w.value = !0; + } + function oe(e) { + let n = k.value ? u("tasks_projects.projects.updated", { name: e.name }) : u("tasks_projects.projects.created", { name: e.name }); + w.value = !1, k.value = null, t.notify("success", n), L(); + } + async function se(e) { + A.value = e.id; + try { + e.status === "ARCHIVED" ? (await bn(t.client, e.id), t.notify("success", u("tasks_projects.projects.unarchived", { name: e.name }))) : (await yn(t.client, e.id), t.notify("success", u("tasks_projects.projects.archived", { name: e.name }))), L(!0); + } catch (e) { + t.notify("error", R(e, u("tasks_projects.projects.save_failed"))); + } finally { + A.value = null; + } + } + async function ce(e) { + if (window.confirm(u("tasks_projects.projects.delete_confirm", { name: e.name }))) { + A.value = e.id; + try { + await xn(t.client, e.id), t.notify("success", u("tasks_projects.projects.deleted", { name: e.name })), L(!0); + } catch (e) { + t.notify("error", R(e, u("tasks_projects.projects.delete_failed"))); + } finally { + A.value = null; + } + } + } + function le(e) { + return e === "ACTIVE" ? "bg-primary-50! text-primary-500!" : "bg-surface-tertiary! text-muted!"; + } + function ue(e) { + return u(e === "ACTIVE" ? "tasks_projects.projects.status.active" : "tasks_projects.projects.status.archived"); + } + return (t, n) => { + let l = x("BaseBreadcrumbItem"), m = x("BaseBreadcrumb"), g = x("BaseIcon"), v = x("BaseButton"), y = x("router-link"), b = x("BasePageHeader"), E = x("BaseInput"), F = x("BaseInputGroup"), I = x("BaseSelectInput"), L = x("BaseFilterWrapper"), R = x("BaseEmptyPlaceholder"), z = x("BaseBadge"), de = x("BaseFormatMoney"), fe = x("BaseDropdownItem"), pe = x("BaseDropdown"), me = x("BaseTable"), he = x("BasePage"); + return _(), r(he, null, { + default: D(() => [ + c(b, { title: C(u)("tasks_projects.projects.title") }, { + actions: D(() => [o("div", mr, [ + c(y, { to: C(H).tasks }, { + default: D(() => [c(v, { variant: "white" }, { + left: D((e) => [c(g, { + name: "ClipboardDocumentListIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: D(() => [s(" " + S(C(u)("tasks_projects.tasks.title")), 1)]), + _: 1 + })]), + _: 1 + }, 8, ["to"]), + c(y, { to: C(H).reports }, { + default: D(() => [c(v, { variant: "white" }, { + left: D((e) => [c(g, { + name: "ChartBarIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: D(() => [s(" " + S(C(u)("tasks_projects.reports.title")), 1)]), + _: 1 + })]), + _: 1 + }, 8, ["to"]), + c(v, { + variant: "primary-outline", + onClick: ne + }, { + right: D((e) => [h.value ? (_(), r(g, { + key: 1, + name: "XMarkIcon", + class: f(e.class) + }, null, 8, ["class"])) : (_(), r(g, { + key: 0, + name: "FunnelIcon", + class: f(e.class) + }, null, 8, ["class"]))]), + default: D(() => [s(S(C(u)("tasks_projects.general.filter")) + " ", 1)]), + _: 1 + }), + c(v, { + variant: "primary", + onClick: ie + }, { + left: D((e) => [c(g, { + name: "PlusIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: D(() => [s(" " + S(C(u)("tasks_projects.projects.new_project")), 1)]), + _: 1 + }) + ])]), + default: D(() => [c(m, null, { + default: D(() => [c(l, { + title: C(u)("tasks_projects.general.home"), + to: "/admin/dashboard" + }, null, 8, ["title"]), c(l, { + title: C(u)("tasks_projects.projects.title"), + to: "#", + active: "" + }, null, 8, ["title"])]), + _: 1 + })]), + _: 1 + }, 8, ["title"]), + c(L, { + show: h.value, + class: "mt-3", + onClear: re + }, { + default: D(() => [c(F, { + label: C(u)("tasks_projects.general.search"), + class: "mt-2 flex-1" + }, { + default: D(() => [c(E, { + modelValue: j.search, + "onUpdate:modelValue": n[0] ||= (e) => j.search = e, + type: "text", + name: "search", + autocomplete: "off", + placeholder: C(u)("tasks_projects.projects.search_placeholder") + }, null, 8, ["modelValue", "placeholder"])]), + _: 1 + }, 8, ["label"]), c(F, { + label: C(u)("tasks_projects.projects.columns.status"), + class: "mt-2 flex-1" + }, { + default: D(() => [c(I, { + modelValue: N.value, + "onUpdate:modelValue": n[1] ||= (e) => N.value = e, + options: M.value, + "label-key": "label" + }, null, 8, ["modelValue", "options"])]), + _: 1 + }, 8, ["label"])]), + _: 1 + }, 8, ["show"]), + O(c(R, { + title: C(u)("tasks_projects.projects.empty_title"), + description: C(u)("tasks_projects.projects.empty_description") + }, { + actions: D(() => [c(v, { + variant: "primary", + onClick: ie + }, { + left: D((e) => [c(g, { + name: "PlusIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: D(() => [s(" " + S(C(u)("tasks_projects.projects.new_project")), 1)]), + _: 1 + })]), + default: D(() => [c(g, { + name: "FolderIcon", + class: "mt-5 mb-4 h-16 w-16 text-subtle" + })]), + _: 1 + }, 8, ["title", "description"]), [[T, ee.value]]), + O(o("div", hr, [c(me, { + ref_key: "tableRef", + ref: d, + data: te, + columns: P.value, + class: "mt-3" + }, { + "cell-name": D(({ row: e }) => [o("div", gr, [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: C(H).project(e.data.id) + }, { + default: D(() => [s(S(e.data.name), 1)]), + _: 2 + }, 1032, ["to"]), e.data.identifier ? (_(), a("span", _r, S(e.data.identifier), 1)) : i("", !0)])])]), + "cell-status": D(({ row: e }) => [c(z, { class: f(["rounded-full", le(e.data.status)]) }, { + default: D(() => [s(S(ue(e.data.status)), 1)]), + _: 2 + }, 1032, ["class"])]), + "cell-customer": D(({ row: e }) => [e.data.customer_id ? (_(), a("span", vr, S(C(Zn)(e.data.customer_id)), 1)) : (_(), a("span", yr, S(C(u)("tasks_projects.projects.internal")), 1))]), + "cell-default_rate": D(({ row: e }) => [e.data.default_rate === null ? (_(), a("span", br, "-")) : (_(), r(de, { + key: 0, + amount: e.data.default_rate + }, null, 8, ["amount"]))]), + "cell-due_date": D(({ row: e }) => [e.data.due_date ? (_(), a("span", xr, S(C(_e)(e.data.due_date)), 1)) : (_(), a("span", Sr, "-"))]), + "cell-actions": D(({ row: e }) => [c(pe, { "content-loading": A.value === e.data.id }, { + activator: D(() => [c(g, { + name: "EllipsisHorizontalIcon", + class: "h-5 text-muted" + })]), + default: D(() => [ + c(fe, { onClick: (t) => ae(e.data) }, { + default: D(() => [c(g, { + name: "PencilIcon", + class: "mr-3 h-5 w-5 text-subtle group-hover:text-muted" + }), s(" " + S(C(u)("tasks_projects.general.edit")), 1)]), + _: 1 + }, 8, ["onClick"]), + c(fe, { onClick: (t) => se(e.data) }, { + default: D(() => [c(g, { + name: e.data.status === "ARCHIVED" ? "ArrowPathIcon" : "ArchiveBoxIcon", + class: "mr-3 h-5 w-5 text-subtle group-hover:text-muted" + }, null, 8, ["name"]), s(" " + S(e.data.status === "ARCHIVED" ? C(u)("tasks_projects.projects.unarchive") : C(u)("tasks_projects.projects.archive")), 1)]), + _: 2 + }, 1032, ["onClick"]), + c(fe, { onClick: (t) => ce(e.data) }, { + default: D(() => [c(g, { + name: "TrashIcon", + class: "mr-3 h-5 w-5 text-subtle group-hover:text-muted" + }), s(" " + S(C(u)("tasks_projects.general.delete")), 1)]), + _: 1 + }, 8, ["onClick"]) + ]), + _: 2 + }, 1032, ["content-loading"])]), + _: 1 + }, 8, ["columns"])], 512), [[T, !ee.value]]), + c(qn, { + show: w.value, + client: e.client, + notify: e.notify, + project: k.value, + onClose: n[2] ||= (e) => w.value = !1, + onSaved: oe + }, null, 8, [ + "show", + "client", + "notify", + "project" + ]) + ]), _: 1 - }, 8, ["disabled"])])]))], 40, qt)) : 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(G).running === null ? "ClockIcon" : "StopIcon", - class: "h-5 w-5 text-white" - }, null, 8, ["name"]), S(G).running === null ? i("", !0) : (g(), a("span", mn, x(I.value), 1))], 8, pn)])) : i("", !0)]); + }); }; } -}), _n = { +}), Er = { class: "py-4" }, Dr = { class: "rounded-xl border border-line-default bg-surface p-5" }, Or = { class: "flex flex-col gap-4 lg:flex-row lg:items-end" }, kr = { key: 0, - class: "relative float-left m-0 ml-2" -}, vn = ["title"], yn = ["aria-label"], bn = { class: "font-medium tabular-nums" }, xn = [ - "disabled", - "title", - "aria-label" -], Sn = /* @__PURE__ */ l({ - __name: "TimerChip", + class: "mt-3 text-xs text-subtle" +}, Ar = { class: "mt-4 overflow-hidden rounded-xl border border-line-default bg-surface" }, jr = { + key: 0, + class: "flex justify-center py-10" +}, Mr = { + key: 1, + class: "px-5 py-8 text-center text-sm text-muted" +}, Nr = { + key: 2, + class: "divide-y divide-line-light" +}, Pr = { class: "text-sm font-medium text-heading" }, Fr = { class: "text-xs text-muted" }, Ir = { + key: 1, + class: "text-subtle" +}, Lr = /* @__PURE__ */ l({ + __name: "ProjectMembersTab", props: { + id: {}, client: { type: [Function, Object] }, - notify: { type: Function } + notify: { type: Function }, + project: {} }, - emits: ["open"], - setup(e, { emit: t }) { - let r = e, s = t, l = de(), u = n(() => ft(G.running?.task_id ?? null)), d = n(() => St(G.elapsedSeconds)); - async function f() { - let e = u.value, t = await G.stop(r.client, { - notify: r.notify, - t: l - }); - t !== null && r.notify("success", l("tasks_projects.timer.stopped", { - name: e, - duration: Ct(t.duration_minutes) - })); + setup(t) { + let l = t, u = B(), d = y([]), p = y([]), m = y(!0), g = y(!1), v = y(null), w = y(null), T = y(""), E = y({}), 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 + }))); + h(() => { + A(); + }); + async function A() { + m.value = !0; + try { + p.value = await Sn(l.client); + } catch (e) { + l.notify("error", R(e, u("tasks_projects.tasks.members_failed"))); + } + try { + d.value = await Rn(l.client, O.value); + } catch (e) { + l.notify("error", R(e, u("tasks_projects.project.members.load_failed"))); + } finally { + m.value = !1; + } } - return (e, t) => { - let n = b("BaseIcon"); - return S(G).running === null ? i("", !0) : (g(), a("li", _n, [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, yn), - o("span", bn, x(d.value), 1), - o("button", { - type: "button", - class: "rounded p-1 hover:bg-white/20 disabled:opacity-50", - disabled: S(G).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, xn) - ], 8, vn)])); - }; - } -}), Cn = { 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." - } -} } }, wn = { class: "relative table-container" }, Tn = { class: "block max-w-64 truncate" }, En = { class: "tabular-nums" }, Dn = { - key: 1, - class: "text-subtle" -}, On = /* @__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 = de(), 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 ? se(e) : ""; - } - function P(e) { - m.to = e ? se(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 We(l.client, t), n = e.data ?? []; - return mt(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", U(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", wn, [c(z, { - ref_key: "tableRef", - ref: p, - data: I, - columns: k.value, - class: "mt-3" - }, { - "cell-date": E(({ row: e }) => [s(x(S(oe)(S(Tt)(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(ft)(e.data.task_id)), 1)]), - "cell-description": E(({ row: e }) => [o("span", Tn, x(e.data.description || "-"), 1)]), - "cell-duration": E(({ row: e }) => [o("span", En, x(S(Ct)(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", Dn, "-"))]), - "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"])])]); - }; - } -}), kn = { class: "flex w-full items-center justify-between" }, An = { class: "space-y-5 px-6 py-6" }, jn = { - key: 0, - class: "rounded-md bg-alert-warning-bg px-3 py-2 text-sm text-alert-warning-text" -}, Mn = { class: "inline-flex overflow-hidden rounded-md border border-line-default" }, Nn = ["disabled", "onClick"], Pn = { - key: 1, - class: "text-sm text-muted" -}, Fn = { class: "flex items-center justify-between border-t border-line-default px-6 py-4" }, In = { key: 1 }, Ln = { class: "flex space-x-3" }, Rn = "09:00", zn = /* @__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 = de(), h = _({ - date: "", - mode: "duration", - duration: "", - start: Rn, - 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 ? Tt(e.started_at) : u.defaultDate ?? Mt(/* @__PURE__ */ new Date()), h.duration = e ? Ct(e.duration_minutes) : "", h.start = e?.started_at ? Et(e.started_at) : Rn, h.end = e?.ended_at ? Et(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 ?? Mt(/* @__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 at(u.client, e); - C.value = t, pt(t); - } catch {} - } - async function I(e) { - try { - let t = await it(u.client, e ?? ""); - return t.forEach(pt), t; - } catch (e) { - return u.notify("error", U(e, m("tasks_projects.time.tasks_failed"))), []; - } - } - function L(e) { - h.date = e ? se(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 = Dt(h.date, h.mode === "range" ? h.start : Rn); - n === null && (e.started_at = m("tasks_projects.time.range_invalid")); - let r = h.mode === "duration" ? wt(h.duration) : null; - h.mode === "duration" && r === null && (e.duration_minutes = m("tasks_projects.time.duration_invalid")); - let i = h.mode === "range" ? Dt(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 = Ot(n, r)) : a.ended_at = i, a; - } - async function B() { - if (D.value || j.value) return; - let e = z(); - if (e !== null) { - D.value = !0; - try { - let t = u.entry, n = t ? await qe(u.client, t.id, e) : await Ke(u.client, e); - d("saved", n); - } catch (e) { - w.value = te(e), u.notify("error", U(e, m("tasks_projects.time.save_failed"))); - } finally { - D.value = !1; - } - } - } - async function V() { - let e = u.entry; - if (!(e === null || O.value || j.value) && window.confirm(m("tasks_projects.time.delete_confirm"))) { - O.value = !0; - try { - await Je(u.client, e.id), d("deleted", e); - } catch (e) { - u.notify("error", U(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"), H = b("BaseButton"), ee = b("BaseModal"); - return g(), r(ee, { - show: t.show, - onClose: l[8] ||= (e) => d("close") - }, { - header: E(() => [o("div", kn, [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(B, ["prevent"]) }, [o("div", An, [ - j.value ? (g(), a("p", jn, 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", Mn, [(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, Nn)), 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", Pn, 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", Fn, [A.value && !j.value ? (g(), r(H, { - key: 0, - type: "button", - variant: "danger", - size: "sm", - loading: O.value, - disabled: O.value, - onClick: V + function j(e) { + return p.value.find((t) => t.id === e)?.name ?? u("tasks_projects.project.time.removed_member"); + } + async function M() { + let e = w.value; + if (!(e === null || g.value)) { + g.value = !0, E.value = {}; + try { + await zn(l.client, O.value, { + user_id: e.id, + rate: me(T.value) + }), l.notify("success", u("tasks_projects.project.members.attached", { name: e.label })), w.value = null, T.value = "", await A(); + } catch (e) { + E.value = le(e), l.notify("error", R(e, u("tasks_projects.project.members.attach_failed"))); + } finally { + g.value = !1; + } + } + } + async function N(e) { + let t = j(e.user_id); + if (window.confirm(u("tasks_projects.project.members.detach_confirm", { name: t }))) { + v.value = e.user_id; + try { + await Bn(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", R(e, u("tasks_projects.project.members.detach_failed"))); + } finally { + v.value = null; + } + } + } + return (t, n) => { + let l = x("BaseSelectInput"), p = x("BaseInputGroup"), h = x("BaseInput"), y = x("BaseIcon"), O = x("BaseButton"), A = x("BaseSpinner"), P = x("BaseFormatMoney"); + return _(), a("div", Er, [o("div", Dr, [o("div", Or, [ + c(p, { + label: C(u)("tasks_projects.project.members.member"), + error: E.value.user_id, + class: "flex-1" }, { - default: E(() => [s(x(S(m)("tasks_projects.general.delete")), 1)]), + default: D(() => [c(l, { + modelValue: w.value, + "onUpdate:modelValue": n[0] ||= (e) => w.value = e, + options: k.value, + placeholder: C(u)("tasks_projects.project.members.attach_placeholder"), + "label-key": "label" + }, null, 8, [ + "modelValue", + "options", + "placeholder" + ])]), _: 1 - }, 8, ["loading", "disabled"])) : (g(), a("span", In)), o("div", Ln, [c(H, { - type: "button", - variant: "primary-outline", - onClick: l[7] ||= (e) => d("close") + }, 8, ["label", "error"]), + c(p, { + label: C(u)("tasks_projects.project.members.rate"), + error: E.value.rate, + "help-text": C(u)("tasks_projects.project.members.rate_help"), + class: "flex-1" }, { - default: E(() => [s(x(j.value ? S(m)("tasks_projects.timer.close") : S(m)("tasks_projects.general.cancel")), 1)]), + default: D(() => [c(h, { + modelValue: T.value, + "onUpdate:modelValue": n[1] ||= (e) => T.value = e, + type: "number", + step: "0.01", + min: "0" + }, null, 8, ["modelValue"])]), _: 1 - }), j.value ? i("", !0) : (g(), r(H, { - key: 0, - type: "submit", + }, 8, [ + "label", + "error", + "help-text" + ]), + c(O, { variant: "primary", - loading: D.value, - disabled: D.value + class: "lg:mb-1", + loading: g.value, + disabled: g.value || w.value === null, + onClick: M }, { - default: E(() => [s(x(A.value ? S(m)("tasks_projects.general.update") : S(m)("tasks_projects.general.save")), 1)]), + left: D((e) => [c(y, { + name: "PlusIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: D(() => [s(" " + S(C(u)("tasks_projects.project.members.attach")), 1)]), _: 1 - }, 8, ["loading", "disabled"]))])])], 32)]), + }, 8, ["loading", "disabled"]) + ]), k.value.length === 0 && !m.value ? (_(), a("p", kr, S(C(u)("tasks_projects.project.members.all_attached")), 1)) : i("", !0)]), o("div", Ar, [m.value ? (_(), a("div", jr, [c(A, { class: "h-6 w-6 text-primary-500" })])) : d.value.length === 0 ? (_(), a("p", Mr, S(C(u)("tasks_projects.project.members.empty")), 1)) : (_(), a("ul", Nr, [(_(!0), a(e, null, b(d.value, (e) => (_(), a("li", { + key: e.id, + class: "flex items-center justify-between px-5 py-4" + }, [o("div", null, [o("p", Pr, S(j(e.user_id)), 1), o("p", Fr, [s(S(C(u)("tasks_projects.project.members.rate")) + ": ", 1), e.rate === null ? (_(), a("span", Ir, S(C(u)("tasks_projects.tasks.none")), 1)) : (_(), r(P, { + key: 0, + amount: e.rate + }, null, 8, ["amount"]))])]), c(O, { + variant: "danger", + size: "sm", + loading: v.value === e.user_id, + disabled: v.value === e.user_id, + onClick: (t) => N(e) + }, { + default: D(() => [s(S(C(u)("tasks_projects.general.delete")), 1)]), _: 1 - }, 8, ["show"]); + }, 8, [ + "loading", + "disabled", + "onClick" + ])]))), 128))]))])]); }; } -}), Bn = { class: "mt-4 flex flex-wrap items-center justify-between gap-3" }, Vn = { class: "flex items-center gap-2" }, Hn = { class: "ml-1 text-sm text-muted" }, Un = { class: "flex items-center gap-2 text-sm" }, Wn = { class: "text-muted" }, Gn = { class: "text-lg font-semibold tabular-nums text-heading" }, Kn = { - key: 0, - class: "mt-6 text-sm text-muted" -}, qn = { - key: 1, - class: "mt-4 grid gap-3 sm:grid-cols-2 lg:grid-cols-4 xl:grid-cols-7" -}, Jn = { class: "flex items-baseline justify-between" }, Yn = { class: "text-xs font-semibold tracking-wide text-heading uppercase" }, Xn = { class: "text-xs text-muted" }, Zn = { class: "text-sm font-medium tabular-nums text-heading" }, Qn = { class: "mt-3 flex-1 space-y-2" }, $n = ["onClick"], er = { class: "flex items-center justify-between gap-2" }, tr = { class: "truncate text-xs font-medium text-heading" }, nr = { class: "shrink-0 text-xs tabular-nums text-muted" }, rr = { +}), Rr = { key: 0, - class: "mt-1 block truncate text-xs text-muted" -}, ir = { class: "mt-1 flex items-center gap-1" }, ar = { class: "text-[11px] text-subtle" }, or = { + class: "py-6" +}, zr = { class: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4" }, Br = { class: "rounded-xl border border-line-default bg-surface p-5" }, Vr = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, Hr = { class: "mt-2 text-2xl font-semibold text-heading" }, Ur = { class: "mt-1 text-xs text-muted" }, Wr = { class: "rounded-xl border border-line-default bg-surface p-5" }, Gr = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, Kr = { class: "mt-2 text-2xl font-semibold text-heading" }, qr = { class: "mt-1 text-xs text-muted" }, Jr = { class: "rounded-xl border border-line-default bg-surface p-5" }, Yr = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, Xr = { class: "mt-2 text-2xl font-semibold text-heading" }, Zr = { class: "rounded-xl border border-line-default bg-surface p-5" }, Qr = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, $r = { class: "mt-2 text-2xl font-semibold text-heading" }, ei = ["title"], ti = { class: "mt-4 rounded-xl border border-line-default bg-surface p-5" }, ni = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, ri = { class: "mt-2 text-sm text-body" }, ii = { class: "mt-3 h-2 w-full overflow-hidden rounded-full bg-surface-tertiary" }, ai = { key: 0, - class: "text-[11px] text-subtle" -}, sr = { + class: "mt-2 text-xs font-medium text-status-red" +}, oi = { + key: 1, + class: "mt-2 text-sm text-subtle" +}, si = { class: "mt-4 rounded-xl border border-line-default bg-surface p-5" }, ci = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, li = { key: 0, - class: "py-2 text-xs text-subtle" -}, cr = ["onClick"], lr = { - key: 2, - class: "mt-4 text-center text-sm text-subtle" -}, ur = /* @__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 = de(), m = v(kt(/* @__PURE__ */ new Date(), u.weekStart)), h = v([]), _ = v(!1), C = n(() => At(m.value)), w = n(() => { - let e = C.value[0], t = C.value[C.value.length - 1]; - return `${Nt(e).day} - ${Nt(t).day}`; - }), D = n(() => C.value.map((e) => { - let t = Mt(e), n = h.value.filter((e) => Tt(e.started_at) === t), r = Nt(e); - return { - key: t, - weekday: r.weekday, - day: r.day, - today: Pt(e), - entries: n, - minutes: A(n) - }; - })), O = n(() => A(h.value)), k = n(() => !_.value && h.value.length === 0); - T(() => u.weekStart, (e) => { - m.value = kt(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 Ge(u.client, { - user_id: u.userId, - from: Mt(C.value[0]), - to: Mt(C.value[C.value.length - 1]) - }); - h.value = e, mt(u.client, e.map((e) => e.task_id).filter((e) => typeof e == "number")); - } catch (e) { - h.value = [], u.notify("error", U(e, p("tasks_projects.time.load_failed"))); - } finally { - _.value = !1; - } - } - function M(e) { - m.value = jt(m.value, e * 7); - } - function N() { - m.value = kt(/* @__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", Bn, [o("div", Vn, [ - 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", Hn, x(w.value), 1) - ]), o("div", Un, [ - o("span", Wn, x(S(p)("tasks_projects.time.week_total")), 1), - o("span", Gn, x(S(Ct)(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", Kn, x(S(p)("tasks_projects.time.unknown_user")), 1)) : (g(), a("div", qn, [(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", Jn, [o("div", null, [o("p", Yn, x(t.weekday), 1), o("p", Xn, x(t.day), 1)]), o("span", Zn, x(S(Ct)(t.minutes)), 1)]), - o("ul", Qn, [(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", er, [o("span", tr, x(S(ft)(e.task_id)), 1), o("span", nr, x(S(Ct)(e.duration_minutes)), 1)]), - e.description ? (g(), a("span", rr, x(e.description), 1)) : i("", !0), - o("span", ir, [ - 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", ar, 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", or, " - " + x(S(p)("tasks_projects.time.billed")), 1)) - ]) - ], 8, $n)]))), 128)), t.entries.length === 0 ? (g(), a("li", sr, 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, cr) - ], 2))), 128))])), - k.value && t.userId !== null ? (g(), a("p", lr, x(S(p)("tasks_projects.time.empty_description")), 1)) : i("", !0) - ]); + class: "mt-2 text-sm whitespace-pre-line text-body" +}, ui = { + key: 1, + class: "mt-2 text-sm text-subtle" +}, di = { + key: 1, + class: "flex justify-center py-16" +}, fi = /* @__PURE__ */ l({ + __name: "ProjectOverviewTab", + props: { project: {} }, + setup(t) { + let r = t, l = B(), u = n(() => r.project?.totals ?? null), d = n(() => r.project?.budget_minutes ?? null), m = n(() => { + let e = d.value, t = u.value?.logged_minutes ?? 0; + return e ? Math.min(100, Math.round(t / e * 100)) : 0; + }), h = n(() => r.project?.customer_id !== null && (u.value?.unbilled_amount ?? 0) > 0), g = n(() => { + let e = d.value, t = u.value?.logged_minutes ?? 0; + return e && t > e ? t - e : 0; + }); + return (n, r) => { + let v = x("BaseFormatMoney"), y = x("BaseIcon"), b = x("BaseButton"), w = x("BaseSpinner"); + return t.project && u.value ? (_(), a("div", Rr, [ + o("div", zr, [ + o("div", Br, [ + o("p", Vr, S(C(l)("tasks_projects.project.overview.tasks")), 1), + o("p", Hr, S(u.value.tasks.total), 1), + o("p", Ur, S(C(l)("tasks_projects.project.overview.open_tasks", { count: u.value.tasks.open })) + " · " + S(C(l)("tasks_projects.project.overview.closed_tasks", { count: u.value.tasks.closed })), 1) + ]), + o("div", Wr, [ + o("p", Gr, S(C(l)("tasks_projects.project.overview.logged")), 1), + o("p", Kr, S(C(ye)(u.value.logged_minutes)), 1), + o("p", qr, S(C(l)("tasks_projects.project.overview.billable")) + ": " + S(C(ye)(u.value.billable_minutes)), 1) + ]), + o("div", Jr, [o("p", Yr, S(C(l)("tasks_projects.project.overview.billable_amount")), 1), o("p", Xr, [c(v, { amount: u.value.billable_amount }, null, 8, ["amount"])])]), + o("div", Zr, [ + o("p", Qr, S(C(l)("tasks_projects.project.overview.unbilled_amount")), 1), + o("p", $r, [c(v, { amount: u.value.unbilled_amount }, null, 8, ["amount"])]), + h.value ? (_(), a("span", { + key: 0, + class: "mt-2 inline-flex cursor-not-allowed opacity-60", + title: C(l)("tasks_projects.project.invoice_soon") + }, [c(b, { + variant: "primary-outline", + size: "sm", + disabled: "" + }, { + left: D((e) => [c(y, { + name: "BanknotesIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: D(() => [s(" " + S(C(l)("tasks_projects.project.invoice_project")), 1)]), + _: 1 + })], 8, ei)) : i("", !0) + ]) + ]), + o("div", ti, [o("p", ni, S(C(l)("tasks_projects.project.overview.budget")), 1), d.value ? (_(), a(e, { key: 0 }, [ + o("p", ri, S(C(l)("tasks_projects.project.overview.budget_used", { + used: C(ye)(u.value.logged_minutes), + total: C(ye)(d.value) + })), 1), + o("div", ii, [o("div", { + class: f(["h-2 rounded-full", g.value > 0 ? "bg-status-red" : "bg-primary-500"]), + style: p({ width: `${m.value}%` }) + }, null, 6)]), + g.value > 0 ? (_(), a("p", ai, S(C(l)("tasks_projects.project.overview.budget_over", { amount: C(ye)(g.value) })), 1)) : i("", !0) + ], 64)) : (_(), a("p", oi, S(C(l)("tasks_projects.project.overview.no_budget")), 1))]), + o("div", si, [o("p", ci, S(C(l)("tasks_projects.project.overview.description")), 1), t.project.description ? (_(), a("p", li, S(t.project.description), 1)) : (_(), a("p", ui, S(C(l)("tasks_projects.project.overview.no_description")), 1))]) + ])) : (_(), a("div", di, [c(w, { class: "h-8 w-8 text-primary-500" })])); }; } -}), dr = { - default_rate: 0, - rounding_minutes: 1, - week_start: 1, - members_see_all_time: !1, - rounding_increments: [ - 1, - 6, - 15, - 30 - ] -}, fr = _({ - adminMode: !1, - userId: null, - settings: { ...dr }, - companySession: 0, - loading: !1 -}); -async function pr(e) { - if (fr.adminMode) return; - fr.loading = !0; - let [t, n] = await Promise.all([ct(e).catch(() => null), st(e).catch(() => null)]); - fr.userId = t, fr.settings = gr(n), fr.loading = !1; -} -function mr() { - fr.userId = null, fr.settings = { ...dr }, fr.companySession += 1, fr.loading = !1; -} -function hr(e) { - fr.adminMode = e; +}), pi = { + project: "", + user: "", + status: "", + search: "" +}; +function mi(e) { + return e === "uninvoiced" || e === "invoiced"; } -function gr(e) { - if (typeof e != "object" || !e) return { ...dr }; - let t = Array.isArray(e.rounding_increments) ? e.rounding_increments.filter((e) => typeof e == "number") : dr.rounding_increments; +function hi(e) { return { - default_rate: _r(e.default_rate, dr.default_rate), - rounding_minutes: _r(e.rounding_minutes, dr.rounding_minutes), - week_start: vr(e.week_start), - members_see_all_time: e.members_see_all_time === !0, - rounding_increments: t.length > 0 ? t : dr.rounding_increments + project: Ci(e.project), + user: Ci(e.user), + status: wi(e.status), + search: Si(e.search).slice(0, 200) }; } -function _r(e, t) { - return typeof e == "number" && Number.isFinite(e) ? e : t; +function gi(e) { + let t = {}; + for (let n of [ + "project", + "user", + "status", + "search" + ]) e[n] !== "" && (t[n] = e[n]); + return t; +} +function _i(e) { + return e.project !== "" || e.user !== "" || e.status !== "" || e.search !== ""; +} +function vi(e) { + return [ + e.project, + e.user, + e.status, + e.search + ].join("|"); +} +function yi(e, t) { + return e.project === t.project && e.user === t.user && e.status === t.status && e.search === t.search; +} +function bi(e, t = {}) { + let n = {}, r = t.projectId ?? xi(e.project); + r !== null && (n.project_id = r); + let i = xi(e.user); + if (i !== null && (n.assignee_id = i), mi(e.status)) n.invoiced = +(e.status === "invoiced"); + else { + let t = xi(e.status); + t !== null && (n.task_status_id = t); + } + return e.search !== "" && (n.search = e.search), n; +} +function xi(e) { + let t = Number(e); + return e !== "" && Number.isInteger(t) && t > 0 ? t : null; } -function vr(e) { - return typeof e == "number" && Number.isInteger(e) && e >= 0 && e <= 6 ? e : dr.week_start; +function Si(e) { + let t = Array.isArray(e) ? e[0] : e; + return typeof t == "string" ? t.trim() : ""; +} +function Ci(e) { + let t = Si(e); + return xi(t) === null ? "" : t; +} +function wi(e) { + let t = Si(e); + return mi(t) ? t : Ci(e); } //#endregion -//#region resources/js/pages/TimePage.vue?vue&type=script&setup=true&lang.ts -var yr = { class: "flex items-center justify-end space-x-5" }, br = { +//#region resources/js/components/TaskFilters.vue?vue&type=script&setup=true&lang.ts +var Ti = { class: "mt-4 flex flex-wrap items-end gap-3" }, Ei = { key: 0, - class: "max-sm:hidden flex items-center gap-2 text-sm text-muted" -}, xr = { - key: 0, - class: "mt-4 flex gap-6 border-b border-line-default" -}, Sr = 5, Cr = /* @__PURE__ */ l({ - __name: "TimePage", + class: "min-w-44 flex-1" +}, Di = { class: "mb-1 block text-xs font-medium text-muted" }, Oi = { class: "min-w-44 flex-1" }, ki = { class: "mb-1 block text-xs font-medium text-muted" }, Ai = { class: "min-w-44 flex-1" }, ji = { class: "mb-1 block text-xs font-medium text-muted" }, Mi = { class: "min-w-44 flex-1" }, Ni = { class: "mb-1 block text-xs font-medium text-muted" }, Pi = 350, Fi = /* @__PURE__ */ l({ + __name: "TaskFilters", props: { - client: { type: [Function, Object] }, - notify: { type: Function }, - router: {} - }, - setup(e) { - let t = e, l = de(), u = v("MINE"), d = v(!1), p = v([]), m = v([]), _ = v(!1), y = v(null), C = v(Mt(/* @__PURE__ */ new Date())), w = v(0), T = n(() => fr.settings.week_start), D = n(() => fr.userId); - h(() => void O()); - async function O() { - fr.userId === null && await pr(t.client), d.value = fr.settings.members_see_all_time || await k(), d.value && await Promise.all([A(), j()]); - } - async function k() { - try { - return ((await We(t.client, { limit: Sr })).data ?? []).some((e) => e.user_id !== fr.userId); - } catch { - return !1; - } - } - async function A() { - try { - p.value = await ot(t.client); - } catch { - p.value = []; - } + modelValue: {}, + projects: {}, + members: {}, + statuses: {}, + lockProject: { + type: Boolean, + default: !1 } - async function j() { - try { - let e = await F(t.client, { - limit: 100, - sort_by: "name" - }); - m.value = e.data ?? []; - } catch { - m.value = []; + }, + emits: ["update:modelValue"], + setup(e, { emit: t }) { + let r = e, s = t, l = B(), u = y(r.modelValue.search), d, f = n(() => [{ + id: "", + label: l("tasks_projects.tasks.filters.all_projects") + }, ...r.projects.map((e) => ({ + id: String(e.id), + label: e.name + }))]), p = n(() => [{ + id: "", + label: l("tasks_projects.tasks.filters.all_members") + }, ...r.members.map((e) => ({ + id: String(e.id), + label: e.name + }))]), h = n(() => [ + { + id: "", + label: l("tasks_projects.tasks.filters.all_statuses") + }, + ...r.statuses.map((e) => ({ + id: String(e.id), + label: e.name + })), + { + id: "uninvoiced", + label: l("tasks_projects.tasks.uninvoiced") + }, + { + id: "invoiced", + label: l("tasks_projects.tasks.invoiced") } + ]), g = n({ + get: () => T(f.value, r.modelValue.project), + set: (e) => D({ project: e?.id ?? "" }) + }), v = n({ + get: () => T(p.value, r.modelValue.user), + set: (e) => D({ user: e?.id ?? "" }) + }), b = n({ + get: () => T(h.value, r.modelValue.status), + set: (e) => D({ status: e?.id ?? "" }) + }), w = n(() => _i(r.modelValue)); + E(() => r.modelValue.search, (e) => { + e !== u.value && (u.value = e); + }), E(u, (e) => { + clearTimeout(d), d = setTimeout(() => D({ search: e.trim() }), Pi); + }), m(() => clearTimeout(d)); + function T(e, t) { + return e.find((e) => e.id === t) ?? e[0]; } - function M(e) { - y.value = null, C.value = e ?? Mt(/* @__PURE__ */ new Date()), _.value = !0; - } - function N(e) { - y.value = e, _.value = !0; - } - function P() { - 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 Je(t.client, e.id), t.notify("success", l("tasks_projects.time.deleted")), w.value += 1; - } catch (e) { - t.notify("error", U(e, l("tasks_projects.time.delete_failed"))); - } + function D(e) { + s("update:modelValue", { + ...r.modelValue, + ...e + }); } - function R(e) { - return u.value === e ? "border-primary-500 text-primary-500" : "border-transparent text-muted hover:border-line-strong hover:text-heading"; + function O() { + u.value = "", s("update:modelValue", { ...pi }); } return (t, n) => { - let h = b("BaseBreadcrumbItem"), v = b("BaseBreadcrumb"), O = b("BaseIcon"), k = b("BaseButton"), A = b("router-link"), j = b("BasePageHeader"), F = b("BasePage"); - return g(), r(F, null, { - default: E(() => [ - c(j, { title: S(l)("tasks_projects.time.title") }, { - actions: E(() => [o("div", yr, [ - S(G).running === null ? i("", !0) : (g(), a("span", br, [c(O, { - name: "ClockIcon", - class: "h-4 w-4 text-primary-500" - }), s(" " + x(S(l)("tasks_projects.timer.running")), 1)])), - c(A, { to: "/admin/modules/tasks-projects/billing" }, { - default: E(() => [c(k, { variant: "white" }, { - left: E((e) => [c(O, { - name: "BanknotesIcon", - class: f(e.class) - }, null, 8, ["class"])]), - default: E(() => [s(" " + x(S(l)("tasks_projects.billing.invoice_time")), 1)]), - _: 1 - })]), - _: 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", xr, [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(ur, { - 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(On, { - 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(zn, { - show: _.value, - client: e.client, - notify: e.notify, - entry: y.value, - "default-date": C.value, - onClose: n[3] ||= (e) => _.value = !1, - onSaved: P, - onDeleted: I - }, null, 8, [ - "show", - "client", - "notify", - "entry", - "default-date" - ]) - ]), - _: 1 - }); + let r = x("BaseSelectInput"), s = x("BaseInput"); + return _(), a("div", Ti, [ + e.lockProject ? i("", !0) : (_(), a("label", Ei, [o("span", Di, S(C(l)("tasks_projects.tasks.filters.project")), 1), c(r, { + modelValue: g.value, + "onUpdate:modelValue": n[0] ||= (e) => g.value = e, + options: f.value, + "label-key": "label" + }, null, 8, ["modelValue", "options"])])), + o("label", Oi, [o("span", ki, S(C(l)("tasks_projects.tasks.filters.member")), 1), c(r, { + modelValue: v.value, + "onUpdate:modelValue": n[1] ||= (e) => v.value = e, + options: p.value, + "label-key": "label" + }, null, 8, ["modelValue", "options"])]), + o("label", Ai, [o("span", ji, S(C(l)("tasks_projects.tasks.filters.status")), 1), c(r, { + modelValue: b.value, + "onUpdate:modelValue": n[2] ||= (e) => b.value = e, + options: h.value, + "label-key": "label" + }, null, 8, ["modelValue", "options"])]), + o("label", Mi, [o("span", Ni, S(C(l)("tasks_projects.tasks.filters.search")), 1), c(s, { + modelValue: u.value, + "onUpdate:modelValue": n[3] ||= (e) => u.value = e, + type: "text", + name: "search", + autocomplete: "off", + placeholder: C(l)("tasks_projects.tasks.search_placeholder") + }, null, 8, ["modelValue", "placeholder"])]), + w.value ? (_(), a("button", { + key: 1, + type: "button", + class: "pb-2 text-sm font-medium text-primary-500 hover:underline", + onClick: O + }, S(C(l)("tasks_projects.tasks.bulk.clear")), 1)) : i("", !0) + ]); }; } -}), wr = { - key: 0, - class: "text-sm text-muted" -}, Tr = { key: 1 }, Er = { +}), Ii = { key: 0, - class: "flex items-center gap-2 text-sm text-muted" -}, Dr = { - key: 1, - class: "text-sm text-muted" -}, Or = { - key: 2, - class: "divide-y divide-line-light" -}, kr = { + class: "mt-3 flex flex-wrap items-center gap-3 rounded-lg border border-primary-200 bg-primary-50 px-4 py-2.5" +}, Li = { class: "text-sm font-medium text-primary-700" }, Ri = { class: "min-w-48" }, zi = ["title"], Bi = /* @__PURE__ */ l({ + __name: "BulkActionBar", + props: { + count: {}, + statuses: {}, + busy: { type: Boolean } + }, + emits: [ + "status", + "delete", + "clear", + "select-page" + ], + setup(e, { emit: t }) { + let r = e, l = t, u = B(), d = y(null), p = n(() => r.statuses.map((e) => ({ + id: e.id, + label: e.name + }))); + return E(d, (e) => { + e !== null && (l("status", e.id), d.value = null); + }), (t, n) => { + let r = x("BaseSelectInput"), m = x("BaseIcon"), h = x("BaseButton"); + return e.count > 0 ? (_(), a("div", Ii, [ + o("span", Li, S(C(u)("tasks_projects.tasks.bulk.selected", { count: e.count })), 1), + o("div", Ri, [c(r, { + modelValue: d.value, + "onUpdate:modelValue": n[0] ||= (e) => d.value = e, + options: p.value, + disabled: e.busy, + placeholder: C(u)("tasks_projects.tasks.bulk.change_status"), + "label-key": "label" + }, null, 8, [ + "modelValue", + "options", + "disabled", + "placeholder" + ])]), + c(h, { + variant: "primary-outline", + size: "sm", + disabled: e.busy, + onClick: n[1] ||= (e) => l("delete") + }, { + left: D((e) => [c(m, { + name: "TrashIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: D(() => [s(" " + S(C(u)("tasks_projects.tasks.bulk.delete")), 1)]), + _: 1 + }, 8, ["disabled"]), + o("span", { + title: C(u)("tasks_projects.tasks.invoice_soon"), + class: "inline-flex cursor-not-allowed opacity-60" + }, [c(h, { + variant: "primary-outline", + size: "sm", + disabled: "" + }, { + left: D((e) => [c(m, { + name: "BanknotesIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: D(() => [s(" " + S(C(u)("tasks_projects.tasks.bulk.invoice")), 1)]), + _: 1 + })], 8, zi), + o("button", { + type: "button", + class: "ml-auto text-sm font-medium text-primary-600 hover:underline", + onClick: n[2] ||= (e) => l("select-page") + }, S(C(u)("tasks_projects.tasks.bulk.select_page")), 1), + o("button", { + type: "button", + class: "text-sm font-medium text-primary-600 hover:underline", + onClick: n[3] ||= (e) => l("clear") + }, S(C(u)("tasks_projects.tasks.bulk.clear")), 1) + ])) : i("", !0); + }; + } +}), Vi = /* @__PURE__ */ l({ + __name: "InvoicedBadge", + props: { state: { default: "none" } }, + setup(e) { + let t = e, a = B(), o = n(() => t.state === "invoiced" || t.state === "uninvoiced"), c = n(() => t.state === "invoiced" ? a("tasks_projects.tasks.invoiced") : a("tasks_projects.tasks.uninvoiced")), l = n(() => t.state === "invoiced" ? "bg-alert-success-bg! text-alert-success-text!" : "bg-alert-warning-bg! text-alert-warning-text!"); + return (e, t) => { + let n = x("BaseBadge"); + return o.value ? (_(), r(n, { + key: 0, + class: f(["rounded-full whitespace-nowrap", l.value]) + }, { + default: D(() => [s(S(c.value), 1)]), + _: 1 + }, 8, ["class"])) : i("", !0); + }; + } +}), Hi = [ + "LOW", + "NORMAL", + "HIGH", + "URGENT" +], Ui = { class: "flex w-full items-center justify-between" }, Wi = { key: 0, - class: "space-y-3" -}, Ar = { class: "flex flex-wrap items-center gap-2" }, jr = ["aria-label", "onClick"], Mr = { class: "flex flex-wrap items-center gap-6" }, Nr = { class: "flex items-center gap-2 text-sm text-body" }, Pr = { class: "flex items-center gap-2 text-sm text-body" }, Fr = { class: "flex gap-3" }, Ir = { - key: 1, - class: "flex items-center gap-3" -}, Lr = { class: "min-w-0 flex-1 truncate text-sm font-medium text-heading" }, Rr = { class: "flex items-center gap-1" }, zr = [ - "disabled", - "title", - "aria-label", - "onClick" -], Br = [ - "disabled", - "title", - "aria-label", - "onClick" -], Vr = [ - "title", - "aria-label", - "onClick" -], Hr = [ - "disabled", - "title", - "aria-label", - "onClick" -], Ur = { - key: 3, - class: "mt-4 space-y-3 rounded-lg border border-line-default p-3" -}, Wr = { class: "flex flex-wrap items-center gap-2" }, Gr = ["aria-label", "onClick"], Kr = { class: "flex flex-wrap items-center gap-6" }, qr = { class: "flex items-center gap-2 text-sm text-body" }, Jr = { class: "flex items-center gap-2 text-sm text-body" }, Yr = { class: "flex gap-3" }, Xr = /* @__PURE__ */ l({ - __name: "TaskStatusEditor", + class: "ml-2 text-sm font-normal text-muted" +}, Gi = { class: "space-y-5 px-6 py-6" }, Ki = { class: "flex items-center justify-between border-t border-line-default px-6 py-4" }, qi = { key: 1 }, Ji = { class: "flex space-x-3" }, Yi = /* @__PURE__ */ l({ + __name: "TaskFormModal", props: { + show: { type: Boolean }, client: { type: [Function, Object] }, - notify: { type: Function } + notify: { type: Function }, + task: {}, + statuses: {}, + members: {}, + projects: {}, + defaults: {}, + lockProject: { type: Boolean }, + compact: { type: Boolean } }, - setup(t) { - let l = t, u = [ - "#94a3b8", - "#3b82f6", - "#22c55e", - "#f59e0b", - "#ef4444", - "#a855f7", - "#0891b2", - "#64748b" - ], d = de(), m = v([]), C = v(!0), w = v(!1), T = v(!1), D = v(null), O = v(!1), k = _({ + emits: [ + "close", + "saved", + "deleted" + ], + setup(e, { emit: t }) { + let l = e, u = t, d = B(), f = v({ 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 $e(l.client), w.value = !1; - } catch (e) { - m.value = [], w.value = vt(e), w.value || l.notify("error", U(e, d("tasks_projects.settings.load_failed"))); - } finally { - C.value = !1; + description: "", + estimateHours: "", + rate: "", + dueDate: "", + billable: !0 + }), p = y(null), m = y(null), h = y(null), g = y(null), b = y(null), w = y({}), T = y(!1), O = y(!1), k = y(!1), j = n(() => !l.compact || k.value), M = n({ + get: () => te(l.projects, m.value), + set: (e) => { + m.value = e?.id ?? null; } + }), N = n(() => l.task !== null), P = n(() => N.value ? d("tasks_projects.tasks.edit_task") : d("tasks_projects.tasks.new_task")), F = n(() => l.statuses.map((e) => ({ + id: e.id, + label: e.name + }))), ee = n(() => l.members.map((e) => ({ + id: e.id, + label: e.name + }))), I = n(() => Hi.map((e, t) => ({ + id: t, + label: d(`tasks_projects.tasks.priority.${e.toLowerCase()}`) + }))); + E(() => l.show, (e) => { + e && L(); + }, { immediate: !0 }); + function te(e, t) { + return t === null ? null : e.find((e) => e.id === t) ?? null; } - 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 N() { - D.value = null, O.value = !0, k.name = "", k.colour = u[0], k.is_default = !1, k.is_closed = !1; + function L() { + let e = l.task; + f.name = e?.name ?? "", f.description = e?.description ?? "", f.estimateHours = he(e?.estimated_minutes ?? null), f.rate = pe(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 = te(F.value, n), m.value = e?.project_id ?? l.defaults?.project_id ?? null, h.value = te(ee.value, e?.assignee_id ?? null), g.value = e?.priority ? I.value[Hi.indexOf(e.priority)] ?? null : null, b.value = e?.customer_id ?? null, w.value = {}, k.value = !1, b.value !== null && Qn(l.client); } - function P() { - D.value = null, O.value = !1; + function ne(e) { + f.dueDate = e ? ve(e) : ""; } - function F() { + function re() { + let e = p.value?.id ?? null; + if (e === null) return null; + let t = g.value === null ? null : Hi[g.value.id]; return { - name: k.name.trim(), - colour: k.colour || null, - is_default: k.is_default, - is_closed: k.is_closed + name: f.name.trim(), + task_status_id: e, + project_id: m.value, + customer_id: m.value === null ? b.value : null, + description: f.description.trim() || null, + assignee_id: h.value?.id ?? null, + priority: t, + due_date: f.dueDate || null, + estimated_minutes: ge(f.estimateHours), + billable: f.billable, + rate: me(f.rate) }; } - async function I() { + async function ie() { if (T.value) return; - if (k.name.trim() === "") { - l.notify("error", d("tasks_projects.settings.status_name_required")); + if (f.name.trim() === "") { + w.value = { name: d("tasks_projects.tasks.name_required") }; return; } - let e = D.value, t = k.name.trim(); - T.value = !0; + let e = re(); + if (e === null) { + l.notify("error", d("tasks_projects.task_statuses.none")); + return; + } + T.value = !0, w.value = {}; try { - e === null ? (await et(l.client, F()), l.notify("success", d("tasks_projects.settings.status_created", { name: t }))) : (await tt(l.client, e, F()), l.notify("success", d("tasks_projects.settings.status_updated", { name: t }))), P(), await j(); + let t = l.task, n = t ? await kn(l.client, t.id, e) : await On(l.client, e); + u("saved", n); } catch (e) { - l.notify("error", U(e, d("tasks_projects.settings.save_failed"))); + w.value = le(e), l.notify("error", R(e, ae(e, "save_failed"))); } finally { T.value = !1; } } - async function L(e) { - if (!T.value && window.confirm(d("tasks_projects.settings.status_delete_confirm", { name: e.name }))) { - T.value = !0; + function ae(e, t) { + return z(e) === "task_locked" ? d("tasks_projects.tasks.locked") : d(`tasks_projects.tasks.${t}`); + } + async function oe() { + let e = l.task; + if (!(e === null || O.value) && window.confirm(d("tasks_projects.tasks.delete_confirm", { name: e.name }))) { + O.value = !0; try { - await nt(l.client, e.id), l.notify("success", d("tasks_projects.settings.status_deleted", { name: e.name })), P(), await j(); + await An(l.client, e.id), u("deleted", e); } catch (e) { - l.notify("error", U(e, d("tasks_projects.settings.delete_failed"))); + l.notify("error", R(e, ae(e, "delete_failed"))); } finally { - T.value = !1; + O.value = !1; } } } - 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 rt(l.client, r.map((e) => e.id)), l.notify("success", d("tasks_projects.settings.status_reordered")); - } catch (e) { - l.notify("error", U(e, d("tasks_projects.settings.reorder_failed"))), await j(); - } finally { - T.value = !1; - } - } 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", wr, x(S(d)("tasks_projects.settings.forbidden")), 1)) : (g(), a("div", Tr, [C.value ? (g(), a("div", Er, [c(l, { class: "h-4 w-4 text-primary-500" })])) : A.value ? (g(), a("p", Dr, x(S(d)("tasks_projects.settings.no_statuses")), 1)) : (g(), a("ul", Or, [(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", kr, [ - 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", Ar, [(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, jr)), 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", Mr, [o("label", Nr, [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", Pr, [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", Fr, [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", Ir, [ - 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", Lr, 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", Rr, [ - 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, zr), - 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, Br), - 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, Vr), - 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, Hr) - ]) - ]))]))), 128))])), O.value ? (g(), a("div", Ur, [ - 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", Wr, [(g(), a(e, null, y(u, (e) => o("button", { - key: e, + let l = x("BaseIcon"), m = x("BaseInput"), v = x("BaseInputGroup"), y = x("BaseSelectInput"), E = x("BaseDatePicker"), te = x("BaseInputGrid"), L = x("BaseSwitch"), re = x("BaseTextarea"), ae = x("BaseButton"), se = x("BaseModal"); + return _(), r(se, { + show: e.show, + onClose: n[12] ||= (e) => u("close") + }, { + header: D(() => [o("div", Ui, [o("span", null, [s(S(P.value) + " ", 1), e.task ? (_(), a("span", Wi, "#" + S(e.task.number), 1)) : i("", !0)]), c(l, { + name: "XMarkIcon", + class: "h-6 w-6 cursor-pointer text-subtle hover:text-body", + onClick: n[0] ||= (e) => u("close") + })])]), + default: D(() => [o("form", { onSubmit: A(ie, ["prevent"]) }, [o("div", Gi, [ + c(v, { + label: C(d)("tasks_projects.tasks.fields.name"), + error: w.value.name, + required: "" + }, { + default: D(() => [c(m, { + modelValue: f.name, + "onUpdate:modelValue": n[1] ||= (e) => f.name = e, + invalid: !!w.value.name, + type: "text" + }, null, 8, ["modelValue", "invalid"])]), + _: 1 + }, 8, ["label", "error"]), + c(te, null, { + default: D(() => [ + e.lockProject ? i("", !0) : (_(), r(v, { + key: 0, + label: C(d)("tasks_projects.tasks.fields.project"), + error: w.value.project_id, + "help-text": C(d)("tasks_projects.tasks.fields.project_help") + }, { + default: D(() => [c(y, { + modelValue: M.value, + "onUpdate:modelValue": n[2] ||= (e) => M.value = e, + options: e.projects, + placeholder: C(d)("tasks_projects.tasks.fields.project_placeholder"), + "label-key": "label" + }, null, 8, [ + "modelValue", + "options", + "placeholder" + ])]), + _: 1 + }, 8, [ + "label", + "error", + "help-text" + ])), + b.value === null ? i("", !0) : (_(), r(v, { + key: 1, + label: C(d)("tasks_projects.tasks.fields.customer"), + "help-text": C(d)("tasks_projects.tasks.fields.customer_help") + }, { + default: D(() => [c(m, { + "model-value": C(Zn)(b.value), + type: "text", + disabled: "" + }, null, 8, ["model-value"])]), + _: 1 + }, 8, ["label", "help-text"])), + c(v, { + label: C(d)("tasks_projects.tasks.fields.status"), + error: w.value.task_status_id + }, { + default: D(() => [c(y, { + modelValue: p.value, + "onUpdate:modelValue": n[3] ||= (e) => p.value = e, + options: F.value, + "label-key": "label" + }, null, 8, ["modelValue", "options"])]), + _: 1 + }, 8, ["label", "error"]), + c(v, { + label: C(d)("tasks_projects.tasks.fields.assignee"), + error: w.value.assignee_id + }, { + default: D(() => [c(y, { + modelValue: h.value, + "onUpdate:modelValue": n[4] ||= (e) => h.value = e, + options: ee.value, + placeholder: C(d)("tasks_projects.tasks.fields.assignee_placeholder"), + "label-key": "label" + }, null, 8, [ + "modelValue", + "options", + "placeholder" + ])]), + _: 1 + }, 8, ["label", "error"]), + j.value ? (_(), r(v, { + key: 2, + label: C(d)("tasks_projects.tasks.fields.priority"), + error: w.value.priority + }, { + default: D(() => [c(y, { + modelValue: g.value, + "onUpdate:modelValue": n[5] ||= (e) => g.value = e, + options: I.value, + placeholder: C(d)("tasks_projects.tasks.fields.priority_placeholder"), + "label-key": "label" + }, null, 8, [ + "modelValue", + "options", + "placeholder" + ])]), + _: 1 + }, 8, ["label", "error"])) : i("", !0), + c(v, { + label: C(d)("tasks_projects.tasks.fields.due_date"), + error: w.value.due_date + }, { + default: D(() => [c(E, { + "model-value": f.dueDate, + "onUpdate:modelValue": ne + }, null, 8, ["model-value"])]), + _: 1 + }, 8, ["label", "error"]), + j.value ? (_(), r(v, { + key: 3, + label: C(d)("tasks_projects.tasks.fields.estimate_hours"), + error: w.value.estimated_minutes + }, { + default: D(() => [c(m, { + modelValue: f.estimateHours, + "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"])) : i("", !0), + j.value ? (_(), r(v, { + key: 4, + label: C(d)("tasks_projects.tasks.fields.rate"), + error: w.value.rate, + "help-text": C(d)("tasks_projects.tasks.fields.rate_help") + }, { + default: D(() => [c(m, { + modelValue: f.rate, + "onUpdate:modelValue": n[7] ||= (e) => f.rate = e, + invalid: !!w.value.rate, + type: "number", + step: "0.01", + min: "0" + }, null, 8, ["modelValue", "invalid"])]), + _: 1 + }, 8, [ + "label", + "error", + "help-text" + ])) : i("", !0) + ]), + _: 1 + }), + c(v, { + label: C(d)("tasks_projects.tasks.fields.billable"), + error: w.value.billable + }, { + default: D(() => [c(L, { + modelValue: f.billable, + "onUpdate:modelValue": n[8] ||= (e) => f.billable = e, + class: "mt-1" + }, null, 8, ["modelValue"])]), + _: 1 + }, 8, ["label", "error"]), + j.value ? (_(), r(v, { + key: 0, + label: C(d)("tasks_projects.tasks.fields.description"), + error: w.value.description + }, { + default: D(() => [c(re, { + modelValue: f.description, + "onUpdate:modelValue": n[9] ||= (e) => f.description = e, + row: 3, + invalid: !!w.value.description + }, null, 8, ["modelValue", "invalid"])]), + _: 1 + }, 8, ["label", "error"])) : i("", !0), + e.compact ? (_(), a("button", { + key: 1, 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, Gr)), 64))])]), - _: 1 - }, 8, ["label"]), - o("div", Kr, [o("label", qr, [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", Jr, [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", Yr, [c(j, { - variant: "primary", - size: "sm", - disabled: T.value, - onClick: I + class: "flex items-center gap-1 text-sm font-medium text-primary-500 hover:underline", + onClick: n[10] ||= (e) => k.value = !k.value + }, [c(l, { + name: k.value ? "ChevronUpIcon" : "ChevronDownIcon", + class: "h-4 w-4" + }, null, 8, ["name"]), s(" " + S(k.value ? C(d)("tasks_projects.tasks.fewer_fields") : C(d)("tasks_projects.tasks.all_fields")), 1)])) : i("", !0) + ]), o("div", Ki, [N.value ? (_(), r(ae, { + key: 0, + type: "button", + variant: "danger", + loading: O.value, + disabled: O.value, + onClick: oe }, { - default: E(() => [s(x(S(d)("tasks_projects.general.save")), 1)]), + default: D(() => [s(S(C(d)("tasks_projects.general.delete")), 1)]), _: 1 - }, 8, ["disabled"]), c(j, { + }, 8, ["loading", "disabled"])) : (_(), a("span", qi)), o("div", Ji, [c(ae, { + type: "button", variant: "primary-outline", - size: "sm", - onClick: P + onClick: n[11] ||= (e) => u("close") }, { - default: E(() => [s(x(S(d)("tasks_projects.general.cancel")), 1)]), + default: D(() => [s(S(C(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 - }))]))]); - }; - } -}), Zr = { class: "space-y-6" }, Qr = "/admin/settings/modules", $r = /* @__PURE__ */ l({ - __name: "TimeSettingsPage", - props: { - client: { type: [Function, Object] }, - notify: { type: Function }, - router: {} - }, - setup(e) { - let t = de(); - return (n, r) => { - let i = b("BaseIcon"), o = b("BaseButton"), l = b("router-link"), u = b("BaseSettingCard"); - return g(), a("div", Zr, [c(u, { - title: S(t)("tasks_projects.settings.general_title"), - description: S(t)("tasks_projects.settings.general_description") - }, { - action: E(() => [c(l, { to: Qr }, { - default: E(() => [c(o, { - variant: "primary-outline", - size: "sm" - }, { - 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 - })]), + }), c(ae, { + type: "submit", + variant: "primary", + loading: T.value, + disabled: T.value + }, { + default: D(() => [s(S(N.value ? C(d)("tasks_projects.general.update") : C(d)("tasks_projects.general.save")), 1)]), _: 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(Xr, { - client: e.client, - notify: e.notify - }, null, 8, ["client", "notify"])]), + }, 8, ["loading", "disabled"])])])], 32)]), _: 1 - }, 8, ["title", "description"])]); + }, 8, ["show"]); }; } -}), ei = "tasks-projects", ti = `/admin/modules/${ei}/time`; -function ni(e) { - e.addMessages(Cn); - let t = (t, n) => { - e.notify(t, n); - }, n = () => { - e.router.push(ti); - }; - e.registerPage({ - id: "time", - module: ei, - path: "time", - component: ai(e, Cr), - meta: { - ability: `${ei}:view-own-time`, - title: "tasks_projects.time.title" - } - }), e.registerHeaderAction({ - id: `${ei}.timer-chip`, - priority: 30, - visible: () => G.running !== null, - component: l({ setup: () => () => d(Sn, { - client: e.client, - notify: t, - onOpen: n - }) }) - }), e.registerCompanyLayoutOverlay({ - id: `${ei}.quick-start`, - component: l({ setup: () => () => d(gn, { - key: fr.companySession, - client: e.client, - notify: t, - enabled: !fr.adminMode, - onOpenTimesheet: n - }) }) - }), e.registerCompanySettingsPage({ - id: `${ei}.settings`, - title: "tasks_projects.settings.title", - icon: "ClockIcon", - path: ei, - priority: 70, - component: ai(e, $r) - }), e.on("bootstrap:completed", ({ adminMode: t }) => { - ri(e, t); - }), e.on("company:changing", () => { - ii(); - }), e.on("company:changed", ({ companyId: t }) => { - ri(e, t === null); - }); -} -async function ri(e, t) { - if (hr(t), t) { - ii(); - return; - } - await pr(e.client), await G.refresh(e.client); -} -function ii() { - G.reset(), ht(), mr(); -} -function ai(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 oi = { en: { tasks_projects: { - board: { - title: "Board", - load_failed: "Unable to load the board.", - move_failed: "Unable to move the task.", - moved: "{name} moved to {status}.", - empty_column: "Nothing here yet", - filters: { - project: "Project", - all_projects: "All projects", - assignee: "Assignee", - all_assignees: "Everyone" - } - }, - tasks: { - title: "Tasks", - all_tasks: "All tasks", - new_task: "New task", - edit_task: "Edit task", - search_placeholder: "Search by name or number", - empty_title: "No tasks yet", - empty_description: "Add a task to put work on the board.", - unassigned: "Unassigned", - billable: "Billable", - overdue: "Overdue", - none: "None", - columns: { - number: "No.", - name: "Name", - project: "Project", - status: "Status", - assignee: "Assignee", - priority: "Priority", - due_date: "Due date" - }, - fields: { - name: "Name", - description: "Description", - project: "Project", - project_placeholder: "No project", - project_help: "Leave empty for a task that stands on its own.", - customer: "Customer", - customer_help: "Taken from the project.", - status: "Status", - assignee: "Assignee", - assignee_placeholder: "Nobody yet", - priority: "Priority", - priority_placeholder: "No priority", - due_date: "Due date", - estimate_hours: "Estimate (hours)", - billable: "Billable", - rate: "Rate override", - rate_help: "Per hour. Leave empty to use the project or member rate." - }, - priority: { - low: "Low", - normal: "Normal", - high: "High", - urgent: "Urgent" - }, - created: "{name} was created.", - updated: "{name} was updated.", - deleted: "{name} was deleted.", - delete_confirm: "Delete {name}? Its time entries go with it.", - name_required: "Enter a task name.", - load_failed: "Unable to load the tasks.", - save_failed: "Unable to save the task.", - delete_failed: "Unable to delete the task.", - projects_failed: "Unable to load the projects.", - members_failed: "Unable to load the members." - }, - task_statuses: { - load_failed: "Unable to load the task statuses.", - none: "No board columns yet." - }, - project: { - load_failed: "Unable to load the project.", - customer: "Customer", - identifier: "Identifier", - due_date: "Due date", - board: "Board", - tabs: { - overview: "Overview", - tasks: "Tasks", - time: "Time", - members: "Members" - }, - overview: { - tasks: "Tasks", - open_tasks: "{count} open", - closed_tasks: "{count} done", - logged: "Logged", - billable: "Billable", - billable_amount: "Billable value", - unbilled_amount: "Unbilled", - budget: "Budget", - budget_used: "{used} of {total}", - budget_over: "Over budget by {amount}", - no_budget: "No budget set.", - description: "Description", - no_description: "No description yet." - }, - time: { - columns: { - date: "Date", - member: "Member", - task: "Task", - minutes: "Duration", - billable: "Billable", - amount: "Amount" - }, - running: "Running", - removed_member: "Removed member", - load_failed: "Unable to load the time entries." - }, - members: { - title: "Members", - member: "Member", - rate: "Rate / hour", - rate_help: "Per hour on this project. Leave empty to use the project default.", - attach: "Add member", - attach_placeholder: "Choose a member", - attached: "{name} was added to the project.", - detached: "{name} was removed from the project.", - detach_confirm: "Remove {name} from this project? Their time entries stay.", - empty: "Nobody is on this project yet.", - all_attached: "Every company member is already on this project.", - load_failed: "Unable to load the project members.", - attach_failed: "Unable to add the member.", - detach_failed: "Unable to remove the member." - } - } -} } }; -//#endregion -//#region node_modules/.pnpm/sortablejs@1.15.7/node_modules/sortablejs/modular/sortable.esm.js -function si(e, t, n) { - return (t = mi(t)) in e ? Object.defineProperty(e, t, { - value: n, - enumerable: !0, - configurable: !0, - writable: !0 - }) : e[t] = n, e; -} -function ci() { - return ci = 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; - }, ci.apply(null, arguments); -} -function li(e, t) { - var n = Object.keys(e); - if (Object.getOwnPropertySymbols) { - var r = Object.getOwnPropertySymbols(e); - t && (r = r.filter(function(t) { - return Object.getOwnPropertyDescriptor(e, t).enumerable; - })), n.push.apply(n, r); - } +}), Xi = "/api/v1/tasks-projects", Zi = { + timeEntries: `${Xi}/time-entries`, + timeEntry: (e) => `${Xi}/time-entries/${e}`, + timer: `${Xi}/timer`, + timerStart: `${Xi}/timer/start`, + timerStop: `${Xi}/timer/stop`, + taskStatuses: `${Xi}/task-statuses`, + taskStatus: (e) => `${Xi}/task-statuses/${e}`, + reorderTaskStatuses: `${Xi}/task-statuses/reorder`, + tasks: `${Xi}/tasks`, + task: (e) => `${Xi}/tasks/${e}`, + members: `${Xi}/members`, + settings: `${Xi}/settings` +}, Qi = { bootstrap: "/api/v1/bootstrap" }, $i = 100, ea = 5, ta = 10; +async function na(e, t) { + let { data: n } = await e.get(Zi.timeEntries, { params: t }); return n; } -function ui(e) { - for (var t = 1; t < arguments.length; t++) { - var n = arguments[t] == null ? {} : arguments[t]; - t % 2 ? li(Object(n), !0).forEach(function(t) { - si(e, t, n[t]); - }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(n)) : li(Object(n)).forEach(function(t) { - Object.defineProperty(e, t, Object.getOwnPropertyDescriptor(n, t)); +async function ra(e, t) { + let n = []; + for (let r = 1; r <= ea; r += 1) { + let i = await na(e, { + ...t, + page: r, + limit: $i }); - } - return e; -} -function di(e, t) { - if (e == null) return {}; - var n, r, i = fi(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 fi(e, t) { - if (e == null) return {}; - var n = {}; - for (var r in e) if ({}.hasOwnProperty.call(e, r)) { - if (t.indexOf(r) !== -1) continue; - n[r] = e[r]; + if (n.push(...i.data ?? []), !i.meta || r >= i.meta.last_page) break; } return n; } -function pi(e, t) { - if (typeof e != "object" || !e) return e; - var n = e[Symbol.toPrimitive]; - if (n !== void 0) { - var r = n.call(e, t || "default"); - if (typeof r != "object") return r; - throw TypeError("@@toPrimitive must return a primitive value."); - } - return (t === "string" ? String : Number)(e); +async function ia(e, t) { + let { data: n } = await e.post(Zi.timeEntries, t); + return n.data; } -function mi(e) { - var t = pi(e, "string"); - return typeof t == "symbol" ? t : t + ""; +async function aa(e, t, n) { + let { data: r } = await e.put(Zi.timeEntry(t), n); + return r.data; } -function hi(e) { - "@babel/helpers - typeof"; - return hi = 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; - }, hi(e); +async function oa(e, t) { + await e.delete(Zi.timeEntry(t)); } -var gi = "1.15.7"; -function _i(e) { - if (typeof window < "u" && window.navigator) return !!/*@__PURE__*/ navigator.userAgent.match(e); +async function sa(e) { + let { data: t } = await e.get(Zi.timer); + return t?.data ?? null; } -var vi = _i(/(?:Trident.*rv[ :]?11\.|msie|iemobile|Windows Phone)/i), yi = _i(/Edge/i), bi = _i(/firefox/i), xi = _i(/safari/i) && !_i(/chrome/i) && !_i(/android/i), Si = _i(/iP(ad|od|hone)/i), Ci = _i(/chrome/i) && _i(/android/i), wi = { - capture: !1, - passive: !1 -}; -function K(e, t, n) { - e.addEventListener(t, n, !vi && wi); +async function ca(e, t) { + let { data: n } = await e.post(Zi.timerStart, t); + return n.data; } -function q(e, t, n) { - e.removeEventListener(t, n, !vi && wi); +async function la(e) { + let { data: t } = await e.post(Zi.timerStop); + return t.data; } -function Ti(e, t) { - if (t) { - if (t[0] === ">" && (t = t.substring(1)), e) try { - if (e.matches) return e.matches(t); - if (e.msMatchesSelector) return e.msMatchesSelector(t); - if (e.webkitMatchesSelector) return e.webkitMatchesSelector(t); - } catch { - return !1; - } - return !1; - } +async function ua(e) { + await e.delete(Zi.timer); } -function Ei(e) { - return e.host && e !== document && e.host.nodeType && e.host !== e ? e.host : e.parentNode; +async function da(e) { + let { data: t } = await e.get(Zi.taskStatuses); + return t.data ?? []; } -function Di(e, t, n, r) { - if (e) { - n ||= document; - do { - if (t != null && (t[0] === ">" ? e.parentNode === n && Ti(e, t) : Ti(e, t)) || r && e === n) return e; - if (e === n) break; - } while (e = Ei(e)); - } - return null; +async function fa(e, t) { + let { data: n } = await e.post(Zi.taskStatuses, t); + return n.data; } -var Oi = /\s+/g; -function ki(e, t, n) { - e && t && (e.classList ? e.classList[n ? "add" : "remove"](t) : e.className = ((" " + e.className + " ").replace(Oi, " ").replace(" " + t + " ", " ") + (n ? " " + t : "")).replace(Oi, " ")); +async function pa(e, t, n) { + let { data: r } = await e.put(Zi.taskStatus(t), n); + return r.data; } -function J(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"); - } +async function ma(e, t) { + await e.delete(Zi.taskStatus(t)); } -function Ai(e, t) { - var n = ""; - if (typeof e == "string") n = e; - else do { - var r = J(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); +async function ha(e, t) { + let { data: n } = await e.post(Zi.reorderTaskStatuses, { ids: t }); + return n.data ?? []; } -function ji(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); - return r; - } - return []; +async function ga(e, t, n = ta) { + let r = { limit: n }; + t.trim() !== "" && (r.search = t.trim()); + let { data: i } = await e.get(Zi.tasks, { params: r }); + return i.data ?? []; } -function Mi() { - return document.scrollingElement || document.documentElement; +async function _a(e, t) { + let { data: n } = await e.get(Zi.task(t)); + return n.data; } -function Ni(e, t, n, r, i) { - if (e.getBoundingClientRect || e === window) { - var a, o, s, c, l, u, d; - if (e !== window && e.parentNode && e !== Mi() ? (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, !vi)) do - if (i && i.getBoundingClientRect && (J(i, "transform") !== "none" || n && J(i, "position") !== "static")) { - var f = i.getBoundingClientRect(); - o -= f.top + parseInt(J(i, "border-top-width")), s -= f.left + parseInt(J(i, "border-left-width")), c = o + a.height, l = s + a.width; - break; - } - while (i = i.parentNode); - if (r && e !== window) { - var p = Ai(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 { - top: o, - left: s, - bottom: c, - right: l, - width: d, - height: u - }; - } +async function va(e) { + let { data: t } = await e.get(Zi.settings); + return t.data; } -function Pi(e, t, n) { - for (var r = Bi(e, !0), i = Ni(e)[t]; r;) { - var a = Ni(r)[n], o = void 0; - if (o = n === "top" || n === "left" ? i >= a : i <= a, !o) return r; - if (r === Mi()) break; - r = Bi(r, !1); - } - return !1; +async function ya(e) { + let { data: t } = await e.get(Qi.bootstrap), n = t?.current_user?.id; + return typeof n == "number" ? n : null; } -function Fi(e, t, n, r) { - for (var i = 0, a = 0, o = e.children; a < o.length;) { - if (o[a].style.display !== "none" && o[a] !== $.ghost && (r || o[a] !== $.dragged) && Di(o[a], n.draggable, e, !1)) { - if (i === t) return o[a]; - i++; - } - a++; - } - return null; +//#endregion +//#region resources/js/stores/session.ts +var ba = { + default_rate: 0, + rounding_minutes: 1, + rounding_direction: "nearest", + week_start: 1, + members_see_all_time: !1, + auto_start_tasks: !1, + lock_invoiced_tasks: !1, + hide_invoiced_on_board: !1, + invoice_project_heading: !1, + invoice_task_description: !0, + invoice_entry_dates: !0, + invoice_entry_times: !1, + invoice_entry_hours: !0, + invoice_entry_descriptions: !1, + rounding_increments: [ + 1, + 5, + 6, + 15, + 30, + 60 + ] +}, xa = v({ + adminMode: !1, + userId: null, + settings: { ...ba }, + companySession: 0, + loading: !1 +}); +async function Sa(e) { + if (xa.adminMode) return; + xa.loading = !0; + let [t, n] = await Promise.all([ya(e).catch(() => null), va(e).catch(() => null)]); + xa.userId = t, xa.settings = Ta(n), xa.loading = !1; } -function Ii(e, t) { - for (var n = e.lastElementChild; n && (n === $.ghost || J(n, "display") === "none" || t && !Ti(n, t));) n = n.previousElementSibling; - return n || null; +function Ca() { + xa.userId = null, xa.settings = { ...ba }, xa.companySession += 1, xa.loading = !1; } -function Li(e, t) { - var n = 0; - if (!e || !e.parentNode) return -1; - for (; e = e.previousElementSibling;) e.nodeName.toUpperCase() !== "TEMPLATE" && e !== $.clone && (!t || Ti(e, t)) && n++; - return n; +function wa(e) { + xa.adminMode = e; } -function Ri(e) { - var t = 0, n = 0, r = Mi(); - if (e) do { - var i = Ai(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 Ta(e) { + if (typeof e != "object" || !e) return { ...ba }; + let t = Array.isArray(e.rounding_increments) ? e.rounding_increments.filter((e) => typeof e == "number") : ba.rounding_increments; + return { + default_rate: Ea(e.default_rate, ba.default_rate), + rounding_minutes: Ea(e.rounding_minutes, ba.rounding_minutes), + rounding_direction: Oa(e.rounding_direction), + week_start: ka(e.week_start), + members_see_all_time: e.members_see_all_time === !0, + auto_start_tasks: Da(e.auto_start_tasks, ba.auto_start_tasks), + lock_invoiced_tasks: Da(e.lock_invoiced_tasks, ba.lock_invoiced_tasks), + hide_invoiced_on_board: Da(e.hide_invoiced_on_board, ba.hide_invoiced_on_board), + invoice_project_heading: Da(e.invoice_project_heading, ba.invoice_project_heading), + invoice_task_description: Da(e.invoice_task_description, ba.invoice_task_description), + invoice_entry_dates: Da(e.invoice_entry_dates, ba.invoice_entry_dates), + invoice_entry_times: Da(e.invoice_entry_times, ba.invoice_entry_times), + invoice_entry_hours: Da(e.invoice_entry_hours, ba.invoice_entry_hours), + invoice_entry_descriptions: Da(e.invoice_entry_descriptions, ba.invoice_entry_descriptions), + rounding_increments: t.length > 0 ? t : ba.rounding_increments + }; } -function zi(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 Ea(e, t) { + return typeof e == "number" && Number.isFinite(e) ? e : t; } -function Bi(e, t) { - if (!e || !e.getBoundingClientRect) return Mi(); - var n = e, r = !1; - do - if (n.clientWidth < n.scrollWidth || n.clientHeight < n.scrollHeight) { - var i = J(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 Mi(); - if (r || t) return n; - r = !0; - } - } - while (n = n.parentNode); - return Mi(); +function Da(e, t) { + return typeof e == "boolean" ? e : t; } -function Vi(e, t) { - if (e && t) for (var n in t) t.hasOwnProperty(n) && (e[n] = t[n]); - return e; +function Oa(e) { + return e === "up" || e === "down" || e === "nearest" ? e : ba.rounding_direction; } -function Hi(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); +function ka(e) { + return typeof e == "number" && Number.isInteger(e) && e >= 0 && e <= 6 ? e : ba.week_start; } -var Ui; -function Wi(e, t) { - return function() { - if (!Ui) { - var n = arguments, r = this; - n.length === 1 ? e.call(r, n[0]) : e.apply(r, n), Ui = setTimeout(function() { - Ui = void 0; - }, t); +//#endregion +//#region resources/js/stores/tasks.ts +var Aa = v({}), ja = /* @__PURE__ */ new Set(), Ma = 5, Na = { + logged_minutes: 0, + billable_minutes: 0, + unbilled_minutes: 0, + unbilled_amount: 0, + invoiced: "none", + running: [] +}, Pa = y(0), Fa = v({}); +function Ia(e) { + return e === null ? "" : Aa[e] ?? `#${e}`; +} +function La(e) { + e && typeof e.id == "number" && typeof e.name == "string" && (Aa[e.id] = e.name); +} +async function Ra(e, t) { + let n = [...new Set(t)].filter((e) => typeof e == "number" && Aa[e] === void 0 && !ja.has(e)); + for (let e of n) ja.add(e); + for (let t = 0; t < n.length; t += Ma) await Promise.all(n.slice(t, t + Ma).map(async (t) => { + try { + La(await _a(e, t)); + } catch {} finally { + ja.delete(t); } + })); +} +var za = Pa; +function U() { + Pa.value += 1; +} +function Ba(e, t) { + Fa[e] = { + ...Fa[e] ?? {}, + ...t }; } -function Gi() { - clearTimeout(Ui), Ui = void 0; +function Va(e) { + if (!e || typeof e.id != "number") return Na; + let t = e.time ?? Na; + return { + ...Na, + ...t, + running: Array.isArray(t.running) ? t.running : [], + ...Fa[e.id] ?? {} + }; } -function Ki(e, t, n) { - e.scrollLeft += t, e.scrollTop += n; +function Ha() { + for (let e of Object.keys(Aa)) delete Aa[Number(e)]; + for (let e of Object.keys(Fa)) delete Fa[Number(e)]; + ja.clear(); } -function qi(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); +//#endregion +//#region resources/js/support/time.ts +var Ua = 60, Wa = 60, Ga = 7; +function Ka(e) { + let t = Number.isFinite(e) && e > 0 ? Math.floor(e) : 0, n = Math.floor(t / 3600), r = Math.floor(t % 3600 / Wa), i = t % Wa; + return `${n}:${lo(r)}:${lo(i)}`; } -function Ji(e, t, n) { - var r = {}; - return Array.from(e.children).forEach(function(i) { - if (Di(i, t.draggable, e, !1) && !i.animated && i !== n) { - var a = Ni(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; +function qa(e) { + let t = e !== null && Number.isFinite(e) && e > 0 ? Math.round(e) : 0; + return `${Math.floor(t / Ua)}:${lo(t % Ua)}`; } -var Yi = "Sortable" + (/* @__PURE__ */ new Date()).getTime(); -function Xi() { - var e = [], t; +function Ja(e) { + let t = e.trim(); + if (t === "") return null; + let n = /^(\d+):([0-5]?\d)$/.exec(t); + if (n) return Number(n[1]) * Ua + Number(n[2]); + if (!/^\d+([.,]\d+)?$/.test(t)) return null; + let r = Number(t.replace(",", ".")); + return Number.isNaN(r) ? null : Math.round(r * Ua); +} +function Ya(e) { + let t = oo(e); + return t === null ? "" : no(t); +} +function Xa(e) { + let t = oo(e); + return t === null ? "" : `${lo(t.getHours())}:${lo(t.getMinutes())}`; +} +function Za(e, t = "09:00") { + let n = so(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 Qa(e, t) { + let n = new Date(e); + return n.setTime(n.getTime() + t * Wa * 1e3), n.toISOString(); +} +function $a(e, t) { + let n = Number.isInteger(t) && t >= 0 && t <= 6 ? t : 1, r = co(e), i = (r.getDay() - n + Ga) % Ga; + return r.setDate(r.getDate() - i), r; +} +function eo(e) { + return Array.from({ length: Ga }, (t, n) => to(e, n)); +} +function to(e, t) { + let n = co(e); + return n.setDate(n.getDate() + t), n; +} +function no(e) { + return `${e.getFullYear()}-${lo(e.getMonth() + 1)}-${lo(e.getDate())}`; +} +function ro(e) { return { - captureAnimationState: function() { - e = [], this.options.animation && [].slice.call(this.el.children).forEach(function(t) { - if (J(t, "display") !== "none" && t !== $.ghost) { - e.push({ - target: t, - rect: Ni(t) - }); - var n = ui({}, e[e.length - 1].rect); - if (t.thisAnimationDuration) { - var r = Ai(t, !0); - r && (n.top -= r.f, n.left -= r.e); - } - t.fromRect = n; - } - }); - }, - addAnimationState: function(t) { - e.push(t); - }, - removeAnimationState: function(t) { - e.splice(zi(e, { target: t }), 1); - }, - animateAll: function(n) { - var r = this; - if (!this.options.animation) { - clearTimeout(t), typeof n == "function" && n(); - return; - } - var i = !1, a = 0; - e.forEach(function(e) { - var t = 0, n = e.target, o = n.fromRect, s = Ni(n), c = n.prevFromRect, l = n.prevToRect, u = e.rect, d = Ai(n, !0); - d && (s.top -= d.f, s.left -= d.e), n.toRect = s, n.thisAnimationDuration && Hi(c, s) && !Hi(o, s) && (u.top - s.top) / (u.left - s.left) === (o.top - s.top) / (o.left - s.left) && (t = Qi(u, c, l, r.options)), Hi(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() { - typeof n == "function" && n(); - }, a) : typeof n == "function" && n(), e = []; - }, - animate: function(e, t, n, r) { - if (r) { - J(e, "transition", ""), J(e, "transform", ""); - var i = Ai(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, J(e, "transform", "translate3d(" + s + "px," + c + "px,0)"), this.forRepaintDummy = Zi(e), J(e, "transition", "transform " + r + "ms" + (this.options.easing ? " " + this.options.easing : "")), J(e, "transform", "translate3d(0,0,0)"), typeof e.animated == "number" && clearTimeout(e.animated), e.animated = setTimeout(function() { - J(e, "transition", ""), J(e, "transform", ""), e.animated = !1, e.animatingX = !1, e.animatingY = !1; - }, r); - } - } + weekday: e.toLocaleDateString(void 0, { weekday: "short" }), + day: e.toLocaleDateString(void 0, { + day: "numeric", + month: "short" + }) }; } -function Zi(e) { - return e.offsetWidth; +function io(e) { + return no(e) === no(/* @__PURE__ */ new Date()); +} +function ao(e, t) { + let n = oo(e); + return n === null ? 0 : Math.max(0, Math.floor((t - n.getTime()) / 1e3)); +} +function oo(e) { + if (!e) return null; + let t = new Date(e); + return Number.isNaN(t.getTime()) ? null : t; +} +function so(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 co(e) { + let t = new Date(e.getTime()); + return t.setHours(0, 0, 0, 0), t; +} +function lo(e) { + return String(e).padStart(2, "0"); +} +//#endregion +//#region resources/js/stores/timer.ts +var W = v({ + running: null, + busy: !1 +}), uo = v({ now: Date.now() }), fo, po = 0; +function mo() { + uo.now = Date.now(); +} +function ho() { + po += 1, fo === void 0 && (mo(), fo = setInterval(mo, 1e3)); +} +function go() { + po = Math.max(0, po - 1), po === 0 && fo !== void 0 && (clearInterval(fo), fo = void 0); } -function Qi(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; +function _o() { + return ho(), g(go, !0), n(() => uo.now); } -var $i = [], ea = { initializeByDefault: !0 }, ta = { - mount: function(e) { - for (var t in ea) ea.hasOwnProperty(t) && !(t in e) && (e[t] = ea[t]); - $i.forEach(function(t) { - if (t.pluginName === e.pluginName) throw `Sortable: Cannot mount plugin ${e.pluginName} more than once`; - }), $i.push(e); - }, - pluginEvent: function(e, t, n) { - var r = this; - this.eventCanceled = !1, n.cancel = function() { - r.eventCanceled = !0; - }; - var i = e + "Global"; - $i.forEach(function(r) { - t[r.pluginName] && (t[r.pluginName][i] && t[r.pluginName][i](ui({ sortable: t }, n)), t.options[r.pluginName] && t[r.pluginName][e] && t[r.pluginName][e](ui({ sortable: t }, n))); - }); - }, - initializePlugins: function(e, t, n, r) { - for (var i in $i.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, ci(n, a.defaults); - } - }), e.options) if (e.options.hasOwnProperty(i)) { - var a = this.modifyOption(e, i, e.options[i]); - a !== void 0 && (e.options[i] = a); - } - }, - getEventProperties: function(e, t) { - var n = {}; - return $i.forEach(function(r) { - typeof r.eventProperties == "function" && ci(n, r.eventProperties.call(t[r.pluginName], e)); - }), n; - }, - modifyOption: function(e, t, n) { - var r; - return $i.forEach(function(i) { - e[i.pluginName] && i.optionListeners && typeof i.optionListeners[t] == "function" && (r = i.optionListeners[t].call(e[i.pluginName], n)); - }), r; - } -}; -function na(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[Yi], t) { - var h, g = t.options, _ = "on" + r.charAt(0).toUpperCase() + r.substr(1); - window.CustomEvent && !vi && !yi ? 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 = ui(ui({}, m), ta.getEventProperties(r, t)); - for (var y in v) h[y] = v[y]; - n && n.dispatchEvent(h), g[_] && g[_].call(t, h); +var vo = !1; +function yo(e, t) { + if (W.running = e && typeof e.id == "number" ? e : null, W.running === null) { + vo && (vo = !1, go()); + return; } + vo || (vo = !0, ho()), t && typeof W.running.task_id == "number" && Ra(t, [W.running.task_id]); } -var ra = ["evt"], ia = function(e, t) { - var n = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : {}, r = n.evt, i = di(n, ra); - ta.pluginEvent.bind($)(e, t, ui({ - dragEl: Y, - parentEl: oa, - ghostEl: X, - rootEl: Z, - nextEl: sa, - lastDownEl: ca, - cloneEl: Q, - cloneHidden: la, - dragStarted: Ta, - putSortable: ha, - activeSortable: $.active, - originalEvent: r, - oldIndex: ua, - oldDraggableIndex: fa, - newIndex: da, - newDraggableIndex: pa, - hideGhostForTarget: Wa, - unhideGhostForTarget: Ga, - cloneNowHidden: function() { - la = !0; - }, - cloneNowShown: function() { - la = !1; - }, - dispatchSortableEvent: function(e) { - aa({ - sortable: t, - name: e, - originalEvent: r - }); - } - }, i)); -}; -function aa(e) { - na(ui({ - putSortable: ha, - cloneEl: Q, - targetEl: Y, - rootEl: Z, - oldIndex: ua, - oldDraggableIndex: fa, - newIndex: da, - newDraggableIndex: pa - }, e)); +function bo(e, t, n) { + e?.notify("error", R(t, e.t(n))); } -var Y, oa, X, Z, sa, ca, Q, la, ua, da, fa, pa, ma, ha, ga = !1, _a = !1, va = [], ya, ba, xa, Sa, Ca, wa, Ta, Ea, Da, Oa = !1, ka = !1, Aa, ja, Ma = [], Na = !1, Pa = [], Fa = typeof document < "u", Ia = Si, La = yi || vi ? "cssFloat" : "float", Ra = Fa && !Ci && !Si && "draggable" in document.createElement("div"), za = function() { - if (Fa) { - if (vi) return !1; - var e = document.createElement("x"); - return e.style.cssText = "pointer-events:auto", e.style.pointerEvents === "auto"; - } -}(), Ba = function(e, t) { - var n = J(e), r = parseInt(n.width) - parseInt(n.paddingLeft) - parseInt(n.paddingRight) - parseInt(n.borderLeftWidth) - parseInt(n.borderRightWidth), i = Fi(e, 0, t), a = Fi(e, 1, t), o = i && J(i), s = a && J(a), c = o && parseInt(o.marginLeft) + parseInt(o.marginRight) + Ni(i).width, l = s && parseInt(s.marginLeft) + parseInt(s.marginRight) + Ni(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[La] === "none" || a && n[La] === "none" && c + l > r) ? "vertical" : "horizontal"; -}, Va = 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; -}, Ha = function(e, t) { - var n; - return va.some(function(r) { - var i = r[Yi].options.emptyInsertThreshold; - if (i && !Ii(r)) { - var a = Ni(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; -}, Ua = 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; - if (e == null && (n || s)) return !0; - if (e == null || e === !1) return !1; - if (n && e === "clone") return e; - if (typeof e == "function") return t(e(r, i, a, o), n)(r, i, a, o); - var c = (n ? r : i).options.group.name; - return e === !0 || typeof e == "string" && e === c || e.join && e.indexOf(c) > -1; - }; - } - var n = {}, r = e.group; - (!r || hi(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; -}, Wa = function() { - !za && X && J(X, "display", "none"); -}, Ga = function() { - !za && X && J(X, "display", ""); -}; -Fa && !Ci && document.addEventListener("click", function(e) { - if (_a) return e.preventDefault(), e.stopPropagation && e.stopPropagation(), e.stopImmediatePropagation && e.stopImmediatePropagation(), _a = !1, !1; -}, !0); -var Ka = function(e) { - if (Y) { - e = e.touches ? e.touches[0] : e; - var t = Ha(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[Yi]._onDragOver(n); - } - } -}, qa = function(e) { - Y && Y.parentNode[Yi]._isOutsideThisEl(e.target); -}; -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 = ci({}, t), e[Yi] = this; - var n = { - group: null, - sort: !0, - disabled: !1, - store: null, - handle: null, - draggable: /^[uo]l$/i.test(e.nodeName) ? ">li" : ">*", - swapThreshold: 1, - invertSwap: !1, - invertedSwapThreshold: null, - removeCloneOnHide: !0, - direction: function() { - return Ba(e, this.options); - }, - ghostClass: "sortable-ghost", - chosenClass: "sortable-chosen", - dragClass: "sortable-drag", - ignore: "a, img", - filter: null, - preventOnFilter: !0, - animation: 0, - easing: null, - setData: function(e, t) { - e.setData("Text", t.textContent); - }, - dropBubble: !1, - dragoverBubble: !1, - dataIdAttr: "data-id", - delay: 0, - delayOnTouchOnly: !1, - touchStartThreshold: (Number.parseInt ? Number : window).parseInt(window.devicePixelRatio, 10) || 1, - forceFallback: !1, - fallbackClass: "sortable-fallback", - fallbackOnBody: !1, - fallbackTolerance: 0, - fallbackOffset: { - x: 0, - y: 0 - }, - supportPointer: $.supportPointer !== !1 && "PointerEvent" in window && (!xi || Si), - emptyInsertThreshold: 5 - }; - for (var r in ta.initializePlugins(this, e, n), n) !(r in t) && (t[r] = n[r]); - for (var i in Ua(t), this) i.charAt(0) === "_" && typeof this[i] == "function" && (this[i] = this[i].bind(this)); - this.nativeDraggable = !t.forceFallback && Ra, this.nativeDraggable && (this.options.touchStartThreshold = 1), t.supportPointer ? K(e, "pointerdown", this._onTapStart) : (K(e, "mousedown", this._onTapStart), K(e, "touchstart", this._onTapStart)), this.nativeDraggable && (K(e, "dragover", this), K(e, "dragenter", this)), va.push(this.el), t.store && t.store.get && this.sort(t.store.get(this) || []), ci(this, Xi()); +function xo(e) { + Ba(e.task_id, { running: [{ + entry_id: e.id, + user_id: e.user_id, + started_at: e.started_at + }] }); } -$.prototype = { - constructor: $, - _isOutsideThisEl: function(e) { - !this.el.contains(e) && e !== this.el && (Ea = null); - }, - _getDirection: function(e, t) { - return typeof this.options.direction == "function" ? this.options.direction.call(this, e, t, Y) : 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 (ro(n), !Y && !(/mousedown|pointerdown/.test(a) && e.button !== 0 || r.disabled) && !c.isContentEditable && !(!this.nativeDraggable && xi && s && s.tagName.toUpperCase() === "SELECT") && (s = Di(s, r.draggable, n, !1), !(s && s.animated) && ca !== s)) { - if (ua = Li(s), fa = Li(s, r.draggable), typeof l == "function") { - if (l.call(this, e, s, this)) { - aa({ - sortable: t, - rootEl: c, - name: "filter", - targetEl: s, - toEl: n, - fromEl: n - }), ia("filter", t, { evt: e }), i && e.preventDefault(); - return; - } - } else if (l && (l = l.split(",").some(function(r) { - if (r = Di(c, r.trim(), n, !1), r) return aa({ - sortable: t, - rootEl: r, - name: "filter", - targetEl: s, - fromEl: n, - toEl: n - }), ia("filter", t, { evt: e }), !0; - }), l)) { - i && e.preventDefault(); - return; - } - (!r.handle || Di(c, r.handle, n, !1)) && this._prepareDragStart(e, o, s); - } - } +var G = { + get running() { + return W.running; }, - _prepareDragStart: function(e, t, n) { - var r = this, i = r.el, a = r.options, o = i.ownerDocument, s; - if (n && !Y && n.parentNode === i) { - var c = Ni(n); - if (Z = i, Y = n, oa = Y.parentNode, sa = Y.nextSibling, ca = n, ma = a.group, $.dragged = Y, ya = { - target: Y, - clientX: (t || e).clientX, - clientY: (t || e).clientY - }, Ca = ya.clientX - c.left, wa = ya.clientY - c.top, this._lastX = (t || e).clientX, this._lastY = (t || e).clientY, Y.style["will-change"] = "all", s = function() { - if (ia("delayEnded", r, { evt: e }), $.eventCanceled) { - r._onDrop(); - return; - } - r._disableDelayedDragEvents(), !bi && r.nativeDraggable && (Y.draggable = !0), r._triggerDragStart(e, t), aa({ - sortable: r, - name: "choose", - originalEvent: e - }), ki(Y, a.chosenClass, !0); - }, a.ignore.split(",").forEach(function(e) { - ji(Y, e.trim(), Xa); - }), K(o, "dragover", Ka), K(o, "mousemove", Ka), K(o, "touchmove", Ka), a.supportPointer ? (K(o, "pointerup", r._onDrop), !this.nativeDraggable && K(o, "pointercancel", r._onDrop)) : (K(o, "mouseup", r._onDrop), K(o, "touchend", r._onDrop), K(o, "touchcancel", r._onDrop)), bi && this.nativeDraggable && (this.options.touchStartThreshold = 4, Y.draggable = !0), ia("delayStart", this, { evt: e }), a.delay && (!a.delayOnTouchOnly || t) && (!this.nativeDraggable || !(yi || vi))) { - if ($.eventCanceled) { - this._onDrop(); - return; - } - a.supportPointer ? (K(o, "pointerup", r._disableDelayedDrag), K(o, "pointercancel", r._disableDelayedDrag)) : (K(o, "mouseup", r._disableDelayedDrag), K(o, "touchend", r._disableDelayedDrag), K(o, "touchcancel", r._disableDelayedDrag)), K(o, "mousemove", r._delayedDragTouchMoveHandler), K(o, "touchmove", r._delayedDragTouchMoveHandler), a.supportPointer && K(o, "pointermove", r._delayedDragTouchMoveHandler), r._dragStartTimer = setTimeout(s, a.delay); - } else s(); - } + get runningTaskId() { + let e = W.running?.task_id; + return typeof e == "number" ? e : null; }, - _delayedDragTouchMoveHandler: function(e) { - var t = e.touches ? e.touches[0] : e; - 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(); + get elapsedSeconds() { + return W.running === null ? 0 : ao(W.running.started_at, uo.now); }, - _disableDelayedDrag: function() { - Y && Xa(Y), clearTimeout(this._dragStartTimer), this._disableDelayedDragEvents(); + get busy() { + return W.busy; }, - _disableDelayedDragEvents: function() { - var e = this.el.ownerDocument; - q(e, "mouseup", this._disableDelayedDrag), q(e, "touchend", this._disableDelayedDrag), q(e, "touchcancel", this._disableDelayedDrag), q(e, "pointerup", this._disableDelayedDrag), q(e, "pointercancel", this._disableDelayedDrag), q(e, "mousemove", this._delayedDragTouchMoveHandler), q(e, "touchmove", this._delayedDragTouchMoveHandler), q(e, "pointermove", this._delayedDragTouchMoveHandler); + isRunningOn(e) { + return W.running !== null && W.running.task_id === e; }, - _triggerDragStart: function(e, t) { - t ||= e.pointerType == "touch" && e, !this.nativeDraggable || t ? this.options.supportPointer ? K(document, "pointermove", this._onTouchMove) : t ? K(document, "touchmove", this._onTouchMove) : K(document, "mousemove", this._onTouchMove) : (K(Y, "dragend", this), K(Z, "dragstart", this._onDragStart)); + async refresh(e) { try { - document.selection ? io(function() { - document.selection.empty(); - }) : window.getSelection().removeAllRanges(); - } catch {} - }, - _dragStarted: function(e, t) { - if (ga = !1, Z && Y) { - ia("dragStarted", this, { evt: t }), this.nativeDraggable && K(document, "dragover", qa); - var n = this.options; - !e && ki(Y, n.dragClass, !1), ki(Y, n.ghostClass, !0), $.active = this, e && this._appendGhost(), aa({ - sortable: this, - name: "start", - originalEvent: t - }); - } else this._nulling(); - }, - _emulateDragOver: function() { - if (ba) { - this._lastX = ba.clientX, this._lastY = ba.clientY, Wa(); - for (var e = document.elementFromPoint(ba.clientX, ba.clientY), t = e; e && e.shadowRoot && (e = e.shadowRoot.elementFromPoint(ba.clientX, ba.clientY), e !== t);) t = e; - if (Y.parentNode[Yi]._isOutsideThisEl(e), t) do { - if (t[Yi]) { - var n = void 0; - if (n = t[Yi]._onDragOver({ - clientX: ba.clientX, - clientY: ba.clientY, - target: e, - rootEl: t - }), n && !this.options.dragoverBubble) break; - } - e = t; - } while (t = Ei(t)); - Ga(); + yo(await sa(e), e); + } catch { + yo(null); } }, - _onTouchMove: function(e) { - if (ya) { - var t = this.options, n = t.fallbackTolerance, r = t.fallbackOffset, i = e.touches ? e.touches[0] : e, a = X && Ai(X, !0), o = X && a && a.a, s = X && a && a.d, c = Ia && ja && Ri(ja), l = (i.clientX - ya.clientX + r.x) / (o || 1) + (c ? c[0] - Ma[0] : 0) / (o || 1), u = (i.clientY - ya.clientY + r.y) / (s || 1) + (c ? c[1] - Ma[1] : 0) / (s || 1); - if (!$.active && !ga) { - if (n && Math.max(Math.abs(i.clientX - this._lastX), Math.abs(i.clientY - this._lastY)) < n) return; - this._onDragStart(e, !0); - } - if (X) { - a ? (a.e += l - (xa || 0), a.f += u - (Sa || 0)) : a = { - a: 1, - b: 0, - c: 0, - d: 1, - e: l, - f: u - }; - var d = `matrix(${a.a},${a.b},${a.c},${a.d},${a.e},${a.f})`; - J(X, "webkitTransform", d), J(X, "mozTransform", d), J(X, "msTransform", d), J(X, "transform", d), xa = l, Sa = u, ba = i; - } - e.cancelable && e.preventDefault(); + async start(e, t, n = null, r) { + if (W.busy) return null; + W.busy = !0; + try { + let r = await ca(e, { + task_id: t, + description: n + }); + return yo(r, e), xo(r), U(), r; + } catch (t) { + return de(t) ? (r?.notify("warning", r.t("tasks_projects.timer.already_running")), await this.refresh(e)) : bo(r, t, "tasks_projects.timer.start_failed"), null; + } finally { + W.busy = !1; } }, - _appendGhost: function() { - if (!X) { - var e = this.options.fallbackOnBody ? document.body : Z, t = Ni(Y, !0, Ia, !0, e), n = this.options; - if (Ia) { - for (ja = e; J(ja, "position") === "static" && J(ja, "transform") === "none" && ja !== document;) ja = ja.parentNode; - ja !== document.body && ja !== document.documentElement ? (ja === document && (ja = Mi()), t.top += ja.scrollTop, t.left += ja.scrollLeft) : ja = Mi(), Ma = Ri(ja); - } - X = Y.cloneNode(!0), ki(X, n.ghostClass, !1), ki(X, n.fallbackClass, !0), ki(X, n.dragClass, !0), J(X, "transition", ""), J(X, "transform", ""), J(X, "box-sizing", "border-box"), J(X, "margin", 0), J(X, "top", t.top), J(X, "left", t.left), J(X, "width", t.width), J(X, "height", t.height), J(X, "opacity", "0.8"), J(X, "position", Ia ? "absolute" : "fixed"), J(X, "zIndex", "100000"), J(X, "pointerEvents", "none"), $.ghost = X, e.appendChild(X), J(X, "transform-origin", Ca / parseInt(X.style.width) * 100 + "% " + wa / parseInt(X.style.height) * 100 + "%"); + async startOnTask(e, t, n = null, r) { + if (W.busy) return null; + W.busy = !0; + try { + let r = await Mn(e, t, n); + return yo(r, e), xo(r), U(), r; + } catch (t) { + return z(t) === "timer_already_running" ? (r?.notify("warning", r.t("tasks_projects.timer.already_running")), await this.refresh(e)) : bo(r, t, "tasks_projects.timer.start_failed"), null; + } finally { + W.busy = !1; } }, - _onDragStart: function(e, t) { - var n = this, r = e.dataTransfer, i = n.options; - if (ia("dragStart", this, { evt: e }), $.eventCanceled) { - this._onDrop(); - return; + async stop(e, t) { + if (W.busy || W.running === null) return null; + let n = W.running.task_id; + W.busy = !0; + try { + let t = await la(e); + return yo(null), Ba(n, { running: [] }), U(), t; + } catch (n) { + return bo(t, n, "tasks_projects.timer.stop_failed"), await this.refresh(e), null; + } finally { + W.busy = !1; } - ia("setupClone", this), $.eventCanceled || (Q = qi(Y), Q.removeAttribute("id"), Q.draggable = !1, Q.style["will-change"] = "", this._hideClone(), ki(Q, this.options.chosenClass, !1), $.clone = Q), n.cloneId = io(function() { - ia("clone", n), !$.eventCanceled && (n.options.removeCloneOnHide || Z.insertBefore(Q, Y), n._hideClone(), aa({ - sortable: n, - name: "clone" - })); - }), !t && ki(Y, i.dragClass, !0), t ? (_a = !0, n._loopId = setInterval(n._emulateDragOver, 50)) : (q(document, "mouseup", n._onDrop), q(document, "touchend", n._onDrop), q(document, "touchcancel", n._onDrop), r && (r.effectAllowed = "move", i.setData && i.setData.call(n, r, Y)), K(document, "drop", n), J(Y, "transform", "translateZ(0)")), ga = !0, n._dragStartId = io(n._dragStarted.bind(n, t, e)), K(document, "selectstart", n), Ta = !0, window.getSelection().removeAllRanges(), xi && J(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 = $.active, l = ma === s, u = o.sort, d = ha || c, f, p = this, m = !1; - if (Na) return; - function h(o, s) { - ia(o, p, ui({ - evt: e, - isOwner: l, - axis: f ? "vertical" : "horizontal", - revert: a, - dragRect: r, - targetRect: i, - canSort: u, - fromSortable: d, - target: n, - completed: _, - onMove: function(n, i) { - return Ya(Z, t, Y, r, n, Ni(n), e, i); - }, - changed: v - }, s)); - } - function g() { - h("dragOverAnimationCapture"), p.captureAnimationState(), p !== d && d.captureAnimationState(); + async stopOnTask(e, t, n) { + if (W.busy) return null; + W.busy = !0; + try { + let n = await Nn(e, t); + return yo(null), Ba(t, { running: [] }), U(), n; + } catch (t) { + return z(t) === "timer_mismatch" ? n?.notify("warning", n.t("tasks_projects.timer.mismatch")) : bo(n, t, "tasks_projects.timer.stop_failed"), await this.refresh(e), null; + } finally { + W.busy = !1; } - function _(r) { - return h("dragOverCompleted", { insertion: r }), r && (l ? c._hideClone() : c._showClone(p), p !== d && (ki(Y, ha ? ha.options.ghostClass : c.options.ghostClass, !1), ki(Y, o.ghostClass, !0)), ha !== p && p !== $.active ? ha = p : p === $.active && ha && (ha = null), d === p && (p._ignoreWhileAnimating = n), p.animateAll(function() { - h("dragOverAnimationComplete"), p._ignoreWhileAnimating = null; - }), p !== d && (d.animateAll(), d._ignoreWhileAnimating = null)), (n === Y && !Y.animated || n === t && !n.animated) && (Ea = null), !o.dragoverBubble && !e.rootEl && n !== document && (Y.parentNode[Yi]._isOutsideThisEl(e.target), !r && Ka(e)), !o.dragoverBubble && e.stopPropagation && e.stopPropagation(), m = !0; + }, + async discard(e, t) { + if (W.busy || W.running === null) return !1; + let n = W.running.task_id; + W.busy = !0; + try { + return await ua(e), yo(null), Ba(n, { running: [] }), U(), !0; + } catch (n) { + return bo(t, n, "tasks_projects.timer.discard_failed"), await this.refresh(e), !1; + } finally { + W.busy = !1; } - function v() { - da = Li(Y), pa = Li(Y, o.draggable), aa({ - sortable: p, - name: "change", - toEl: t, - newIndex: da, - newDraggableIndex: pa, - originalEvent: e + }, + reset() { + W.busy = !1, yo(null); + } +}, So = { class: "flex items-center gap-1.5" }, Co = [ + "disabled", + "title", + "aria-label" +], wo = ["title", "aria-label"], To = ["disabled", "title"], Eo = [ + "disabled", + "title", + "aria-label" +], Do = ["title"], Oo = /* @__PURE__ */ l({ + __name: "TaskRunControl", + props: { + client: {}, + notify: {}, + task: {}, + members: { default: () => [] }, + size: { default: "sm" } + }, + setup(t) { + let r = t, i = B(), l = _o(), u = n(() => ({ + notify: r.notify, + t: i + })), d = n(() => G.isRunningOn(r.task.id)), p = n(() => G.runningTaskId !== null && !d.value), m = n(() => Ia(G.runningTaskId)), h = n(() => Va(r.task).running.filter((e) => e.user_id !== xa.userId && e.entry_id !== G.running?.id)), g = n(() => Ka(G.elapsedSeconds)), v = n(() => r.size === "md" ? "h-5 w-5" : "h-4 w-4"), y = n(() => r.size === "md" ? "p-2" : "p-1.5"); + function w(e) { + return r.members.find((t) => t.id === e)?.name ?? `#${e}`; + } + function T(e) { + let t = r.members.find((t) => t.id === e); + return t ? be(t.name) : "?"; + } + function E(e) { + return Ka(ao(e.started_at, l.value)); + } + function D(e) { + return i("tasks_projects.timer.running_by", { + name: w(e.user_id), + time: E(e) }); } - if (e.preventDefault !== void 0 && e.cancelable && e.preventDefault(), n = Di(n, o.draggable, t, !0), h("dragOver"), $.eventCanceled) return m; - if (Y.contains(e.target) || n.animated && n.animatingX && n.animatingY || p._ignoreWhileAnimating === n) return _(!1); - if (_a = !1, c && !o.disabled && (l ? u || (a = oa !== Z) : ha === this || (this.lastPutMode = ma.checkPull(this, c, Y, e)) && s.checkPut(this, c, Y, e))) { - if (f = this._getDirection(e, n) === "vertical", r = Ni(Y), h("dragOverValid"), $.eventCanceled) return m; - if (a) return oa = Z, g(), this._hideClone(), h("revert"), $.eventCanceled || (sa ? Z.insertBefore(Y, sa) : Z.appendChild(Y)), _(!0); - var y = Ii(t, o.draggable); - if (!y || $a(e, f, this) && !y.animated) { - if (y === Y) return _(!1); - if (y && t === e.target && (n = y), n && (i = Ni(n)), Ya(Z, t, Y, r, n, i, e, !!n) !== !1) return g(), y && y.nextSibling ? t.insertBefore(Y, y.nextSibling) : t.appendChild(Y), oa = t, v(), _(!0); - } else if (y && Qa(e, f, this)) { - var b = Fi(t, 0, o, !0); - if (b === Y) return _(!1); - if (n = b, i = Ni(n), Ya(Z, t, Y, r, n, i, e, !1) !== !1) return g(), t.insertBefore(Y, b), oa = t, v(), _(!0); - } else if (n.parentNode === t) { - i = Ni(n); - var x = 0, S, C = Y.parentNode !== t, w = !Va(Y.animated && Y.toRect || r, n.animated && n.toRect || i, f), T = f ? "top" : "left", E = Pi(n, "top", "top") || Pi(Y, "top", "top"), D = E ? E.scrollTop : void 0; - Ea !== n && (S = i[T], Oa = !1, ka = !w && o.invertSwap || C), x = eo(e, n, i, f, w ? 1 : o.swapThreshold, o.invertedSwapThreshold == null ? o.swapThreshold : o.invertedSwapThreshold, ka, Ea === n); - var O; - if (x !== 0) { - var k = Li(Y); - do - k -= x, O = oa.children[k]; - while (O && (J(O, "display") === "none" || O === X)); - } - if (x === 0 || O === n) return _(!1); - Ea = n, Da = x; - var A = n.nextElementSibling, j = !1; - j = x === 1; - var M = Ya(Z, t, Y, r, n, i, e, j); - if (M !== !1) return (M === 1 || M === -1) && (j = M === 1), Na = !0, setTimeout(Za, 30), g(), j && !A ? t.appendChild(Y) : n.parentNode.insertBefore(Y, j ? A : n), E && Ki(E, 0, D - E.scrollTop), oa = Y.parentNode, S !== void 0 && !ka && (Aa = Math.abs(S - Ni(n)[T])), v(), _(!0); - } - if (t.contains(Y)) return _(!1); + async function O() { + await G.startOnTask(r.client, r.task.id, null, u.value) !== null && r.notify("success", i("tasks_projects.timer.started", { name: r.task.name })); } - return !1; - }, - _ignoreWhileAnimating: null, - _offMoveEvents: function() { - q(document, "mousemove", this._onTouchMove), q(document, "touchmove", this._onTouchMove), q(document, "pointermove", this._onTouchMove), q(document, "dragover", Ka), q(document, "mousemove", Ka), q(document, "touchmove", Ka); - }, - _offUpEvents: function() { - var e = this.el.ownerDocument; - q(e, "mouseup", this._onDrop), q(e, "touchend", this._onDrop), q(e, "pointerup", this._onDrop), q(e, "pointercancel", this._onDrop), q(e, "touchcancel", this._onDrop), q(document, "selectstart", this); - }, - _onDrop: function(e) { - var t = this.el, n = this.options; - if (da = Li(Y), pa = Li(Y, n.draggable), ia("drop", this, { evt: e }), oa = Y && Y.parentNode, da = Li(Y), pa = Li(Y, n.draggable), $.eventCanceled) { - this._nulling(); - return; + async function k() { + let e = await G.stopOnTask(r.client, r.task.id, u.value); + e !== null && r.notify("success", i("tasks_projects.timer.stopped", { + name: r.task.name, + duration: qa(e.duration_minutes) + })); } - ga = !1, ka = !1, Oa = !1, clearInterval(this._loopId), clearTimeout(this._dragStartTimer), ao(this.cloneId), ao(this._dragStartId), this.nativeDraggable && (q(document, "drop", this), q(t, "dragstart", this._onDragStart)), this._offMoveEvents(), this._offUpEvents(), xi && J(document.body, "user-select", ""), J(Y, "transform", ""), e && (Ta && (e.cancelable && e.preventDefault(), !n.dropBubble && e.stopPropagation()), X && X.parentNode && X.parentNode.removeChild(X), (Z === oa || ha && ha.lastPutMode !== "clone") && Q && Q.parentNode && Q.parentNode.removeChild(Q), Y && (this.nativeDraggable && q(Y, "dragend", this), Xa(Y), Y.style["will-change"] = "", Ta && !ga && ki(Y, ha ? ha.options.ghostClass : this.options.ghostClass, !1), ki(Y, this.options.chosenClass, !1), aa({ - sortable: this, - name: "unchoose", - toEl: oa, - newIndex: null, - newDraggableIndex: null, - originalEvent: e - }), Z === oa ? da !== ua && da >= 0 && (aa({ - sortable: this, - name: "update", - toEl: oa, - originalEvent: e - }), aa({ - sortable: this, - name: "sort", - toEl: oa, - originalEvent: e - })) : (da >= 0 && (aa({ - rootEl: oa, - name: "add", - toEl: oa, - fromEl: Z, - originalEvent: e - }), aa({ - sortable: this, - name: "remove", - toEl: oa, - originalEvent: e - }), aa({ - rootEl: oa, - name: "sort", - toEl: oa, - fromEl: Z, - originalEvent: e - }), aa({ - sortable: this, - name: "sort", - toEl: oa, - originalEvent: e - })), ha && ha.save()), $.active && ((da == null || da === -1) && (da = ua, pa = fa), aa({ - sortable: this, - name: "end", - toEl: oa, - originalEvent: e - }), this.save()))), this._nulling(); - }, - _nulling: function() { - ia("nulling", this), Z = Y = oa = X = sa = Q = ca = la = ya = ba = Ta = da = pa = ua = fa = Ea = Da = ha = ma = $.dragged = $.ghost = $.clone = $.active = null; - var e = this.el; - Pa.forEach(function(t) { - e.contains(t) && (t.checked = !0); - }), Pa.length = xa = Sa = 0; - }, - handleEvent: function(e) { - switch (e.type) { - case "drop": - case "dragend": - this._onDrop(e); - break; - case "dragenter": - case "dragover": - Y && (this._onDragOver(e), Ja(e)); - break; - case "selectstart": e.preventDefault(); + async function j() { + await G.stop(r.client, u.value) !== null && await O(); } + return (n, r) => { + let l = x("BaseIcon"); + return _(), a("div", So, [d.value ? (_(), a(e, { key: 0 }, [o("button", { + type: "button", + class: f(["rounded-md text-status-red hover:bg-hover disabled:opacity-50", y.value]), + disabled: C(G).busy, + title: C(i)("tasks_projects.timer.stop_on", { name: t.task.name }), + "aria-label": C(i)("tasks_projects.timer.stop_on", { name: t.task.name }), + onClick: A(k, ["stop"]) + }, [c(l, { + name: "StopIcon", + class: f(v.value) + }, null, 8, ["class"])], 10, Co), o("span", { class: f(["font-medium tabular-nums text-primary-500", t.size === "md" ? "text-base" : "text-xs"]) }, S(g.value), 3)], 64)) : p.value ? (_(), a(e, { key: 1 }, [o("button", { + type: "button", + class: f(["cursor-not-allowed rounded-md text-subtle", y.value]), + disabled: "", + title: C(i)("tasks_projects.timer.busy_elsewhere", { name: m.value }), + "aria-label": C(i)("tasks_projects.timer.busy_elsewhere", { name: m.value }) + }, [c(l, { + name: "PlayIcon", + class: f(v.value) + }, null, 8, ["class"])], 10, wo), o("button", { + type: "button", + class: "rounded-md px-1.5 py-0.5 text-[11px] font-medium text-primary-500 hover:bg-hover disabled:opacity-50", + disabled: C(G).busy, + title: C(i)("tasks_projects.timer.stop_and_start"), + onClick: A(j, ["stop"]) + }, S(C(i)("tasks_projects.timer.stop_and_start")), 9, To)], 64)) : (_(), a("button", { + key: 2, + type: "button", + class: f(["rounded-md text-primary-500 hover:bg-hover disabled:opacity-50", y.value]), + disabled: C(G).busy, + title: C(i)("tasks_projects.timer.start_on", { name: t.task.name }), + "aria-label": C(i)("tasks_projects.timer.start_on", { name: t.task.name }), + onClick: A(O, ["stop"]) + }, [c(l, { + name: "PlayIcon", + class: f(v.value) + }, null, 8, ["class"])], 10, Eo)), (_(!0), a(e, null, b(h.value, (e) => (_(), a("span", { + key: e.entry_id, + class: "flex items-center gap-1 rounded-full bg-surface-tertiary px-1.5 py-0.5 text-[11px] text-muted", + title: D(e) + }, [c(l, { + name: "ClockIcon", + class: "h-3.5 w-3.5 text-primary-500" + }), s(" " + S(T(e.user_id)), 1)], 8, Do))), 128))]); + }; + } +}), ko = { class: "relative table-container" }, Ao = { class: "inline-flex items-center whitespace-nowrap" }, jo = { class: "tabular-nums" }, Mo = { + key: 1, + class: "text-subtle" +}, No = ["title"], Po = 10, Fo = "whitespace-nowrap px-3 py-3 text-left text-xs font-medium text-muted uppercase tracking-wider", Io = "px-3 py-4 text-sm text-muted whitespace-nowrap", Lo = /* @__PURE__ */ l({ + __name: "TaskList", + props: { + client: {}, + notify: {}, + filters: {}, + statuses: {}, + members: {}, + projects: {}, + projectId: { default: null } }, - toArray: function() { - for (var e = [], t, n = this.el.children, r = 0, i = n.length, a = this.options; r < i; r++) t = n[r], Di(t, a.draggable, this.el, !1) && e.push(t.getAttribute(a.dataIdAttr) || no(t)); - return e; - }, - sort: function(e, t) { - var n = {}, r = this.el; - this.toArray().forEach(function(e, t) { - var i = r.children[t]; - Di(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(); - }, - save: function() { - var e = this.options.store; - e && e.set && e.set(this); - }, - closest: function(e, t) { - return Di(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 = ta.modifyOption(this, e, t); - n[e] = r === void 0 ? t : r, e === "group" && Ua(n); - }, - destroy: function() { - ia("destroy", this); - var e = this.el; - e[Yi] = null, q(e, "mousedown", this._onTapStart), q(e, "touchstart", this._onTapStart), q(e, "pointerdown", this._onTapStart), this.nativeDraggable && (q(e, "dragover", this), q(e, "dragenter", this)), Array.prototype.forEach.call(e.querySelectorAll("[draggable]"), function(e) { - e.removeAttribute("draggable"); - }), this._onDrop(), this._disableDelayedDragEvents(), va.splice(va.indexOf(this.el), 1), this.el = e = null; - }, - _hideClone: function() { - if (!la) { - if (ia("hideClone", this), $.eventCanceled) return; - J(Q, "display", "none"), this.options.removeCloneOnHide && Q.parentNode && Q.parentNode.removeChild(Q), la = !0; + emits: ["changed"], + setup(e, { expose: t, emit: l }) { + let u = e, d = l, m = { + number: "number", + name: "name" + }, h = B(), g = y(null), v = y(!0), b = y(0), w = y([]), k = y([]), A = y(!1), j = y(!1), M = y(null), N = y({}), P = y(null), F = n(() => u.projects.map((e) => ({ + id: e.id, + label: e.name + }))), ee = n(() => u.filters.search !== "" || u.filters.status !== "" || u.filters.user !== "" || u.projectId === null && u.filters.project !== ""), I = n(() => !v.value && b.value === 0 && !ee.value), te = n(() => [ + { + key: "select", + label: "", + sortable: !1, + tdClass: "w-8" + }, + { + key: "number", + label: h("tasks_projects.tasks.columns.number"), + sortable: !0, + sortBy: "number", + tdClass: "text-muted" + }, + { + key: "name", + label: h("tasks_projects.tasks.columns.name"), + sortable: !0, + sortBy: "name", + thClass: "extra", + tdClass: "font-medium text-heading" + }, + { + key: "status", + label: h("tasks_projects.tasks.columns.status"), + sortable: !1 + }, + { + key: "assignee", + label: h("tasks_projects.tasks.columns.assignee"), + sortable: !1 + }, + { + key: "logged", + label: h("tasks_projects.tasks.columns.logged"), + sortable: !1 + }, + { + key: "unbilled", + label: h("tasks_projects.tasks.columns.unbilled"), + sortable: !1 + }, + { + key: "invoiced", + label: h("tasks_projects.tasks.columns.invoiced"), + sortable: !1 + }, + { + key: "timer", + label: h("tasks_projects.tasks.columns.timer"), + sortable: !1 + }, + { + key: "actions", + label: h("tasks_projects.general.actions"), + sortable: !1, + tdClass: "text-right text-sm font-medium" + } + ].map((e) => ({ + defaultThClass: Fo, + defaultTdClass: Io, + ...e + }))); + E(() => vi(u.filters), () => ne()), E(() => u.projectId, () => ne()), E(za, () => ne(!0)); + async function L({ page: e, sort: t }) { + let n = hn(t, m), r = { + page: e, + limit: Po, + ...bi(u.filters, { projectId: u.projectId }), + ...n + }; + v.value = !0; + try { + let e = await Dn(u.client, r), t = e.data ?? [], n = e.meta; + return b.value = n?.total ?? t.length, w.value = t, k.value = k.value.filter((e) => t.some((t) => t.id === e)), { + data: t, + pagination: { + totalPages: n?.last_page ?? 1, + currentPage: n?.current_page ?? 1, + totalCount: n?.total ?? t.length, + limit: n?.per_page ?? Po + } + }; + } catch (e) { + return u.notify("error", R(e, h("tasks_projects.tasks.load_failed"))), w.value = [], { + data: [], + pagination: { + totalPages: 1, + currentPage: 1, + totalCount: 0, + limit: Po + } + }; + } finally { + v.value = !1; + } + } + function ne(e = !1) { + g.value?.refresh(e); + } + function re(e) { + return k.value.includes(e.id); } - }, - _showClone: function(e) { - if (e.lastPutMode !== "clone") { - this._hideClone(); - return; + function ie(e) { + k.value = re(e) ? k.value.filter((t) => t !== e.id) : [...k.value, e.id]; } - if (la) { - if (ia("showClone", this), $.eventCanceled) return; - Y.parentNode == Z && !this.options.group.revertClone ? Z.insertBefore(Q, Y) : sa ? Z.insertBefore(Q, sa) : Z.appendChild(Q), this.options.group.revertClone && this.animate(Y, Q), J(Q, "display", ""), la = !1; + function ae() { + k.value = w.value.map((e) => e.id); } - } -}; -function Ja(e) { - e.dataTransfer && (e.dataTransfer.dropEffect = "move"), e.cancelable && e.preventDefault(); -} -function Ya(e, t, n, r, i, a, o, s) { - var c, l = e[Yi], u = l.options.onMove, d; - return window.CustomEvent && !vi && !yi ? 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 || Ni(t), c.willInsertAfter = s, c.originalEvent = o, e.dispatchEvent(c), u && (d = u.call(l, c, o)), d; -} -function Xa(e) { - e.draggable = !1; -} -function Za() { - Na = !1; -} -function Qa(e, t, n) { - var r = Ni(Fi(n.el, 0, n.options, !0)), i = Ji(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 $a(e, t, n) { - var r = Ni(Ii(n.el, n.options.draggable)), i = Ji(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 eo(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 && Aa < l * i) { - if (!Oa && (Da === 1 ? c > u + l * a / 2 : c < d - l * a / 2) && (Oa = !0), Oa) f = !0; - else if (Da === 1 ? c < u + Aa : c > d - Aa) return -Da; - } else if (c > u + l * (1 - i) / 2 && c < d - l * (1 - i) / 2) return to(t); - } - return f ||= o, f && (c < u + l * a / 2 || c > d - l * a / 2) ? c > u + l / 2 ? 1 : -1 : 0; -} -function to(e) { - return Li(Y) < Li(e) ? 1 : -1; -} -function no(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 ro(e) { - Pa.length = 0; - for (var t = e.getElementsByTagName("input"), n = t.length; n--;) { - var r = t[n]; - r.checked && Pa.push(r); - } -} -function io(e) { - return setTimeout(e, 0); -} -function ao(e) { - return clearTimeout(e); -} -Fa && K(document, "touchmove", function(e) { - ($.active || ga) && e.cancelable && e.preventDefault(); -}), $.utils = { - on: K, - off: q, - css: J, - find: ji, - is: function(e, t) { - return !!Di(e, t, e, !1); - }, - extend: Vi, - throttle: Wi, - closest: Di, - toggleClass: ki, - clone: qi, - index: Li, - nextTick: io, - cancelNextTick: ao, - detectDirection: Ba, - getChild: Fi, - expando: Yi -}, $.get = function(e) { - return e[Yi]; -}, $.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 && ($.utils = ui(ui({}, $.utils), e.utils)), ta.mount(e); - }); -}, $.create = function(e, t) { - return new $(e, t); -}, $.version = gi; -var oo = [], so, co, lo = !1, uo, fo, po, mo; -function ho() { - function e() { - for (var e in this.defaults = { - scroll: !0, - forceAutoScrollFallback: !1, - scrollSensitivity: 30, - scrollSpeed: 10, - bubbleScroll: !0 - }, this) e.charAt(0) === "_" && typeof this[e] == "function" && (this[e] = this[e].bind(this)); - } - return e.prototype = { - dragStarted: function(e) { - var t = e.originalEvent; - this.sortable.nativeDraggable ? K(document, "dragover", this._handleAutoScroll) : this.options.supportPointer ? K(document, "pointermove", this._handleFallbackAutoScroll) : t.touches ? K(document, "touchmove", this._handleFallbackAutoScroll) : K(document, "mousemove", this._handleFallbackAutoScroll); - }, - dragOverCompleted: function(e) { - var t = e.originalEvent; - !this.options.dragOverBubble && !t.rootEl && this._handleAutoScroll(t); - }, - drop: function() { - this.sortable.nativeDraggable ? q(document, "dragover", this._handleAutoScroll) : (q(document, "pointermove", this._handleFallbackAutoScroll), q(document, "touchmove", this._handleFallbackAutoScroll), q(document, "mousemove", this._handleFallbackAutoScroll)), _o(), go(), Gi(); - }, - nulling: function() { - po = co = so = lo = mo = uo = fo = null, oo.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 (po = e, t || this.options.forceAutoScrollFallback || yi || vi || xi) { - vo(e, this.options, a, t); - var o = Bi(a, !0); - lo && (!mo || r !== uo || i !== fo) && (mo && _o(), mo = setInterval(function() { - var a = Bi(document.elementFromPoint(r, i), !0); - a !== o && (o = a, go()), vo(e, n.options, a, t); - }, 10), uo = r, fo = i); - } else { - if (!this.options.bubbleScroll || Bi(a, !0) === Mi()) { - go(); - return; + function oe() { + k.value = []; + } + function se() { + M.value = null, N.value = { project_id: u.projectId }, j.value = !0; + } + function ce(e) { + M.value = e, N.value = {}, j.value = !0; + } + function le(e) { + let t = M.value ? h("tasks_projects.tasks.updated", { name: e.name }) : h("tasks_projects.tasks.created", { name: e.name }); + j.value = !1, M.value = null, u.notify("success", t), U(), d("changed"); + } + function ue(e) { + j.value = !1, M.value = null, u.notify("success", h("tasks_projects.tasks.deleted", { name: e.name })), U(), d("changed"); + } + function z(e) { + return u.statuses.find((t) => t.id === e.task_status_id) ?? null; + } + function de(e) { + return e.assignee_id === null ? h("tasks_projects.tasks.unassigned") : u.members.find((t) => t.id === e.assignee_id)?.name ?? `#${e.assignee_id}`; + } + function fe(e) { + return qa(Va(e).logged_minutes); + } + async function pe(e) { + if (window.confirm(h("tasks_projects.tasks.delete_confirm", { name: e.name }))) { + P.value = e.id; + try { + await An(u.client, e.id), u.notify("success", h("tasks_projects.tasks.deleted", { name: e.name })), U(), d("changed"); + } catch (e) { + u.notify("error", R(e, h("tasks_projects.tasks.delete_failed"))); + } finally { + P.value = null; } - vo(e, this.options, Bi(a, !1), !1); } } - }, ci(e, { - pluginName: "scroll", - initializeByDefault: !0 - }); -} -function go() { - oo.forEach(function(e) { - clearInterval(e.pid); - }), oo = []; -} -function _o() { - clearInterval(mo); -} -var vo = Wi(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 = Mi(), l = !1, u; - co !== n && (co = n, go(), so = t.scroll, u = t.scrollFn, so === !0 && (so = Bi(n, !0))); - var d = 0, f = so; - do { - var p = f, m = Ni(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 = J(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 (!oo[d]) for (var A = 0; A <= d; A++) oo[A] || (oo[A] = {}); - (oo[d].vx != O || oo[d].vy != k || oo[d].el !== p) && (oo[d].el = p, oo[d].vx = O, oo[d].vy = k, clearInterval(oo[d].pid), (O != 0 || k != 0) && (l = !0, oo[d].pid = setInterval(function() { - r && this.layer === 0 && $.active._onTouchMove(po); - var t = oo[this.layer].vy ? oo[this.layer].vy * s : 0, n = oo[this.layer].vx ? oo[this.layer].vx * s : 0; - (typeof u != "function" || u.call($.dragged.parentNode[Yi], n, t, e, po, oo[this.layer].el) === "continue") && Ki(oo[this.layer].el, n, t); - }.bind({ layer: d }), 24))), d++; - } while (t.bubbleScroll && f !== c && (f = Bi(f, !1))); - lo = l; - } -}, 30), yo = 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; - o(); - var l = t.changedTouches && t.changedTouches.length ? t.changedTouches[0] : t, u = document.elementFromPoint(l.clientX, l.clientY); - s(), c && !c.el.contains(u) && (a("spill"), this.onSpill({ - dragEl: r, - putSortable: n - })); + async function me(e) { + await ge({ + action: "status", + ids: [...k.value], + task_status_id: e + }, "applied"); + } + async function he() { + let e = k.value.length; + window.confirm(h("tasks_projects.tasks.bulk.delete_confirm", { count: e })) && await ge({ + action: "delete", + ids: [...k.value] + }, "deleted"); + } + async function ge(e, t) { + if (!(A.value || e.ids.length === 0)) { + A.value = !0; + try { + let n = await Fn(u.client, e); + n.failed.length > 0 ? u.notify("warning", h("tasks_projects.tasks.bulk.partial", { + count: n.updated, + failed: n.failed.length + })) : n.updated === 0 ? u.notify("warning", h("tasks_projects.tasks.bulk.nothing")) : u.notify("success", h(`tasks_projects.tasks.bulk.${t}`, { count: n.updated })), oe(), U(), d("changed"); + } catch (e) { + u.notify("error", R(e, h("tasks_projects.tasks.bulk.failed"))); + } finally { + A.value = !1; + } + } + } + return t({ + openCreate: se, + refresh: ne + }), (t, n) => { + let l = x("BaseIcon"), u = x("BaseButton"), d = x("BaseEmptyPlaceholder"), m = x("BaseCheckbox"), v = x("router-link"), y = x("BaseFormatMoney"), b = x("BaseDropdownItem"), w = x("BaseDropdown"), E = x("BaseTable"); + return _(), a("div", null, [ + c(Bi, { + count: k.value.length, + statuses: e.statuses, + busy: A.value, + onStatus: me, + onDelete: he, + onClear: oe, + onSelectPage: ae + }, null, 8, [ + "count", + "statuses", + "busy" + ]), + O(c(d, { + title: C(h)("tasks_projects.tasks.empty_title"), + description: C(h)("tasks_projects.tasks.empty_description") + }, { + actions: D(() => [c(u, { + variant: "primary", + onClick: se + }, { + left: D((e) => [c(l, { + name: "PlusIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: D(() => [s(" " + S(C(h)("tasks_projects.tasks.new_task")), 1)]), + _: 1 + })]), + default: D(() => [c(l, { + name: "ClipboardDocumentListIcon", + class: "mt-5 mb-4 h-16 w-16 text-subtle" + })]), + _: 1 + }, 8, ["title", "description"]), [[T, I.value]]), + O(o("div", ko, [c(E, { + ref_key: "tableRef", + ref: g, + data: L, + columns: te.value, + class: "mt-3" + }, { + "cell-select": D(({ row: e }) => [c(m, { + "model-value": re(e.data), + "aria-label": e.data.name, + onChange: (t) => ie(e.data) + }, null, 8, [ + "model-value", + "aria-label", + "onChange" + ])]), + "cell-number": D(({ row: e }) => [s("#" + S(e.data.number), 1)]), + "cell-name": D(({ row: e }) => [c(v, { + class: "hover:text-primary-500", + to: C(H).task(e.data.id) + }, { + default: D(() => [s(S(e.data.name), 1)]), + _: 2 + }, 1032, ["to"]), e.data.due_date ? (_(), a("span", { + key: 0, + class: f(["mt-0.5 block text-xs font-normal", C(xe)(e.data.due_date) && !e.data.closed_at ? "font-medium text-status-red" : "text-muted"]) + }, S(C(h)("tasks_projects.tasks.columns.due_date")) + ": " + S(C(_e)(e.data.due_date)), 3)) : i("", !0)]), + "cell-status": D(({ row: e }) => [o("span", Ao, [o("span", { + class: f(["mr-2 inline-block h-2.5 w-2.5 shrink-0 rounded-full", z(e.data)?.colour ? "" : "bg-line-default"]), + style: p(z(e.data)?.colour ? { backgroundColor: z(e.data)?.colour } : void 0) + }, null, 6), s(" " + S(z(e.data)?.name ?? "-"), 1)])]), + "cell-assignee": D(({ row: e }) => [o("span", { class: f(e.data.assignee_id === null ? "text-subtle" : "") }, S(de(e.data)), 3)]), + "cell-logged": D(({ row: e }) => [o("span", jo, S(fe(e.data)), 1)]), + "cell-unbilled": D(({ row: e }) => [C(Va)(e.data).unbilled_amount > 0 ? (_(), r(y, { + key: 0, + amount: C(Va)(e.data).unbilled_amount + }, null, 8, ["amount"])) : (_(), a("span", Mo, "-"))]), + "cell-invoiced": D(({ row: e }) => [c(Vi, { state: C(Va)(e.data).invoiced }, null, 8, ["state"])]), + "cell-timer": D(({ row: t }) => [c(Oo, { + client: e.client, + notify: e.notify, + task: t.data, + members: e.members + }, null, 8, [ + "client", + "notify", + "task", + "members" + ])]), + "cell-actions": D(({ row: e }) => [c(w, { "content-loading": P.value === e.data.id }, { + activator: D(() => [c(l, { + name: "EllipsisHorizontalIcon", + class: "h-5 text-muted" + })]), + default: D(() => [ + c(b, { onClick: (t) => ce(e.data) }, { + default: D(() => [c(l, { + name: "PencilIcon", + class: "mr-3 h-5 w-5 text-subtle group-hover:text-muted" + }), s(" " + S(C(h)("tasks_projects.general.edit")), 1)]), + _: 1 + }, 8, ["onClick"]), + o("div", { + class: "group flex cursor-not-allowed items-center px-4 py-2 text-sm font-normal text-subtle", + title: C(h)("tasks_projects.tasks.invoice_soon") + }, [c(l, { + name: "BanknotesIcon", + class: "mr-3 h-5 w-5 text-subtle" + }), s(" " + S(C(h)("tasks_projects.tasks.invoice_task")), 1)], 8, No), + c(b, { onClick: (t) => pe(e.data) }, { + default: D(() => [c(l, { + name: "TrashIcon", + class: "mr-3 h-5 w-5 text-subtle group-hover:text-muted" + }), s(" " + S(C(h)("tasks_projects.general.delete")), 1)]), + _: 1 + }, 8, ["onClick"]) + ]), + _: 2 + }, 1032, ["content-loading"])]), + _: 1 + }, 8, ["columns"])], 512), [[T, !I.value]]), + c(Yi, { + show: j.value, + client: e.client, + notify: e.notify, + task: M.value, + statuses: e.statuses, + members: e.members, + projects: F.value, + defaults: N.value, + "lock-project": e.projectId !== null, + compact: M.value === null, + onClose: n[0] ||= (e) => j.value = !1, + onSaved: le, + onDeleted: ue + }, null, 8, [ + "show", + "client", + "notify", + "task", + "statuses", + "members", + "projects", + "defaults", + "lock-project", + "compact" + ]) + ]); + }; } -}; -function bo() {} -bo.prototype = { - startIndex: null, - dragStart: function(e) { - var t = e.oldDraggableIndex; - this.startIndex = t; - }, - onSpill: function(e) { - var t = e.dragEl, n = e.putSortable; - this.sortable.captureAnimationState(), n && n.captureAnimationState(); - var r = Fi(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: yo -}, ci(bo, { pluginName: "revertOnSpill" }); -function xo() {} -xo.prototype = { - onSpill: function(e) { - var t = e.dragEl, n = e.putSortable || this.sortable; - n.captureAnimationState(), t.parentNode && t.parentNode.removeChild(t), n.animateAll(); +}), Ro = { class: "py-4" }, zo = { class: "flex flex-wrap items-end justify-between gap-3" }, Bo = /* @__PURE__ */ l({ + __name: "ProjectTasksTab", + props: { + id: {}, + client: { type: [Function, Object] }, + notify: { type: Function }, + project: {} }, - drop: yo -}, ci(xo, { pluginName: "removeOnSpill" }), $.mount(new ho()), $.mount(xo, bo); -//#endregion -//#region resources/js/api/board.ts -var So = { - 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 Co(e, t) { - let { data: n } = await e.get(So.board, { params: t }); - return n.data; -} -async function wo(e) { - let { data: t } = await e.get(So.taskStatuses); - return t.data; -} -async function To(e, t) { - let { data: n } = await e.get(So.tasks, { params: t }); - return n; -} -async function Eo(e, t) { - let { data: n } = await e.post(So.tasks, t); - return n.data; -} -async function Do(e, t, n) { - let { data: r } = await e.put(So.task(t), n); - return r.data; -} -async function Oo(e, t) { - await e.delete(So.task(t)); -} -async function ko(e, t, n) { - let { data: r } = await e.post(So.moveTask(t), n); - return r.data; -} -async function Ao(e, t) { - let { data: n } = await e.get(M.project(t)); - return n.data; -} -async function jo(e, t) { - let { data: n } = await e.get(So.projectMembers(t)); - return n.data; -} -async function Mo(e, t, n) { - let { data: r } = await e.post(So.projectMembers(t), n); - return r.data; -} -async function No(e, t, n) { - await e.delete(So.projectMember(t, n)); -} -async function Po(e, t) { - let { data: n } = await e.get(So.timeEntries, { params: t }); - return n; -} -//#endregion -//#region resources/js/types/task.ts -var Fo = [ - "LOW", - "NORMAL", - "HIGH", - "URGENT" -], Io = { class: "flex w-full items-center justify-between" }, Lo = { + emits: ["refresh"], + setup(e, { emit: t }) { + let r = e, i = t, l = B(), u = y(null), d = y([]), p = y([]), m = y({ ...pi }), g = n(() => r.project?.id ?? Number(r.id)), v = n(() => r.project === null ? [] : [r.project]); + h(() => void b()); + async function b() { + try { + d.value = await En(r.client); + } catch (e) { + r.notify("error", R(e, l("tasks_projects.task_statuses.load_failed"))); + } + try { + p.value = await Sn(r.client); + } catch { + p.value = []; + } + } + function w() { + i("refresh"); + } + return (t, n) => { + let r = x("BaseIcon"), i = x("BaseButton"); + return _(), a("div", Ro, [o("div", zo, [c(Fi, { + modelValue: m.value, + "onUpdate:modelValue": n[0] ||= (e) => m.value = e, + class: "flex-1", + projects: v.value, + members: p.value, + statuses: d.value, + "lock-project": "" + }, null, 8, [ + "modelValue", + "projects", + "members", + "statuses" + ]), c(i, { + variant: "primary", + onClick: n[1] ||= (e) => u.value?.openCreate() + }, { + left: D((e) => [c(r, { + name: "PlusIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: D(() => [s(" " + S(C(l)("tasks_projects.tasks.new_task")), 1)]), + _: 1 + })]), c(Lo, { + ref_key: "listRef", + ref: u, + client: e.client, + notify: e.notify, + filters: m.value, + statuses: d.value, + members: p.value, + projects: v.value, + "project-id": g.value, + onChanged: w + }, null, 8, [ + "client", + "notify", + "filters", + "statuses", + "members", + "projects", + "project-id" + ])]); + }; + } +}), Vo = { class: "flex w-full items-center justify-between" }, Ho = { class: "space-y-5 px-6 py-6" }, Uo = { key: 0, - class: "ml-2 text-sm font-normal text-muted" -}, Ro = { class: "space-y-5 px-6 py-6" }, zo = { class: "flex items-center justify-between border-t border-line-default px-6 py-4" }, Bo = { key: 1 }, Vo = { class: "flex space-x-3" }, Ho = /* @__PURE__ */ l({ - __name: "TaskDrawer", + class: "rounded-md bg-alert-warning-bg px-3 py-2 text-sm text-alert-warning-text" +}, Wo = { class: "inline-flex overflow-hidden rounded-md border border-line-default" }, Go = ["disabled", "onClick"], Ko = { + key: 1, + class: "text-sm text-muted" +}, qo = { class: "flex items-center justify-between border-t border-line-default px-6 py-4" }, Jo = { key: 1 }, Yo = { class: "flex space-x-3" }, Xo = "09:00", Zo = /* @__PURE__ */ l({ + __name: "TimeEntryModal", props: { show: { type: Boolean }, client: { type: [Function, Object] }, notify: { type: Function }, - task: {}, - statuses: {}, - members: {}, - projects: {}, - defaults: {}, - lockProject: { type: Boolean } + entry: {}, + defaultDate: {}, + defaultTask: {}, + lockTask: { type: Boolean } }, emits: [ "close", "saved", "deleted" ], - setup(e, { emit: t }) { - let l = e, u = t, d = de(), f = _({ - name: "", + setup(t, { emit: l }) { + let u = t, d = l, p = ["duration", "range"], m = B(), h = v({ + date: "", + mode: "duration", + duration: "", + start: Xo, + end: "", description: "", - estimateHours: "", - rate: "", - dueDate: "", billable: !0 - }), 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 = 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 - }))), P = n(() => l.members.map((e) => ({ - id: e.id, - label: e.name - }))), F = n(() => Fo.map((e, t) => ({ - id: t, - label: d(`tasks_projects.tasks.priority.${e.toLowerCase()}`) - }))); - T(() => l.show, (e) => { - e && L(); + }), g = y(null), w = y({}), T = y(!1), O = y(!1), k = n(() => u.entry !== null), j = n(() => u.entry?.invoice_id != null), M = n(() => j.value ? m("tasks_projects.time.view_entry") : k.value ? m("tasks_projects.time.edit_entry") : m("tasks_projects.time.new_entry")); + E(() => u.show, (e) => { + e && N(); }, { immediate: !0 }); - function I(e, t) { - return t === null ? null : e.find((e) => e.id === t) ?? null; - } - function L() { - let e = l.task; - f.name = e?.name ?? "", f.description = e?.description ?? "", f.estimateHours = ie(e?.estimated_minutes ?? null), f.rate = ne(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 = 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[Fo.indexOf(e.priority)] ?? null : null, C.value = e?.customer_id ?? null, w.value = {}; + function N() { + let e = u.entry; + w.value = {}, g.value = e === null ? u.defaultTask ?? null : null, h.date = e ? Ya(e.started_at) : u.defaultDate ?? no(/* @__PURE__ */ new Date()), h.duration = e ? qa(e.duration_minutes) : "", h.start = e?.started_at ? Xa(e.started_at) : Xo, h.end = e?.ended_at ? Xa(e.ended_at) : "", h.description = e?.description ?? "", h.billable = e ? e.billable : g.value?.billable ?? !0, h.mode = e !== null && P(e) ? "range" : "duration", h.date === "" && (h.date = u.defaultDate ?? no(/* @__PURE__ */ new Date())), e !== null && F(e.task_id); } - function R(e) { - f.dueDate = e ? se(e) : ""; + 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; } - function z() { - let e = p.value?.id ?? null; - if (e === null) return null; - let t = y.value === null ? null : Fo[y.value.id]; - return { - name: f.name.trim(), - task_status_id: e, - project_id: m.value, - customer_id: m.value === null ? C.value : null, - description: f.description.trim() || null, - assignee_id: h.value?.id ?? null, - priority: t, - due_date: f.dueDate || null, - estimated_minutes: ae(f.estimateHours), - billable: f.billable, - rate: re(f.rate) - }; + async function F(e) { + try { + let t = await _a(u.client, e); + g.value = t, La(t); + } catch {} } - async function B() { - if (D.value) return; - if (f.name.trim() === "") { - w.value = { name: d("tasks_projects.tasks.name_required") }; - return; - } - let e = z(); - if (e === null) { - l.notify("error", d("tasks_projects.task_statuses.none")); - return; - } - D.value = !0, w.value = {}; + async function ee(e) { try { - let t = l.task, n = t ? await Do(l.client, t.id, e) : await Eo(l.client, e); - u("saved", n); + let t = await ga(u.client, e ?? ""); + return t.forEach(La), t; } catch (e) { - w.value = te(e), l.notify("error", U(e, d("tasks_projects.tasks.save_failed"))); - } finally { - D.value = !1; + return u.notify("error", R(e, m("tasks_projects.time.tasks_failed"))), []; } } - async function V() { - let e = l.task; - if (!(e === null || O.value) && window.confirm(d("tasks_projects.tasks.delete_confirm", { name: e.name }))) { + function I(e) { + h.date = e ? ve(e) : ""; + } + function te(e) { + g.value = e, e !== null && u.entry === null && (h.billable = e.billable !== !1); + } + function L() { + let e = {}, t = g.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 = Za(h.date, h.mode === "range" ? h.start : Xo); + n === null && (e.started_at = m("tasks_projects.time.range_invalid")); + let r = h.mode === "duration" ? Ja(h.duration) : null; + h.mode === "duration" && r === null && (e.duration_minutes = m("tasks_projects.time.duration_invalid")); + let i = h.mode === "range" ? Za(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 = Qa(n, r)) : a.ended_at = i, a; + } + async function ne() { + if (T.value || j.value) return; + let e = L(); + if (e !== null) { + T.value = !0; + try { + let t = u.entry, n = t ? await aa(u.client, t.id, e) : await ia(u.client, e); + d("saved", n); + } catch (e) { + w.value = le(e), u.notify("error", R(e, m("tasks_projects.time.save_failed"))); + } finally { + T.value = !1; + } + } + } + async function re() { + let e = u.entry; + if (!(e === null || O.value || j.value) && window.confirm(m("tasks_projects.time.delete_confirm"))) { O.value = !0; try { - await Oo(l.client, e.id), u("deleted", e); + await oa(u.client, e.id), d("deleted", e); } catch (e) { - l.notify("error", U(e, d("tasks_projects.tasks.delete_failed"))); + u.notify("error", R(e, m("tasks_projects.time.delete_failed"))); } finally { O.value = !1; } } } - 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"), H = b("BaseButton"), ee = b("BaseModal"); - return g(), r(ee, { - show: e.show, - onClose: n[11] ||= (e) => u("close") + return (n, l) => { + let u = x("BaseIcon"), v = x("BaseInput"), y = x("BaseMultiselect"), E = x("BaseInputGroup"), N = x("BaseDatePicker"), P = x("BaseInputGrid"), F = x("BaseTextarea"), L = x("BaseSwitch"), ie = x("BaseButton"), ae = x("BaseModal"); + return _(), r(ae, { + show: t.show, + onClose: l[8] ||= (e) => d("close") }, { - header: E(() => [o("div", Io, [o("span", null, [s(x(M.value) + " ", 1), e.task ? (g(), a("span", Lo, "#" + x(e.task.number), 1)) : i("", !0)]), c(l, { + header: D(() => [o("div", Vo, [o("span", null, S(M.value), 1), c(u, { name: "XMarkIcon", class: "h-6 w-6 cursor-pointer text-subtle hover:text-body", - onClick: n[0] ||= (e) => u("close") + onClick: l[0] ||= (e) => d("close") })])]), - default: E(() => [o("form", { onSubmit: k(B, ["prevent"]) }, [o("div", Ro, [ - c(_, { - label: S(d)("tasks_projects.tasks.fields.name"), - error: w.value.name, + default: D(() => [o("form", { onSubmit: A(ne, ["prevent"]) }, [o("div", Ho, [ + j.value ? (_(), a("p", Uo, S(C(m)("tasks_projects.time.stamped_notice")), 1)) : i("", !0), + c(E, { + label: C(m)("tasks_projects.time.fields.task"), + error: w.value.task_id, required: "" }, { - default: E(() => [c(m, { - modelValue: f.name, - "onUpdate:modelValue": n[1] ||= (e) => f.name = e, - invalid: !!w.value.name, - type: "text" - }, null, 8, ["modelValue", "invalid"])]), + default: D(() => [t.lockTask ? (_(), r(v, { + key: 0, + "model-value": g.value?.name ?? "", + type: "text", + disabled: "" + }, null, 8, ["model-value"])) : (_(), r(y, { + key: 1, + "model-value": g.value, + options: ee, + disabled: j.value, + invalid: !!w.value.task_id, + placeholder: C(m)("tasks_projects.time.fields.task_placeholder"), + "initial-search": g.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) => te(e) + }, null, 8, [ + "model-value", + "disabled", + "invalid", + "placeholder", + "initial-search" + ]))]), _: 1 }, 8, ["label", "error"]), - c(I, null, { - default: E(() => [ - e.lockProject ? i("", !0) : (g(), r(_, { - key: 0, - label: S(d)("tasks_projects.tasks.fields.project"), - error: w.value.project_id, - "help-text": S(d)("tasks_projects.tasks.fields.project_help") - }, { - default: E(() => [c(v, { - modelValue: A.value, - "onUpdate:modelValue": n[2] ||= (e) => A.value = e, - options: e.projects, - placeholder: S(d)("tasks_projects.tasks.fields.project_placeholder"), - "label-key": "label" - }, null, 8, [ - "modelValue", - "options", - "placeholder" - ])]), - _: 1 - }, 8, [ - "label", - "error", - "help-text" - ])), - C.value === null ? i("", !0) : (g(), r(_, { - key: 1, - label: S(d)("tasks_projects.tasks.fields.customer"), - "help-text": S(d)("tasks_projects.tasks.fields.customer_help") - }, { - default: E(() => [c(m, { - "model-value": `#${C.value}`, - type: "text", - disabled: "" - }, null, 8, ["model-value"])]), - _: 1 - }, 8, ["label", "help-text"])), - c(_, { - label: S(d)("tasks_projects.tasks.fields.status"), - error: w.value.task_status_id - }, { - default: E(() => [c(v, { - modelValue: p.value, - "onUpdate:modelValue": n[3] ||= (e) => p.value = e, - options: N.value, - "label-key": "label" - }, null, 8, ["modelValue", "options"])]), - _: 1 - }, 8, ["label", "error"]), - c(_, { - label: S(d)("tasks_projects.tasks.fields.assignee"), - error: w.value.assignee_id - }, { - 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", - "options", - "placeholder" - ])]), - _: 1 - }, 8, ["label", "error"]), - c(_, { - label: S(d)("tasks_projects.tasks.fields.priority"), - error: w.value.priority - }, { - 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", - "options", - "placeholder" - ])]), - _: 1 - }, 8, ["label", "error"]), - c(_, { - label: S(d)("tasks_projects.tasks.fields.due_date"), - error: w.value.due_date - }, { - default: E(() => [c(T, { - "model-value": f.dueDate, - "onUpdate:modelValue": R - }, null, 8, ["model-value"])]), - _: 1 - }, 8, ["label", "error"]), - c(_, { - label: S(d)("tasks_projects.tasks.fields.estimate_hours"), - error: w.value.estimated_minutes - }, { - default: E(() => [c(m, { - modelValue: f.estimateHours, - "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"]), - c(_, { - label: S(d)("tasks_projects.tasks.fields.rate"), - error: w.value.rate, - "help-text": S(d)("tasks_projects.tasks.fields.rate_help") - }, { - default: E(() => [c(m, { - modelValue: f.rate, - "onUpdate:modelValue": n[7] ||= (e) => f.rate = e, - invalid: !!w.value.rate, - type: "number", - step: "0.01", - min: "0" - }, null, 8, ["modelValue", "invalid"])]), - _: 1 - }, 8, [ - "label", - "error", - "help-text" - ]) - ]), + c(P, null, { + default: D(() => [c(E, { + label: C(m)("tasks_projects.time.fields.date"), + error: w.value.date, + required: "" + }, { + default: D(() => [c(N, { + "model-value": h.date, + disabled: j.value, + invalid: !!w.value.date, + "onUpdate:modelValue": I + }, null, 8, [ + "model-value", + "disabled", + "invalid" + ])]), + _: 1 + }, 8, ["label", "error"]), c(E, { label: C(m)("tasks_projects.time.fields.mode") }, { + default: D(() => [o("div", Wo, [(_(), a(e, null, b(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 + }, S(C(m)(`tasks_projects.time.mode.${e}`)), 11, Go)), 64))])]), + _: 1 + }, 8, ["label"])]), _: 1 }), - c(_, { - label: S(d)("tasks_projects.tasks.fields.billable"), - error: w.value.billable + h.mode === "duration" ? (_(), r(E, { + key: 1, + label: C(m)("tasks_projects.time.fields.duration"), + error: w.value.duration_minutes, + "help-text": C(m)("tasks_projects.time.fields.duration_help"), + required: "" }, { - default: E(() => [c(L, { - modelValue: f.billable, - "onUpdate:modelValue": n[8] ||= (e) => f.billable = e, - class: "mt-1" - }, null, 8, ["modelValue"])]), + default: D(() => [c(v, { + 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"]), - c(_, { - label: S(d)("tasks_projects.tasks.fields.description"), + }, 8, [ + "label", + "error", + "help-text" + ])) : (_(), r(P, { key: 2 }, { + default: D(() => [c(E, { + label: C(m)("tasks_projects.time.fields.start"), + error: w.value.started_at, + required: "" + }, { + default: D(() => [c(v, { + 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(E, { + label: C(m)("tasks_projects.time.fields.end"), + error: w.value.ended_at, + required: "" + }, { + default: D(() => [c(v, { + 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(E, { + label: C(m)("tasks_projects.time.fields.description"), error: w.value.description }, { - default: E(() => [c(z, { - modelValue: f.description, - "onUpdate:modelValue": n[9] ||= (e) => f.description = e, + default: D(() => [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", "invalid"])]), + }, null, 8, [ + "modelValue", + "disabled", + "invalid" + ])]), + _: 1 + }, 8, ["label", "error"]), + c(E, { + label: C(m)("tasks_projects.time.fields.billable"), + error: w.value.billable + }, { + default: D(() => [j.value ? (_(), a("span", Ko, S(h.billable ? C(m)("tasks_projects.time.billable") : C(m)("tasks_projects.time.non_billable")), 1)) : (_(), r(L, { + key: 0, + modelValue: h.billable, + "onUpdate:modelValue": l[6] ||= (e) => h.billable = e, + class: "flex" + }, null, 8, ["modelValue"]))]), _: 1 }, 8, ["label", "error"]) - ]), o("div", zo, [j.value ? (g(), r(H, { + ]), o("div", qo, [k.value && !j.value ? (_(), r(ie, { key: 0, type: "button", variant: "danger", + size: "sm", loading: O.value, disabled: O.value, - onClick: V + onClick: re }, { - default: E(() => [s(x(S(d)("tasks_projects.general.delete")), 1)]), + default: D(() => [s(S(C(m)("tasks_projects.general.delete")), 1)]), _: 1 - }, 8, ["loading", "disabled"])) : (g(), a("span", Bo)), o("div", Vo, [c(H, { + }, 8, ["loading", "disabled"])) : (_(), a("span", Jo)), o("div", Yo, [c(ie, { type: "button", variant: "primary-outline", - onClick: n[10] ||= (e) => u("close") + onClick: l[7] ||= (e) => d("close") }, { - default: E(() => [s(x(S(d)("tasks_projects.general.cancel")), 1)]), + default: D(() => [s(S(j.value ? C(m)("tasks_projects.timer.close") : C(m)("tasks_projects.general.cancel")), 1)]), _: 1 - }), c(H, { + }), j.value ? i("", !0) : (_(), r(ie, { + key: 0, type: "submit", variant: "primary", - loading: D.value, - disabled: D.value + loading: T.value, + disabled: T.value }, { - default: E(() => [s(x(j.value ? S(d)("tasks_projects.general.update") : S(d)("tasks_projects.general.save")), 1)]), + default: D(() => [s(S(k.value ? C(m)("tasks_projects.general.update") : C(m)("tasks_projects.general.save")), 1)]), _: 1 - }, 8, ["loading", "disabled"])])])], 32)]), + }, 8, ["loading", "disabled"]))])])], 32)]), + _: 1 + }, 8, ["show"]); + }; + } +}), Qo = { class: "py-4" }, $o = { class: "flex justify-end" }, es = { class: "relative mt-3 table-container" }, ts = { class: "font-medium text-heading" }, ns = { + key: 0, + class: "block text-xs text-muted" +}, rs = { + key: 1, + class: "text-subtle" +}, is = { + key: 0, + class: "text-xs text-primary-500" +}, as = 15, os = 100, ss = /* @__PURE__ */ l({ + __name: "ProjectTimeTab", + props: { + id: {}, + client: { type: [Function, Object] }, + notify: { type: Function }, + project: {} + }, + emits: ["refresh"], + setup(e, { emit: t }) { + let l = e, u = t, d = B(), p = _o(), m = y(null), g = y([]), v = y([]), b = y(!1), w = y(null), T = n(() => l.project?.id ?? Number(l.id)), O = n(() => [ + { + key: "started_at", + label: d("tasks_projects.project.time.columns.date"), + sortable: !1 + }, + { + key: "user", + label: d("tasks_projects.project.time.columns.member"), + sortable: !1 + }, + { + key: "task", + label: d("tasks_projects.project.time.columns.task"), + sortable: !1, + thClass: "extra" + }, + { + key: "duration_minutes", + label: d("tasks_projects.project.time.columns.minutes"), + sortable: !1 + }, + { + key: "billable", + label: d("tasks_projects.project.time.columns.billable"), + sortable: !1 + }, + { + key: "amount", + label: d("tasks_projects.project.time.columns.amount"), + sortable: !1 + }, + { + key: "actions", + label: d("tasks_projects.general.actions"), + sortable: !1, + tdClass: "text-right text-sm font-medium" + } + ]); + h(() => { + k(); + }), E(za, () => m.value?.refresh(!0)); + async function k() { + try { + g.value = await Sn(l.client); + } catch (e) { + l.notify("error", R(e, d("tasks_projects.tasks.members_failed"))); + } + try { + let e = await Dn(l.client, { + project_id: T.value, + limit: os + }); + v.value = e.data ?? []; + } catch (e) { + l.notify("error", R(e, d("tasks_projects.tasks.load_failed"))); + } + } + async function A({ page: e }) { + let t = { + page: e, + limit: as, + project_id: T.value + }; + try { + let e = await Vn(l.client, t); + return { + data: e.data ?? [], + pagination: { + totalPages: e.meta?.last_page ?? 1, + currentPage: e.meta?.current_page ?? 1, + totalCount: e.meta?.total ?? 0, + limit: e.meta?.per_page ?? as + } + }; + } catch (e) { + return l.notify("error", R(e, d("tasks_projects.project.time.load_failed"))), { + data: [], + pagination: { + totalPages: 1, + currentPage: 1, + totalCount: 0, + limit: as + } + }; + } + } + function j(e) { + return g.value.find((t) => t.id === e)?.name ?? d("tasks_projects.project.time.removed_member"); + } + function M(e) { + return v.value.find((t) => t.id === e)?.name ?? `#${e}`; + } + function N(e) { + return e.is_running ? Ka(ao(e.started_at, p.value)) : ye(e.duration_minutes); + } + function P() { + w.value = null, b.value = !0; + } + function F(e) { + e.is_running || (w.value = e, b.value = !0); + } + function ee() { + let e = w.value ? d("tasks_projects.time.updated") : d("tasks_projects.time.created"); + b.value = !1, w.value = null, l.notify("success", e), m.value?.refresh(!0), U(), u("refresh"); + } + function I() { + b.value = !1, w.value = null, l.notify("success", d("tasks_projects.time.deleted")), m.value?.refresh(!0), U(), u("refresh"); + } + async function te(e) { + if (window.confirm(d("tasks_projects.time.delete_confirm"))) try { + await oa(l.client, e.id), l.notify("success", d("tasks_projects.time.deleted")), m.value?.refresh(!0), U(), u("refresh"); + } catch (e) { + l.notify("error", R(e, d("tasks_projects.time.delete_failed"))); + } + } + return (t, n) => { + let l = x("BaseIcon"), u = x("BaseButton"), p = x("BaseFormatMoney"), h = x("BaseDropdownItem"), g = x("BaseDropdown"), v = x("BaseTable"); + return _(), a("div", Qo, [ + o("div", $o, [c(u, { + variant: "primary", + onClick: P + }, { + left: D((e) => [c(l, { + name: "PlusIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: D(() => [s(" " + S(C(d)("tasks_projects.project.time.add_entry")), 1)]), + _: 1 + })]), + o("div", es, [c(v, { + ref_key: "tableRef", + ref: m, + data: A, + columns: O.value + }, { + "cell-started_at": D(({ row: e }) => [s(S(e.data.started_at ? C(_e)(e.data.started_at) : "-"), 1)]), + "cell-user": D(({ row: e }) => [s(S(j(e.data.user_id)), 1)]), + "cell-task": D(({ row: e }) => [o("span", ts, S(M(e.data.task_id)), 1), e.data.description ? (_(), a("span", ns, S(e.data.description), 1)) : i("", !0)]), + "cell-duration_minutes": D(({ row: e }) => [o("span", { class: f(["tabular-nums", e.data.is_running ? "font-medium text-primary-500" : ""]) }, S(N(e.data)), 3)]), + "cell-billable": D(({ row: e }) => [e.data.billable ? (_(), r(l, { + key: 0, + name: "CheckCircleIcon", + class: "h-5 w-5 text-status-green" + })) : (_(), a("span", rs, "-"))]), + "cell-amount": D(({ row: e }) => [c(p, { amount: e.data.amount }, null, 8, ["amount"])]), + "cell-actions": D(({ row: e }) => [e.data.is_running ? (_(), a("span", is, S(C(d)("tasks_projects.project.time.running")), 1)) : (_(), r(g, { key: 1 }, { + activator: D(() => [c(l, { + name: "EllipsisHorizontalIcon", + class: "h-5 text-muted" + })]), + default: D(() => [c(h, { onClick: (t) => F(e.data) }, { + default: D(() => [c(l, { + name: "PencilIcon", + class: "mr-3 h-5 w-5 text-subtle group-hover:text-muted" + }), s(" " + S(C(d)("tasks_projects.general.edit")), 1)]), + _: 1 + }, 8, ["onClick"]), e.data.invoice_id === null ? (_(), r(h, { + key: 0, + onClick: (t) => te(e.data) + }, { + default: D(() => [c(l, { + name: "TrashIcon", + class: "mr-3 h-5 w-5 text-subtle group-hover:text-muted" + }), s(" " + S(C(d)("tasks_projects.general.delete")), 1)]), + _: 1 + }, 8, ["onClick"])) : i("", !0)]), + _: 2 + }, 1024))]), + _: 1 + }, 8, ["columns"])]), + c(Zo, { + show: b.value, + client: e.client, + notify: e.notify, + entry: w.value, + onClose: n[0] ||= (e) => b.value = !1, + onSaved: ee, + onDeleted: I + }, null, 8, [ + "show", + "client", + "notify", + "entry" + ]) + ]); + }; + } +}), cs = { + viewProject: `${V}:view-project`, + editProject: `${V}:edit-project`, + viewTask: `${V}:view-task`, + viewOwnTime: `${V}:view-own-time` +}; +function ls(e) { + e.addMessages(dn), e.registerPage({ + id: "projects", + module: V, + path: "projects", + component: we(e, Tr), + meta: { + ability: cs.viewProject, + title: "tasks_projects.projects.title" + } + }), e.registerPage({ + id: "project", + module: V, + path: "projects/:id", + component: we(e, pr), + meta: { + ability: cs.viewProject, + title: "tasks_projects.projects.title" + }, + children: [ + { + id: "overview", + path: "", + component: we(e, fi), + meta: { + ability: cs.viewProject, + title: "tasks_projects.project.tabs.overview" + } + }, + { + id: "tasks", + path: "tasks", + component: we(e, Bo), + meta: { + ability: cs.viewTask, + title: "tasks_projects.project.tabs.tasks" + } + }, + { + id: "time", + path: "time", + component: we(e, ss), + meta: { + ability: cs.viewOwnTime, + title: "tasks_projects.project.tabs.time" + } + }, + { + id: "members", + path: "members", + component: we(e, Lr), + meta: { + ability: cs.editProject, + title: "tasks_projects.project.tabs.members" + } + } + ] + }); +} +//#endregion +//#region resources/js/messages/reports.ts +var us = { en: { tasks_projects: { reports: { + title: "Reports", + load_failed: "Unable to load the report.", + empty_title: "Nothing logged in this range", + empty_description: "Pick a wider range, or log some time against a task.", + range: { + this_week: "This week", + this_month: "This month", + last_month: "Last month", + this_quarter: "This quarter", + this_year: "This year", + custom: "Custom", + from: "From", + to: "To" + }, + summary: { + logged: "Logged", + billable: "Billable", + amount: "Amount", + unbilled: "Unbilled", + currency: "Currency #{id}", + base_currency: "Company currency" + }, + split: { + title: "Billable against the rest", + billable: "Billable", + non_billable: "Not billable", + nothing: "No time logged in this range." + }, + tables: { + by_project: "By project", + by_member: "By member", + by_customer: "By customer", + project: "Project", + member: "Member", + customer: "Customer", + no_project: "No project", + no_customer: "Internal", + unknown_member: "Removed member", + currency: "Currency", + logged: "Logged", + billable: "Billable", + amount: "Amount", + unbilled: "Unbilled" + } +} } } }, ds = { summary: `${fn}/reports/summary` }; +async function fs(e, t) { + let { data: n } = await e.get(ds.summary, { params: t }); + return ps(n?.data, t); +} +function ps(e, t) { + let n = gs(e) ? e : {}; + return { + from: ys(n.from, t.from ?? ""), + to: ys(n.to, t.to ?? ""), + totals: hs(n.totals).map(ms), + by_project: hs(n.by_project).map((e) => ({ + ...ms(e), + project_id: vs(e.project_id), + label: ys(e.label, "") + })), + by_member: hs(n.by_member).map((e) => ({ + ...ms(e), + user_id: vs(e.user_id), + label: ys(e.label, "") + })), + by_customer: hs(n.by_customer).map((e) => ({ + ...ms(e), + customer_id: vs(e.customer_id) + })), + by_billable: hs(n.by_billable).map((e) => ({ + ...ms(e), + billable: e.billable === !0 + })) + }; +} +function ms(e) { + return { + currency_id: vs(e.currency_id), + minutes: _s(e.minutes), + amount: _s(e.amount), + billable_minutes: _s(e.billable_minutes), + billable_amount: _s(e.billable_amount), + unbilled_amount: _s(e.unbilled_amount) + }; +} +function hs(e) { + return Array.isArray(e) ? e.filter(gs) : []; +} +function gs(e) { + return typeof e == "object" && !!e; +} +function _s(e) { + return typeof e == "number" && Number.isFinite(e) ? e : 0; +} +function vs(e) { + return typeof e == "number" && Number.isFinite(e) ? e : null; +} +function ys(e, t) { + return typeof e == "string" && e.trim() !== "" ? e : t; +} +//#endregion +//#region resources/js/components/ReportBreakdownTable.vue?vue&type=script&setup=true&lang.ts +var bs = { class: "mt-6" }, xs = { class: "text-sm font-semibold tracking-wider text-muted uppercase" }, Ss = { class: "relative table-container" }, Cs = { + key: 0, + class: "text-subtle" +}, ws = { key: 1 }, Ts = /* @__PURE__ */ l({ + __name: "ReportBreakdownTable", + props: { + title: {}, + labelHeading: {}, + rows: {}, + showCurrency: { type: Boolean } + }, + setup(e) { + let t = e, i = B(), l = n(() => [ + { + key: "label", + label: t.labelHeading, + thClass: "extra", + tdClass: "font-medium text-heading" + }, + ...t.showCurrency ? [{ + key: "currency_id", + label: i("tasks_projects.reports.tables.currency") + }] : [], + { + key: "minutes", + label: i("tasks_projects.reports.tables.logged"), + dataType: "numeric" + }, + { + key: "billable_minutes", + label: i("tasks_projects.reports.tables.billable"), + dataType: "numeric" + }, + { + key: "amount", + label: i("tasks_projects.reports.tables.amount"), + dataType: "numeric" + }, + { + key: "unbilled_amount", + label: i("tasks_projects.reports.tables.unbilled"), + dataType: "numeric" + } + ]), u = n(() => t.showCurrency ? "currency" : "plain"); + return (t, n) => { + let i = x("BaseFormatMoney"), d = x("BaseTable"); + return _(), a("section", bs, [o("h3", xs, S(e.title), 1), o("div", Ss, [(_(), r(d, { + key: u.value, + data: e.rows, + columns: l.value, + class: "mt-2" + }, { + "cell-currency_id": D(({ row: e }) => [e.data.currency_id === null ? (_(), a("span", Cs, "-")) : (_(), a("span", ws, "#" + S(e.data.currency_id), 1))]), + "cell-minutes": D(({ row: e }) => [s(S(C(qa)(e.data.minutes)), 1)]), + "cell-billable_minutes": D(({ row: e }) => [s(S(C(qa)(e.data.billable_minutes)), 1)]), + "cell-amount": D(({ row: e }) => [c(i, { amount: e.data.amount }, null, 8, ["amount"])]), + "cell-unbilled_amount": D(({ row: e }) => [c(i, { amount: e.data.unbilled_amount }, null, 8, ["amount"])]), _: 1 - }, 8, ["show"]); + }, 8, ["data", "columns"]))])]); }; } -}), Uo = { class: "flex items-center justify-end space-x-5" }, Wo = { +}), Es = 3; +function Ds(e, t, n = /* @__PURE__ */ new Date()) { + let r = n.getFullYear(), i = n.getMonth(); + switch (e) { + case "THIS_WEEK": { + let e = $a(n, t); + return ks(e, to(e, 6)); + } + case "LAST_MONTH": return ks(new Date(r, i - 1, 1), new Date(r, i, 0)); + case "THIS_QUARTER": { + let e = Math.floor(i / Es) * Es; + return ks(new Date(r, e, 1), new Date(r, e + Es, 0)); + } + case "THIS_YEAR": return ks(new Date(r, 0, 1), new Date(r, 12, 0)); + default: return ks(new Date(r, i, 1), new Date(r, i + 1, 0)); + } +} +function Os(e, t) { + return t <= 0 ? 0 : Math.min(100, Math.max(0, Math.round(e / t * 100))); +} +function ks(e, t) { + return { + from: no(e), + to: no(t) + }; +} +//#endregion +//#region resources/js/pages/ReportsPage.vue?vue&type=script&setup=true&lang.ts +var As = { + key: 0, + class: "mt-2 text-sm text-muted" +}, js = { class: "flex items-center justify-end space-x-5" }, Ms = { class: "mt-4 flex flex-wrap gap-2" }, Ns = ["onClick"], Ps = { key: 0, class: "flex justify-center py-16" -}, Go = { - key: 2, - class: "flex items-start gap-4 overflow-x-auto pb-4" -}, Ko = { class: "flex items-center justify-between border-b border-line-light px-3 py-2.5" }, qo = { class: "flex items-center" }, Jo = { class: "text-sm font-semibold text-heading" }, Yo = { class: "ml-2 text-xs text-muted" }, Xo = [ - "aria-label", - "title", - "onClick" -], Zo = ["data-status-id"], Qo = ["data-task-id", "onClick"], $o = { class: "flex items-start justify-between gap-2" }, es = { class: "text-sm font-medium text-heading" }, ts = { class: "mt-2 flex flex-wrap items-center gap-2 text-xs text-muted" }, ns = ["title"], rs = { - key: 1, - class: "inline-flex items-center text-body" -}, is = { class: "mt-3 flex items-center justify-between" }, as = { - key: 1, - class: "text-xs text-subtle" -}, os = ["title"], ss = { +}, Fs = { key: 0, - class: "px-3 pt-2 text-xs text-subtle" -}, cs = { class: "px-3 pt-2 pb-3" }, ls = ["onClick"], us = /* @__PURE__ */ l({ - __name: "BoardPage", + class: "text-xs font-medium tracking-wider text-muted uppercase" +}, Is = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, Ls = { class: "mt-1 text-2xl font-semibold text-heading" }, Rs = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, zs = { class: "mt-1 text-2xl font-semibold text-heading" }, Bs = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, Vs = { class: "mt-1 text-2xl font-semibold text-heading" }, Hs = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, Us = { class: "mt-1 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-3 flex h-2 w-full overflow-hidden rounded-full bg-surface-tertiary" }, qs = { class: "mt-3 flex flex-wrap gap-6 text-sm" }, Js = { class: "inline-flex items-center text-body" }, Ys = { class: "ml-1 font-medium text-heading" }, Xs = { class: "ml-1 text-muted" }, Zs = { class: "inline-flex items-center text-body" }, Qs = { class: "ml-1 font-medium text-heading" }, $s = { class: "ml-1 text-muted" }, ec = { + key: 1, + class: "mt-2 text-sm text-subtle" +}, tc = "THIS_MONTH", nc = /* @__PURE__ */ l({ + __name: "ReportsPage", props: { client: { type: [Function, Object] }, - notify: { type: Function } + notify: { type: Function }, + router: {} }, setup(t) { - let l = t, u = de(), 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(), I = !1, L = n(() => C.value.map((e) => ({ - id: e.id, - label: e.name - }))), R = n(() => _.value.map((e) => ({ - id: e.id, - label: e.name - }))), z = n(() => d.value.map((e) => e.status)), B = n(() => !w.value && d.value.length === 0); - T([D, O], () => { - ee(); - }), h(() => { - H(), ee(); - }), m(() => { - for (let e of N.values()) e.destroy(); - N.clear(), P.clear(); - }); - async function H() { - try { - let e = await F(l.client, { - limit: 100, - status: "ACTIVE", - sort_by: "name" - }); - C.value = e.data; - } catch (e) { - l.notify("error", U(e, u("tasks_projects.tasks.projects_failed"))); + let l = t, u = B(), d = y(null), m = y([]), g = y(!0), v = y(tc), w = y(""), T = y(""), E = n(() => [ + { + id: "THIS_WEEK", + label: u("tasks_projects.reports.range.this_week") + }, + { + id: "THIS_MONTH", + label: u("tasks_projects.reports.range.this_month") + }, + { + id: "LAST_MONTH", + label: u("tasks_projects.reports.range.last_month") + }, + { + id: "THIS_QUARTER", + label: u("tasks_projects.reports.range.this_quarter") + }, + { + id: "THIS_YEAR", + label: u("tasks_projects.reports.range.this_year") + }, + { + id: "CUSTOM", + label: u("tasks_projects.reports.range.custom") } - try { - _.value = await V(l.client); - } catch (e) { - l.notify("error", U(e, u("tasks_projects.tasks.members_failed"))); + ]), O = n(() => d.value?.totals ?? []), k = n(() => new Set(O.value.map((e) => e.currency_id)).size > 1), A = n(() => O.value.length > 0), j = n(() => (d.value?.by_project ?? []).map((e) => ({ + ...e, + id: `${e.project_id ?? "none"}-${e.currency_id ?? "base"}`, + label: e.project_id === null ? u("tasks_projects.reports.tables.no_project") : e.label || `#${e.project_id}` + }))), M = n(() => (d.value?.by_member ?? []).map((e) => ({ + ...e, + id: `${e.user_id ?? "none"}-${e.currency_id ?? "base"}`, + label: oe(e) + }))), N = n(() => (d.value?.by_customer ?? []).map((e) => ({ + ...e, + id: `${e.customer_id ?? "none"}-${e.currency_id ?? "base"}`, + label: e.customer_id === null ? u("tasks_projects.reports.tables.no_customer") : Zn(e.customer_id) + }))), P = n(() => se(!0)), F = n(() => se(!1)), ee = n(() => P.value + F.value), I = n(() => Os(P.value, ee.value)); + h(() => { + te(tc), ae(); + }); + function te(e) { + if (v.value = e, e !== "CUSTOM") { + let t = Ds(e, xa.settings.week_start); + w.value = t.from, T.value = t.to; } + re(); } - async function ee() { - let e = {}; - D.value && (e.project_id = D.value.id), O.value && (e.assignee_id = O.value.id), w.value = !0; + function L(e) { + w.value = e ? ve(e) : "", v.value = "CUSTOM", re(); + } + function ne(e) { + T.value = e ? ve(e) : "", v.value = "CUSTOM", re(); + } + async function re() { + g.value = !0; try { - d.value = await Co(l.client, e), d.value.some((e) => e.tasks.some((e) => e.customer_id !== null)) && Se(l.client); + let e = await fs(l.client, ie()); + d.value = e, e.by_customer.some((e) => e.customer_id !== null) && Qn(l.client); } catch (e) { - l.notify("error", U(e, u("tasks_projects.board.load_failed"))); + d.value = null, l.notify("error", R(e, u("tasks_projects.reports.load_failed"))); } finally { - w.value = !1; + g.value = !1; } } - function te(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, $.create(n, { - group: "tasks", - animation: 150, - draggable: "[data-task-id]", - ghostClass: "opacity-40", - onStart: () => { - I = !0; - }, - onEnd: (e) => { - ie(e), setTimeout(() => { - I = !1; - }); - } - })))); - } - function ne(e) { - let t = e.item, n = e.oldIndex ?? 0; - t.parentNode?.removeChild(t), e.from.insertBefore(t, e.from.children[n] ?? null); - } - function re(e) { - return d.value.find((t) => t.status.id === e); + function ie() { + let e = {}; + return w.value !== "" && (e.from = w.value), T.value !== "" && (e.to = T.value), e; } - async function ie(e) { - let t = Number(e.from.dataset.statusId), n = Number(e.to.dataset.statusId), r = e.oldIndex ?? 0, i = e.newIndex ?? 0; - if (ne(e), Number.isNaN(t) || Number.isNaN(n) || t === n && r === i) return; - let a = re(t), o = re(n); - if (!a || !o) return; - let s = { - from: [...a.tasks], - to: [...o.tasks] - }, [c] = a.tasks.splice(r, 1); - if (!c) { - a.tasks = s.from; - return; - } - o.tasks.splice(i, 0, c); - let d = o.tasks[i - 1] ?? null, f = o.tasks[i + 1] ?? null; + async function ae() { try { - let e = await ko(l.client, c.id, { - task_status_id: n, - before_id: d?.id ?? null, - after_id: f?.id ?? null - }); - Object.assign(c, e), t !== n && l.notify("success", u("tasks_projects.board.moved", { - name: c.name, - status: o.status.name - })); - } catch (e) { - a.tasks = s.from, o.tasks = s.to, l.notify("error", U(e, u("tasks_projects.board.move_failed"))); - } + m.value = await Sn(l.client); + } catch {} } - function ae(e) { - A.value = null, j.value = { - task_status_id: e.id, - project_id: D.value?.id ?? null - }, k.value = !0; + function oe(e) { + let t = m.value.find((t) => t.id === e.user_id)?.name ?? ""; + return t === "" ? e.label === "" ? e.user_id === null ? u("tasks_projects.reports.tables.unknown_member") : `#${e.user_id}` : e.label : t; } function se(e) { - I || (A.value = e, j.value = {}, k.value = !0); + return (d.value?.by_billable ?? []).filter((t) => t.billable === e).reduce((e, t) => e + t.minutes, 0); } function ce(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), ee(); - } - function fe(e) { - k.value = !1, A.value = null, l.notify("success", u("tasks_projects.tasks.deleted", { name: e.name })), ee(); - } - function pe() { - D.value = null, O.value = null; - } - function me(e) { - if (e.project_id === null) return null; - let t = C.value.find((t) => t.id === e.project_id); - return t?.identifier || t?.name || null; - } - function he(e) { - return [C.value.find((t) => t.id === e.project_id)?.name, xe(e.customer_id)].filter(Boolean).join(" · "); - } - function ge(e) { - if (e.assignee_id === null) return null; - let t = _.value.find((t) => t.id === e.assignee_id); - return t ? le(t.name) : `#${e.assignee_id}`; - } - function _e(e) { - return e.assignee_id === null ? u("tasks_projects.tasks.unassigned") : _.value.find((t) => t.id === e.assignee_id)?.name ?? `#${e.assignee_id}`; + return e === null ? u("tasks_projects.reports.summary.base_currency") : u("tasks_projects.reports.summary.currency", { id: e }); } - function ve(e) { - return u(`tasks_projects.tasks.priority.${e.toLowerCase()}`); + function le(e) { + return v.value === e.id ? "border-primary-500 bg-primary-50 text-primary-500" : "border-line-default bg-surface text-muted hover:text-heading"; } - function ye(e) { - return M[e]; + function ue() { + te(tc); } - 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"), V = b("BaseEmptyPlaceholder"), H = b("BasePage"); - return g(), r(H, null, { - default: E(() => [ - c(M, { title: S(u)("tasks_projects.board.title") }, { - actions: E(() => [o("div", Uo, [c(T, { to: "/admin/modules/tasks-projects" }, { - default: E(() => [c(C, { variant: "white" }, { - left: E((e) => [c(v, { + return (t, n) => { + let l = x("BaseBreadcrumbItem"), m = x("BaseBreadcrumb"), h = x("BaseIcon"), v = x("BaseButton"), y = x("router-link"), re = x("BasePageHeader"), ie = x("BaseDatePicker"), ae = x("BaseInputGroup"), oe = x("BaseFilterWrapper"), se = x("BaseSpinner"), R = x("BaseEmptyPlaceholder"), z = x("BaseFormatMoney"), de = x("BasePage"); + return _(), r(de, null, { + default: D(() => [ + c(re, { title: C(u)("tasks_projects.reports.title") }, { + actions: D(() => [o("div", js, [c(y, { to: C(H).tasks }, { + default: D(() => [c(v, { variant: "white" }, { + left: D((e) => [c(h, { + name: "ClipboardDocumentListIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: D(() => [s(" " + S(C(u)("tasks_projects.tasks.title")), 1)]), + _: 1 + })]), + _: 1 + }, 8, ["to"]), c(y, { to: C(H).projects }, { + default: D(() => [c(v, { variant: "white" }, { + left: D((e) => [c(h, { name: "FolderIcon", class: f(e.class) }, null, 8, ["class"])]), - default: E(() => [s(" " + x(S(u)("tasks_projects.projects.title")), 1)]), + default: D(() => [s(" " + S(C(u)("tasks_projects.projects.title")), 1)]), _: 1 })]), _: 1 - })])]), - default: E(() => [c(h, null, { - default: E(() => [ - c(m, { - title: S(u)("tasks_projects.general.home"), + }, 8, ["to"])])]), + default: D(() => [c(m, null, { + default: D(() => [ + c(l, { + title: C(u)("tasks_projects.general.home"), to: "/admin/dashboard" }, null, 8, ["title"]), - c(m, { - title: S(u)("tasks_projects.projects.title"), - to: "/admin/modules/tasks-projects" - }, null, 8, ["title"]), - c(m, { - title: S(u)("tasks_projects.board.title"), + c(l, { + title: C(u)("tasks_projects.tasks.title"), + to: C(H).tasks + }, null, 8, ["title", "to"]), + c(l, { + title: C(u)("tasks_projects.reports.title"), to: "#", active: "" }, null, 8, ["title"]) ]), _: 1 - })]), + }), d.value ? (_(), a("p", As, S(C(_e)(d.value.from)) + " – " + S(C(_e)(d.value.to)), 1)) : i("", !0)]), _: 1 }, 8, ["title"]), - c(F, { + o("div", Ms, [(_(!0), a(e, null, b(E.value, (e) => (_(), a("button", { + key: e.id, + type: "button", + class: f(["rounded-md border px-3 py-1.5 text-sm font-medium", le(e)]), + onClick: (t) => te(e.id) + }, S(e.label), 11, Ns))), 128))]), + c(oe, { show: !0, - class: "mt-4", - onClear: pe + "row-on-xl": "", + class: "mt-3", + onClear: ue }, { - default: E(() => [c(P, { - label: S(u)("tasks_projects.board.filters.project"), + default: D(() => [c(ae, { + label: C(u)("tasks_projects.reports.range.from"), class: "mt-2 flex-1" }, { - default: E(() => [c(N, { - modelValue: D.value, - "onUpdate:modelValue": l[0] ||= (e) => D.value = e, - options: L.value, - placeholder: S(u)("tasks_projects.board.filters.all_projects"), - "label-key": "label" - }, null, 8, [ - "modelValue", - "options", - "placeholder" - ])]), + default: D(() => [c(ie, { + "model-value": w.value, + "onUpdate:modelValue": L + }, null, 8, ["model-value"])]), _: 1 - }, 8, ["label"]), c(P, { - label: S(u)("tasks_projects.board.filters.assignee"), + }, 8, ["label"]), c(ae, { + label: C(u)("tasks_projects.reports.range.to"), class: "mt-2 flex-1" }, { - default: E(() => [c(N, { - modelValue: O.value, - "onUpdate:modelValue": l[1] ||= (e) => O.value = e, - options: R.value, - placeholder: S(u)("tasks_projects.board.filters.all_assignees"), - "label-key": "label" - }, null, 8, [ - "modelValue", - "options", - "placeholder" - ])]), + default: D(() => [c(ie, { + "model-value": T.value, + "onUpdate:modelValue": ne + }, null, 8, ["model-value"])]), _: 1 }, 8, ["label"])]), _: 1 }), - w.value && d.value.length === 0 ? (g(), a("div", Wo, [c(I, { class: "h-8 w-8 text-primary-500" })])) : B.value ? (g(), r(V, { + g.value && d.value === null ? (_(), a("div", Ps, [c(se, { class: "h-8 w-8 text-primary-500" })])) : A.value ? (_(), a(e, { key: 2 }, [ + (_(!0), a(e, null, b(O.value, (e) => (_(), a("div", { + key: e.currency_id ?? "base", + class: "mt-4 rounded-xl border border-line-default bg-surface p-5" + }, [k.value ? (_(), a("p", Fs, S(ce(e.currency_id)), 1)) : i("", !0), o("div", { class: f(["grid grid-cols-2 gap-4 sm:grid-cols-4", k.value ? "mt-3" : ""]) }, [ + o("div", null, [o("p", Is, S(C(u)("tasks_projects.reports.summary.logged")), 1), o("p", Ls, S(C(qa)(e.minutes)), 1)]), + o("div", null, [o("p", Rs, S(C(u)("tasks_projects.reports.summary.billable")), 1), o("p", zs, S(C(qa)(e.billable_minutes)), 1)]), + o("div", null, [o("p", Bs, S(C(u)("tasks_projects.reports.summary.amount")), 1), o("p", Vs, [c(z, { amount: e.amount }, null, 8, ["amount"])])]), + o("div", null, [o("p", Hs, S(C(u)("tasks_projects.reports.summary.unbilled")), 1), o("p", Us, [c(z, { amount: e.unbilled_amount }, null, 8, ["amount"])])]) + ], 2)]))), 128)), + o("section", Ws, [o("p", Gs, S(C(u)("tasks_projects.reports.split.title")), 1), ee.value > 0 ? (_(), a(e, { key: 0 }, [o("div", Ks, [o("div", { + class: "h-2 bg-primary-500", + style: p({ width: `${I.value}%` }) + }, null, 4)]), o("div", qs, [o("span", Js, [ + n[0] ||= o("span", { class: "mr-2 inline-block h-2.5 w-2.5 rounded-full bg-primary-500" }, null, -1), + s(" " + S(C(u)("tasks_projects.reports.split.billable")) + ": ", 1), + o("span", Ys, S(C(qa)(P.value)), 1), + o("span", Xs, "(" + S(I.value) + "%)", 1) + ]), o("span", Zs, [ + n[1] ||= o("span", { class: "mr-2 inline-block h-2.5 w-2.5 rounded-full bg-surface-tertiary" }, null, -1), + s(" " + S(C(u)("tasks_projects.reports.split.non_billable")) + ": ", 1), + o("span", Qs, S(C(qa)(F.value)), 1), + o("span", $s, "(" + S(100 - I.value) + "%)", 1) + ])])], 64)) : (_(), a("p", ec, S(C(u)("tasks_projects.reports.split.nothing")), 1))]), + c(Ts, { + title: C(u)("tasks_projects.reports.tables.by_project"), + "label-heading": C(u)("tasks_projects.reports.tables.project"), + rows: j.value, + "show-currency": k.value + }, null, 8, [ + "title", + "label-heading", + "rows", + "show-currency" + ]), + c(Ts, { + title: C(u)("tasks_projects.reports.tables.by_member"), + "label-heading": C(u)("tasks_projects.reports.tables.member"), + rows: M.value, + "show-currency": k.value + }, null, 8, [ + "title", + "label-heading", + "rows", + "show-currency" + ]), + c(Ts, { + title: C(u)("tasks_projects.reports.tables.by_customer"), + "label-heading": C(u)("tasks_projects.reports.tables.customer"), + rows: N.value, + "show-currency": k.value + }, null, 8, [ + "title", + "label-heading", + "rows", + "show-currency" + ]) + ], 64)) : (_(), r(R, { key: 1, - title: S(u)("tasks_projects.task_statuses.none"), - description: S(u)("tasks_projects.tasks.empty_description") + title: C(u)("tasks_projects.reports.empty_title"), + description: C(u)("tasks_projects.reports.empty_description") }, { - default: E(() => [c(v, { - name: "ViewColumnsIcon", + default: D(() => [c(h, { + name: "ChartBarIcon", class: "mt-5 mb-4 h-16 w-16 text-subtle" })]), _: 1 - }, 8, ["title", "description"])) : (g(), a("div", Go, [(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" - }, [ - o("header", Ko, [o("div", qo, [ - 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), - o("h3", Jo, x(t.status.name), 1), - o("span", Yo, x(t.tasks.length), 1) - ]), o("button", { - type: "button", - class: "rounded-md p-1 text-subtle hover:bg-hover hover:text-body", - "aria-label": S(u)("tasks_projects.tasks.new_task"), - title: S(u)("tasks_projects.tasks.new_task"), - onClick: (e) => ae(t.status) - }, [c(v, { - name: "PlusIcon", - class: "h-4 w-4" - })], 8, Xo)]), - o("div", { - ref_for: !0, - ref: (e) => te(t.status.id, e), - "data-status-id": t.status.id, - class: "min-h-[80px] space-y-2 px-3 pt-3" - }, [(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) => se(e) - }, [ - o("div", $o, [o("p", es, x(e.name), 1), e.priority ? (g(), a("span", { - key: 0, - class: f(["shrink-0 rounded-full px-2 py-0.5 text-[11px] font-medium", ye(e.priority)]) - }, x(ve(e.priority)), 3)) : i("", !0)]), - o("div", ts, [ - o("span", null, "#" + x(e.number), 1), - me(e) ? (g(), a("span", { - key: 0, - class: "rounded-sm bg-surface-tertiary px-1.5 py-0.5 text-[11px] text-body", - title: he(e) - }, x(me(e)), 9, ns)) : i("", !0), - e.billable ? (g(), a("span", rs, [c(v, { - name: "CurrencyDollarIcon", - class: "mr-0.5 h-3.5 w-3.5" - }), s(" " + x(S(u)("tasks_projects.tasks.billable")), 1)])) : i("", !0) - ]), - o("div", is, [e.due_date ? (g(), a("span", { - key: 0, - class: f(["text-xs", S(ue)(e.due_date) && !e.closed_at ? "font-medium text-status-red" : "text-muted"]) - }, x(S(oe)(e.due_date)), 3)) : (g(), a("span", as, "-")), ge(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: _e(e) - }, x(ge(e)), 9, os)) : i("", !0)]) - ], 8, Qo))), 128))], 8, Zo), - t.tasks.length === 0 ? (g(), a("p", ss, x(S(u)("tasks_projects.board.empty_column")), 1)) : i("", !0), - o("div", cs, [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) => ae(t.status) - }, " + " + x(S(u)("tasks_projects.tasks.new_task")), 9, ls)]) - ]))), 128))])), - c(Ho, { - show: k.value, - client: t.client, - notify: t.notify, - task: A.value, - statuses: z.value, - members: _.value, - projects: L.value, - defaults: j.value, - onClose: l[2] ||= (e) => k.value = !1, - onSaved: ce, - onDeleted: fe - }, null, 8, [ - "show", - "client", - "notify", - "task", - "statuses", - "members", - "projects", - "defaults" - ]) + }, 8, ["title", "description"])) ]), _: 1 }); }; } -}), ds = { - key: 0, - class: "mt-2 flex flex-wrap items-center gap-3 text-sm text-muted" -}, fs = { +}), rc = "tasks-projects"; +function ic(e) { + e.addMessages(us), e.registerPage({ + id: "reports", + module: rc, + path: "reports", + component: we(e, nc), + meta: { + ability: `${rc}:view-own-time`, + title: "tasks_projects.reports.title" + } + }), e.on("company:changing", () => { + $n(); + }); +} +//#endregion +//#region resources/js/messages/tasks.ts +var ac = { en: { tasks_projects: { + board: { + title: "Board", + load_failed: "Unable to load the board.", + move_failed: "Unable to move the task.", + moved: "{name} moved to {status}.", + empty_column: "Nothing here yet", + hidden_invoiced: "Invoiced tasks are hidden on the board." + }, + tasks: { + title: "Tasks", + all_tasks: "All tasks", + new_task: "New task", + edit_task: "Edit task", + all_fields: "All fields", + fewer_fields: "Fewer fields", + search_placeholder: "Search by name or number", + empty_title: "No tasks yet", + empty_description: "Add a task to put work on the board.", + unassigned: "Unassigned", + billable: "Billable", + overdue: "Overdue", + none: "None", + no_project: "No project", + internal: "Internal", + invoiced: "Invoiced", + uninvoiced: "Unbilled", + invoice_task: "Invoice task", + invoice_soon: "Coming with invoicing", + locked: "This task is on an invoice and cannot be changed.", + views: { + list: "List", + board: "Board", + week: "Week" + }, + filters: { + project: "Project", + all_projects: "All projects", + member: "Member", + all_members: "Everyone", + status: "Status", + all_statuses: "Any status", + invoicing: "Invoicing", + search: "Search" + }, + columns: { + number: "No.", + name: "Name", + project: "Project", + status: "Status", + assignee: "Assignee", + priority: "Priority", + due_date: "Due date", + logged: "Logged", + unbilled: "Unbilled", + invoiced: "Invoicing", + timer: "Timer" + }, + bulk: { + selected: "{count} selected", + select_page: "Select this page", + clear: "Clear", + change_status: "Move to", + delete: "Delete", + invoice: "Invoice", + delete_confirm: "Delete {count} tasks? Their time entries go with them.", + applied: "{count} tasks were updated.", + deleted: "{count} tasks were deleted.", + partial: "{count} tasks were updated, {failed} were refused.", + nothing: "No task was changed.", + failed: "Unable to apply the change." + }, + detail: { + estimate: "Estimate", + logged: "Logged", + unbilled: "Unbilled", + no_estimate: "No estimate", + project: "Project", + customer: "Customer", + status: "Status", + assignee: "Assignee", + priority: "Priority", + due_date: "Due date", + description: "Description", + no_description: "No description yet.", + status_saved: "The status was changed to {name}.", + status_failed: "Unable to change the status.", + not_found: "That task could not be loaded." + }, + time_log: { + title: "Time log", + add_item: "Add item", + add_disabled: "Stop the running timer to log an entry by hand.", + running: "Running", + empty: "No time logged against this task yet.", + load_failed: "Unable to load the time log.", + stamped: "Invoiced", + stamped_delete: "Invoiced time belongs to its invoice and cannot be deleted.", + columns: { + start_date: "Start date", + start_time: "Start", + end_date: "End date", + end_time: "End", + duration: "Duration", + description: "Description", + billable: "Billable", + member: "Member" + } + }, + created: "{name} was created.", + updated: "{name} was updated.", + deleted: "{name} was deleted.", + delete_confirm: "Delete {name}? Its time entries go with it.", + name_required: "Enter a task name.", + load_failed: "Unable to load the tasks.", + save_failed: "Unable to save the task.", + delete_failed: "Unable to delete the task.", + projects_failed: "Unable to load the projects.", + members_failed: "Unable to load the members.", + fields: { + name: "Name", + description: "Description", + project: "Project", + project_placeholder: "No project", + project_help: "Leave empty for a task that stands on its own.", + customer: "Customer", + customer_help: "Taken from the project.", + status: "Status", + assignee: "Assignee", + assignee_placeholder: "Nobody yet", + priority: "Priority", + priority_placeholder: "No priority", + due_date: "Due date", + estimate_hours: "Estimate (hours)", + billable: "Billable", + rate: "Rate override", + rate_help: "Per hour. Leave empty to use the project or member rate." + }, + priority: { + low: "Low", + normal: "Normal", + high: "High", + urgent: "Urgent" + } + }, + task_statuses: { + load_failed: "Unable to load the task statuses.", + none: "No board columns yet." + } +} } }, oc = { class: "mt-6 rounded-xl border border-line-default bg-surface" }, sc = { class: "flex items-center justify-between border-b border-line-light px-5 py-3" }, cc = { class: "text-sm font-semibold text-heading" }, lc = ["title"], uc = { class: "overflow-x-auto" }, dc = { class: "min-w-full text-sm" }, fc = { class: "bg-surface-secondary text-xs tracking-wide text-muted uppercase" }, pc = { class: "px-4 py-2 text-left font-medium" }, mc = { class: "px-4 py-2 text-left font-medium" }, hc = { class: "px-4 py-2 text-left font-medium" }, gc = { class: "px-4 py-2 text-left font-medium" }, _c = { class: "px-4 py-2 text-left font-medium" }, vc = { class: "px-4 py-2 text-left font-medium" }, yc = { class: "px-4 py-2 text-left font-medium" }, bc = { class: "px-4 py-2 text-left font-medium" }, xc = { class: "px-4 py-2 text-right font-medium" }, Sc = ["onClick"], Cc = { class: "px-4 py-2.5 whitespace-nowrap" }, wc = { class: "px-4 py-2.5 whitespace-nowrap tabular-nums" }, Tc = { class: "px-4 py-2.5 whitespace-nowrap" }, Ec = { key: 0, - class: "rounded-sm bg-surface-tertiary px-2 py-0.5 text-body" -}, ps = { key: 1 }, ms = { class: "text-body" }, hs = { + class: "text-primary-500" +}, Dc = { key: 1 }, Oc = { class: "px-4 py-2.5 whitespace-nowrap tabular-nums" }, kc = { class: "px-4 py-2.5 whitespace-nowrap tabular-nums" }, Ac = { class: "max-w-64 truncate px-4 py-2.5" }, jc = { class: "px-4 py-2.5" }, Mc = { + key: 1, + class: "text-subtle" +}, Nc = { class: "px-4 py-2.5 whitespace-nowrap" }, Pc = ["title"], Fc = { key: 2, + class: "text-xs text-primary-500" +}, Ic = { key: 0 }, Lc = { + colspan: "9", + class: "px-4 py-8 text-center text-sm text-subtle" +}, Rc = /* @__PURE__ */ l({ + __name: "TimeLogGrid", + props: { + client: {}, + notify: {}, + task: {}, + members: { default: () => [] } + }, + setup(t) { + let l = t, u = B(), d = _o(), p = y([]), m = y(!1), h = y(!1), g = y(null), v = n(() => ({ + id: l.task.id, + name: l.task.name, + number: l.task.number, + project_id: l.task.project_id, + billable: l.task.billable + })), w = n(() => G.isRunningOn(l.task.id)); + E(() => l.task.id, () => void T(), { immediate: !0 }), E(za, () => void T()); + async function T() { + m.value = !0; + try { + p.value = await Pn(l.client, l.task.id); + } catch (e) { + p.value = [], l.notify("error", R(e, u("tasks_projects.tasks.time_log.load_failed"))); + } finally { + m.value = !1; + } + } + function O(e) { + return e.invoice_id !== null; + } + function k(e) { + return l.members.find((t) => t.id === e)?.name ?? `#${e}`; + } + function j(e) { + return e.is_running ? Ka(ao(e.started_at, d.value)) : qa(e.duration_minutes); + } + function M() { + w.value || (g.value = null, h.value = !0); + } + function N(e) { + e.is_running || (g.value = e, h.value = !0); + } + function P() { + let e = g.value ? u("tasks_projects.time.updated") : u("tasks_projects.time.created"); + h.value = !1, g.value = null, l.notify("success", e), U(); + } + function F() { + h.value = !1, g.value = null, l.notify("success", u("tasks_projects.time.deleted")), U(); + } + async function ee(e) { + if (O(e)) { + l.notify("warning", u("tasks_projects.tasks.time_log.stamped_delete")); + return; + } + if (window.confirm(u("tasks_projects.time.delete_confirm"))) try { + await oa(l.client, e.id), l.notify("success", u("tasks_projects.time.deleted")), U(); + } catch (e) { + l.notify("error", R(e, u("tasks_projects.time.delete_failed"))); + } + } + return (n, l) => { + let d = x("BaseSpinner"), y = x("BaseIcon"), T = x("BaseButton"), E = x("BaseDropdownItem"), I = x("BaseDropdown"); + return _(), a("section", oc, [ + o("header", sc, [o("h2", cc, [s(S(C(u)("tasks_projects.tasks.time_log.title")) + " ", 1), m.value ? (_(), r(d, { + key: 0, + class: "ml-2 inline-block h-4 w-4 text-primary-500" + })) : i("", !0)]), o("span", { title: w.value ? C(u)("tasks_projects.tasks.time_log.add_disabled") : void 0 }, [c(T, { + variant: "primary-outline", + size: "sm", + disabled: w.value, + onClick: M + }, { + left: D((e) => [c(y, { + name: "PlusIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: D(() => [s(" " + S(C(u)("tasks_projects.tasks.time_log.add_item")), 1)]), + _: 1 + }, 8, ["disabled"])], 8, lc)]), + o("div", uc, [o("table", dc, [o("thead", fc, [o("tr", null, [ + o("th", pc, S(C(u)("tasks_projects.tasks.time_log.columns.start_date")), 1), + o("th", mc, S(C(u)("tasks_projects.tasks.time_log.columns.start_time")), 1), + o("th", hc, S(C(u)("tasks_projects.tasks.time_log.columns.end_date")), 1), + o("th", gc, S(C(u)("tasks_projects.tasks.time_log.columns.end_time")), 1), + o("th", _c, S(C(u)("tasks_projects.tasks.time_log.columns.duration")), 1), + o("th", vc, S(C(u)("tasks_projects.tasks.time_log.columns.description")), 1), + o("th", yc, S(C(u)("tasks_projects.tasks.time_log.columns.billable")), 1), + o("th", bc, S(C(u)("tasks_projects.tasks.time_log.columns.member")), 1), + o("th", xc, S(C(u)("tasks_projects.general.actions")), 1) + ])]), o("tbody", null, [(_(!0), a(e, null, b(p.value, (e) => (_(), a("tr", { + key: e.id, + class: f(["border-t border-line-light", e.is_running ? "bg-primary-50" : "cursor-pointer hover:bg-hover"]), + onClick: (t) => N(e) + }, [ + o("td", Cc, S(C(_e)(C(Ya)(e.started_at)) || "-"), 1), + o("td", wc, S(C(Xa)(e.started_at) || "-"), 1), + o("td", Tc, [e.is_running ? (_(), a("span", Ec, S(C(u)("tasks_projects.tasks.time_log.running")), 1)) : (_(), a("span", Dc, S(C(_e)(C(Ya)(e.ended_at)) || "-"), 1))]), + o("td", Oc, S(e.is_running ? "-" : C(Xa)(e.ended_at) || "-"), 1), + o("td", kc, [o("span", { class: f(e.is_running ? "font-medium text-primary-500" : "") }, S(j(e)), 3)]), + o("td", Ac, S(e.description || "-"), 1), + o("td", jc, [e.billable ? (_(), r(y, { + key: 0, + name: "CheckCircleIcon", + class: "h-5 w-5 text-status-green" + })) : (_(), a("span", Mc, "-"))]), + o("td", Nc, S(k(e.user_id)), 1), + o("td", { + class: "px-4 py-2.5 text-right whitespace-nowrap", + onClick: l[0] ||= A(() => {}, ["stop"]) + }, [O(e) ? (_(), a("span", { + key: 0, + class: "text-xs text-muted", + title: C(u)("tasks_projects.tasks.time_log.stamped_delete") + }, S(C(u)("tasks_projects.tasks.time_log.stamped")), 9, Pc)) : e.is_running ? (_(), a("span", Fc, S(C(u)("tasks_projects.tasks.time_log.running")), 1)) : (_(), r(I, { key: 1 }, { + activator: D(() => [c(y, { + name: "EllipsisHorizontalIcon", + class: "h-5 text-muted" + })]), + default: D(() => [c(E, { onClick: (t) => N(e) }, { + default: D(() => [c(y, { + name: "PencilIcon", + class: "mr-3 h-5 w-5 text-subtle group-hover:text-muted" + }), s(" " + S(C(u)("tasks_projects.general.edit")), 1)]), + _: 1 + }, 8, ["onClick"]), c(E, { onClick: (t) => ee(e) }, { + default: D(() => [c(y, { + name: "TrashIcon", + class: "mr-3 h-5 w-5 text-subtle group-hover:text-muted" + }), s(" " + S(C(u)("tasks_projects.general.delete")), 1)]), + _: 1 + }, 8, ["onClick"])]), + _: 2 + }, 1024))]) + ], 10, Sc))), 128)), p.value.length === 0 && !m.value ? (_(), a("tr", Ic, [o("td", Lc, S(C(u)("tasks_projects.tasks.time_log.empty")), 1)])) : i("", !0)])])]), + c(Zo, { + show: h.value, + client: t.client, + notify: t.notify, + entry: g.value, + "default-task": v.value, + "lock-task": "", + onClose: l[1] ||= (e) => h.value = !1, + onSaved: P, + onDeleted: F + }, null, 8, [ + "show", + "client", + "notify", + "entry", + "default-task" + ]) + ]); + }; + } +}), zc = { + key: 0, + class: "mt-2 flex flex-wrap items-center gap-3 text-sm text-muted" +}, Bc = { class: "rounded-sm bg-surface-tertiary px-2 py-0.5 text-body" }, Vc = { + key: 1, class: "text-subtle" -}, gs = { key: 3 }, _s = { class: "text-body" }, vs = { class: "flex items-center justify-end space-x-5" }, ys = { class: "mt-6 flex overflow-x-auto border-b border-line-default" }, bs = [ - "href", - "aria-current", - "onClick" -], xs = { +}, Hc = { + key: 0, + class: "flex flex-wrap items-center justify-end gap-3" +}, Uc = ["title"], Wc = { key: 0, class: "flex justify-center py-16" -}, Ss = "extension.page.tasks-projects.project", Cs = /* @__PURE__ */ l({ - __name: "ProjectDetailPage", +}, Gc = { class: "mt-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4" }, Kc = { class: "rounded-xl border border-line-default bg-surface p-5" }, qc = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, Jc = { class: "mt-2" }, Yc = { class: "mt-2 text-xs text-muted" }, Xc = { class: "text-body" }, Zc = { class: "rounded-xl border border-line-default bg-surface p-5" }, Qc = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, $c = { class: "mt-2 text-2xl font-semibold text-heading" }, el = { class: "mt-1 text-xs text-muted" }, tl = { class: "text-body" }, nl = { class: "rounded-xl border border-line-default bg-surface p-5" }, rl = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, il = { class: "mt-2 text-2xl font-semibold text-heading" }, al = { class: "mt-1 text-xs text-muted" }, ol = { class: "rounded-xl border border-line-default bg-surface p-5" }, sl = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, cl = { class: "mt-2 text-lg font-semibold text-heading" }, ll = { class: "mt-1 text-xs text-muted" }, ul = { class: "mt-4 rounded-xl border border-line-default bg-surface p-5" }, dl = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, fl = { + key: 0, + class: "mt-2 text-sm whitespace-pre-line text-body" +}, pl = { + key: 1, + class: "mt-2 text-sm text-subtle" +}, ml = 100, hl = /* @__PURE__ */ l({ + __name: "TaskPage", props: { id: {}, client: { type: [Function, Object] }, @@ -4847,2413 +5266,3328 @@ var Fo = [ router: {} }, setup(t) { - let l = t, u = de(), d = v(null), p = v(!0), m = v(!1), _ = v(!1), C = n(() => Number(l.id)), w = n(() => [ - { - id: "overview", - label: u("tasks_projects.project.tabs.overview"), - name: `${Ss}.overview` - }, - { - id: "tasks", - label: u("tasks_projects.project.tabs.tasks"), - name: `${Ss}.tasks` - }, - { - id: "time", - label: u("tasks_projects.project.tabs.time"), - name: `${Ss}.time` - }, - { - id: "members", - label: u("tasks_projects.project.tabs.members"), - name: `${Ss}.members` + let l = t, u = { + 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" + }, d = B(), p = l.router, m = y(null), g = y([]), v = y([]), b = y([]), w = y(!0), T = y(!1), O = y(!1), k = y(!1), A = n(() => Number(l.id)), j = n(() => Va(m.value)), M = n(() => m.value?.name ?? d("tasks_projects.tasks.title")), N = n(() => g.value.map((e) => ({ + id: e.id, + label: e.name + }))), P = n(() => b.value.map((e) => ({ + id: e.id, + label: e.name + }))), F = n(() => b.value.find((e) => e.id === m.value?.project_id)), ee = n(() => { + let e = m.value?.assignee_id ?? null; + return e === null ? d("tasks_projects.tasks.unassigned") : v.value.find((t) => t.id === e)?.name ?? `#${e}`; + }), I = n({ + get: () => N.value.find((e) => e.id === m.value?.task_status_id) ?? null, + set: (e) => { + e !== null && ie(e); } - ]), D = n(() => String(l.router.currentRoute.value.name ?? "")), O = n(() => d.value?.name ?? u("tasks_projects.projects.title")), k = n(() => xe(d.value?.customer_id ?? null)); - T(C, () => { - j(); - }), T(D, (e) => A(e)), h(() => { - A(D.value), j(); + }), te = n(() => m.value?.priority ? d(`tasks_projects.tasks.priority.${m.value.priority.toLowerCase()}`) : null); + E(A, () => void L()), E(za, () => void L(!0)), h(() => { + L(), ne(); }); - function A(e) { - e === Ss && l.router.replace({ - name: `${Ss}.overview`, - params: { id: l.id } - }); + async function L(e = !1) { + if (!(!Number.isInteger(A.value) || A.value <= 0)) { + w.value = !e; + try { + let e = await jn(l.client, A.value); + m.value = e, La(e), e.customer_id !== null && Qn(l.client); + } catch (t) { + e || l.notify("error", R(t, d("tasks_projects.tasks.detail.not_found"))); + } finally { + w.value = !1; + } + } } - async function j() { - p.value = !0; + async function ne() { try { - d.value = await Ao(l.client, C.value), typeof d.value?.customer_id == "number" && await Se(l.client); + g.value = await En(l.client); } catch (e) { - l.notify("error", U(e, u("tasks_projects.project.load_failed"))); - } finally { - p.value = !1; + l.notify("error", R(e, d("tasks_projects.task_statuses.load_failed"))); + } + try { + v.value = await Sn(l.client); + } catch { + v.value = []; + } + try { + let e = await gn(l.client, { + limit: ml, + status: "ACTIVE", + sort_by: "name" + }); + b.value = e.data ?? []; + } catch { + b.value = []; } } - function M(e) { + function re(e, t) { return { name: e.name, - params: { id: l.id } + task_status_id: e.task_status_id, + project_id: e.project_id, + customer_id: e.project_id === null ? e.customer_id : null, + description: e.description, + assignee_id: e.assignee_id, + priority: e.priority, + due_date: e.due_date, + estimated_minutes: e.estimated_minutes, + billable: e.billable, + rate: e.rate, + ...t }; } - function N(e) { - return D.value === e.name; + async function ie(e) { + let t = m.value; + if (t === null || T.value || t.task_status_id === e.id) return; + let n = t.task_status_id; + t.task_status_id = e.id, T.value = !0; + try { + m.value = await kn(l.client, t.id, re(t, { task_status_id: e.id })), l.notify("success", d("tasks_projects.tasks.detail.status_saved", { name: e.label })), U(); + } catch (e) { + t.task_status_id = n, l.notify("error", R(e, z(e) === "task_locked" ? d("tasks_projects.tasks.locked") : d("tasks_projects.tasks.detail.status_failed"))); + } finally { + T.value = !1; + } } - function P(e) { - _.value = !1, l.notify("success", u("tasks_projects.projects.updated", { name: e.name })), j(); + function ae() { + p.push(H.tasks); } - async function F() { - let e = d.value; - if (!(e === null || m.value)) { - m.value = !0; + function oe(e) { + k.value = !1, m.value = e, La(e), l.notify("success", d("tasks_projects.tasks.updated", { name: e.name })), U(); + } + async function se() { + let e = m.value; + if (!(e === null || O.value) && window.confirm(d("tasks_projects.tasks.delete_confirm", { name: e.name }))) { + O.value = !0; try { - e.status === "ARCHIVED" ? (await z(l.client, e.id), l.notify("success", u("tasks_projects.projects.unarchived", { name: e.name }))) : (await R(l.client, e.id), l.notify("success", u("tasks_projects.projects.archived", { name: e.name }))), await j(); + await An(l.client, e.id), l.notify("success", d("tasks_projects.tasks.deleted", { name: e.name })), U(), ae(); } catch (e) { - l.notify("error", U(e, u("tasks_projects.projects.save_failed"))); + l.notify("error", R(e, z(e) === "task_locked" ? d("tasks_projects.tasks.locked") : d("tasks_projects.tasks.delete_failed"))); } finally { - m.value = !1; + O.value = !1; } } } - function I(e) { - return e === "ACTIVE" ? "bg-primary-50! text-primary-500!" : "bg-surface-tertiary! text-muted!"; - } - function L(e) { - return u(e === "ACTIVE" ? "tasks_projects.projects.status.active" : "tasks_projects.projects.status.archived"); - } return (n, l) => { - let h = b("BaseBreadcrumbItem"), v = b("BaseBreadcrumb"), C = b("BaseBadge"), T = b("BaseIcon"), D = b("BaseButton"), A = b("router-link"), R = b("BasePageHeader"), z = b("BaseSpinner"), B = b("router-view"), V = b("BasePage"); - return g(), r(V, null, { - default: E(() => [ - c(R, { title: O.value }, { - actions: E(() => [o("div", vs, [ - c(A, { to: "/admin/modules/tasks-projects/board" }, { - default: E(() => [c(D, { variant: "white" }, { - left: E((e) => [c(T, { - name: "ViewColumnsIcon", - class: f(e.class) - }, null, 8, ["class"])]), - default: E(() => [s(" " + x(S(u)("tasks_projects.project.board")), 1)]), - _: 1 - })]), + let p = x("BaseBreadcrumbItem"), h = x("BaseBreadcrumb"), y = x("router-link"), b = x("BaseIcon"), E = x("BaseButton"), A = x("BasePageHeader"), L = x("BaseSpinner"), ne = x("BaseSelectInput"), re = x("BaseFormatMoney"), ie = x("BasePage"); + return _(), r(ie, null, { + default: D(() => [ + c(A, { title: M.value }, { + actions: D(() => [m.value ? (_(), a("div", Hc, [ + c(Oo, { + client: t.client, + notify: t.notify, + task: m.value, + members: v.value, + size: "md" + }, null, 8, [ + "client", + "notify", + "task", + "members" + ]), + o("span", { + title: C(d)("tasks_projects.tasks.invoice_soon"), + class: "inline-flex cursor-not-allowed opacity-60" + }, [c(E, { + variant: "white", + disabled: "" + }, { + left: D((e) => [c(b, { + name: "BanknotesIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: D(() => [s(" " + S(C(d)("tasks_projects.tasks.invoice_task")), 1)]), _: 1 - }), - d.value ? (g(), r(D, { - key: 0, + })], 8, Uc), + c(E, { variant: "primary-outline", - loading: m.value, - disabled: m.value, - onClick: F + loading: O.value, + disabled: O.value, + onClick: se }, { - default: E(() => [s(x(d.value.status === "ARCHIVED" ? S(u)("tasks_projects.projects.unarchive") : S(u)("tasks_projects.projects.archive")), 1)]), + default: D(() => [s(S(C(d)("tasks_projects.general.delete")), 1)]), _: 1 - }, 8, ["loading", "disabled"])) : i("", !0), - d.value ? (g(), r(D, { - key: 1, + }, 8, ["loading", "disabled"]), + c(E, { variant: "primary", - onClick: l[0] ||= (e) => _.value = !0 + onClick: l[0] ||= (e) => k.value = !0 }, { - left: E((e) => [c(T, { + left: D((e) => [c(b, { name: "PencilIcon", class: f(e.class) }, null, 8, ["class"])]), - default: E(() => [s(" " + x(S(u)("tasks_projects.general.edit")), 1)]), + default: D(() => [s(" " + S(C(d)("tasks_projects.general.edit")), 1)]), _: 1 - })) : i("", !0) - ])]), - default: E(() => [c(v, null, { - default: E(() => [ - c(h, { - title: S(u)("tasks_projects.general.home"), + }) + ])) : i("", !0)]), + default: D(() => [c(h, null, { + default: D(() => [ + c(p, { + title: C(d)("tasks_projects.general.home"), to: "/admin/dashboard" }, null, 8, ["title"]), - c(h, { - title: S(u)("tasks_projects.projects.title"), - to: "/admin/modules/tasks-projects" - }, null, 8, ["title"]), - c(h, { - title: O.value, + c(p, { + title: C(d)("tasks_projects.tasks.title"), + to: C(H).tasks + }, null, 8, ["title", "to"]), + c(p, { + title: m.value ? `#${m.value.number}` : M.value, to: "#", active: "" - }, null, 8, ["title"]) - ]), - _: 1 - }), d.value ? (g(), a("div", ds, [ - c(C, { class: f(["rounded-full", I(d.value.status)]) }, { - default: E(() => [s(x(L(d.value.status)), 1)]), - _: 1 - }, 8, ["class"]), - d.value.identifier ? (g(), a("span", fs, x(d.value.identifier), 1)) : i("", !0), - d.value.customer_id ? (g(), a("span", ps, [s(x(S(u)("tasks_projects.project.customer")) + ": ", 1), o("span", ms, x(k.value), 1)])) : (g(), a("span", hs, x(S(u)("tasks_projects.projects.internal")), 1)), - d.value.due_date ? (g(), a("span", gs, [s(x(S(u)("tasks_projects.project.due_date")) + ": ", 1), o("span", _s, x(S(oe)(d.value.due_date)), 1)])) : i("", !0) - ])) : i("", !0)]), - _: 1 - }, 8, ["title"]), - o("nav", ys, [(g(!0), a(e, null, y(w.value, (e) => (g(), r(A, { - key: e.id, - to: M(e), - custom: "" - }, { - default: E(({ href: t, navigate: n }) => [o("a", { - href: t, - "aria-current": N(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", N(e) ? "border-primary-400 text-heading" : "border-transparent text-muted hover:border-line-strong hover:text-body"]), - onClick: n - }, x(e.label), 11, bs)]), - _: 2 - }, 1032, ["to"]))), 128))]), - p.value && d.value === null ? (g(), a("div", xs, [c(z, { class: "h-8 w-8 text-primary-500" })])) : (g(), r(B, { - key: 1, - project: d.value, - onRefresh: j - }, null, 8, ["project"])), - c(_e, { - show: _.value, - client: t.client, - notify: t.notify, - project: d.value, - onClose: l[1] ||= (e) => _.value = !1, - onSaved: P - }, null, 8, [ - "show", - "client", - "notify", - "project" - ]) - ]), - _: 1 - }); - }; - } -}), ws = { class: "py-4" }, Ts = { class: "rounded-xl border border-line-default bg-surface p-5" }, Es = { class: "flex flex-col gap-4 lg:flex-row lg:items-end" }, Ds = { - key: 0, - class: "mt-3 text-xs text-subtle" -}, Os = { class: "mt-4 overflow-hidden rounded-xl border border-line-default bg-surface" }, ks = { - key: 0, - class: "flex justify-center py-10" -}, As = { - key: 1, - class: "px-5 py-8 text-center text-sm text-muted" -}, js = { - key: 2, - class: "divide-y divide-line-light" -}, Ms = { class: "text-sm font-medium text-heading" }, Ns = { class: "text-xs text-muted" }, Ps = { - key: 1, - class: "text-subtle" -}, Fs = /* @__PURE__ */ l({ - __name: "ProjectMembersTab", - props: { - id: {}, - client: { type: [Function, Object] }, - notify: { type: Function }, - project: {} - }, - setup(t) { - let l = t, u = de(), 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 - }))); - h(() => { - A(); - }); - async function A() { - m.value = !0; - try { - p.value = await V(l.client); - } catch (e) { - l.notify("error", U(e, u("tasks_projects.tasks.members_failed"))); - } - try { - d.value = await jo(l.client, O.value); - } catch (e) { - l.notify("error", U(e, u("tasks_projects.project.members.load_failed"))); - } finally { - 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 = w.value; - if (!(e === null || _.value)) { - _.value = !0, D.value = {}; - try { - await Mo(l.client, O.value, { - user_id: e.id, - rate: re(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 = te(e), l.notify("error", U(e, u("tasks_projects.project.members.attach_failed"))); - } finally { - _.value = !1; - } - } - } - 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 No(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", U(e, u("tasks_projects.project.members.detach_failed"))); - } finally { - C.value = null; - } - } - } - 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", ws, [o("div", Ts, [o("div", Es, [ - c(p, { - label: S(u)("tasks_projects.project.members.member"), - error: D.value.user_id, - class: "flex-1" - }, { - default: E(() => [c(l, { - modelValue: w.value, - "onUpdate:modelValue": n[0] ||= (e) => w.value = e, - options: k.value, - placeholder: S(u)("tasks_projects.project.members.attach_placeholder"), - "label-key": "label" - }, null, 8, [ - "modelValue", - "options", - "placeholder" - ])]), - _: 1 - }, 8, ["label", "error"]), - c(p, { - label: S(u)("tasks_projects.project.members.rate"), - error: D.value.rate, - "help-text": S(u)("tasks_projects.project.members.rate_help"), - class: "flex-1" - }, { - default: E(() => [c(h, { - modelValue: T.value, - "onUpdate:modelValue": n[1] ||= (e) => T.value = e, - type: "number", - step: "0.01", - min: "0" - }, null, 8, ["modelValue"])]), - _: 1 - }, 8, [ - "label", - "error", - "help-text" - ]), - c(O, { - variant: "primary", - class: "lg:mb-1", - loading: _.value, - disabled: _.value || w.value === null, - onClick: M - }, { - left: E((e) => [c(v, { - name: "PlusIcon", - class: f(e.class) - }, null, 8, ["class"])]), - default: E(() => [s(" " + x(S(u)("tasks_projects.project.members.attach")), 1)]), - _: 1 - }, 8, ["loading", "disabled"]) - ]), k.value.length === 0 && !m.value ? (g(), a("p", Ds, x(S(u)("tasks_projects.project.members.all_attached")), 1)) : i("", !0)]), o("div", Os, [m.value ? (g(), a("div", ks, [c(A, { class: "h-6 w-6 text-primary-500" })])) : d.value.length === 0 ? (g(), a("p", As, x(S(u)("tasks_projects.project.members.empty")), 1)) : (g(), a("ul", js, [(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" - }, [o("div", null, [o("p", Ms, x(j(e.user_id)), 1), o("p", Ns, [s(x(S(u)("tasks_projects.project.members.rate")) + ": ", 1), e.rate === null ? (g(), a("span", Ps, x(S(u)("tasks_projects.tasks.none")), 1)) : (g(), r(P, { - key: 0, - amount: e.rate - }, null, 8, ["amount"]))])]), c(O, { - variant: "danger", - size: "sm", - loading: C.value === e.user_id, - disabled: C.value === e.user_id, - onClick: (t) => N(e) - }, { - default: E(() => [s(x(S(u)("tasks_projects.general.delete")), 1)]), - _: 1 - }, 8, [ - "loading", - "disabled", - "onClick" - ])]))), 128))]))])]); - }; - } -}), Is = { - key: 0, - class: "py-6" -}, Ls = { class: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4" }, 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: "mt-1 text-xs text-muted" }, Hs = { class: "rounded-xl border border-line-default bg-surface p-5" }, Us = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, Ws = { class: "mt-2 text-2xl font-semibold text-heading" }, Gs = { class: "mt-1 text-xs text-muted" }, Ks = { class: "rounded-xl border border-line-default bg-surface p-5" }, qs = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, Js = { class: "mt-2 text-2xl font-semibold text-heading" }, Ys = { class: "rounded-xl border border-line-default bg-surface p-5" }, Xs = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, Zs = { class: "mt-2 text-2xl font-semibold text-heading" }, Qs = { class: "mt-4 rounded-xl border border-line-default bg-surface p-5" }, $s = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, ec = { class: "mt-2 text-sm text-body" }, tc = { class: "mt-3 h-2 w-full overflow-hidden rounded-full bg-surface-tertiary" }, nc = { - key: 0, - class: "mt-2 text-xs font-medium text-status-red" -}, rc = { - key: 1, - class: "mt-2 text-sm text-subtle" -}, ic = { class: "mt-4 rounded-xl border border-line-default bg-surface p-5" }, ac = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, oc = { - key: 0, - class: "mt-2 text-sm whitespace-pre-line text-body" -}, sc = { - key: 1, - class: "mt-2 text-sm text-subtle" -}, cc = { - key: 1, - class: "flex justify-center py-16" -}, lc = /* @__PURE__ */ l({ - __name: "ProjectOverviewTab", - props: { project: {} }, - setup(t) { - let l = t, u = de(), d = n(() => l.project?.totals ?? null), m = n(() => l.project?.budget_minutes ?? null), h = n(() => { - let e = m.value, t = d.value?.logged_minutes ?? 0; - return e ? Math.min(100, Math.round(t / e * 100)) : 0; - }), _ = n(() => { - let e = l.project?.customer_id ?? null; - return e === null || (d.value?.unbilled_amount ?? 0) <= 0 ? null : `/admin/modules/tasks-projects/billing?customer_id=${e}`; - }), v = n(() => { - let e = m.value, t = d.value?.logged_minutes ?? 0; - return e && t > e ? t - e : 0; - }); - return (n, l) => { - let y = b("BaseFormatMoney"), C = b("router-link"), w = b("BaseSpinner"); - return t.project && d.value ? (g(), a("div", Is, [ - o("div", Ls, [ - o("div", Rs, [ - o("p", zs, x(S(u)("tasks_projects.project.overview.tasks")), 1), - o("p", Bs, x(d.value.tasks.total), 1), - o("p", Vs, x(S(u)("tasks_projects.project.overview.open_tasks", { count: d.value.tasks.open })) + " · " + x(S(u)("tasks_projects.project.overview.closed_tasks", { count: d.value.tasks.closed })), 1) - ]), - o("div", Hs, [ - o("p", Us, x(S(u)("tasks_projects.project.overview.logged")), 1), - o("p", Ws, x(S(ce)(d.value.logged_minutes)), 1), - o("p", Gs, x(S(u)("tasks_projects.project.overview.billable")) + ": " + x(S(ce)(d.value.billable_minutes)), 1) - ]), - o("div", Ks, [o("p", qs, x(S(u)("tasks_projects.project.overview.billable_amount")), 1), o("p", Js, [c(y, { amount: d.value.billable_amount }, null, 8, ["amount"])])]), - o("div", Ys, [ - o("p", Xs, x(S(u)("tasks_projects.project.overview.unbilled_amount")), 1), - o("p", Zs, [c(y, { amount: d.value.unbilled_amount }, null, 8, ["amount"])]), - _.value ? (g(), r(C, { - key: 0, - class: "mt-1 block text-xs font-medium text-primary-500 hover:underline", - to: _.value - }, { - default: E(() => [s(x(S(u)("tasks_projects.billing.view_unbilled")), 1)]), + }, null, 8, ["title"]) + ]), _: 1 - }, 8, ["to"])) : i("", !0) - ]) + }), m.value ? (_(), a("div", zc, [ + o("span", Bc, "#" + S(m.value.number), 1), + m.value.project_id ? (_(), r(y, { + key: 0, + class: "hover:text-primary-500", + to: C(H).project(m.value.project_id) + }, { + default: D(() => [s(S(F.value?.name ?? `#${m.value.project_id}`), 1)]), + _: 1 + }, 8, ["to"])) : (_(), a("span", Vc, S(C(d)("tasks_projects.tasks.no_project")), 1)), + m.value.customer_id ? (_(), r(y, { + key: 2, + class: "hover:text-primary-500", + to: C(H).customer(m.value.customer_id) + }, { + default: D(() => [s(S(C(Zn)(m.value.customer_id)), 1)]), + _: 1 + }, 8, ["to"])) : i("", !0), + te.value && m.value.priority ? (_(), a("span", { + key: 3, + class: f(["rounded-full px-2 py-0.5 text-xs font-medium", u[m.value.priority]]) + }, S(te.value), 3)) : i("", !0), + c(Vi, { state: j.value.invoiced }, null, 8, ["state"]) + ])) : i("", !0)]), + _: 1 + }, 8, ["title"]), + w.value && m.value === null ? (_(), a("div", Wc, [c(L, { class: "h-8 w-8 text-primary-500" })])) : m.value ? (_(), a(e, { key: 1 }, [ + o("div", Gc, [ + o("div", Kc, [ + o("p", qc, S(C(d)("tasks_projects.tasks.detail.status")), 1), + o("div", Jc, [c(ne, { + modelValue: I.value, + "onUpdate:modelValue": l[1] ||= (e) => I.value = e, + options: N.value, + disabled: T.value, + "label-key": "label" + }, null, 8, [ + "modelValue", + "options", + "disabled" + ])]), + o("p", Yc, [s(S(C(d)("tasks_projects.tasks.detail.assignee")) + ": ", 1), o("span", Xc, S(ee.value), 1)]) + ]), + o("div", Zc, [ + o("p", Qc, S(C(d)("tasks_projects.tasks.detail.logged")), 1), + o("p", $c, S(C(ye)(j.value.logged_minutes)), 1), + o("p", el, [s(S(C(d)("tasks_projects.tasks.detail.estimate")) + ": ", 1), o("span", tl, S(m.value.estimated_minutes ? C(ye)(m.value.estimated_minutes) : C(d)("tasks_projects.tasks.detail.no_estimate")), 1)]) + ]), + o("div", nl, [ + o("p", rl, S(C(d)("tasks_projects.tasks.detail.unbilled")), 1), + o("p", il, [c(re, { amount: j.value.unbilled_amount }, null, 8, ["amount"])]), + o("p", al, S(C(ye)(j.value.unbilled_minutes)), 1) + ]), + o("div", ol, [ + o("p", sl, S(C(d)("tasks_projects.tasks.detail.due_date")), 1), + o("p", cl, S(m.value.due_date ? C(_e)(m.value.due_date) : "-"), 1), + o("p", ll, S(m.value.billable ? C(d)("tasks_projects.tasks.billable") : C(d)("tasks_projects.time.non_billable")), 1) + ]) + ]), + o("div", ul, [o("p", dl, S(C(d)("tasks_projects.tasks.detail.description")), 1), m.value.description ? (_(), a("p", fl, S(m.value.description), 1)) : (_(), a("p", pl, S(C(d)("tasks_projects.tasks.detail.no_description")), 1))]), + c(Rc, { + client: t.client, + notify: t.notify, + task: m.value, + members: v.value + }, null, 8, [ + "client", + "notify", + "task", + "members" + ]) + ], 64)) : i("", !0), + m.value ? (_(), r(Yi, { + key: 2, + show: k.value, + client: t.client, + notify: t.notify, + task: m.value, + statuses: g.value, + members: v.value, + projects: P.value, + onClose: l[2] ||= (e) => k.value = !1, + onSaved: oe, + onDeleted: ae + }, null, 8, [ + "show", + "client", + "notify", + "task", + "statuses", + "members", + "projects" + ])) : i("", !0) ]), - o("div", Qs, [o("p", $s, x(S(u)("tasks_projects.project.overview.budget")), 1), m.value ? (g(), a(e, { key: 0 }, [ - o("p", ec, x(S(u)("tasks_projects.project.overview.budget_used", { - used: S(ce)(d.value.logged_minutes), - total: S(ce)(m.value) - })), 1), - o("div", tc, [o("div", { - class: f(["h-2 rounded-full", v.value > 0 ? "bg-status-red" : "bg-primary-500"]), - style: p({ width: `${h.value}%` }) - }, null, 6)]), - v.value > 0 ? (g(), a("p", nc, x(S(u)("tasks_projects.project.overview.budget_over", { amount: S(ce)(v.value) })), 1)) : i("", !0) - ], 64)) : (g(), a("p", rc, x(S(u)("tasks_projects.project.overview.no_budget")), 1))]), - o("div", ic, [o("p", ac, x(S(u)("tasks_projects.project.overview.description")), 1), t.project.description ? (g(), a("p", oc, x(t.project.description), 1)) : (g(), a("p", sc, x(S(u)("tasks_projects.project.overview.no_description")), 1))]) - ])) : (g(), a("div", cc, [c(w, { class: "h-8 w-8 text-primary-500" })])); + _: 1 + }); }; } -}), uc = { class: "relative table-container" }, dc = ["onClick"], fc = { class: "inline-flex items-center" }, pc = { - key: 1, - class: "text-subtle" -}, mc = { - key: 1, - class: "text-subtle" -}, hc = 10, gc = 350, _c = /* @__PURE__ */ l({ - __name: "TaskList", +}), gl = ["aria-label"], _l = ["aria-current", "onClick"], vl = { class: "max-sm:hidden" }, yl = /* @__PURE__ */ l({ + __name: "ViewSwitcher", props: { - client: { type: [Function, Object] }, - notify: { type: Function }, - projectId: {}, - filterable: { type: Boolean } + active: {}, + query: {} }, - emits: ["changed"], - setup(e, { expose: t, emit: l }) { - let u = e, d = l, y = { - number: "number", - name: "name", - priority: "priority", - due_date: "due_date" - }, C = de(), O = v(null), k = v(!0), A = v(0), j = v([]), M = v([]), N = v([]), I = v(!1), L = v(null), R = v({}), z = v(null), B = _({ - search: "", - status: null, - assignee: null - }), H = n(() => j.value.map((e) => ({ - id: e.id, - label: e.name - }))), ee = n(() => M.value.map((e) => ({ - id: e.id, - label: e.name - }))), te = n(() => B.search.trim() !== "" || B.status !== null || B.assignee !== null), ne = n(() => !k.value && A.value === 0 && !te.value), re = n(() => [ - { - key: "number", - label: C("tasks_projects.tasks.columns.number"), - sortable: !0, - sortBy: "number", - tdClass: "text-muted" - }, - { - key: "name", - label: C("tasks_projects.tasks.columns.name"), - sortable: !0, - sortBy: "name", - thClass: "extra", - tdClass: "font-medium text-heading" - }, - { - key: "status", - label: C("tasks_projects.tasks.columns.status"), - sortable: !1 - }, - { - key: "assignee", - label: C("tasks_projects.tasks.columns.assignee"), - sortable: !1 - }, + emits: ["select"], + setup(t, { emit: r }) { + let i = t, s = r, l = B(), u = n(() => [ { - key: "priority", - label: C("tasks_projects.tasks.columns.priority"), - sortable: !0, - sortBy: "priority" + id: "list", + name: Ce.list, + label: l("tasks_projects.tasks.views.list"), + icon: "ListBulletIcon" }, { - key: "due_date", - label: C("tasks_projects.tasks.columns.due_date"), - sortable: !0, - sortBy: "due_date" + id: "board", + name: Ce.board, + label: l("tasks_projects.tasks.views.board"), + icon: "ViewColumnsIcon" }, { - key: "actions", - label: C("tasks_projects.general.actions"), - sortable: !1, - tdClass: "text-right text-sm font-medium" + id: "week", + name: Ce.week, + label: l("tasks_projects.tasks.views.week"), + icon: "CalendarDaysIcon" } - ]), ie = { - 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" - }, ae; - T(() => B.search, () => { - clearTimeout(ae), ae = setTimeout(() => le(), gc); - }), T([ - () => B.status, - () => B.assignee, - () => u.projectId - ], () => le()), h(() => { - se(); - }), m(() => clearTimeout(ae)); - async function se() { + ]); + function d(e) { + return i.active === e.name || e.id === "list" && i.active === Ce.tasks; + } + function p(e) { + d(e) || s("select", { + name: e.name, + query: i.query + }); + } + return (t, n) => { + let r = x("BaseIcon"); + return _(), a("nav", { + class: "inline-flex overflow-hidden rounded-lg border border-line-default", + "aria-label": C(l)("tasks_projects.tasks.title") + }, [(_(!0), a(e, null, b(u.value, (e) => (_(), a("button", { + key: e.id, + type: "button", + class: f(["flex items-center gap-1.5 border-r border-line-default px-3 py-1.5 text-sm font-medium last:border-r-0", d(e) ? "bg-primary-50 text-primary-500" : "bg-surface text-muted hover:bg-hover hover:text-heading"]), + "aria-current": d(e) ? "page" : void 0, + onClick: (t) => p(e) + }, [c(r, { + name: e.icon, + class: "h-4 w-4" + }, null, 8, ["name"]), o("span", vl, S(e.label), 1)], 10, _l))), 128))], 8, gl); + }; + } +}), bl = { class: "flex flex-wrap items-center justify-end gap-3" }, xl = 100, Sl = /* @__PURE__ */ l({ + __name: "TasksPage", + props: { + client: { type: [Function, Object] }, + notify: { type: Function }, + router: {} + }, + setup(e) { + let t = e, i = B(), a = t.router, l = y([]), u = y([]), d = y([]), p = y(!1), m = n(() => t.router.currentRoute.value), g = n(() => hi(m.value.query)), v = n(() => gi(g.value)), b = n(() => String(m.value.name ?? "")), w = n(() => d.value.map((e) => ({ + id: e.id, + label: e.name + }))); + E(b, (e) => T(e)), h(() => { + T(b.value), O(); + }); + function T(e) { + e === Ce.tasks && a.replace({ + name: Ce.list, + query: v.value + }); + } + async function O() { try { - j.value = await wo(u.client); + l.value = await En(t.client); } catch (e) { - u.notify("error", U(e, C("tasks_projects.task_statuses.load_failed"))); + t.notify("error", R(e, i("tasks_projects.task_statuses.load_failed"))); } try { - M.value = await V(u.client); + u.value = await Sn(t.client); } catch (e) { - u.notify("error", U(e, C("tasks_projects.tasks.members_failed"))); + t.notify("error", R(e, i("tasks_projects.tasks.members_failed"))); } - if (!u.projectId) try { - let e = await F(u.client, { - limit: 100, + try { + let e = await gn(t.client, { + limit: xl, status: "ACTIVE", sort_by: "name" }); - N.value = e.data.map((e) => ({ - id: e.id, - label: e.name - })); + d.value = e.data ?? []; } catch (e) { - u.notify("error", U(e, C("tasks_projects.tasks.projects_failed"))); + t.notify("error", R(e, i("tasks_projects.tasks.projects_failed"))); } } - async function ce({ page: e, sort: t }) { - let n = { - page: e, - limit: hc, - ...P(t, y) - }; - u.projectId && (n.project_id = u.projectId), B.status && (n.task_status_id = B.status.id), B.assignee && (n.assignee_id = B.assignee.id), B.search.trim() !== "" && (n.search = B.search.trim()), k.value = !0; - try { - let e = await To(u.client, n); - return A.value = e.meta.total, { - data: e.data, - pagination: { - totalPages: e.meta.last_page, - currentPage: e.meta.current_page, - totalCount: e.meta.total, - limit: e.meta.per_page - } - }; - } catch (e) { - return u.notify("error", U(e, C("tasks_projects.tasks.load_failed"))), { - data: [], - pagination: { - totalPages: 1, - currentPage: 1, - totalCount: 0, - limit: hc - } - }; - } finally { - k.value = !1; + function k(e) { + yi(e, g.value) || a.replace({ + name: b.value === Ce.tasks ? Ce.list : b.value, + query: gi(e) + }); + } + function A(e) { + a.push(e); + } + function j(e) { + p.value = !1, t.notify("success", i("tasks_projects.tasks.created", { name: e.name })), U(); + } + return (t, n) => { + let a = x("BaseBreadcrumbItem"), m = x("BaseBreadcrumb"), h = x("BaseIcon"), y = x("BaseButton"), T = x("router-link"), E = x("BasePageHeader"), O = x("router-view"), M = x("BasePage"); + return _(), r(M, null, { + default: D(() => [ + c(E, { title: C(i)("tasks_projects.tasks.title") }, { + actions: D(() => [o("div", bl, [ + c(yl, { + active: b.value, + query: v.value, + onSelect: A + }, null, 8, ["active", "query"]), + c(T, { to: C(H).projects }, { + default: D(() => [c(y, { variant: "white" }, { + left: D((e) => [c(h, { + name: "FolderIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: D(() => [s(" " + S(C(i)("tasks_projects.projects.title")), 1)]), + _: 1 + })]), + _: 1 + }, 8, ["to"]), + c(T, { to: C(H).reports }, { + default: D(() => [c(y, { variant: "white" }, { + left: D((e) => [c(h, { + name: "ChartBarIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: D(() => [s(" " + S(C(i)("tasks_projects.reports.title")), 1)]), + _: 1 + })]), + _: 1 + }, 8, ["to"]), + c(y, { + variant: "primary", + onClick: n[0] ||= (e) => p.value = !0 + }, { + left: D((e) => [c(h, { + name: "PlusIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: D(() => [s(" " + S(C(i)("tasks_projects.tasks.new_task")), 1)]), + _: 1 + }) + ])]), + default: D(() => [c(m, null, { + default: D(() => [c(a, { + title: C(i)("tasks_projects.general.home"), + to: "/admin/dashboard" + }, null, 8, ["title"]), c(a, { + title: C(i)("tasks_projects.tasks.title"), + to: "#", + active: "" + }, null, 8, ["title"])]), + _: 1 + })]), + _: 1 + }, 8, ["title"]), + c(Fi, { + "model-value": g.value, + projects: d.value, + members: u.value, + statuses: l.value, + "onUpdate:modelValue": k + }, null, 8, [ + "model-value", + "projects", + "members", + "statuses" + ]), + c(O, { + filters: g.value, + statuses: l.value, + members: u.value, + projects: d.value + }, null, 8, [ + "filters", + "statuses", + "members", + "projects" + ]), + c(Yi, { + show: p.value, + client: e.client, + notify: e.notify, + task: null, + statuses: l.value, + members: u.value, + projects: w.value, + defaults: { project_id: g.value.project === "" ? null : Number(g.value.project) }, + compact: "", + onClose: n[1] ||= (e) => p.value = !1, + onSaved: j + }, null, 8, [ + "show", + "client", + "notify", + "statuses", + "members", + "projects", + "defaults" + ]) + ]), + _: 1 + }); + }; + } +}); +//#endregion +//#region node_modules/.pnpm/sortablejs@1.15.7/node_modules/sortablejs/modular/sortable.esm.js +function Cl(e, t, n) { + return (t = Al(t)) in e ? Object.defineProperty(e, t, { + value: n, + enumerable: !0, + configurable: !0, + writable: !0 + }) : e[t] = n, e; +} +function wl() { + return wl = 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; + }, wl.apply(null, arguments); +} +function Tl(e, t) { + var n = Object.keys(e); + if (Object.getOwnPropertySymbols) { + var r = Object.getOwnPropertySymbols(e); + t && (r = r.filter(function(t) { + return Object.getOwnPropertyDescriptor(e, t).enumerable; + })), n.push.apply(n, r); + } + return n; +} +function El(e) { + for (var t = 1; t < arguments.length; t++) { + var n = arguments[t] == null ? {} : arguments[t]; + t % 2 ? Tl(Object(n), !0).forEach(function(t) { + Cl(e, t, n[t]); + }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(n)) : Tl(Object(n)).forEach(function(t) { + Object.defineProperty(e, t, Object.getOwnPropertyDescriptor(n, t)); + }); + } + return e; +} +function Dl(e, t) { + if (e == null) return {}; + var n, r, i = Ol(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 Ol(e, t) { + if (e == null) return {}; + var n = {}; + for (var r in e) if ({}.hasOwnProperty.call(e, r)) { + if (t.indexOf(r) !== -1) continue; + n[r] = e[r]; + } + return n; +} +function kl(e, t) { + if (typeof e != "object" || !e) return e; + var n = e[Symbol.toPrimitive]; + if (n !== void 0) { + var r = n.call(e, t || "default"); + if (typeof r != "object") return r; + throw TypeError("@@toPrimitive must return a primitive value."); + } + return (t === "string" ? String : Number)(e); +} +function Al(e) { + var t = kl(e, "string"); + return typeof t == "symbol" ? t : t + ""; +} +function jl(e) { + "@babel/helpers - typeof"; + return jl = 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; + }, jl(e); +} +var Ml = "1.15.7"; +function Nl(e) { + if (typeof window < "u" && window.navigator) return !!/*@__PURE__*/ navigator.userAgent.match(e); +} +var Pl = Nl(/(?:Trident.*rv[ :]?11\.|msie|iemobile|Windows Phone)/i), Fl = Nl(/Edge/i), Il = Nl(/firefox/i), Ll = Nl(/safari/i) && !Nl(/chrome/i) && !Nl(/android/i), Rl = Nl(/iP(ad|od|hone)/i), zl = Nl(/chrome/i) && Nl(/android/i), Bl = { + capture: !1, + passive: !1 +}; +function K(e, t, n) { + e.addEventListener(t, n, !Pl && Bl); +} +function q(e, t, n) { + e.removeEventListener(t, n, !Pl && Bl); +} +function Vl(e, t) { + if (t) { + if (t[0] === ">" && (t = t.substring(1)), e) try { + if (e.matches) return e.matches(t); + if (e.msMatchesSelector) return e.msMatchesSelector(t); + if (e.webkitMatchesSelector) return e.webkitMatchesSelector(t); + } catch { + return !1; + } + return !1; + } +} +function Hl(e) { + return e.host && e !== document && e.host.nodeType && e.host !== e ? e.host : e.parentNode; +} +function Ul(e, t, n, r) { + if (e) { + n ||= document; + do { + if (t != null && (t[0] === ">" ? e.parentNode === n && Vl(e, t) : Vl(e, t)) || r && e === n) return e; + if (e === n) break; + } while (e = Hl(e)); + } + return null; +} +var Wl = /\s+/g; +function Gl(e, t, n) { + e && t && (e.classList ? e.classList[n ? "add" : "remove"](t) : e.className = ((" " + e.className + " ").replace(Wl, " ").replace(" " + t + " ", " ") + (n ? " " + t : "")).replace(Wl, " ")); +} +function J(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 Kl(e, t) { + var n = ""; + if (typeof e == "string") n = e; + else do { + var r = J(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 ql(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); + return r; + } + return []; +} +function Jl() { + return document.scrollingElement || document.documentElement; +} +function Yl(e, t, n, r, i) { + if (e.getBoundingClientRect || e === window) { + var a, o, s, c, l, u, d; + if (e !== window && e.parentNode && e !== Jl() ? (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, !Pl)) do + if (i && i.getBoundingClientRect && (J(i, "transform") !== "none" || n && J(i, "position") !== "static")) { + var f = i.getBoundingClientRect(); + o -= f.top + parseInt(J(i, "border-top-width")), s -= f.left + parseInt(J(i, "border-left-width")), c = o + a.height, l = s + a.width; + break; } + while (i = i.parentNode); + if (r && e !== window) { + var p = Kl(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); } - function le(e = !1) { - O.value?.refresh(e); - } - function fe() { - B.search = "", B.status = null, B.assignee = null; + return { + top: o, + left: s, + bottom: c, + right: l, + width: d, + height: u + }; + } +} +function Xl(e, t, n) { + for (var r = nu(e, !0), i = Yl(e)[t]; r;) { + var a = Yl(r)[n], o = void 0; + if (o = n === "top" || n === "left" ? i >= a : i <= a, !o) return r; + if (r === Jl()) break; + r = nu(r, !1); + } + return !1; +} +function Zl(e, t, n, r) { + for (var i = 0, a = 0, o = e.children; a < o.length;) { + if (o[a].style.display !== "none" && o[a] !== $.ghost && (r || o[a] !== $.dragged) && Ul(o[a], n.draggable, e, !1)) { + if (i === t) return o[a]; + i++; } - function pe() { - L.value = null, R.value = { project_id: u.projectId ?? null }, I.value = !0; + a++; + } + return null; +} +function Ql(e, t) { + for (var n = e.lastElementChild; n && (n === $.ghost || J(n, "display") === "none" || t && !Vl(n, t));) n = n.previousElementSibling; + return n || null; +} +function $l(e, t) { + var n = 0; + if (!e || !e.parentNode) return -1; + for (; e = e.previousElementSibling;) e.nodeName.toUpperCase() !== "TEMPLATE" && e !== $.clone && (!t || Vl(e, t)) && n++; + return n; +} +function eu(e) { + var t = 0, n = 0, r = Jl(); + if (e) do { + var i = Kl(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 tu(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 nu(e, t) { + if (!e || !e.getBoundingClientRect) return Jl(); + var n = e, r = !1; + do + if (n.clientWidth < n.scrollWidth || n.clientHeight < n.scrollHeight) { + var i = J(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 Jl(); + if (r || t) return n; + r = !0; + } } - function me(e) { - L.value = e, R.value = {}, I.value = !0; + while (n = n.parentNode); + return Jl(); +} +function ru(e, t) { + if (e && t) for (var n in t) t.hasOwnProperty(n) && (e[n] = t[n]); + return e; +} +function iu(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 au; +function ou(e, t) { + return function() { + if (!au) { + var n = arguments, r = this; + n.length === 1 ? e.call(r, n[0]) : e.apply(r, n), au = setTimeout(function() { + au = void 0; + }, t); } - function he(e) { - let t = L.value ? C("tasks_projects.tasks.updated", { name: e.name }) : C("tasks_projects.tasks.created", { name: e.name }); - I.value = !1, L.value = null, u.notify("success", t), le(!0), d("changed"); + }; +} +function su() { + clearTimeout(au), au = void 0; +} +function cu(e, t, n) { + e.scrollLeft += t, e.scrollTop += n; +} +function lu(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 uu(e, t, n) { + var r = {}; + return Array.from(e.children).forEach(function(i) { + if (Ul(i, t.draggable, e, !1) && !i.animated && i !== n) { + var a = Yl(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); } - function ge(e) { - I.value = !1, L.value = null, u.notify("success", C("tasks_projects.tasks.deleted", { name: e.name })), le(!0), d("changed"); + }), r.width = r.right - r.left, r.height = r.bottom - r.top, r.x = r.left, r.y = r.top, r; +} +var du = "Sortable" + (/* @__PURE__ */ new Date()).getTime(); +function fu() { + var e = [], t; + return { + captureAnimationState: function() { + e = [], this.options.animation && [].slice.call(this.el.children).forEach(function(t) { + if (J(t, "display") !== "none" && t !== $.ghost) { + e.push({ + target: t, + rect: Yl(t) + }); + var n = El({}, e[e.length - 1].rect); + if (t.thisAnimationDuration) { + var r = Kl(t, !0); + r && (n.top -= r.f, n.left -= r.e); + } + t.fromRect = n; + } + }); + }, + addAnimationState: function(t) { + e.push(t); + }, + removeAnimationState: function(t) { + e.splice(tu(e, { target: t }), 1); + }, + animateAll: function(n) { + var r = this; + if (!this.options.animation) { + clearTimeout(t), typeof n == "function" && n(); + return; + } + var i = !1, a = 0; + e.forEach(function(e) { + var t = 0, n = e.target, o = n.fromRect, s = Yl(n), c = n.prevFromRect, l = n.prevToRect, u = e.rect, d = Kl(n, !0); + d && (s.top -= d.f, s.left -= d.e), n.toRect = s, n.thisAnimationDuration && iu(c, s) && !iu(o, s) && (u.top - s.top) / (u.left - s.left) === (o.top - s.top) / (o.left - s.left) && (t = mu(u, c, l, r.options)), iu(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() { + typeof n == "function" && n(); + }, a) : typeof n == "function" && n(), e = []; + }, + animate: function(e, t, n, r) { + if (r) { + J(e, "transition", ""), J(e, "transform", ""); + var i = Kl(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, J(e, "transform", "translate3d(" + s + "px," + c + "px,0)"), this.forRepaintDummy = pu(e), J(e, "transition", "transform " + r + "ms" + (this.options.easing ? " " + this.options.easing : "")), J(e, "transform", "translate3d(0,0,0)"), typeof e.animated == "number" && clearTimeout(e.animated), e.animated = setTimeout(function() { + J(e, "transition", ""), J(e, "transform", ""), e.animated = !1, e.animatingX = !1, e.animatingY = !1; + }, r); + } } - function _e(e) { - return j.value.find((t) => t.id === e.task_status_id) ?? null; + }; +} +function pu(e) { + return e.offsetWidth; +} +function mu(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 hu = [], gu = { initializeByDefault: !0 }, _u = { + mount: function(e) { + for (var t in gu) gu.hasOwnProperty(t) && !(t in e) && (e[t] = gu[t]); + hu.forEach(function(t) { + if (t.pluginName === e.pluginName) throw `Sortable: Cannot mount plugin ${e.pluginName} more than once`; + }), hu.push(e); + }, + pluginEvent: function(e, t, n) { + var r = this; + this.eventCanceled = !1, n.cancel = function() { + r.eventCanceled = !0; + }; + var i = e + "Global"; + hu.forEach(function(r) { + t[r.pluginName] && (t[r.pluginName][i] && t[r.pluginName][i](El({ sortable: t }, n)), t.options[r.pluginName] && t[r.pluginName][e] && t[r.pluginName][e](El({ sortable: t }, n))); + }); + }, + initializePlugins: function(e, t, n, r) { + for (var i in hu.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, wl(n, a.defaults); + } + }), e.options) if (e.options.hasOwnProperty(i)) { + var a = this.modifyOption(e, i, e.options[i]); + a !== void 0 && (e.options[i] = a); } - function ve(e) { - return e.assignee_id === null ? C("tasks_projects.tasks.unassigned") : M.value.find((t) => t.id === e.assignee_id)?.name ?? `#${e.assignee_id}`; + }, + getEventProperties: function(e, t) { + var n = {}; + return hu.forEach(function(r) { + typeof r.eventProperties == "function" && wl(n, r.eventProperties.call(t[r.pluginName], e)); + }), n; + }, + modifyOption: function(e, t, n) { + var r; + return hu.forEach(function(i) { + e[i.pluginName] && i.optionListeners && typeof i.optionListeners[t] == "function" && (r = i.optionListeners[t].call(e[i.pluginName], n)); + }), r; + } +}; +function vu(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[du], t) { + var h, g = t.options, _ = "on" + r.charAt(0).toUpperCase() + r.substr(1); + window.CustomEvent && !Pl && !Fl ? 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 = El(El({}, m), _u.getEventProperties(r, t)); + for (var y in v) h[y] = v[y]; + n && n.dispatchEvent(h), g[_] && g[_].call(t, h); + } +} +var yu = ["evt"], bu = function(e, t) { + var n = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : {}, r = n.evt, i = Dl(n, yu); + _u.pluginEvent.bind($)(e, t, El({ + dragEl: Y, + parentEl: Su, + ghostEl: X, + rootEl: Z, + nextEl: Cu, + lastDownEl: wu, + cloneEl: Q, + cloneHidden: Tu, + dragStarted: Vu, + putSortable: ju, + activeSortable: $.active, + originalEvent: r, + oldIndex: Eu, + oldDraggableIndex: Ou, + newIndex: Du, + newDraggableIndex: ku, + hideGhostForTarget: od, + unhideGhostForTarget: sd, + cloneNowHidden: function() { + Tu = !0; + }, + cloneNowShown: function() { + Tu = !1; + }, + dispatchSortableEvent: function(e) { + xu({ + sortable: t, + name: e, + originalEvent: r + }); } - function ye(e) { - return C(`tasks_projects.tasks.priority.${e.toLowerCase()}`); + }, i)); +}; +function xu(e) { + vu(El({ + putSortable: ju, + cloneEl: Q, + targetEl: Y, + rootEl: Z, + oldIndex: Eu, + oldDraggableIndex: Ou, + newIndex: Du, + newDraggableIndex: ku + }, e)); +} +var Y, Su, X, Z, Cu, wu, Q, Tu, Eu, Du, Ou, ku, Au, ju, Mu = !1, Nu = !1, Pu = [], Fu, Iu, Lu, Ru, zu, Bu, Vu, Hu, Uu, Wu = !1, Gu = !1, Ku, qu, Ju = [], Yu = !1, Xu = [], Zu = typeof document < "u", Qu = Rl, $u = Fl || Pl ? "cssFloat" : "float", ed = Zu && !zl && !Rl && "draggable" in document.createElement("div"), td = function() { + if (Zu) { + if (Pl) return !1; + var e = document.createElement("x"); + return e.style.cssText = "pointer-events:auto", e.style.pointerEvents === "auto"; + } +}(), nd = function(e, t) { + var n = J(e), r = parseInt(n.width) - parseInt(n.paddingLeft) - parseInt(n.paddingRight) - parseInt(n.borderLeftWidth) - parseInt(n.borderRightWidth), i = Zl(e, 0, t), a = Zl(e, 1, t), o = i && J(i), s = a && J(a), c = o && parseInt(o.marginLeft) + parseInt(o.marginRight) + Yl(i).width, l = s && parseInt(s.marginLeft) + parseInt(s.marginRight) + Yl(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[$u] === "none" || a && n[$u] === "none" && c + l > r) ? "vertical" : "horizontal"; +}, rd = 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; +}, id = function(e, t) { + var n; + return Pu.some(function(r) { + var i = r[du].options.emptyInsertThreshold; + if (i && !Ql(r)) { + var a = Yl(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; } - function be(e) { - return ie[e]; + }), n; +}, ad = 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; + if (e == null && (n || s)) return !0; + if (e == null || e === !1) return !1; + if (n && e === "clone") return e; + if (typeof e == "function") return t(e(r, i, a, o), n)(r, i, a, o); + var c = (n ? r : i).options.group.name; + return e === !0 || typeof e == "string" && e === c || e.join && e.indexOf(c) > -1; + }; + } + var n = {}, r = e.group; + (!r || jl(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; +}, od = function() { + !td && X && J(X, "display", "none"); +}, sd = function() { + !td && X && J(X, "display", ""); +}; +Zu && !zl && document.addEventListener("click", function(e) { + if (Nu) return e.preventDefault(), e.stopPropagation && e.stopPropagation(), e.stopImmediatePropagation && e.stopImmediatePropagation(), Nu = !1, !1; +}, !0); +var cd = function(e) { + if (Y) { + e = e.touches ? e.touches[0] : e; + var t = id(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[du]._onDragOver(n); } - async function xe(e) { - if (window.confirm(C("tasks_projects.tasks.delete_confirm", { name: e.name }))) { - z.value = e.id; - try { - await Oo(u.client, e.id), u.notify("success", C("tasks_projects.tasks.deleted", { name: e.name })), le(!0), d("changed"); - } catch (e) { - u.notify("error", U(e, C("tasks_projects.tasks.delete_failed"))); - } finally { - z.value = null; + } +}, ld = function(e) { + Y && Y.parentNode[du]._isOutsideThisEl(e.target); +}; +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 = wl({}, t), e[du] = this; + var n = { + group: null, + sort: !0, + disabled: !1, + store: null, + handle: null, + draggable: /^[uo]l$/i.test(e.nodeName) ? ">li" : ">*", + swapThreshold: 1, + invertSwap: !1, + invertedSwapThreshold: null, + removeCloneOnHide: !0, + direction: function() { + return nd(e, this.options); + }, + ghostClass: "sortable-ghost", + chosenClass: "sortable-chosen", + dragClass: "sortable-drag", + ignore: "a, img", + filter: null, + preventOnFilter: !0, + animation: 0, + easing: null, + setData: function(e, t) { + e.setData("Text", t.textContent); + }, + dropBubble: !1, + dragoverBubble: !1, + dataIdAttr: "data-id", + delay: 0, + delayOnTouchOnly: !1, + touchStartThreshold: (Number.parseInt ? Number : window).parseInt(window.devicePixelRatio, 10) || 1, + forceFallback: !1, + fallbackClass: "sortable-fallback", + fallbackOnBody: !1, + fallbackTolerance: 0, + fallbackOffset: { + x: 0, + y: 0 + }, + supportPointer: $.supportPointer !== !1 && "PointerEvent" in window && (!Ll || Rl), + emptyInsertThreshold: 5 + }; + for (var r in _u.initializePlugins(this, e, n), n) !(r in t) && (t[r] = n[r]); + for (var i in ad(t), this) i.charAt(0) === "_" && typeof this[i] == "function" && (this[i] = this[i].bind(this)); + this.nativeDraggable = !t.forceFallback && ed, this.nativeDraggable && (this.options.touchStartThreshold = 1), t.supportPointer ? K(e, "pointerdown", this._onTapStart) : (K(e, "mousedown", this._onTapStart), K(e, "touchstart", this._onTapStart)), this.nativeDraggable && (K(e, "dragover", this), K(e, "dragenter", this)), Pu.push(this.el), t.store && t.store.get && this.sort(t.store.get(this) || []), wl(this, fu()); +} +$.prototype = { + constructor: $, + _isOutsideThisEl: function(e) { + !this.el.contains(e) && e !== this.el && (Hu = null); + }, + _getDirection: function(e, t) { + return typeof this.options.direction == "function" ? this.options.direction.call(this, e, t, Y) : 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 (yd(n), !Y && !(/mousedown|pointerdown/.test(a) && e.button !== 0 || r.disabled) && !c.isContentEditable && !(!this.nativeDraggable && Ll && s && s.tagName.toUpperCase() === "SELECT") && (s = Ul(s, r.draggable, n, !1), !(s && s.animated) && wu !== s)) { + if (Eu = $l(s), Ou = $l(s, r.draggable), typeof l == "function") { + if (l.call(this, e, s, this)) { + xu({ + sortable: t, + rootEl: c, + name: "filter", + targetEl: s, + toEl: n, + fromEl: n + }), bu("filter", t, { evt: e }), i && e.preventDefault(); + return; + } + } else if (l && (l = l.split(",").some(function(r) { + if (r = Ul(c, r.trim(), n, !1), r) return xu({ + sortable: t, + rootEl: r, + name: "filter", + targetEl: s, + fromEl: n, + toEl: n + }), bu("filter", t, { evt: e }), !0; + }), l)) { + i && e.preventDefault(); + return; } + (!r.handle || Ul(c, r.handle, n, !1)) && this._prepareDragStart(e, o, s); } } - return t({ - openCreate: pe, - refresh: le - }), (t, n) => { - let l = b("BaseInput"), u = b("BaseInputGroup"), d = b("BaseSelectInput"), m = b("BaseFilterWrapper"), h = b("BaseIcon"), _ = b("BaseButton"), v = b("BaseEmptyPlaceholder"), y = b("BaseDropdownItem"), T = b("BaseDropdown"), k = b("BaseTable"); - return g(), a("div", null, [ - e.filterable ? (g(), r(m, { - key: 0, - show: !0, - class: "mt-3", - onClear: fe - }, { - default: E(() => [ - c(u, { - label: S(C)("tasks_projects.general.search"), - class: "mt-2 flex-1" - }, { - default: E(() => [c(l, { - modelValue: B.search, - "onUpdate:modelValue": n[0] ||= (e) => B.search = e, - type: "text", - name: "search", - autocomplete: "off", - placeholder: S(C)("tasks_projects.tasks.search_placeholder") - }, null, 8, ["modelValue", "placeholder"])]), - _: 1 - }, 8, ["label"]), - c(u, { - label: S(C)("tasks_projects.tasks.columns.status"), - class: "mt-2 flex-1" - }, { - default: E(() => [c(d, { - modelValue: B.status, - "onUpdate:modelValue": n[1] ||= (e) => B.status = e, - options: H.value, - placeholder: S(C)("tasks_projects.tasks.all_tasks"), - "label-key": "label" - }, null, 8, [ - "modelValue", - "options", - "placeholder" - ])]), - _: 1 - }, 8, ["label"]), - c(u, { - label: S(C)("tasks_projects.tasks.columns.assignee"), - class: "mt-2 flex-1" - }, { - default: E(() => [c(d, { - modelValue: B.assignee, - "onUpdate:modelValue": n[2] ||= (e) => B.assignee = e, - options: ee.value, - placeholder: S(C)("tasks_projects.board.filters.all_assignees"), - "label-key": "label" - }, null, 8, [ - "modelValue", - "options", - "placeholder" - ])]), - _: 1 - }, 8, ["label"]) - ]), - _: 1 - })) : i("", !0), - D(c(v, { - title: S(C)("tasks_projects.tasks.empty_title"), - description: S(C)("tasks_projects.tasks.empty_description") - }, { - actions: E(() => [c(_, { - variant: "primary", - onClick: pe - }, { - left: E((e) => [c(h, { - name: "PlusIcon", - class: f(e.class) - }, null, 8, ["class"])]), - default: E(() => [s(" " + x(S(C)("tasks_projects.tasks.new_task")), 1)]), - _: 1 - })]), - default: E(() => [c(h, { - name: "ClipboardDocumentListIcon", - class: "mt-5 mb-4 h-16 w-16 text-subtle" - })]), - _: 1 - }, 8, ["title", "description"]), [[w, ne.value]]), - D(o("div", uc, [c(k, { - ref_key: "tableRef", - ref: O, - data: ce, - columns: re.value, - class: "mt-3" - }, { - "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) => me(e.data) - }, x(e.data.name), 9, dc)]), - "cell-status": E(({ row: e }) => [o("span", fc, [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: 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", pc, "-"))]), - "cell-due_date": E(({ row: e }) => [e.data.due_date ? (g(), a("span", { - key: 0, - class: f(S(ue)(e.data.due_date) && !e.data.closed_at ? "font-medium text-status-red" : "") - }, x(S(oe)(e.data.due_date)), 3)) : (g(), a("span", mc, "-"))]), - "cell-actions": E(({ row: e }) => [c(T, { "content-loading": z.value === e.data.id }, { - activator: E(() => [c(h, { - name: "EllipsisHorizontalIcon", - class: "h-5 text-muted" - })]), - default: E(() => [c(y, { onClick: (t) => me(e.data) }, { - default: E(() => [c(h, { - name: "PencilIcon", - class: "mr-3 h-5 w-5 text-subtle group-hover:text-muted" - }), s(" " + x(S(C)("tasks_projects.general.edit")), 1)]), - _: 1 - }, 8, ["onClick"]), c(y, { onClick: (t) => xe(e.data) }, { - default: E(() => [c(h, { - name: "TrashIcon", - class: "mr-3 h-5 w-5 text-subtle group-hover:text-muted" - }), s(" " + x(S(C)("tasks_projects.general.delete")), 1)]), - _: 1 - }, 8, ["onClick"])]), - _: 2 - }, 1032, ["content-loading"])]), - _: 1 - }, 8, ["columns"])], 512), [[w, !ne.value]]), - c(Ho, { - show: I.value, - client: e.client, - notify: e.notify, - task: L.value, - statuses: j.value, - members: M.value, - projects: N.value, - defaults: R.value, - "lock-project": !!e.projectId, - onClose: n[3] ||= (e) => I.value = !1, - onSaved: he, - onDeleted: ge - }, null, 8, [ - "show", - "client", - "notify", - "task", - "statuses", - "members", - "projects", - "defaults", - "lock-project" - ]) - ]); - }; - } -}), vc = { class: "py-4" }, yc = { class: "flex justify-end" }, bc = /* @__PURE__ */ l({ - __name: "ProjectTasksTab", - props: { - id: {}, - client: { type: [Function, Object] }, - notify: { type: Function }, - project: {} }, - emits: ["refresh"], - setup(e, { emit: t }) { - let r = e, i = t, l = de(), u = v(null), d = n(() => r.project?.id ?? Number(r.id)); - function p() { - i("refresh"); + _prepareDragStart: function(e, t, n) { + var r = this, i = r.el, a = r.options, o = i.ownerDocument, s; + if (n && !Y && n.parentNode === i) { + var c = Yl(n); + if (Z = i, Y = n, Su = Y.parentNode, Cu = Y.nextSibling, wu = n, Au = a.group, $.dragged = Y, Fu = { + target: Y, + clientX: (t || e).clientX, + clientY: (t || e).clientY + }, zu = Fu.clientX - c.left, Bu = Fu.clientY - c.top, this._lastX = (t || e).clientX, this._lastY = (t || e).clientY, Y.style["will-change"] = "all", s = function() { + if (bu("delayEnded", r, { evt: e }), $.eventCanceled) { + r._onDrop(); + return; + } + r._disableDelayedDragEvents(), !Il && r.nativeDraggable && (Y.draggable = !0), r._triggerDragStart(e, t), xu({ + sortable: r, + name: "choose", + originalEvent: e + }), Gl(Y, a.chosenClass, !0); + }, a.ignore.split(",").forEach(function(e) { + ql(Y, e.trim(), fd); + }), K(o, "dragover", cd), K(o, "mousemove", cd), K(o, "touchmove", cd), a.supportPointer ? (K(o, "pointerup", r._onDrop), !this.nativeDraggable && K(o, "pointercancel", r._onDrop)) : (K(o, "mouseup", r._onDrop), K(o, "touchend", r._onDrop), K(o, "touchcancel", r._onDrop)), Il && this.nativeDraggable && (this.options.touchStartThreshold = 4, Y.draggable = !0), bu("delayStart", this, { evt: e }), a.delay && (!a.delayOnTouchOnly || t) && (!this.nativeDraggable || !(Fl || Pl))) { + if ($.eventCanceled) { + this._onDrop(); + return; + } + a.supportPointer ? (K(o, "pointerup", r._disableDelayedDrag), K(o, "pointercancel", r._disableDelayedDrag)) : (K(o, "mouseup", r._disableDelayedDrag), K(o, "touchend", r._disableDelayedDrag), K(o, "touchcancel", r._disableDelayedDrag)), K(o, "mousemove", r._delayedDragTouchMoveHandler), K(o, "touchmove", r._delayedDragTouchMoveHandler), a.supportPointer && K(o, "pointermove", r._delayedDragTouchMoveHandler), r._dragStartTimer = setTimeout(s, a.delay); + } else s(); + } + }, + _delayedDragTouchMoveHandler: function(e) { + var t = e.touches ? e.touches[0] : e; + 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() { + Y && fd(Y), clearTimeout(this._dragStartTimer), this._disableDelayedDragEvents(); + }, + _disableDelayedDragEvents: function() { + var e = this.el.ownerDocument; + q(e, "mouseup", this._disableDelayedDrag), q(e, "touchend", this._disableDelayedDrag), q(e, "touchcancel", this._disableDelayedDrag), q(e, "pointerup", this._disableDelayedDrag), q(e, "pointercancel", this._disableDelayedDrag), q(e, "mousemove", this._delayedDragTouchMoveHandler), q(e, "touchmove", this._delayedDragTouchMoveHandler), q(e, "pointermove", this._delayedDragTouchMoveHandler); + }, + _triggerDragStart: function(e, t) { + t ||= e.pointerType == "touch" && e, !this.nativeDraggable || t ? this.options.supportPointer ? K(document, "pointermove", this._onTouchMove) : t ? K(document, "touchmove", this._onTouchMove) : K(document, "mousemove", this._onTouchMove) : (K(Y, "dragend", this), K(Z, "dragstart", this._onDragStart)); + try { + document.selection ? bd(function() { + document.selection.empty(); + }) : window.getSelection().removeAllRanges(); + } catch {} + }, + _dragStarted: function(e, t) { + if (Mu = !1, Z && Y) { + bu("dragStarted", this, { evt: t }), this.nativeDraggable && K(document, "dragover", ld); + var n = this.options; + !e && Gl(Y, n.dragClass, !1), Gl(Y, n.ghostClass, !0), $.active = this, e && this._appendGhost(), xu({ + sortable: this, + name: "start", + originalEvent: t + }); + } else this._nulling(); + }, + _emulateDragOver: function() { + if (Iu) { + this._lastX = Iu.clientX, this._lastY = Iu.clientY, od(); + for (var e = document.elementFromPoint(Iu.clientX, Iu.clientY), t = e; e && e.shadowRoot && (e = e.shadowRoot.elementFromPoint(Iu.clientX, Iu.clientY), e !== t);) t = e; + if (Y.parentNode[du]._isOutsideThisEl(e), t) do { + if (t[du]) { + var n = void 0; + if (n = t[du]._onDragOver({ + clientX: Iu.clientX, + clientY: Iu.clientY, + target: e, + rootEl: t + }), n && !this.options.dragoverBubble) break; + } + e = t; + } while (t = Hl(t)); + sd(); } - return (t, n) => { - let r = b("BaseIcon"), i = b("BaseButton"); - return g(), a("div", vc, [o("div", yc, [c(i, { - variant: "primary", - onClick: n[0] ||= (e) => u.value?.openCreate() - }, { - left: E((e) => [c(r, { - name: "PlusIcon", - class: f(e.class) - }, null, 8, ["class"])]), - default: E(() => [s(" " + x(S(l)("tasks_projects.tasks.new_task")), 1)]), - _: 1 - })]), c(_c, { - ref_key: "listRef", - ref: u, - client: e.client, - notify: e.notify, - "project-id": d.value, - filterable: "", - onChanged: p - }, null, 8, [ - "client", - "notify", - "project-id" - ])]); - }; - } -}), xc = { class: "relative py-4 table-container" }, Sc = { class: "font-medium text-heading" }, Cc = { - key: 0, - class: "block text-xs text-muted" -}, wc = { - key: 0, - class: "text-primary-500" -}, Tc = { key: 1 }, Ec = { - key: 1, - class: "text-subtle" -}, Dc = 15, Oc = /* @__PURE__ */ l({ - __name: "ProjectTimeTab", - props: { - id: {}, - client: { type: [Function, Object] }, - notify: { type: Function }, - project: {} }, - setup(e) { - let t = e, l = de(), 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"), - sortable: !1 - }, - { - key: "user", - label: l("tasks_projects.project.time.columns.member"), - sortable: !1 - }, - { - key: "task", - label: l("tasks_projects.project.time.columns.task"), - sortable: !1, - thClass: "extra" - }, - { - key: "duration_minutes", - label: l("tasks_projects.project.time.columns.minutes"), - sortable: !1 - }, - { - key: "billable", - label: l("tasks_projects.project.time.columns.billable"), - sortable: !1 - }, - { - key: "amount", - label: l("tasks_projects.project.time.columns.amount"), - sortable: !1, - tdClass: "text-right" - } - ]); - h(() => { - m(); - }); - async function m() { - try { - u.value = await V(t.client); - } catch (e) { - t.notify("error", U(e, l("tasks_projects.tasks.members_failed"))); - } - try { - let e = await To(t.client, { - project_id: f.value, - limit: 100 - }); - d.value = e.data; - } catch (e) { - t.notify("error", U(e, l("tasks_projects.tasks.load_failed"))); + _onTouchMove: function(e) { + if (Fu) { + var t = this.options, n = t.fallbackTolerance, r = t.fallbackOffset, i = e.touches ? e.touches[0] : e, a = X && Kl(X, !0), o = X && a && a.a, s = X && a && a.d, c = Qu && qu && eu(qu), l = (i.clientX - Fu.clientX + r.x) / (o || 1) + (c ? c[0] - Ju[0] : 0) / (o || 1), u = (i.clientY - Fu.clientY + r.y) / (s || 1) + (c ? c[1] - Ju[1] : 0) / (s || 1); + if (!$.active && !Mu) { + if (n && Math.max(Math.abs(i.clientX - this._lastX), Math.abs(i.clientY - this._lastY)) < n) return; + this._onDragStart(e, !0); } - } - async function _({ page: e }) { - let n = { - page: e, - limit: Dc, - project_id: f.value - }; - try { - let e = await Po(t.client, n); - return { - data: e.data, - pagination: { - totalPages: e.meta.last_page, - currentPage: e.meta.current_page, - totalCount: e.meta.total, - limit: e.meta.per_page - } - }; - } catch (e) { - return t.notify("error", U(e, l("tasks_projects.project.time.load_failed"))), { - data: [], - pagination: { - totalPages: 1, - currentPage: 1, - totalCount: 0, - limit: Dc - } + if (X) { + a ? (a.e += l - (Lu || 0), a.f += u - (Ru || 0)) : a = { + a: 1, + b: 0, + c: 0, + d: 1, + e: l, + f: u }; + var d = `matrix(${a.a},${a.b},${a.c},${a.d},${a.e},${a.f})`; + J(X, "webkitTransform", d), J(X, "mozTransform", d), J(X, "msTransform", d), J(X, "transform", d), Lu = l, Ru = u, Iu = i; } + e.cancelable && e.preventDefault(); } - function y(e) { - return u.value.find((t) => t.id === e)?.name ?? l("tasks_projects.project.time.removed_member"); + }, + _appendGhost: function() { + if (!X) { + var e = this.options.fallbackOnBody ? document.body : Z, t = Yl(Y, !0, Qu, !0, e), n = this.options; + if (Qu) { + for (qu = e; J(qu, "position") === "static" && J(qu, "transform") === "none" && qu !== document;) qu = qu.parentNode; + qu !== document.body && qu !== document.documentElement ? (qu === document && (qu = Jl()), t.top += qu.scrollTop, t.left += qu.scrollLeft) : qu = Jl(), Ju = eu(qu); + } + X = Y.cloneNode(!0), Gl(X, n.ghostClass, !1), Gl(X, n.fallbackClass, !0), Gl(X, n.dragClass, !0), J(X, "transition", ""), J(X, "transform", ""), J(X, "box-sizing", "border-box"), J(X, "margin", 0), J(X, "top", t.top), J(X, "left", t.left), J(X, "width", t.width), J(X, "height", t.height), J(X, "opacity", "0.8"), J(X, "position", Qu ? "absolute" : "fixed"), J(X, "zIndex", "100000"), J(X, "pointerEvents", "none"), $.ghost = X, e.appendChild(X), J(X, "transform-origin", zu / parseInt(X.style.width) * 100 + "% " + Bu / parseInt(X.style.height) * 100 + "%"); } - function C(e) { - return d.value.find((t) => t.id === e)?.name ?? `#${e}`; + }, + _onDragStart: function(e, t) { + var n = this, r = e.dataTransfer, i = n.options; + if (bu("dragStart", this, { evt: e }), $.eventCanceled) { + this._onDrop(); + return; } - return (e, t) => { - let n = b("BaseIcon"), u = b("BaseFormatMoney"), d = b("BaseTable"); - return g(), a("div", xc, [c(d, { - data: _, - columns: p.value - }, { - "cell-started_at": E(({ row: e }) => [s(x(e.data.started_at ? S(oe)(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", Sc, x(C(e.data.task_id)), 1), e.data.description ? (g(), a("span", Cc, x(e.data.description), 1)) : i("", !0)]), - "cell-duration_minutes": E(({ row: e }) => [e.data.is_running ? (g(), a("span", wc, x(S(l)("tasks_projects.project.time.running")), 1)) : (g(), a("span", Tc, x(S(ce)(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" - })) : (g(), a("span", Ec, "-"))]), - "cell-amount": E(({ row: e }) => [c(u, { amount: e.data.amount }, null, 8, ["amount"])]), - _: 1 - }, 8, ["columns"])]); - }; - } -}), kc = { class: "flex items-center justify-end space-x-5" }, Ac = /* @__PURE__ */ l({ - __name: "TasksPage", - props: { - client: { type: [Function, Object] }, - notify: { type: Function } + bu("setupClone", this), $.eventCanceled || (Q = lu(Y), Q.removeAttribute("id"), Q.draggable = !1, Q.style["will-change"] = "", this._hideClone(), Gl(Q, this.options.chosenClass, !1), $.clone = Q), n.cloneId = bd(function() { + bu("clone", n), !$.eventCanceled && (n.options.removeCloneOnHide || Z.insertBefore(Q, Y), n._hideClone(), xu({ + sortable: n, + name: "clone" + })); + }), !t && Gl(Y, i.dragClass, !0), t ? (Nu = !0, n._loopId = setInterval(n._emulateDragOver, 50)) : (q(document, "mouseup", n._onDrop), q(document, "touchend", n._onDrop), q(document, "touchcancel", n._onDrop), r && (r.effectAllowed = "move", i.setData && i.setData.call(n, r, Y)), K(document, "drop", n), J(Y, "transform", "translateZ(0)")), Mu = !0, n._dragStartId = bd(n._dragStarted.bind(n, t, e)), K(document, "selectstart", n), Vu = !0, window.getSelection().removeAllRanges(), Ll && J(document.body, "user-select", "none"); }, - setup(e) { - let t = de(), 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", kc, [c(m, { to: "/admin/modules/tasks-projects/board" }, { - default: E(() => [c(p, { variant: "white" }, { - left: E((e) => [c(d, { - name: "ViewColumnsIcon", - class: f(e.class) - }, null, 8, ["class"])]), - default: E(() => [s(" " + x(S(t)("tasks_projects.board.title")), 1)]), - _: 1 - })]), - _: 1 - }), c(p, { - variant: "primary", - onClick: a[0] ||= (e) => n.value?.openCreate() - }, { - left: E((e) => [c(d, { - name: "PlusIcon", - class: f(e.class) - }, null, 8, ["class"])]), - default: E(() => [s(" " + x(S(t)("tasks_projects.tasks.new_task")), 1)]), - _: 1 - })])]), - default: E(() => [c(u, null, { - default: E(() => [ - c(l, { - title: S(t)("tasks_projects.general.home"), - to: "/admin/dashboard" - }, null, 8, ["title"]), - c(l, { - title: S(t)("tasks_projects.projects.title"), - to: "/admin/modules/tasks-projects" - }, null, 8, ["title"]), - c(l, { - title: S(t)("tasks_projects.tasks.title"), - to: "#", - active: "" - }, null, 8, ["title"]) - ]), - _: 1 - })]), - _: 1 - }, 8, ["title"]), c(_c, { - ref_key: "listRef", - ref: n, - client: e.client, - notify: e.notify, - filterable: "" - }, null, 8, ["client", "notify"])]), - _: 1 - }); - }; - } -}); -//#endregion -//#region resources/js/support/page.ts -function jc(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/registrations/board.ts -var Mc = "tasks-projects", Nc = { - viewProject: `${Mc}:view-project`, - editProject: `${Mc}:edit-project`, - viewTask: `${Mc}:view-task`, - viewOwnTime: `${Mc}:view-own-time` -}; -function Pc(e) { - e.addMessages(oi), e.registerPage({ - id: "board", - module: Mc, - path: "board", - component: jc(e, us), - meta: { - ability: Nc.viewTask, - title: "tasks_projects.board.title" + _onDragOver: function(e) { + var t = this.el, n = e.target, r, i, a, o = this.options, s = o.group, c = $.active, l = Au === s, u = o.sort, d = ju || c, f, p = this, m = !1; + if (Yu) return; + function h(o, s) { + bu(o, p, El({ + evt: e, + isOwner: l, + axis: f ? "vertical" : "horizontal", + revert: a, + dragRect: r, + targetRect: i, + canSort: u, + fromSortable: d, + target: n, + completed: _, + onMove: function(n, i) { + return dd(Z, t, Y, r, n, Yl(n), e, i); + }, + changed: v + }, s)); } - }), e.registerPage({ - id: "tasks", - module: Mc, - path: "tasks", - component: jc(e, Ac), - meta: { - ability: Nc.viewTask, - title: "tasks_projects.tasks.title" + function g() { + h("dragOverAnimationCapture"), p.captureAnimationState(), p !== d && d.captureAnimationState(); } - }), e.registerPage({ - id: "project", - module: Mc, - path: "projects/:id", - component: jc(e, Cs), - meta: { - ability: Nc.viewProject, - title: "tasks_projects.projects.title" - }, - children: [ - { - id: "overview", - path: "", - component: jc(e, lc), - meta: { - ability: Nc.viewProject, - title: "tasks_projects.project.tabs.overview" - } - }, - { - id: "tasks", - path: "tasks", - component: jc(e, bc), - meta: { - ability: Nc.viewTask, - title: "tasks_projects.project.tabs.tasks" - } - }, - { - id: "time", - path: "time", - component: jc(e, Oc), - meta: { - ability: Nc.viewOwnTime, - title: "tasks_projects.project.tabs.time" - } - }, - { - id: "members", - path: "members", - component: jc(e, Fs), - meta: { - ability: Nc.editProject, - title: "tasks_projects.project.tabs.members" + function _(r) { + return h("dragOverCompleted", { insertion: r }), r && (l ? c._hideClone() : c._showClone(p), p !== d && (Gl(Y, ju ? ju.options.ghostClass : c.options.ghostClass, !1), Gl(Y, o.ghostClass, !0)), ju !== p && p !== $.active ? ju = p : p === $.active && ju && (ju = null), d === p && (p._ignoreWhileAnimating = n), p.animateAll(function() { + h("dragOverAnimationComplete"), p._ignoreWhileAnimating = null; + }), p !== d && (d.animateAll(), d._ignoreWhileAnimating = null)), (n === Y && !Y.animated || n === t && !n.animated) && (Hu = null), !o.dragoverBubble && !e.rootEl && n !== document && (Y.parentNode[du]._isOutsideThisEl(e.target), !r && cd(e)), !o.dragoverBubble && e.stopPropagation && e.stopPropagation(), m = !0; + } + function v() { + Du = $l(Y), ku = $l(Y, o.draggable), xu({ + sortable: p, + name: "change", + toEl: t, + newIndex: Du, + newDraggableIndex: ku, + originalEvent: e + }); + } + if (e.preventDefault !== void 0 && e.cancelable && e.preventDefault(), n = Ul(n, o.draggable, t, !0), h("dragOver"), $.eventCanceled) return m; + if (Y.contains(e.target) || n.animated && n.animatingX && n.animatingY || p._ignoreWhileAnimating === n) return _(!1); + if (Nu = !1, c && !o.disabled && (l ? u || (a = Su !== Z) : ju === this || (this.lastPutMode = Au.checkPull(this, c, Y, e)) && s.checkPut(this, c, Y, e))) { + if (f = this._getDirection(e, n) === "vertical", r = Yl(Y), h("dragOverValid"), $.eventCanceled) return m; + if (a) return Su = Z, g(), this._hideClone(), h("revert"), $.eventCanceled || (Cu ? Z.insertBefore(Y, Cu) : Z.appendChild(Y)), _(!0); + var y = Ql(t, o.draggable); + if (!y || hd(e, f, this) && !y.animated) { + if (y === Y) return _(!1); + if (y && t === e.target && (n = y), n && (i = Yl(n)), dd(Z, t, Y, r, n, i, e, !!n) !== !1) return g(), y && y.nextSibling ? t.insertBefore(Y, y.nextSibling) : t.appendChild(Y), Su = t, v(), _(!0); + } else if (y && md(e, f, this)) { + var b = Zl(t, 0, o, !0); + if (b === Y) return _(!1); + if (n = b, i = Yl(n), dd(Z, t, Y, r, n, i, e, !1) !== !1) return g(), t.insertBefore(Y, b), Su = t, v(), _(!0); + } else if (n.parentNode === t) { + i = Yl(n); + var x = 0, S, C = Y.parentNode !== t, w = !rd(Y.animated && Y.toRect || r, n.animated && n.toRect || i, f), T = f ? "top" : "left", E = Xl(n, "top", "top") || Xl(Y, "top", "top"), D = E ? E.scrollTop : void 0; + Hu !== n && (S = i[T], Wu = !1, Gu = !w && o.invertSwap || C), x = gd(e, n, i, f, w ? 1 : o.swapThreshold, o.invertedSwapThreshold == null ? o.swapThreshold : o.invertedSwapThreshold, Gu, Hu === n); + var O; + if (x !== 0) { + var k = $l(Y); + do + k -= x, O = Su.children[k]; + while (O && (J(O, "display") === "none" || O === X)); } + if (x === 0 || O === n) return _(!1); + Hu = n, Uu = x; + var A = n.nextElementSibling, j = !1; + j = x === 1; + var M = dd(Z, t, Y, r, n, i, e, j); + if (M !== !1) return (M === 1 || M === -1) && (j = M === 1), Yu = !0, setTimeout(pd, 30), g(), j && !A ? t.appendChild(Y) : n.parentNode.insertBefore(Y, j ? A : n), E && cu(E, 0, D - E.scrollTop), Su = Y.parentNode, S !== void 0 && !Gu && (Ku = Math.abs(S - Yl(n)[T])), v(), _(!0); } - ] - }); -} -//#endregion -//#region resources/js/messages/billing.ts -var Fc = { en: { tasks_projects: { billing: { - title: "Invoice time", - subtitle: "Turn unbilled hours into a draft invoice.", - invoice_time: "Invoice time", - unbilled: "Unbilled", - view_unbilled: "Invoice this time", - steps: { - customer: "Customer", - entries: "Entries", - preview: "Preview", - create: "Create" + if (t.contains(Y)) return _(!1); + } + return !1; + }, + _ignoreWhileAnimating: null, + _offMoveEvents: function() { + q(document, "mousemove", this._onTouchMove), q(document, "touchmove", this._onTouchMove), q(document, "pointermove", this._onTouchMove), q(document, "dragover", cd), q(document, "mousemove", cd), q(document, "touchmove", cd); + }, + _offUpEvents: function() { + var e = this.el.ownerDocument; + q(e, "mouseup", this._onDrop), q(e, "touchend", this._onDrop), q(e, "pointerup", this._onDrop), q(e, "pointercancel", this._onDrop), q(e, "touchcancel", this._onDrop), q(document, "selectstart", this); + }, + _onDrop: function(e) { + var t = this.el, n = this.options; + if (Du = $l(Y), ku = $l(Y, n.draggable), bu("drop", this, { evt: e }), Su = Y && Y.parentNode, Du = $l(Y), ku = $l(Y, n.draggable), $.eventCanceled) { + this._nulling(); + return; + } + Mu = !1, Gu = !1, Wu = !1, clearInterval(this._loopId), clearTimeout(this._dragStartTimer), xd(this.cloneId), xd(this._dragStartId), this.nativeDraggable && (q(document, "drop", this), q(t, "dragstart", this._onDragStart)), this._offMoveEvents(), this._offUpEvents(), Ll && J(document.body, "user-select", ""), J(Y, "transform", ""), e && (Vu && (e.cancelable && e.preventDefault(), !n.dropBubble && e.stopPropagation()), X && X.parentNode && X.parentNode.removeChild(X), (Z === Su || ju && ju.lastPutMode !== "clone") && Q && Q.parentNode && Q.parentNode.removeChild(Q), Y && (this.nativeDraggable && q(Y, "dragend", this), fd(Y), Y.style["will-change"] = "", Vu && !Mu && Gl(Y, ju ? ju.options.ghostClass : this.options.ghostClass, !1), Gl(Y, this.options.chosenClass, !1), xu({ + sortable: this, + name: "unchoose", + toEl: Su, + newIndex: null, + newDraggableIndex: null, + originalEvent: e + }), Z === Su ? Du !== Eu && Du >= 0 && (xu({ + sortable: this, + name: "update", + toEl: Su, + originalEvent: e + }), xu({ + sortable: this, + name: "sort", + toEl: Su, + originalEvent: e + })) : (Du >= 0 && (xu({ + rootEl: Su, + name: "add", + toEl: Su, + fromEl: Z, + originalEvent: e + }), xu({ + sortable: this, + name: "remove", + toEl: Su, + originalEvent: e + }), xu({ + rootEl: Su, + name: "sort", + toEl: Su, + fromEl: Z, + originalEvent: e + }), xu({ + sortable: this, + name: "sort", + toEl: Su, + originalEvent: e + })), ju && ju.save()), $.active && ((Du == null || Du === -1) && (Du = Eu, ku = Ou), xu({ + sortable: this, + name: "end", + toEl: Su, + originalEvent: e + }), this.save()))), this._nulling(); }, - back: "Back", - next: "Continue", - start_over: "Start over", - customer: { - title: "Who are you invoicing?", - description: "Customers with billable time that has not reached an invoice yet.", - entries: "{count} entries", - empty_title: "Nothing to invoice", - empty_description: "Billable time appears here once it has been logged against a task that belongs to a customer.", - load_failed: "Unable to load the customers with unbilled time.", - names_failed: "Unable to load the customer names; ids are shown instead.", - unnamed: "Customer #{id}", - from: "From", - to: "To", - clear_range: "Clear dates" + _nulling: function() { + bu("nulling", this), Z = Y = Su = X = Cu = Q = wu = Tu = Fu = Iu = Vu = Du = ku = Eu = Ou = Hu = Uu = ju = Au = $.dragged = $.ghost = $.clone = $.active = null; + var e = this.el; + Xu.forEach(function(t) { + e.contains(t) && (t.checked = !0); + }), Xu.length = Lu = Ru = 0; }, - entries: { - title: "Which time goes on the invoice?", - grouping: "Group lines by", - group_by: { - task: "Task", - project: "Project", - member: "Member", - summary: "One summary line" - }, - select_all: "Select all", - selected: "{count} of {total} entries selected", - selected_total: "Selected: {hours}", - no_description: "No description", - columns: { - date: "Date", - task: "Task", - project: "Project", - member: "Member", - duration: "Duration", - amount: "Amount" - }, - empty_title: "No unbilled time", - empty_description: "This customer has nothing waiting to be invoiced in this range.", - load_failed: "Unable to load the unbilled time.", - none_selected: "Select at least one entry." + handleEvent: function(e) { + switch (e.type) { + case "drop": + case "dragend": + this._onDrop(e); + break; + case "dragenter": + case "dragover": + Y && (this._onDragOver(e), ud(e)); + break; + case "selectstart": e.preventDefault(); + } }, - preview: { - title: "Check the invoice", - lines: "Invoice lines", - columns: { - description: "Description", - quantity: "Hours", - price: "Rate", - total: "Amount" - }, - sub_total: "Subtotal", - total: "Total", - invoice_date: "Invoice date", - due_date: "Due date", - invoice_number: "Invoice number", - invoice_number_auto: "Generated by the company number format.", - template: "Template", - exchange_rate: "Exchange rate", - exchange_rate_help: "1 {currency} in the company currency.", - prepare_failed: "Unable to prepare the invoice.", - templates_failed: "Unable to load the invoice templates.", - number_failed: "Unable to read the next invoice number. Type one in.", - rate_failed: "Unable to read the exchange rate. Type one in.", - create: "Create invoice", - invalid: "The invoice was refused. Fix the fields below and try again." + toArray: function() { + for (var e = [], t, n = this.el.children, r = 0, i = n.length, a = this.options; r < i; r++) t = n[r], Ul(t, a.draggable, this.el, !1) && e.push(t.getAttribute(a.dataIdAttr) || vd(t)); + return e; }, - create: { - creating: "Creating the invoice", - stamping: "Marking the time as invoiced", - created_title: "Invoice {number} created", - created_description: "{count} entries were marked as invoiced.", - view_invoice: "Open the invoice", - invoice_more: "Invoice more time", - failed: "Unable to create the invoice.", - stamp_failed_title: "The invoice was created, but the time is not marked yet", - stamp_failed_description: "Invoice {number} exists. The time entries still count as unbilled until they are stamped, which is safe to run again.", - retry_stamp: "Retry stamping", - stamped: "The time entries were marked as invoiced." + sort: function(e, t) { + var n = {}, r = this.el; + this.toArray().forEach(function(e, t) { + var i = r.children[t]; + Ul(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(); + }, + save: function() { + var e = this.options.store; + e && e.set && e.set(this); + }, + closest: function(e, t) { + return Ul(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 = _u.modifyOption(this, e, t); + n[e] = r === void 0 ? t : r, e === "group" && ad(n); + }, + destroy: function() { + bu("destroy", this); + var e = this.el; + e[du] = null, q(e, "mousedown", this._onTapStart), q(e, "touchstart", this._onTapStart), q(e, "pointerdown", this._onTapStart), this.nativeDraggable && (q(e, "dragover", this), q(e, "dragenter", this)), Array.prototype.forEach.call(e.querySelectorAll("[draggable]"), function(e) { + e.removeAttribute("draggable"); + }), this._onDrop(), this._disableDelayedDragEvents(), Pu.splice(Pu.indexOf(this.el), 1), this.el = e = null; + }, + _hideClone: function() { + if (!Tu) { + if (bu("hideClone", this), $.eventCanceled) return; + J(Q, "display", "none"), this.options.removeCloneOnHide && Q.parentNode && Q.parentNode.removeChild(Q), Tu = !0; + } + }, + _showClone: function(e) { + if (e.lastPutMode !== "clone") { + this._hideClone(); + return; + } + if (Tu) { + if (bu("showClone", this), $.eventCanceled) return; + Y.parentNode == Z && !this.options.group.revertClone ? Z.insertBefore(Q, Y) : Cu ? Z.insertBefore(Q, Cu) : Z.appendChild(Q), this.options.group.revertClone && this.animate(Y, Q), J(Q, "display", ""), Tu = !1; + } } -} } } }, Ic = "/api/v1/tasks-projects", Lc = { - customers: `${Ic}/billing/customers`, - unbilled: `${Ic}/billing/unbilled`, - prepare: `${Ic}/billing/prepare`, - confirm: `${Ic}/billing/confirm` -}, Rc = { - bootstrap: "/api/v1/bootstrap", - customers: "/api/v1/customers", - invoices: "/api/v1/invoices", - invoiceTemplates: "/api/v1/invoices/templates", - nextNumber: "/api/v1/next-number", - exchangeRate: (e) => `/api/v1/currencies/${e}/exchange-rate` }; -async function zc(e, t = {}) { - let { data: n } = await e.get(Lc.customers, { params: t }); - return n.data ?? []; +function ud(e) { + e.dataTransfer && (e.dataTransfer.dropEffect = "move"), e.cancelable && e.preventDefault(); } -async function Bc(e, t, n = {}) { - let { data: r } = await e.get(Lc.unbilled, { params: { - customer_id: t, - ...n - } }); - return r.data; +function dd(e, t, n, r, i, a, o, s) { + var c, l = e[du], u = l.options.onMove, d; + return window.CustomEvent && !Pl && !Fl ? 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 || Yl(t), c.willInsertAfter = s, c.originalEvent = o, e.dispatchEvent(c), u && (d = u.call(l, c, o)), d; } -async function Vc(e, t, n) { - let { data: r } = await e.post(Lc.prepare, { - entry_ids: t, - grouping: n - }); - return r.data; +function fd(e) { + e.draggable = !1; } -async function Hc(e, t, n) { - let { data: r } = await e.post(Lc.confirm, { - invoice_id: t, - items: n - }); - return r?.stamped ?? 0; +function pd() { + Yu = !1; } -async function Uc(e, t = 200) { - let { data: n } = await e.get(Rc.customers, { params: { limit: t } }); - return n.data ?? []; +function md(e, t, n) { + var r = Yl(Zl(n.el, 0, n.options, !0)), i = uu(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; } -async function Wc(e, t) { - let { data: n } = await e.post(Rc.invoices, t); - return n.data; +function hd(e, t, n) { + var r = Yl(Ql(n.el, n.options.draggable)), i = uu(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; } -async function Gc(e) { - let { data: t } = await e.get(Rc.invoiceTemplates); - return t?.invoiceTemplates ?? []; +function gd(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 && Ku < l * i) { + if (!Wu && (Uu === 1 ? c > u + l * a / 2 : c < d - l * a / 2) && (Wu = !0), Wu) f = !0; + else if (Uu === 1 ? c < u + Ku : c > d - Ku) return -Uu; + } else if (c > u + l * (1 - i) / 2 && c < d - l * (1 - i) / 2) return _d(t); + } + return f ||= o, f && (c < u + l * a / 2 || c > d - l * a / 2) ? c > u + l / 2 ? 1 : -1 : 0; } -async function Kc(e, t) { - let n = { key: "invoice" }; - t !== void 0 && (n.userId = t); - let { data: r } = await e.get(Rc.nextNumber, { params: n }); - return r?.success && typeof r.nextNumber == "string" ? r.nextNumber : null; +function _d(e) { + return $l(Y) < $l(e) ? 1 : -1; } -async function qc(e, t) { - let { data: n } = await e.get(Rc.exchangeRate(t)), r = Array.isArray(n?.exchangeRate) ? n.exchangeRate[0] : n?.exchangeRate, i = Number(r); - return Number.isFinite(i) && i > 0 ? i : null; +function vd(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); } -async function Jc(e) { - let { data: t } = await e.get(Rc.bootstrap), n = t?.current_company_settings ?? {}, r = t?.current_user_settings ?? {}, i = Number(n.invoice_due_date_days), a = r.default_invoice_template; - return { - currency: t?.current_company_currency ?? null, - dueDateDays: Number.isFinite(i) && i >= 0 ? i : 0, - setDueDateAutomatically: n.invoice_set_due_date_automatically === "YES", - autoGenerateNumber: n.invoice_auto_generate !== "NO", - defaultTemplate: typeof a == "string" && a !== "" ? a : null - }; +function yd(e) { + Xu.length = 0; + for (var t = e.getElementsByTagName("input"), n = t.length; n--;) { + var r = t[n]; + r.checked && Xu.push(r); + } +} +function bd(e) { + return setTimeout(e, 0); +} +function xd(e) { + return clearTimeout(e); +} +Zu && K(document, "touchmove", function(e) { + ($.active || Mu) && e.cancelable && e.preventDefault(); +}), $.utils = { + on: K, + off: q, + css: J, + find: ql, + is: function(e, t) { + return !!Ul(e, t, e, !1); + }, + extend: ru, + throttle: ou, + closest: Ul, + toggleClass: Gl, + clone: lu, + index: $l, + nextTick: bd, + cancelNextTick: xd, + detectDirection: nd, + getChild: Zl, + expando: du +}, $.get = function(e) { + return e[du]; +}, $.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 && ($.utils = El(El({}, $.utils), e.utils)), _u.mount(e); + }); +}, $.create = function(e, t) { + return new $(e, t); +}, $.version = Ml; +var Sd = [], Cd, wd, Td = !1, Ed, Dd, Od, kd; +function Ad() { + function e() { + for (var e in this.defaults = { + scroll: !0, + forceAutoScrollFallback: !1, + scrollSensitivity: 30, + scrollSpeed: 10, + bubbleScroll: !0 + }, this) e.charAt(0) === "_" && typeof this[e] == "function" && (this[e] = this[e].bind(this)); + } + return e.prototype = { + dragStarted: function(e) { + var t = e.originalEvent; + this.sortable.nativeDraggable ? K(document, "dragover", this._handleAutoScroll) : this.options.supportPointer ? K(document, "pointermove", this._handleFallbackAutoScroll) : t.touches ? K(document, "touchmove", this._handleFallbackAutoScroll) : K(document, "mousemove", this._handleFallbackAutoScroll); + }, + dragOverCompleted: function(e) { + var t = e.originalEvent; + !this.options.dragOverBubble && !t.rootEl && this._handleAutoScroll(t); + }, + drop: function() { + this.sortable.nativeDraggable ? q(document, "dragover", this._handleAutoScroll) : (q(document, "pointermove", this._handleFallbackAutoScroll), q(document, "touchmove", this._handleFallbackAutoScroll), q(document, "mousemove", this._handleFallbackAutoScroll)), Md(), jd(), su(); + }, + nulling: function() { + Od = wd = Cd = Td = kd = Ed = Dd = null, Sd.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 (Od = e, t || this.options.forceAutoScrollFallback || Fl || Pl || Ll) { + Nd(e, this.options, a, t); + var o = nu(a, !0); + Td && (!kd || r !== Ed || i !== Dd) && (kd && Md(), kd = setInterval(function() { + var a = nu(document.elementFromPoint(r, i), !0); + a !== o && (o = a, jd()), Nd(e, n.options, a, t); + }, 10), Ed = r, Dd = i); + } else { + if (!this.options.bubbleScroll || nu(a, !0) === Jl()) { + jd(); + return; + } + Nd(e, this.options, nu(a, !1), !1); + } + } + }, wl(e, { + pluginName: "scroll", + initializeByDefault: !0 + }); +} +function jd() { + Sd.forEach(function(e) { + clearInterval(e.pid); + }), Sd = []; } +function Md() { + clearInterval(kd); +} +var Nd = ou(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 = Jl(), l = !1, u; + wd !== n && (wd = n, jd(), Cd = t.scroll, u = t.scrollFn, Cd === !0 && (Cd = nu(n, !0))); + var d = 0, f = Cd; + do { + var p = f, m = Yl(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 = J(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 (!Sd[d]) for (var A = 0; A <= d; A++) Sd[A] || (Sd[A] = {}); + (Sd[d].vx != O || Sd[d].vy != k || Sd[d].el !== p) && (Sd[d].el = p, Sd[d].vx = O, Sd[d].vy = k, clearInterval(Sd[d].pid), (O != 0 || k != 0) && (l = !0, Sd[d].pid = setInterval(function() { + r && this.layer === 0 && $.active._onTouchMove(Od); + var t = Sd[this.layer].vy ? Sd[this.layer].vy * s : 0, n = Sd[this.layer].vx ? Sd[this.layer].vx * s : 0; + (typeof u != "function" || u.call($.dragged.parentNode[du], n, t, e, Od, Sd[this.layer].el) === "continue") && cu(Sd[this.layer].el, n, t); + }.bind({ layer: d }), 24))), d++; + } while (t.bubbleScroll && f !== c && (f = nu(f, !1))); + Td = l; + } +}, 30), Pd = 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; + o(); + var l = t.changedTouches && t.changedTouches.length ? t.changedTouches[0] : t, u = document.elementFromPoint(l.clientX, l.clientY); + s(), c && !c.el.contains(u) && (a("spill"), this.onSpill({ + dragEl: r, + putSortable: n + })); + } +}; +function Fd() {} +Fd.prototype = { + startIndex: null, + dragStart: function(e) { + var t = e.oldDraggableIndex; + this.startIndex = t; + }, + onSpill: function(e) { + var t = e.dragEl, n = e.putSortable; + this.sortable.captureAnimationState(), n && n.captureAnimationState(); + var r = Zl(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: Pd +}, wl(Fd, { pluginName: "revertOnSpill" }); +function Id() {} +Id.prototype = { + onSpill: function(e) { + var t = e.dragEl, n = e.putSortable || this.sortable; + n.captureAnimationState(), t.parentNode && t.parentNode.removeChild(t), n.animateAll(); + }, + drop: Pd +}, wl(Id, { pluginName: "removeOnSpill" }), $.mount(new Ad()), $.mount(Id, Fd); //#endregion -//#region resources/js/pages/BillingPage.vue?vue&type=script&setup=true&lang.ts -var Yc = { class: "flex items-center justify-end space-x-5" }, Xc = { class: "mt-6 flex flex-wrap items-center gap-x-6 gap-y-3" }, Zc = { +//#region resources/js/components/TaskCard.vue?vue&type=script&setup=true&lang.ts +var Ld = { class: "flex items-start justify-between gap-2" }, Rd = { class: "text-sm font-medium text-heading" }, zd = { class: "mt-2 flex flex-wrap items-center gap-2 text-xs text-muted" }, Bd = ["title"], Vd = { class: "mt-3 flex items-center justify-between gap-2" }, Hd = { key: 0, - class: "flex justify-center py-16" -}, Qc = { + class: "text-xs tabular-nums text-muted" +}, Ud = { class: "mt-2 flex items-center justify-between" }, Wd = { key: 1, - class: "mt-6" -}, $c = { class: "text-base font-semibold text-heading" }, el = { class: "mt-1 text-sm text-muted" }, tl = { class: "mt-4 flex flex-wrap items-end gap-4" }, nl = { - key: 0, - class: "mt-5 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3" -}, rl = ["onClick"], il = { class: "text-sm font-semibold text-heading" }, al = { class: "mt-1 text-xs text-muted" }, ol = { class: "mt-3 text-xl font-semibold text-heading" }, sl = { - key: 2, - class: "mt-6" -}, cl = { class: "flex flex-wrap items-end justify-between gap-4" }, ll = { class: "text-base font-semibold text-heading" }, ul = { class: "mt-1 text-sm text-muted" }, dl = { + class: "text-xs text-subtle" +}, Gd = ["title"], Kd = /* @__PURE__ */ l({ + __name: "TaskCard", + props: { + client: {}, + notify: {}, + task: {}, + projects: { default: () => [] }, + members: { default: () => [] } + }, + emits: ["open"], + setup(e, { emit: t }) { + let r = e, s = t, l = { + 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" + }, u = B(), d = n(() => Va(r.task)), p = n(() => r.projects.find((e) => e.id === r.task.project_id)), m = n(() => r.task.project_id === null ? null : p.value?.identifier || p.value?.name || null), h = n(() => [p.value?.name, Zn(r.task.customer_id)].filter(Boolean).join(" · ")), g = n(() => r.members.find((e) => e.id === r.task.assignee_id)), v = n(() => r.task.assignee_id === null ? null : g.value ? be(g.value.name) : `#${r.task.assignee_id}`), y = n(() => g.value?.name ?? (r.task.assignee_id === null ? u("tasks_projects.tasks.unassigned") : `#${r.task.assignee_id}`)), b = n(() => qa(d.value.logged_minutes)); + function x(e) { + return u(`tasks_projects.tasks.priority.${e.toLowerCase()}`); + } + return (t, n) => (_(), a("article", { + class: "cursor-pointer rounded-lg border border-line-default bg-surface p-3 shadow-sm hover:bg-hover", + onClick: n[1] ||= (t) => s("open", e.task) + }, [ + o("div", Ld, [o("p", Rd, S(e.task.name), 1), e.task.priority ? (_(), a("span", { + key: 0, + class: f(["shrink-0 rounded-full px-2 py-0.5 text-[11px] font-medium", l[e.task.priority]]) + }, S(x(e.task.priority)), 3)) : i("", !0)]), + o("div", zd, [ + o("span", null, "#" + S(e.task.number), 1), + m.value ? (_(), a("span", { + key: 0, + class: "rounded-sm bg-surface-tertiary px-1.5 py-0.5 text-[11px] text-body", + title: h.value + }, S(m.value), 9, Bd)) : i("", !0), + c(Vi, { state: d.value.invoiced }, null, 8, ["state"]) + ]), + o("div", Vd, [c(Oo, { + client: e.client, + notify: e.notify, + task: e.task, + members: e.members, + onClick: n[0] ||= A(() => {}, ["stop"]) + }, null, 8, [ + "client", + "notify", + "task", + "members" + ]), d.value.logged_minutes > 0 ? (_(), a("span", Hd, S(b.value), 1)) : i("", !0)]), + o("div", Ud, [e.task.due_date ? (_(), a("span", { + key: 0, + class: f(["text-xs", C(xe)(e.task.due_date) && !e.task.closed_at ? "font-medium text-status-red" : "text-muted"]) + }, S(C(_e)(e.task.due_date)), 3)) : (_(), a("span", Wd, "-")), v.value ? (_(), 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: y.value + }, S(v.value), 9, Gd)) : i("", !0)]) + ])); + } +}), qd = { class: "mt-4" }, Jd = { key: 0, + class: "mb-3 text-xs text-subtle" +}, Yd = { + key: 1, class: "flex justify-center py-16" -}, fl = { class: "mt-4 flex flex-wrap items-center justify-between gap-3 rounded-lg border border-line-default bg-surface-secondary px-4 py-3" }, pl = { class: "flex cursor-pointer items-center gap-2 text-sm font-medium text-heading" }, ml = ["checked"], hl = { class: "text-sm text-muted" }, gl = { class: "flex flex-wrap items-center justify-between gap-3 bg-surface-secondary px-4 py-3" }, _l = { class: "flex cursor-pointer items-center gap-2 text-sm font-semibold text-heading" }, vl = ["checked", "onChange"], yl = { class: "text-sm text-muted" }, bl = { class: "overflow-x-auto" }, xl = { class: "w-full table-auto" }, Sl = { class: "bg-surface text-xs tracking-wider text-muted uppercase" }, Cl = { class: "px-4 py-2 text-left font-medium" }, wl = { class: "px-4 py-2 text-left font-medium" }, Tl = { class: "px-4 py-2 text-left font-medium" }, El = { class: "px-4 py-2 text-left font-medium" }, Dl = { class: "px-4 py-2 text-right font-medium" }, Ol = { class: "px-4 py-2 text-right font-medium" }, kl = { class: "divide-y divide-line-default bg-surface text-sm" }, Al = { class: "pl-4" }, jl = ["checked", "onChange"], Ml = { class: "px-4 py-2 whitespace-nowrap text-muted" }, Nl = { class: "px-4 py-2" }, Pl = { class: "text-heading" }, Fl = { class: "block text-xs text-subtle" }, Il = { class: "px-4 py-2 text-muted" }, Ll = { class: "px-4 py-2 text-muted" }, Rl = { class: "px-4 py-2 text-right whitespace-nowrap text-muted" }, zl = { class: "px-4 py-2 text-right whitespace-nowrap text-heading" }, Bl = { class: "mt-5 flex flex-wrap items-center justify-between gap-4" }, Vl = { class: "text-sm font-medium text-heading" }, Hl = { class: "flex items-center gap-3" }, Ul = { +}, Xd = { key: 3, - class: "mt-6" -}, Wl = { class: "text-base font-semibold text-heading" }, Gl = { - key: 0, - class: "flex justify-center py-16" -}, Kl = { - key: 0, - class: "mt-4 rounded-lg border border-status-red bg-surface px-4 py-3 text-sm text-status-red" -}, ql = { class: "mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4" }, Jl = { - key: 0, - class: "mt-1 block text-xs text-subtle" -}, Yl = { class: "mt-1 block text-xs text-subtle" }, Xl = { class: "mt-5 overflow-hidden rounded-xl border border-line-default" }, Zl = { class: "overflow-x-auto" }, Ql = { class: "w-full table-auto" }, $l = { class: "bg-surface-secondary text-xs tracking-wider text-muted uppercase" }, eu = { class: "px-4 py-2 text-left font-medium" }, tu = { class: "px-4 py-2 text-right font-medium" }, nu = { class: "px-4 py-2 text-right font-medium" }, ru = { class: "px-4 py-2 text-right font-medium" }, iu = { class: "divide-y divide-line-default bg-surface text-sm" }, au = { class: "px-4 py-3" }, ou = { class: "font-medium text-heading" }, su = { - key: 0, - class: "mt-1 block text-xs whitespace-pre-line text-subtle" -}, cu = { class: "px-4 py-3 text-right whitespace-nowrap text-muted" }, lu = { class: "px-4 py-3 text-right whitespace-nowrap text-muted" }, uu = { class: "px-4 py-3 text-right whitespace-nowrap font-medium text-heading" }, du = { class: "bg-surface-secondary text-sm" }, fu = { - class: "px-4 py-2 text-right text-muted", - colspan: "3" -}, pu = { class: "px-4 py-2 text-right whitespace-nowrap text-heading" }, mu = { - class: "px-4 py-2 text-right font-semibold text-heading", - colspan: "3" -}, hu = { class: "px-4 py-2 text-right whitespace-nowrap font-semibold text-heading" }, gu = { class: "mt-5 flex items-center justify-end gap-3" }, _u = { - key: 4, - class: "mt-6" -}, vu = { - key: 0, - class: "flex flex-col items-center gap-3 py-16" -}, yu = { class: "text-sm text-muted" }, bu = { + class: "flex items-start gap-4 overflow-x-auto pb-4" +}, Zd = { class: "flex items-center justify-between border-b border-line-light px-3 py-2.5" }, Qd = { class: "flex items-center" }, $d = { class: "text-sm font-semibold text-heading" }, ef = { class: "ml-2 text-xs text-muted" }, tf = [ + "aria-label", + "title", + "onClick" +], nf = ["data-status-id"], rf = { key: 0, - class: "rounded-xl border border-status-yellow bg-surface p-5" -}, xu = { class: "text-sm font-semibold text-heading" }, Su = { class: "mt-1 text-sm text-muted" }, Cu = { class: "mt-4 flex flex-wrap items-center gap-3" }, wu = { - key: 1, - class: "rounded-xl border border-line-default bg-surface p-6 text-center" -}, Tu = { class: "mt-3 text-base font-semibold text-heading" }, Eu = { class: "mt-1 text-sm text-muted" }, Du = { class: "mt-3 text-2xl font-semibold text-heading" }, Ou = { class: "mt-5 flex flex-wrap items-center justify-center gap-3" }, ku = { - key: 2, - class: "flex justify-center py-16" -}, Au = "/admin/invoices", ju = /* @__PURE__ */ l({ - __name: "BillingPage", + class: "px-3 pt-2 text-xs text-subtle" +}, af = { class: "px-3 pt-2 pb-3" }, of = ["onClick"], sf = /* @__PURE__ */ l({ + __name: "TasksBoardView", props: { client: { type: [Function, Object] }, notify: { type: Function }, - router: {} + filters: {}, + statuses: {}, + members: {}, + projects: {} }, - setup(t) { - let l = t, u = [ - "task", - "project", - "member", - "summary" - ], d = de(), p = v(1), m = v(!0), C = v([]), w = v({}), D = _({ - from: "", - to: "" - }), O = v(null), k = v(null), A = v("task"), j = v([]), M = v(!1), N = v(null), P = v([]), F = v(null), I = v(!1), L = _({ - invoiceDate: "", - dueDate: "", - invoiceNumber: "", - templateName: "", - exchangeRate: "" - }), R = v({}), z = v(!1), B = v(!1), V = v(null), H = v(null), ee = v(!1), ne = v(!1), re = n(() => [ - d("tasks_projects.billing.steps.customer"), - d("tasks_projects.billing.steps.entries"), - d("tasks_projects.billing.steps.preview"), - d("tasks_projects.billing.steps.create") - ]), ie = n(() => u.map((e) => ({ - id: e, - label: d(`tasks_projects.billing.entries.group_by.${e}`) - }))), ae = n({ - get: () => ie.value.find((e) => e.id === A.value) ?? ie.value[0], - set: (e) => { - A.value = e.id; - } - }), le = n(() => P.value.map((e) => ({ name: e.name }))), ue = n({ - get: () => le.value.find((e) => e.name === L.templateName) ?? { name: L.templateName }, - set: (e) => { - L.templateName = e.name; - } - }), fe = n(() => (k.value?.entries ?? []).filter((e) => e.currency_id === (O.value?.currency_id ?? null))), pe = n(() => { - let e = {}; - for (let t of fe.value) e[t.id] = t; - return e; - }), me = n(() => (k.value?.groups[A.value] ?? []).filter((e) => e.currency_id === (O.value?.currency_id ?? null))), he = n(() => fe.value.length), ge = n(() => he.value > 0 && j.value.length === he.value), _e = n(() => j.value.reduce((e, t) => e + (pe.value[t]?.minutes ?? 0), 0)), ve = n(() => j.value.reduce((e, t) => e + (pe.value[t]?.amount ?? 0), 0)), ye = n(() => O.value === null ? null : je(O.value.customer_id, O.value.currency_id)), be = n(() => { - let e = N.value?.currency?.id ?? null; - return e === null || O.value === null ? !1 : Ae(O.value.customer_id) !== e; - }), xe = n(() => N.value?.autoGenerateNumber !== !0 || R.value.invoice_number !== void 0), Se = n(() => { - let e = {}; - return D.from !== "" && (e.from = D.from), D.to !== "" && (e.to = D.to), e; + setup(t, { expose: s }) { + let l = t, u = B(), d = y([]), h = y(!0), g = y(!1), v = y(null), w = y({}), T = /* @__PURE__ */ new Map(), O = /* @__PURE__ */ new Map(), k = !1, A = n(() => l.projects.map((e) => ({ + id: e.id, + label: e.name + }))), j = n(() => !h.value && d.value.length === 0), M = n(() => xa.settings.hide_invoiced_on_board); + function N(e) { + return (e.tasks ?? []).filter((e) => { + let t = Va(e).invoiced; + return M.value && t === "invoiced" ? !1 : mi(l.filters.status) ? l.filters.status === "invoiced" ? t === "invoiced" : t !== "invoiced" : !0; + }); + } + E(() => `${l.filters.project}|${l.filters.user}`, () => void P(), { immediate: !0 }), E(za, () => void P()), m(() => { + for (let e of T.values()) e.destroy(); + T.clear(), O.clear(); }); - T(() => [D.from, D.to], () => void Ee()), h(() => void Ce()); - async function Ce() { - m.value = !0, await Promise.all([ - Te(), - Ee(), - De(), - Oe() - ]), m.value = !1; - let e = we(); - if (e !== null) { - let t = C.value.find((t) => t.customer_id === e); - t && await Fe(t); + async function P() { + let e = {}, t = xi(l.filters.project), n = xi(l.filters.user); + t !== null && (e.project_id = t), n !== null && (e.assignee_id = n), h.value = !0; + try { + d.value = await Tn(l.client, e), d.value.some((e) => e.tasks.some((e) => e.customer_id !== null)) && Qn(l.client); + } catch (e) { + l.notify("error", R(e, u("tasks_projects.board.load_failed"))); + } finally { + h.value = !1; } } - function we() { - let e = l.router.currentRoute.value.query.customer_id, t = Array.isArray(e) ? e[0] : e ?? new URLSearchParams(window.location.search).get("customer_id"), n = Number(t); - return Number.isInteger(n) && n > 0 ? n : null; + function F(e, t) { + let n = t instanceof HTMLElement ? t : null; + O.get(e) !== n && (T.get(e)?.destroy(), T.delete(e), O.delete(e), n !== null && (O.set(e, n), T.set(e, $.create(n, { + group: "tasks", + animation: 150, + draggable: "[data-task-id]", + ghostClass: "opacity-40", + onStart: () => { + k = !0; + }, + onEnd: (e) => { + te(e), setTimeout(() => { + k = !1; + }); + } + })))); } - async function Te() { - try { - N.value = await Jc(l.client); - } catch { - N.value = null; - } + function ee(e) { + let t = e.item, n = e.oldIndex ?? 0; + t.parentNode?.removeChild(t), e.from.insertBefore(t, e.from.children[n] ?? null); + } + function I(e) { + return d.value.find((t) => t.status.id === e); } - async function Ee() { + async function te(e) { + let t = Number(e.from.dataset.statusId), n = Number(e.to.dataset.statusId), r = e.oldIndex ?? 0, i = e.newIndex ?? 0; + if (ee(e), Number.isNaN(t) || Number.isNaN(n) || t === n && r === i) return; + let a = I(t), o = I(n); + if (!a || !o) return; + let s = N(a)[r], c = s ? a.tasks.findIndex((e) => e.id === s.id) : -1; + if (!s || c === -1) return; + let d = { + from: [...a.tasks], + to: [...o.tasks] + }; + a.tasks.splice(c, 1); + let f = N(o), p = f[i - 1] ?? null, m = f[i] ?? null, h = p === null ? 0 : o.tasks.findIndex((e) => e.id === p.id) + 1; + o.tasks.splice(h, 0, s); try { - C.value = await zc(l.client, Se.value); + let e = await In(l.client, s.id, { + task_status_id: n, + before_id: p?.id ?? null, + after_id: m?.id ?? null + }); + Object.assign(s, e), t !== n && l.notify("success", u("tasks_projects.board.moved", { + name: s.name, + status: o.status.name + })); } catch (e) { - C.value = [], l.notify("error", U(e, d("tasks_projects.billing.customer.load_failed"))); + a.tasks = d.from, o.tasks = d.to, l.notify("error", R(e, u("tasks_projects.board.move_failed"))); } } - async function De() { - try { - let e = {}; - for (let t of await Uc(l.client)) e[t.id] = t; - w.value = e; - } catch { - w.value = {}; - } + function L(e) { + let t = e ?? l.statuses.find((e) => e.is_default) ?? l.statuses[0]; + v.value = null, w.value = { + task_status_id: t?.id ?? null, + project_id: xi(l.filters.project) + }, g.value = !0; } - async function Oe() { - try { - P.value = await Gc(l.client); - } catch (e) { - P.value = [], l.notify("error", U(e, d("tasks_projects.billing.preview.templates_failed"))); + function ne(e) { + k || (v.value = e, w.value = {}, g.value = !0); + } + function re(e) { + let t = v.value ? u("tasks_projects.tasks.updated", { name: e.name }) : u("tasks_projects.tasks.created", { name: e.name }); + g.value = !1, v.value = null, l.notify("success", t), U(); + } + function ie(e) { + g.value = !1, v.value = null, l.notify("success", u("tasks_projects.tasks.deleted", { name: e.name })), U(); + } + return s({ openCreate: () => L(null) }), (n, s) => { + let l = x("BaseSpinner"), m = x("BaseIcon"), y = x("BaseEmptyPlaceholder"); + return _(), a("section", qd, [ + M.value ? (_(), a("p", Jd, S(C(u)("tasks_projects.board.hidden_invoiced")), 1)) : i("", !0), + h.value && d.value.length === 0 ? (_(), a("div", Yd, [c(l, { class: "h-8 w-8 text-primary-500" })])) : j.value ? (_(), r(y, { + key: 2, + title: C(u)("tasks_projects.task_statuses.none"), + description: C(u)("tasks_projects.tasks.empty_description") + }, { + default: D(() => [c(m, { + name: "ViewColumnsIcon", + class: "mt-5 mb-4 h-16 w-16 text-subtle" + })]), + _: 1 + }, 8, ["title", "description"])) : (_(), a("div", Xd, [(_(!0), a(e, null, b(d.value, (n) => (_(), a("section", { + key: n.status.id, + class: "w-64 shrink-0 rounded-xl border border-line-default bg-surface-secondary" + }, [ + o("header", Zd, [o("div", Qd, [ + o("span", { + class: f(["mr-2 inline-block h-2.5 w-2.5 shrink-0 rounded-full", n.status.colour ? "" : "bg-line-default"]), + style: p(n.status.colour ? { backgroundColor: n.status.colour } : void 0) + }, null, 6), + o("h3", $d, S(n.status.name), 1), + o("span", ef, S(N(n).length), 1) + ]), o("button", { + type: "button", + class: "rounded-md p-1 text-subtle hover:bg-hover hover:text-body", + "aria-label": C(u)("tasks_projects.tasks.new_task"), + title: C(u)("tasks_projects.tasks.new_task"), + onClick: (e) => L(n.status) + }, [c(m, { + name: "PlusIcon", + class: "h-4 w-4" + })], 8, tf)]), + o("div", { + ref_for: !0, + ref: (e) => F(n.status.id, e), + "data-status-id": n.status.id, + class: "min-h-20 space-y-2 px-3 pt-3" + }, [(_(!0), a(e, null, b(N(n), (e) => (_(), r(Kd, { + key: e.id, + "data-task-id": e.id, + client: t.client, + notify: t.notify, + task: e, + projects: t.projects, + members: t.members, + onOpen: ne + }, null, 8, [ + "data-task-id", + "client", + "notify", + "task", + "projects", + "members" + ]))), 128))], 8, nf), + N(n).length === 0 ? (_(), a("p", rf, S(C(u)("tasks_projects.board.empty_column")), 1)) : i("", !0), + o("div", af, [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) => L(n.status) + }, " + " + S(C(u)("tasks_projects.tasks.new_task")), 9, of)]) + ]))), 128))])), + c(Yi, { + show: g.value, + client: t.client, + notify: t.notify, + task: v.value, + statuses: t.statuses, + members: t.members, + projects: A.value, + defaults: w.value, + compact: v.value === null, + onClose: s[0] ||= (e) => g.value = !1, + onSaved: re, + onDeleted: ie + }, null, 8, [ + "show", + "client", + "notify", + "task", + "statuses", + "members", + "projects", + "defaults", + "compact" + ]) + ]); + }; + } +}), cf = /* @__PURE__ */ l({ + __name: "TasksListView", + props: { + client: { type: [Function, Object] }, + notify: { type: Function }, + filters: {}, + statuses: {}, + members: {}, + projects: {} + }, + setup(e, { expose: t }) { + let n = y(null); + return t({ openCreate: () => n.value?.openCreate() }), (t, i) => (_(), r(Lo, { + ref_key: "listRef", + ref: n, + client: e.client, + notify: e.notify, + filters: e.filters, + statuses: e.statuses, + members: e.members, + projects: e.projects + }, null, 8, [ + "client", + "notify", + "filters", + "statuses", + "members", + "projects" + ])); + } +}), lf = { class: "relative table-container" }, uf = { class: "block max-w-64 truncate" }, df = { class: "tabular-nums" }, ff = { + key: 1, + class: "text-subtle" +}, pf = /* @__PURE__ */ l({ + __name: "AllTimeTable", + props: { + client: { type: [Function, Object] }, + notify: { type: Function }, + members: {}, + memberId: {}, + projectId: {}, + reloadToken: {} + }, + emits: ["edit", "delete"], + setup(e, { emit: t }) { + let l = e, u = t, d = B(), p = y(null), m = v({ + from: "", + to: "", + billing: "ALL" + }), h = 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") + } + ]), g = n({ + get: () => w(h.value, m.billing), + set: (e) => { + m.billing = typeof e.id == "string" ? e.id : "ALL"; + } + }), b = 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" } + ]); + E(m, () => T()), E([() => l.memberId, () => l.projectId], () => T()), E(() => l.reloadToken, () => T(!0)); + function w(e, t) { + return e.find((e) => e.id === t) ?? e[0]; } - function ke(e) { - let t = w.value[e], n = t?.display_name ?? t?.name; - return n && n !== "" ? n : d("tasks_projects.billing.customer.unnamed", { id: e }); - } - function Ae(e) { - let t = w.value[e]; - return t?.currency_id ?? t?.currency?.id ?? null; + function T(e = !1) { + p.value?.refresh(e); } - function je(e, t) { - if (t === null) return null; - let n = w.value[e]; - if (n?.currency && n.currency.id === t) return n.currency; - let r = N.value?.currency ?? null; - return r !== null && r.id === t ? r : null; + function O() { + m.from = "", m.to = "", m.billing = "ALL"; } - function Me() { - D.from = "", D.to = ""; + function k(e) { + m.from = e ? ve(e) : ""; } - function Ne(e) { - D.from = e ? se(e) : ""; + function A(e) { + m.to = e ? ve(e) : ""; } - function Pe(e) { - D.to = e ? se(e) : ""; + function j(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 Fe(e) { - O.value = e, p.value = 2, M.value = !0, k.value = null, j.value = []; + async function M({ page: e }) { + let t = { + page: e, + limit: 25 + }; + l.memberId !== null && (t.user_id = l.memberId), l.projectId !== null && (t.project_id = l.projectId), m.from !== "" && (t.from = m.from), m.to !== "" && (t.to = m.to), m.billing !== "ALL" && (t.billed = m.billing === "BILLED"); try { - k.value = await Bc(l.client, e.customer_id, Se.value), j.value = fe.value.map((e) => e.id); + let e = await na(l.client, t), n = e.data ?? []; + return Ra(l.client, n.map((e) => e.task_id).filter((e) => typeof e == "number")), { + data: n, + pagination: N(e.meta, n.length) + }; } catch (e) { - l.notify("error", U(e, d("tasks_projects.billing.entries.load_failed"))); - } finally { - M.value = !1; + return l.notify("error", R(e, d("tasks_projects.time.load_failed"))), { + data: [], + pagination: N(null, 0) + }; } } - function Ie(e) { - return j.value.includes(e); - } - function Le(e) { - j.value = Ie(e) ? j.value.filter((t) => t !== e) : [...j.value, e]; - } - function Re() { - j.value = ge.value ? [] : fe.value.map((e) => e.id); - } - function ze(e) { - return e.entry_ids.length > 0 && e.entry_ids.every((e) => Ie(e)); - } - function Be(e) { - if (ze(e)) { - j.value = j.value.filter((t) => !e.entry_ids.includes(t)); - return; - } - let t = e.entry_ids.filter((e) => !Ie(e)); - j.value = [...j.value, ...t]; + function N(e, t) { + return { + totalPages: e?.last_page ?? 1, + currentPage: e?.current_page ?? 1, + totalCount: e?.total ?? t, + count: t, + limit: e?.per_page ?? 25 + }; } - function Ve(e) { - return e.entry_ids.map((e) => pe.value[e]).filter((e) => e !== void 0); + return (e, t) => { + let n = x("BaseDatePicker"), l = x("BaseInputGroup"), v = x("BaseSelectInput"), y = x("BaseFilterWrapper"), w = x("BaseBadge"), T = x("BaseFormatMoney"), E = x("BaseIcon"), N = x("BaseDropdownItem"), P = x("BaseDropdown"), F = x("BaseTable"); + return _(), a("section", null, [c(y, { + show: "", + "row-on-xl": "", + class: "mt-3", + onClear: O + }, { + default: D(() => [ + c(l, { + label: C(d)("tasks_projects.time.filters.from"), + class: "mt-2 flex-1" + }, { + default: D(() => [c(n, { + "model-value": m.from, + "onUpdate:modelValue": k + }, null, 8, ["model-value"])]), + _: 1 + }, 8, ["label"]), + c(l, { + label: C(d)("tasks_projects.time.filters.to"), + class: "mt-2 flex-1" + }, { + default: D(() => [c(n, { + "model-value": m.to, + "onUpdate:modelValue": A + }, null, 8, ["model-value"])]), + _: 1 + }, 8, ["label"]), + c(l, { + label: C(d)("tasks_projects.time.filters.billing"), + class: "mt-2 flex-1" + }, { + default: D(() => [c(v, { + modelValue: g.value, + "onUpdate:modelValue": t[0] ||= (e) => g.value = e, + options: h.value, + "label-key": "label" + }, null, 8, ["modelValue", "options"])]), + _: 1 + }, 8, ["label"]) + ]), + _: 1 + }), o("div", lf, [c(F, { + ref_key: "tableRef", + ref: p, + data: M, + columns: b.value, + class: "mt-3" + }, { + "cell-date": D(({ row: e }) => [s(S(C(_e)(C(Ya)(e.data.started_at))), 1)]), + "cell-member": D(({ row: e }) => [s(S(j(e.data.user_id)), 1)]), + "cell-task": D(({ row: e }) => [s(S(C(Ia)(e.data.task_id)), 1)]), + "cell-description": D(({ row: e }) => [o("span", uf, S(e.data.description || "-"), 1)]), + "cell-duration": D(({ row: e }) => [o("span", df, S(C(qa)(e.data.duration_minutes)), 1)]), + "cell-billable": D(({ row: e }) => [c(w, { class: f(["rounded-full", e.data.billable ? "bg-primary-50! text-primary-500!" : "bg-surface-tertiary! text-muted!"]) }, { + default: D(() => [s(S(e.data.billable ? C(d)("tasks_projects.time.billable") : C(d)("tasks_projects.time.non_billable")), 1)]), + _: 2 + }, 1032, ["class"])]), + "cell-amount": D(({ row: e }) => [e.data.billable ? (_(), r(T, { + key: 0, + amount: e.data.amount + }, null, 8, ["amount"])) : (_(), a("span", ff, "-"))]), + "cell-actions": D(({ row: e }) => [c(P, null, { + activator: D(() => [c(E, { + name: "EllipsisHorizontalIcon", + class: "h-5 text-muted" + })]), + default: D(() => [c(N, { onClick: (t) => u("edit", e.data) }, { + default: D(() => [c(E, { + name: "PencilIcon", + class: "mr-3 h-5 w-5 text-subtle group-hover:text-muted" + }), s(" " + S(C(d)("tasks_projects.general.edit")), 1)]), + _: 1 + }, 8, ["onClick"]), e.data.invoice_id === null ? (_(), r(N, { + key: 0, + onClick: (t) => u("delete", e.data) + }, { + default: D(() => [c(E, { + name: "TrashIcon", + class: "mr-3 h-5 w-5 text-subtle group-hover:text-muted" + }), s(" " + S(C(d)("tasks_projects.general.delete")), 1)]), + _: 1 + }, 8, ["onClick"])) : i("", !0)]), + _: 2 + }, 1024)]), + _: 1 + }, 8, ["columns"])])]); + }; + } +}), mf = { class: "mt-4 flex flex-wrap items-center justify-between gap-3" }, hf = { class: "flex items-center gap-2" }, gf = { class: "ml-1 text-sm text-muted" }, _f = { class: "flex items-center gap-2 text-sm" }, vf = { class: "text-muted" }, yf = { class: "text-lg font-semibold tabular-nums text-heading" }, bf = { + key: 0, + class: "mt-6 text-sm text-muted" +}, xf = { + key: 1, + class: "mt-4 grid gap-3 sm:grid-cols-2 lg:grid-cols-4 xl:grid-cols-7" +}, Sf = { class: "flex items-baseline justify-between" }, Cf = { class: "text-xs font-semibold tracking-wide text-heading uppercase" }, wf = { class: "text-xs text-muted" }, Tf = { class: "text-sm font-medium tabular-nums text-heading" }, Ef = { class: "mt-3 flex-1 space-y-2" }, Df = ["onClick"], Of = { class: "flex items-center justify-between gap-2" }, kf = { class: "truncate text-xs font-medium text-heading" }, Af = { class: "shrink-0 text-xs tabular-nums text-muted" }, jf = { + key: 0, + class: "mt-1 block truncate text-xs text-muted" +}, Mf = { class: "mt-1 flex items-center gap-1" }, Nf = { class: "text-[11px] text-subtle" }, Pf = { + key: 0, + class: "text-[11px] text-subtle" +}, Ff = { + key: 0, + class: "py-2 text-xs text-subtle" +}, If = ["onClick"], Lf = { + key: 2, + class: "mt-4 text-center text-sm text-subtle" +}, Rf = /* @__PURE__ */ l({ + __name: "WeekTimesheet", + props: { + client: { type: [Function, Object] }, + notify: { type: Function }, + userId: {}, + projectId: {}, + weekStart: {}, + reloadToken: {} + }, + emits: ["add", "edit"], + setup(t, { emit: l }) { + let u = t, d = l, p = B(), m = y($a(/* @__PURE__ */ new Date(), u.weekStart)), h = y([]), g = y(!1), v = n(() => eo(m.value)), w = n(() => { + let e = v.value[0], t = v.value[v.value.length - 1]; + return `${ro(e).day} - ${ro(t).day}`; + }), T = n(() => v.value.map((e) => { + let t = no(e), n = h.value.filter((e) => Ya(e.started_at) === t), r = ro(e); + return { + key: t, + weekday: r.weekday, + day: r.day, + today: io(e), + entries: n, + minutes: A(n) + }; + })), O = n(() => A(h.value)), k = n(() => !g.value && h.value.length === 0); + E(() => u.weekStart, (e) => { + m.value = $a(m.value, e); + }), E([ + m, + () => u.userId, + () => u.projectId, + () => u.reloadToken + ], () => void j(), { immediate: !0 }); + function A(e) { + return e.reduce((e, t) => e + (t.duration_minutes ?? 0), 0); } - async function He() { - if (j.value.length === 0) { - l.notify("warning", d("tasks_projects.billing.entries.none_selected")); + async function j() { + if (u.userId === null) { + h.value = []; return; } - p.value = 3, I.value = !0, F.value = null, R.value = {}, z.value = !1; + g.value = !0; try { - let e = await Vc(l.client, j.value, A.value); - F.value = e, await Ue(e); + let e = { + user_id: u.userId, + from: no(v.value[0]), + to: no(v.value[v.value.length - 1]) + }; + u.projectId && (e.project_id = u.projectId); + let t = await ra(u.client, e); + h.value = t, Ra(u.client, t.map((e) => e.task_id).filter((e) => typeof e == "number")); } catch (e) { - l.notify("error", U(e, d("tasks_projects.billing.preview.prepare_failed"))), p.value = 2; + h.value = [], u.notify("error", R(e, p("tasks_projects.time.load_failed"))); } finally { - I.value = !1; + g.value = !1; } } - async function Ue(e) { - L.invoiceDate = e.invoice_date, L.dueDate = We(e.invoice_date), L.templateName = N.value?.defaultTemplate ?? P.value[0]?.name ?? ""; - let [t, n] = await Promise.all([Kc(l.client, e.customer_id).catch(() => null), be.value && O.value?.currency_id ? qc(l.client, O.value.currency_id).catch(() => null) : Promise.resolve(null)]); - L.invoiceNumber = t ?? "", t === null && l.notify("warning", d("tasks_projects.billing.preview.number_failed")), be.value ? (L.exchangeRate = n === null ? "" : String(n), n === null && l.notify("warning", d("tasks_projects.billing.preview.rate_failed"))) : L.exchangeRate = ""; - } - function We(e) { - let t = N.value; - if (t === null || !t.setDueDateAutomatically) return ""; - let n = /* @__PURE__ */ new Date(`${e}T00:00:00`); - return Number.isNaN(n.getTime()) ? "" : (n.setDate(n.getDate() + t.dueDateDays), se(n)); - } - function Ge(e) { - L.invoiceDate = e ? se(e) : "", L.dueDate = We(L.invoiceDate); - } - function Ke(e) { - L.dueDate = e ? se(e) : ""; + function M(e) { + m.value = to(m.value, e * 7); } - function qe(e) { - return { - invoice_date: L.invoiceDate, - due_date: L.dueDate === "" ? null : L.dueDate, - customer_id: e.customer_id, - invoice_number: L.invoiceNumber, - currency_id: Ae(e.customer_id) ?? e.currency_id, - exchange_rate: be.value && L.exchangeRate !== "" ? Number(L.exchangeRate) : null, - discount: e.discount, - discount_type: e.discount_type, - discount_val: e.discount_val, - tax: e.tax, - sub_total: e.sub_total, - total: e.total, - tax_included: !1, - notes: e.notes, - template_name: L.templateName, - items: e.items.map((e) => ({ ...e })), - taxes: [] - }; + function N() { + m.value = $a(/* @__PURE__ */ new Date(), u.weekStart); } - async function Je() { - let e = F.value; - if (!(e === null || B.value)) { - p.value = 4, B.value = !0, R.value = {}, z.value = !1, V.value = null, H.value = null, ee.value = !1; - try { - let t = await Wc(l.client, qe(e)); - V.value = t, await Ye(t, e); - } catch (e) { - gt(e) === 422 ? (R.value = te(e), z.value = !0, p.value = 3, l.notify("error", U(e, d("tasks_projects.billing.preview.invalid")))) : (p.value = 3, l.notify("error", U(e, d("tasks_projects.billing.create.failed")))); - } finally { - B.value = !1; - } - } + return (n, l) => { + let u = x("BaseIcon"), m = x("BaseButton"), h = x("BaseSpinner"); + return _(), a("section", null, [ + o("header", mf, [o("div", hf, [ + c(m, { + variant: "white", + size: "sm", + title: C(p)("tasks_projects.time.previous_week"), + onClick: l[0] ||= (e) => M(-1) + }, { + default: D(() => [c(u, { + name: "ChevronLeftIcon", + class: "h-4 w-4" + })]), + _: 1 + }, 8, ["title"]), + c(m, { + variant: "white", + size: "sm", + onClick: N + }, { + default: D(() => [s(S(C(p)("tasks_projects.time.this_week")), 1)]), + _: 1 + }), + c(m, { + variant: "white", + size: "sm", + title: C(p)("tasks_projects.time.next_week"), + onClick: l[1] ||= (e) => M(1) + }, { + default: D(() => [c(u, { + name: "ChevronRightIcon", + class: "h-4 w-4" + })]), + _: 1 + }, 8, ["title"]), + o("span", gf, S(w.value), 1) + ]), o("div", _f, [ + o("span", vf, S(C(p)("tasks_projects.time.week_total")), 1), + o("span", yf, S(C(qa)(O.value)), 1), + g.value ? (_(), r(h, { + key: 0, + class: "h-4 w-4 text-primary-500" + })) : i("", !0) + ])]), + t.userId === null ? (_(), a("p", bf, S(C(p)("tasks_projects.time.unknown_user")), 1)) : (_(), a("div", xf, [(_(!0), a(e, null, b(T.value, (t) => (_(), 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", Sf, [o("div", null, [o("p", Cf, S(t.weekday), 1), o("p", wf, S(t.day), 1)]), o("span", Tf, S(C(qa)(t.minutes)), 1)]), + o("ul", Ef, [(_(!0), a(e, null, b(t.entries, (e) => (_(), 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", Of, [o("span", kf, S(C(Ia)(e.task_id)), 1), o("span", Af, S(C(qa)(e.duration_minutes)), 1)]), + e.description ? (_(), a("span", jf, S(e.description), 1)) : i("", !0), + o("span", Mf, [ + 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", Nf, S(e.billable ? C(p)("tasks_projects.time.billable") : C(p)("tasks_projects.time.non_billable")), 1), + e.invoice_id === null ? i("", !0) : (_(), a("span", Pf, " - " + S(C(p)("tasks_projects.time.billed")), 1)) + ]) + ], 8, Df)]))), 128)), t.entries.length === 0 ? (_(), a("li", Ff, S(C(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(" " + S(C(p)("tasks_projects.time.add_entry")), 1)], 8, If) + ], 2))), 128))])), + k.value && t.userId !== null ? (_(), a("p", Lf, S(C(p)("tasks_projects.time.empty_description")), 1)) : i("", !0) + ]); + }; + } +}), zf = { + key: 0, + class: "mt-4 flex gap-6 border-b border-line-default" +}, Bf = 5, Vf = /* @__PURE__ */ l({ + __name: "TasksWeekView", + props: { + client: { type: [Function, Object] }, + notify: { type: Function }, + filters: {}, + statuses: {}, + members: {}, + projects: {} + }, + setup(e) { + let t = e, s = B(), l = y("MINE"), u = y(!1), d = y(!1), p = y(null), m = y(no(/* @__PURE__ */ new Date())), g = y(0), v = n(() => xa.settings.week_start), b = n(() => xi(t.filters.project)), x = n(() => xi(t.filters.user) ?? xa.userId); + h(() => void w()), E(za, () => { + g.value += 1; + }); + async function w() { + xa.userId === null && await Sa(t.client), u.value = xa.settings.members_see_all_time || await T(); } - async function Ye(e, t) { - let n = e.items ?? [], r = []; - if (t.groups.forEach((e, t) => { - let i = n[t]; - i && r.push({ - invoice_item_id: i.id, - entry_ids: e.entry_ids - }); - }), r.length === 0) { - ee.value = !0; - return; + async function T() { + try { + return ((await na(t.client, { limit: Bf })).data ?? []).some((e) => e.user_id !== xa.userId); + } catch { + return !1; } - ne.value = !0; - try { - H.value = await Hc(l.client, e.id, r), ee.value = !1; + } + function D(e) { + p.value = null, m.value = e ?? no(/* @__PURE__ */ new Date()), d.value = !0; + } + function O(e) { + p.value = e, d.value = !0; + } + function k() { + let e = p.value ? s("tasks_projects.time.updated") : s("tasks_projects.time.created"); + d.value = !1, p.value = null, t.notify("success", e), g.value += 1, U(); + } + function A() { + d.value = !1, p.value = null, t.notify("success", s("tasks_projects.time.deleted")), g.value += 1, U(); + } + async function j(e) { + if (window.confirm(s("tasks_projects.time.delete_confirm"))) try { + await oa(t.client, e.id), t.notify("success", s("tasks_projects.time.deleted")), g.value += 1, U(); } catch (e) { - ee.value = !0, l.notify("error", U(e, d("tasks_projects.billing.create.stamp_failed_title"))); - } finally { - ne.value = !1; + t.notify("error", R(e, s("tasks_projects.time.delete_failed"))); } } - async function Xe() { - let e = V.value, t = F.value; - e === null || t === null || ne.value || (await Ye(e, t), ee.value || l.notify("success", d("tasks_projects.billing.create.stamped"))); + function M(e) { + return l.value === e ? "border-primary-500 text-primary-500" : "border-transparent text-muted hover:border-line-strong hover:text-heading"; } - async function Ze() { - p.value = 1, O.value = null, k.value = null, F.value = null, V.value = null, H.value = null, ee.value = !1, j.value = [], R.value = {}, z.value = !1, await Ee(); + return (t, n) => (_(), a("section", null, [ + u.value ? (_(), a("nav", zf, [o("button", { + type: "button", + class: f(["-mb-px border-b-2 px-1 pb-3 text-sm font-medium", M("MINE")]), + onClick: n[0] ||= (e) => l.value = "MINE" + }, S(C(s)("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", M("ALL")]), + onClick: n[1] ||= (e) => l.value = "ALL" + }, S(C(s)("tasks_projects.time.all_time")), 3)])) : i("", !0), + l.value === "MINE" ? (_(), r(Rf, { + key: 1, + client: e.client, + notify: e.notify, + "user-id": x.value, + "project-id": b.value, + "week-start": v.value, + "reload-token": g.value, + onAdd: D, + onEdit: O + }, null, 8, [ + "client", + "notify", + "user-id", + "project-id", + "week-start", + "reload-token" + ])) : (_(), r(pf, { + key: 2, + client: e.client, + notify: e.notify, + members: e.members, + "member-id": C(xi)(e.filters.user), + "project-id": b.value, + "reload-token": g.value, + onEdit: O, + onDelete: j + }, null, 8, [ + "client", + "notify", + "members", + "member-id", + "project-id", + "reload-token" + ])), + c(Zo, { + show: d.value, + client: e.client, + notify: e.notify, + entry: p.value, + "default-date": m.value, + onClose: n[2] ||= (e) => d.value = !1, + onSaved: k, + onDeleted: A + }, null, 8, [ + "show", + "client", + "notify", + "entry", + "default-date" + ]) + ])); + } +}), Hf = { + viewTask: `${V}:view-task`, + viewOwnTime: `${V}:view-own-time` +}; +function Uf(e) { + e.addMessages(ac), e.registerPage({ + id: "tasks", + module: V, + path: "", + component: we(e, Sl), + meta: { + ability: Hf.viewTask, + title: "tasks_projects.tasks.title" + }, + children: [ + { + id: "list", + path: "", + component: we(e, cf), + meta: { + ability: Hf.viewTask, + title: "tasks_projects.tasks.views.list" + } + }, + { + id: "board", + path: "board", + component: we(e, sf), + meta: { + ability: Hf.viewTask, + title: "tasks_projects.board.title" + } + }, + { + id: "week", + path: "week", + component: we(e, Vf), + meta: { + ability: Hf.viewOwnTime, + title: "tasks_projects.time.title" + } + } + ] + }), e.registerPage({ + id: "task", + module: V, + path: "tasks/:id", + component: we(e, hl), + meta: { + ability: Hf.viewTask, + title: "tasks_projects.tasks.title" } - function Qe(e) { - return p.value > e ? "border-primary-500 bg-primary-500 text-white" : p.value === e ? "border-primary-500 text-primary-500" : "border-line-default text-subtle"; + }), e.registerPage({ + id: "time", + module: V, + path: "time", + component: Wf(e), + meta: { + ability: Hf.viewOwnTime, + title: "tasks_projects.time.title" } - return (t, n) => { - let l = b("BaseBreadcrumbItem"), u = b("BaseBreadcrumb"), h = b("BaseIcon"), _ = b("BaseButton"), v = b("router-link"), w = b("BasePageHeader"), T = b("BaseSpinner"), k = b("BaseDatePicker"), A = b("BaseInputGroup"), N = b("BaseFormatMoney"), P = b("BaseEmptyPlaceholder"), U = b("BaseSelectInput"), te = b("BaseInput"), se = b("BasePage"); - return g(), r(se, null, { - default: E(() => [ - c(w, { title: S(d)("tasks_projects.billing.title") }, { - actions: E(() => [o("div", Yc, [c(v, { to: "/admin/modules/tasks-projects/time" }, { - default: E(() => [c(_, { variant: "white" }, { - left: E((e) => [c(h, { - name: "ClockIcon", - class: f(e.class) - }, null, 8, ["class"])]), - default: E(() => [s(" " + x(S(d)("tasks_projects.time.title")), 1)]), - _: 1 - })]), - _: 1 - })])]), - default: E(() => [c(u, null, { - default: E(() => [ - c(l, { - title: S(d)("tasks_projects.general.home"), - to: "/admin/dashboard" - }, null, 8, ["title"]), - c(l, { - title: S(d)("tasks_projects.projects.title"), - to: "/admin/modules/tasks-projects" - }, null, 8, ["title"]), - c(l, { - title: S(d)("tasks_projects.billing.title"), - to: "#", - active: "" - }, null, 8, ["title"]) - ]), - _: 1 - })]), - _: 1 - }, 8, ["title"]), - o("ol", Xc, [(g(!0), a(e, null, y(re.value, (e, t) => (g(), a("li", { - key: e, - class: "flex items-center gap-2" - }, [o("span", { class: f(["flex h-7 w-7 items-center justify-center rounded-full border text-xs font-semibold", Qe(t + 1)]) }, x(t + 1), 3), o("span", { class: f(["text-sm font-medium", p.value === t + 1 ? "text-heading" : "text-muted"]) }, x(e), 3)]))), 128))]), - m.value ? (g(), a("div", Zc, [c(T, { class: "h-8 w-8 text-primary-500" })])) : p.value === 1 ? (g(), a("section", Qc, [ - o("h2", $c, x(S(d)("tasks_projects.billing.customer.title")), 1), - o("p", el, x(S(d)("tasks_projects.billing.customer.description")), 1), - o("div", tl, [ - c(A, { - label: S(d)("tasks_projects.billing.customer.from"), - class: "w-full sm:w-48" - }, { - default: E(() => [c(k, { - "model-value": D.from, - "onUpdate:modelValue": Ne - }, null, 8, ["model-value"])]), - _: 1 - }, 8, ["label"]), - c(A, { - label: S(d)("tasks_projects.billing.customer.to"), - class: "w-full sm:w-48" - }, { - default: E(() => [c(k, { - "model-value": D.to, - "onUpdate:modelValue": Pe - }, null, 8, ["model-value"])]), - _: 1 - }, 8, ["label"]), - D.from !== "" || D.to !== "" ? (g(), r(_, { - key: 0, - variant: "primary-outline", - onClick: Me - }, { - default: E(() => [s(x(S(d)("tasks_projects.billing.customer.clear_range")), 1)]), - _: 1 - })) : i("", !0) - ]), - C.value.length > 0 ? (g(), a("div", nl, [(g(!0), a(e, null, y(C.value, (e) => (g(), a("button", { - key: `${e.customer_id}-${e.currency_id ?? "none"}`, - type: "button", - class: "rounded-xl border border-line-default bg-surface p-5 text-left transition hover:border-primary-500", - onClick: (t) => Fe(e) - }, [ - o("p", il, x(ke(e.customer_id)), 1), - o("p", al, x(S(d)("tasks_projects.billing.customer.entries", { count: e.entries })) + " · " + x(S(ce)(e.minutes)), 1), - o("p", ol, [c(N, { - amount: e.amount, - currency: je(e.customer_id, e.currency_id) - }, null, 8, ["amount", "currency"])]) - ], 8, rl))), 128))])) : (g(), r(P, { - key: 1, - title: S(d)("tasks_projects.billing.customer.empty_title"), - description: S(d)("tasks_projects.billing.customer.empty_description") - }, { - default: E(() => [c(h, { - name: "BanknotesIcon", - class: "mt-5 mb-4 h-16 w-16 text-subtle" - })]), - _: 1 - }, 8, ["title", "description"])) - ])) : p.value === 2 ? (g(), a("section", sl, [o("div", cl, [o("div", null, [o("h2", ll, x(S(d)("tasks_projects.billing.entries.title")), 1), o("p", ul, x(O.value ? ke(O.value.customer_id) : ""), 1)]), c(A, { - label: S(d)("tasks_projects.billing.entries.grouping"), - class: "w-full sm:w-56" - }, { - default: E(() => [c(U, { - modelValue: ae.value, - "onUpdate:modelValue": n[0] ||= (e) => ae.value = e, - options: ie.value, - "label-key": "label" - }, null, 8, ["modelValue", "options"])]), - _: 1 - }, 8, ["label"])]), M.value ? (g(), a("div", dl, [c(T, { class: "h-8 w-8 text-primary-500" })])) : he.value > 0 ? (g(), a(e, { key: 1 }, [ - o("div", fl, [o("label", pl, [o("input", { - type: "checkbox", - class: "h-4 w-4 cursor-pointer rounded border-line-strong", - checked: ge.value, - onChange: Re - }, null, 40, ml), s(" " + x(S(d)("tasks_projects.billing.entries.select_all")), 1)]), o("p", hl, x(S(d)("tasks_projects.billing.entries.selected", { - count: j.value.length, - total: he.value - })), 1)]), - (g(!0), a(e, null, y(me.value, (t) => (g(), a("div", { - key: `${t.label}-${t.key ?? "none"}-${t.currency_id ?? "none"}`, - class: "mt-4 overflow-hidden rounded-xl border border-line-default" - }, [o("div", gl, [o("label", _l, [o("input", { - type: "checkbox", - class: "h-4 w-4 cursor-pointer rounded border-line-strong", - checked: ze(t), - onChange: (e) => Be(t) - }, null, 40, vl), s(" " + x(t.label), 1)]), o("p", yl, [s(x(S(ce)(t.minutes)) + " · ", 1), c(N, { - amount: t.amount, - currency: ye.value - }, null, 8, ["amount", "currency"])])]), o("div", bl, [o("table", xl, [o("thead", Sl, [o("tr", null, [ - n[6] ||= o("th", { class: "w-10" }, null, -1), - o("th", Cl, x(S(d)("tasks_projects.billing.entries.columns.date")), 1), - o("th", wl, x(S(d)("tasks_projects.billing.entries.columns.task")), 1), - o("th", Tl, x(S(d)("tasks_projects.billing.entries.columns.project")), 1), - o("th", El, x(S(d)("tasks_projects.billing.entries.columns.member")), 1), - o("th", Dl, x(S(d)("tasks_projects.billing.entries.columns.duration")), 1), - o("th", Ol, x(S(d)("tasks_projects.billing.entries.columns.amount")), 1) - ])]), o("tbody", kl, [(g(!0), a(e, null, y(Ve(t), (e) => (g(), a("tr", { key: e.id }, [ - o("td", Al, [o("input", { - type: "checkbox", - class: "h-4 w-4 cursor-pointer rounded border-line-strong", - checked: Ie(e.id), - onChange: (t) => Le(e.id) - }, null, 40, jl)]), - o("td", Ml, x(S(oe)(e.date)), 1), - o("td", Nl, [o("span", Pl, x(e.task_name), 1), o("span", Fl, x(e.description || S(d)("tasks_projects.billing.entries.no_description")), 1)]), - o("td", Il, x(e.project_name ?? "-"), 1), - o("td", Ll, x(e.user_name), 1), - o("td", Rl, x(S(ce)(e.minutes)), 1), - o("td", zl, [c(N, { - amount: e.amount, - currency: ye.value - }, null, 8, ["amount", "currency"])]) - ]))), 128))])])])]))), 128)), - o("div", Bl, [o("p", Vl, [s(x(S(d)("tasks_projects.billing.entries.selected_total", { hours: S(ce)(_e.value) })) + " · ", 1), c(N, { - amount: ve.value, - currency: ye.value - }, null, 8, ["amount", "currency"])]), o("div", Hl, [c(_, { - variant: "primary-outline", - onClick: Ze - }, { - default: E(() => [s(x(S(d)("tasks_projects.billing.back")), 1)]), - _: 1 - }), c(_, { - variant: "primary", - disabled: j.value.length === 0, - onClick: He - }, { - default: E(() => [s(x(S(d)("tasks_projects.billing.next")), 1)]), - _: 1 - }, 8, ["disabled"])])]) - ], 64)) : (g(), r(P, { - key: 2, - title: S(d)("tasks_projects.billing.entries.empty_title"), - description: S(d)("tasks_projects.billing.entries.empty_description") - }, { - actions: E(() => [c(_, { - variant: "primary", - onClick: Ze - }, { - default: E(() => [s(x(S(d)("tasks_projects.billing.back")), 1)]), - _: 1 - })]), - default: E(() => [c(h, { - name: "ClockIcon", - class: "mt-5 mb-4 h-16 w-16 text-subtle" - })]), - _: 1 - }, 8, ["title", "description"]))])) : p.value === 3 ? (g(), a("section", Ul, [o("h2", Wl, x(S(d)("tasks_projects.billing.preview.title")), 1), I.value || F.value === null ? (g(), a("div", Gl, [c(T, { class: "h-8 w-8 text-primary-500" })])) : (g(), a(e, { key: 1 }, [ - z.value ? (g(), a("div", Kl, x(S(d)("tasks_projects.billing.preview.invalid")), 1)) : i("", !0), - o("div", ql, [ - c(A, { - label: S(d)("tasks_projects.billing.preview.invoice_date"), - error: R.value.invoice_date, - required: "" - }, { - default: E(() => [c(k, { - "model-value": L.invoiceDate, - "onUpdate:modelValue": Ge - }, null, 8, ["model-value"])]), - _: 1 - }, 8, ["label", "error"]), - c(A, { - label: S(d)("tasks_projects.billing.preview.due_date"), - error: R.value.due_date - }, { - default: E(() => [c(k, { - "model-value": L.dueDate, - "onUpdate:modelValue": Ke - }, null, 8, ["model-value"])]), - _: 1 - }, 8, ["label", "error"]), - c(A, { - label: S(d)("tasks_projects.billing.preview.invoice_number"), - error: R.value.invoice_number, - required: "" - }, { - default: E(() => [c(te, { - modelValue: L.invoiceNumber, - "onUpdate:modelValue": n[1] ||= (e) => L.invoiceNumber = e, - type: "text", - name: "invoice_number", - disabled: !xe.value - }, null, 8, ["modelValue", "disabled"]), xe.value ? i("", !0) : (g(), a("span", Jl, x(S(d)("tasks_projects.billing.preview.invoice_number_auto")), 1))]), - _: 1 - }, 8, ["label", "error"]), - c(A, { - label: S(d)("tasks_projects.billing.preview.template"), - error: R.value.template_name, - required: "" - }, { - default: E(() => [c(U, { - modelValue: ue.value, - "onUpdate:modelValue": n[2] ||= (e) => ue.value = e, - options: le.value, - "label-key": "name" - }, null, 8, ["modelValue", "options"])]), - _: 1 - }, 8, ["label", "error"]), - be.value ? (g(), r(A, { - key: 0, - label: S(d)("tasks_projects.billing.preview.exchange_rate"), - error: R.value.exchange_rate, - required: "" - }, { - default: E(() => [c(te, { - modelValue: L.exchangeRate, - "onUpdate:modelValue": n[3] ||= (e) => L.exchangeRate = e, - type: "text", - name: "exchange_rate" - }, null, 8, ["modelValue"]), o("span", Yl, x(S(d)("tasks_projects.billing.preview.exchange_rate_help", { currency: ye.value?.code ?? "" })), 1)]), - _: 1 - }, 8, ["label", "error"])) : i("", !0) - ]), - o("div", Xl, [o("div", Zl, [o("table", Ql, [ - o("thead", $l, [o("tr", null, [ - o("th", eu, x(S(d)("tasks_projects.billing.preview.columns.description")), 1), - o("th", tu, x(S(d)("tasks_projects.billing.preview.columns.quantity")), 1), - o("th", nu, x(S(d)("tasks_projects.billing.preview.columns.price")), 1), - o("th", ru, x(S(d)("tasks_projects.billing.preview.columns.total")), 1) - ])]), - o("tbody", iu, [(g(!0), a(e, null, y(F.value.items, (e, t) => (g(), a("tr", { key: `${e.name}-${t}` }, [ - o("td", au, [o("span", ou, x(e.name), 1), e.description ? (g(), a("span", su, x(e.description), 1)) : i("", !0)]), - o("td", cu, x(e.quantity), 1), - o("td", lu, [c(N, { - amount: e.price, - currency: ye.value - }, null, 8, ["amount", "currency"])]), - o("td", uu, [c(N, { - amount: e.total, - currency: ye.value - }, null, 8, ["amount", "currency"])]) - ]))), 128))]), - o("tfoot", du, [o("tr", null, [o("td", fu, x(S(d)("tasks_projects.billing.preview.sub_total")), 1), o("td", pu, [c(N, { - amount: F.value.sub_total, - currency: ye.value - }, null, 8, ["amount", "currency"])])]), o("tr", null, [o("td", mu, x(S(d)("tasks_projects.billing.preview.total")), 1), o("td", hu, [c(N, { - amount: F.value.total, - currency: ye.value - }, null, 8, ["amount", "currency"])])])]) - ])])]), - o("div", gu, [c(_, { - variant: "primary-outline", - onClick: n[4] ||= (e) => p.value = 2 - }, { - default: E(() => [s(x(S(d)("tasks_projects.billing.back")), 1)]), - _: 1 - }), c(_, { - variant: "primary", - loading: B.value, - disabled: B.value, - onClick: Je - }, { - left: E((e) => [B.value ? i("", !0) : (g(), r(h, { - key: 0, - name: "DocumentPlusIcon", - class: f(e.class) - }, null, 8, ["class"]))]), - default: E(() => [s(" " + x(S(d)("tasks_projects.billing.preview.create")), 1)]), - _: 1 - }, 8, ["loading", "disabled"])]) - ], 64))])) : (g(), a("section", _u, [B.value || ne.value ? (g(), a("div", vu, [c(T, { class: "h-8 w-8 text-primary-500" }), o("p", yu, x(B.value ? S(d)("tasks_projects.billing.create.creating") : S(d)("tasks_projects.billing.create.stamping")), 1)])) : V.value ? (g(), a(e, { key: 1 }, [ee.value ? (g(), a("div", bu, [ - o("p", xu, x(S(d)("tasks_projects.billing.create.stamp_failed_title")), 1), - o("p", Su, x(S(d)("tasks_projects.billing.create.stamp_failed_description", { number: V.value.invoice_number })), 1), - o("div", Cu, [c(_, { - variant: "primary", - loading: ne.value, - onClick: Xe - }, { - default: E(() => [s(x(S(d)("tasks_projects.billing.create.retry_stamp")), 1)]), - _: 1 - }, 8, ["loading"]), c(v, { to: `${Au}/${V.value.id}/view` }, { - default: E(() => [c(_, { variant: "white" }, { - default: E(() => [s(x(S(d)("tasks_projects.billing.create.view_invoice")), 1)]), - _: 1 - })]), - _: 1 - }, 8, ["to"])]) - ])) : (g(), a("div", wu, [ - c(h, { - name: "CheckCircleIcon", - class: "mx-auto h-12 w-12 text-primary-500" - }), - o("p", Tu, x(S(d)("tasks_projects.billing.create.created_title", { number: V.value.invoice_number })), 1), - o("p", Eu, x(S(d)("tasks_projects.billing.create.created_description", { count: H.value ?? 0 })), 1), - o("p", Du, [c(N, { - amount: V.value.total, - currency: ye.value - }, null, 8, ["amount", "currency"])]), - o("div", Ou, [c(v, { to: `${Au}/${V.value.id}/view` }, { - default: E(() => [c(_, { variant: "primary" }, { - default: E(() => [s(x(S(d)("tasks_projects.billing.create.view_invoice")), 1)]), - _: 1 - })]), - _: 1 - }, 8, ["to"]), c(_, { - variant: "white", - onClick: Ze - }, { - default: E(() => [s(x(S(d)("tasks_projects.billing.create.invoice_more")), 1)]), - _: 1 - })]) - ]))], 64)) : (g(), a("div", ku, [c(_, { - variant: "primary-outline", - onClick: n[5] ||= (e) => p.value = 3 - }, { - default: E(() => [s(x(S(d)("tasks_projects.billing.back")), 1)]), - _: 1 - })]))])) - ]), + }); +} +function Wf(e) { + return l({ setup: () => (h(() => { + e.router.replace(H.week); + }), () => null) }); +} +//#endregion +//#region resources/js/components/QuickStartOverlay.vue?vue&type=script&setup=true&lang.ts +var Gf = { + key: 0, + class: "fixed right-6 bottom-20 z-40 flex flex-col items-end gap-3" +}, Kf = ["aria-label"], qf = { class: "flex items-center justify-between border-b border-line-default px-4 py-3" }, Jf = { class: "text-sm font-semibold text-heading" }, Yf = ["aria-label"], Xf = { + key: 0, + class: "space-y-4 px-4 py-4" +}, Zf = { class: "truncate text-sm font-medium text-heading" }, Qf = { class: "mt-1 text-2xl font-semibold tabular-nums text-primary-500" }, $f = { + key: 0, + class: "mt-1 text-xs text-muted" +}, ep = { class: "flex items-center gap-2" }, tp = { + key: 1, + class: "space-y-3 px-4 py-4" +}, np = { class: "block" }, rp = { class: "sr-only" }, ip = ["placeholder"], ap = { + key: 0, + class: "text-xs text-muted" +}, op = { + key: 1, + class: "max-h-48 space-y-1 overflow-y-auto" +}, sp = ["onClick"], cp = { + key: 2, + class: "text-xs text-muted" +}, lp = ["placeholder", "aria-label"], up = { class: "flex items-center justify-between" }, dp = ["title", "aria-label"], fp = { + key: 0, + class: "tabular-nums" +}, pp = 300, mp = /* @__PURE__ */ l({ + __name: "QuickStartOverlay", + props: { + client: { type: [Function, Object] }, + notify: { type: Function }, + enabled: { type: Boolean } + }, + emits: ["open-week"], + setup(l, { emit: u }) { + let d = l, p = u, h = B(), g = y(!1), v = y(""), T = y([]), A = y(!1), j = y(null), M = y(""), N, P = n(() => ({ + notify: d.notify, + t: h + })), F = n(() => Ia(G.running?.task_id ?? null)), ee = n(() => Ka(G.elapsedSeconds)); + E(() => d.enabled, (e) => { + e || L(); + }), E(g, (e) => { + e && G.running === null && I(); + }), E(v, () => { + clearTimeout(N), N = setTimeout(() => void I(), pp); + }), m(() => clearTimeout(N)); + async function I() { + A.value = !0; + try { + let e = await ga(d.client, v.value); + T.value = e, e.forEach(La); + } catch (e) { + T.value = [], d.notify("error", R(e, h("tasks_projects.time.tasks_failed"))); + } finally { + A.value = !1; + } + } + function te(e) { + j.value = e, La(e); + } + function L() { + g.value = !1, v.value = "", T.value = [], j.value = null, M.value = ""; + } + async function ne() { + let e = j.value; + e !== null && await G.start(d.client, e.id, M.value.trim() || null, P.value) !== null && (d.notify("success", h("tasks_projects.timer.started", { name: e.name })), L()); + } + async function re() { + let e = F.value, t = await G.stop(d.client, P.value); + t !== null && (d.notify("success", h("tasks_projects.timer.stopped", { + name: e, + duration: qa(t.duration_minutes) + })), L()); + } + async function ie() { + window.confirm(h("tasks_projects.timer.discard_confirm")) && await G.discard(d.client, P.value) && (d.notify("success", h("tasks_projects.timer.discarded")), L()); + } + return (n, u) => { + let d = x("BaseIcon"), m = x("BaseButton"); + return _(), r(t, { to: "body" }, [l.enabled ? (_(), a("div", Gf, [g.value ? (_(), a("section", { + key: 0, + class: "w-80 max-w-[calc(100vw-3rem)] rounded-xl border border-line-default bg-surface shadow-2xl", + "aria-label": C(h)("tasks_projects.timer.panel_title"), + onKeydown: k(L, ["esc"]) + }, [o("header", qf, [o("h2", Jf, S(C(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": C(h)("tasks_projects.timer.close"), + onClick: L + }, [c(d, { + name: "XMarkIcon", + class: "h-5 w-5" + })], 8, Yf)]), C(G).running === null ? (_(), a("div", tp, [ + o("label", np, [o("span", rp, S(C(h)("tasks_projects.timer.search_tasks")), 1), O(o("input", { + "onUpdate:modelValue": u[0] ||= (e) => v.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: C(h)("tasks_projects.timer.search_tasks") + }, null, 8, ip), [[w, v.value]])]), + A.value ? (_(), a("p", ap, S(C(h)("tasks_projects.general.search")), 1)) : T.value.length > 0 ? (_(), a("ul", op, [(_(!0), a(e, null, b(T.value, (e) => (_(), 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) => te(e) + }, S(e.name), 11, sp)]))), 128))])) : (_(), a("p", cp, S(C(h)("tasks_projects.timer.no_tasks")), 1)), + O(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: C(h)("tasks_projects.timer.description_placeholder"), + "aria-label": C(h)("tasks_projects.time.fields.description") + }, null, 8, lp), [[w, M.value]]), + o("div", up, [o("button", { + type: "button", + class: "text-xs text-primary-500 hover:underline", + onClick: u[2] ||= (e) => p("open-week") + }, S(C(h)("tasks_projects.timer.open_timesheet")), 1), c(m, { + variant: "primary", + disabled: j.value === null || C(G).busy, + onClick: ne + }, { + left: D((e) => [c(d, { + name: "PlayIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: D(() => [s(" " + S(C(h)("tasks_projects.timer.start")), 1)]), + _: 1 + }, 8, ["disabled"])]) + ])) : (_(), a("div", Xf, [o("div", null, [ + o("p", Zf, S(F.value), 1), + o("p", Qf, S(ee.value), 1), + C(G).running.description ? (_(), a("p", $f, S(C(G).running.description), 1)) : i("", !0) + ]), o("div", ep, [c(m, { + variant: "primary", + disabled: C(G).busy, + onClick: re + }, { + left: D((e) => [c(d, { + name: "StopIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: D(() => [s(" " + S(C(h)("tasks_projects.timer.stop")), 1)]), _: 1 - }); + }, 8, ["disabled"]), c(m, { + variant: "primary-outline", + disabled: C(G).busy, + onClick: ie + }, { + default: D(() => [s(S(C(h)("tasks_projects.timer.discard")), 1)]), + _: 1 + }, 8, ["disabled"])])]))], 40, Kf)) : 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: C(h)("tasks_projects.timer.quick_start"), + "aria-label": C(h)("tasks_projects.timer.quick_start"), + onClick: u[3] ||= (e) => g.value = !g.value + }, [c(d, { + name: C(G).running === null ? "ClockIcon" : "StopIcon", + class: "h-5 w-5 text-white" + }, null, 8, ["name"]), C(G).running === null ? i("", !0) : (_(), a("span", fp, S(ee.value), 1))], 8, dp)])) : i("", !0)]); }; } -}), Mu = "tasks-projects"; -function Nu(e) { - e.addMessages(Fc), e.registerPage({ - id: "billing", - module: Mu, - path: "billing", - component: jc(e, ju), - meta: { - ability: `${Mu}:invoice-tasks`, - title: "tasks_projects.billing.title" +}), hp = { + key: 0, + class: "relative float-left m-0 ml-2" +}, gp = ["title"], _p = ["aria-label", "title"], vp = { class: "font-medium tabular-nums" }, yp = [ + "disabled", + "title", + "aria-label" +], bp = /* @__PURE__ */ l({ + __name: "TimerChip", + props: { + client: { type: [Function, Object] }, + notify: { type: Function } + }, + emits: ["open"], + setup(e, { emit: t }) { + let r = e, s = t, l = B(), u = n(() => Ia(G.running?.task_id ?? null)), d = n(() => Ka(G.elapsedSeconds)); + async function f() { + let e = u.value, t = await G.stop(r.client, { + notify: r.notify, + t: l + }); + t !== null && r.notify("success", l("tasks_projects.timer.stopped", { + name: e, + duration: qa(t.duration_minutes) + })); } - }); -} -//#endregion -//#region resources/js/messages/reports.ts -var Pu = { en: { tasks_projects: { reports: { - title: "Reports", - load_failed: "Unable to load the report.", - empty_title: "Nothing logged in this range", - empty_description: "Pick a wider range, or log some time against a task.", - range: { + return (e, t) => { + let n = x("BaseIcon"); + return C(G).running === null ? i("", !0) : (_(), a("li", hp, [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: C(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": C(l)("tasks_projects.timer.open_task"), + title: C(l)("tasks_projects.timer.open_task"), + onClick: t[0] ||= (e) => s("open") + }, S(u.value), 9, _p), + o("span", vp, S(d.value), 1), + o("button", { + type: "button", + class: "rounded p-1 hover:bg-white/20 disabled:opacity-50", + disabled: C(G).busy, + title: C(l)("tasks_projects.timer.stop"), + "aria-label": C(l)("tasks_projects.timer.stop"), + onClick: f + }, [c(n, { + name: "StopIcon", + class: "h-4 w-4 text-white" + })], 8, yp) + ], 8, gp)])); + }; + } +}), xp = { en: { tasks_projects: { + time: { + title: "Time", + my_time: "My time", + all_time: "All time", this_week: "This week", - this_month: "This month", - last_month: "Last month", - this_quarter: "This quarter", - this_year: "This year", - custom: "Custom", - from: "From", - to: "To" - }, - summary: { - logged: "Logged", + 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", - amount: "Amount", + non_billable: "Not billable", + billed: "Billed", unbilled: "Unbilled", - currency: "Currency #{id}", - base_currency: "Company currency" + 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." }, - split: { - title: "Billable against the rest", - billable: "Billable", - non_billable: "Not billable", - nothing: "No time logged in this range." + 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 week", + open_task: "Open the running task", + start_on: "Start the timer on {name}", + stop_on: "Stop the timer on {name}", + busy_elsewhere: "Your timer is running on {name}.", + stop_and_start: "Stop and start", + running_by: "{name} has been running since {time}.", + mismatch: "Your timer is no longer on this task. It has been reloaded.", + 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." }, - tables: { - by_project: "By project", - by_member: "By member", - by_customer: "By customer", - project: "Project", - member: "Member", - customer: "Customer", - no_project: "No project", - no_customer: "Internal", - unknown_member: "Removed member", - currency: "Currency", - logged: "Logged", - billable: "Billable", - amount: "Amount", - unbilled: "Unbilled" + 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", + behaviour_title: "Task behaviour", + behaviour_description: "What happens when a task is created, invoiced or shown on the board. Change these in the module settings form.", + rounding_direction: "Rounding", + rounding_direction_nearest: "To the nearest increment", + rounding_direction_up: "Up to the increment", + rounding_direction_down: "Down to the increment", + rounding_increment: "Increment", + rounding_increment_value: "{count} minutes", + auto_start_tasks: "Start the timer on a new task", + lock_invoiced_tasks: "Lock invoiced tasks", + hide_invoiced_on_board: "Hide invoiced tasks on the board", + invoice_title: "Invoice lines", + invoice_description: "What an invoice line built from a task carries. Change these in the module settings form.", + invoice_project_heading: "Project heading", + invoice_task_description: "Task description", + invoice_entry_dates: "Entry dates", + invoice_entry_times: "Entry times", + invoice_entry_hours: "Entry hours", + invoice_entry_descriptions: "Entry descriptions", + on: "On", + off: "Off", + 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." } -} } } }, Fu = { summary: `${j}/reports/summary` }; -async function Iu(e, t) { - let { data: n } = await e.get(Fu.summary, { params: t }); - return Lu(n?.data, t); -} -function Lu(e, t) { - let n = Bu(e) ? e : {}; - return { - from: Uu(n.from, t.from ?? ""), - to: Uu(n.to, t.to ?? ""), - totals: zu(n.totals).map(Ru), - by_project: zu(n.by_project).map((e) => ({ - ...Ru(e), - project_id: Hu(e.project_id), - label: Uu(e.label, "") - })), - by_member: zu(n.by_member).map((e) => ({ - ...Ru(e), - user_id: Hu(e.user_id), - label: Uu(e.label, "") - })), - by_customer: zu(n.by_customer).map((e) => ({ - ...Ru(e), - customer_id: Hu(e.customer_id) - })), - by_billable: zu(n.by_billable).map((e) => ({ - ...Ru(e), - billable: e.billable === !0 - })) - }; -} -function Ru(e) { - return { - currency_id: Hu(e.currency_id), - minutes: Vu(e.minutes), - amount: Vu(e.amount), - billable_minutes: Vu(e.billable_minutes), - billable_amount: Vu(e.billable_amount), - unbilled_amount: Vu(e.unbilled_amount) - }; -} -function zu(e) { - return Array.isArray(e) ? e.filter(Bu) : []; -} -function Bu(e) { - return typeof e == "object" && !!e; -} -function Vu(e) { - return typeof e == "number" && Number.isFinite(e) ? e : 0; -} -function Hu(e) { - return typeof e == "number" && Number.isFinite(e) ? e : null; -} -function Uu(e, t) { - return typeof e == "string" && e.trim() !== "" ? e : t; -} -//#endregion -//#region resources/js/components/ReportBreakdownTable.vue?vue&type=script&setup=true&lang.ts -var Wu = { class: "mt-6" }, Gu = { class: "text-sm font-semibold tracking-wider text-muted uppercase" }, Ku = { class: "relative table-container" }, qu = { +} } }, Sp = { key: 0, - class: "text-subtle" -}, Ju = { key: 1 }, Yu = /* @__PURE__ */ l({ - __name: "ReportBreakdownTable", + class: "text-sm text-muted" +}, Cp = { key: 1 }, wp = { + key: 0, + class: "flex items-center gap-2 text-sm text-muted" +}, Tp = { + key: 1, + class: "text-sm text-muted" +}, Ep = { + key: 2, + class: "divide-y divide-line-light" +}, Dp = { + key: 0, + class: "space-y-3" +}, Op = { class: "flex flex-wrap items-center gap-2" }, kp = ["aria-label", "onClick"], Ap = { class: "flex flex-wrap items-center gap-6" }, jp = { class: "flex items-center gap-2 text-sm text-body" }, Mp = { class: "flex items-center gap-2 text-sm text-body" }, Np = { class: "flex gap-3" }, Pp = { + key: 1, + class: "flex items-center gap-3" +}, Fp = { class: "min-w-0 flex-1 truncate text-sm font-medium text-heading" }, Ip = { class: "flex items-center gap-1" }, Lp = [ + "disabled", + "title", + "aria-label", + "onClick" +], Rp = [ + "disabled", + "title", + "aria-label", + "onClick" +], zp = [ + "title", + "aria-label", + "onClick" +], Bp = [ + "disabled", + "title", + "aria-label", + "onClick" +], Vp = { + key: 3, + class: "mt-4 space-y-3 rounded-lg border border-line-default p-3" +}, Hp = { class: "flex flex-wrap items-center gap-2" }, Up = ["aria-label", "onClick"], Wp = { class: "flex flex-wrap items-center gap-6" }, Gp = { class: "flex items-center gap-2 text-sm text-body" }, Kp = { class: "flex items-center gap-2 text-sm text-body" }, qp = { class: "flex gap-3" }, Jp = /* @__PURE__ */ l({ + __name: "TaskStatusEditor", props: { - title: {}, - labelHeading: {}, - rows: {}, - showCurrency: { type: Boolean } + client: { type: [Function, Object] }, + notify: { type: Function } }, - setup(e) { - let t = e, i = de(), l = n(() => [ - { - key: "label", - label: t.labelHeading, - thClass: "extra", - tdClass: "font-medium text-heading" - }, - ...t.showCurrency ? [{ - key: "currency_id", - label: i("tasks_projects.reports.tables.currency") - }] : [], - { - key: "minutes", - label: i("tasks_projects.reports.tables.logged"), - dataType: "numeric" - }, - { - key: "billable_minutes", - label: i("tasks_projects.reports.tables.billable"), - dataType: "numeric" - }, - { - key: "amount", - label: i("tasks_projects.reports.tables.amount"), - dataType: "numeric" - }, - { - key: "unbilled_amount", - label: i("tasks_projects.reports.tables.unbilled"), - dataType: "numeric" + setup(t) { + let l = t, u = [ + "#94a3b8", + "#3b82f6", + "#22c55e", + "#f59e0b", + "#ef4444", + "#a855f7", + "#0891b2", + "#64748b" + ], d = B(), m = y([]), g = y(!0), w = y(!1), T = y(!1), E = y(null), O = y(!1), k = v({ + name: "", + colour: "", + is_default: !1, + is_closed: !1 + }), A = n(() => !g.value && m.value.length === 0); + h(() => void j()); + async function j() { + g.value = !0; + try { + m.value = await da(l.client), w.value = !1; + } catch (e) { + m.value = [], w.value = fe(e), w.value || l.notify("error", R(e, d("tasks_projects.settings.load_failed"))); + } finally { + g.value = !1; } - ]), u = n(() => t.showCurrency ? "currency" : "plain"); + } + function M(e) { + O.value = !1, E.value = e.id, k.name = e.name, k.colour = e.colour ?? "", k.is_default = e.is_default, k.is_closed = e.is_closed; + } + function N() { + E.value = null, O.value = !0, k.name = "", k.colour = u[0], k.is_default = !1, k.is_closed = !1; + } + function P() { + E.value = null, O.value = !1; + } + function F() { + return { + name: k.name.trim(), + colour: k.colour || null, + is_default: k.is_default, + is_closed: k.is_closed + }; + } + async function ee() { + if (T.value) return; + if (k.name.trim() === "") { + l.notify("error", d("tasks_projects.settings.status_name_required")); + return; + } + let e = E.value, t = k.name.trim(); + T.value = !0; + try { + e === null ? (await fa(l.client, F()), l.notify("success", d("tasks_projects.settings.status_created", { name: t }))) : (await pa(l.client, e, F()), l.notify("success", d("tasks_projects.settings.status_updated", { name: t }))), P(), await j(); + } catch (e) { + l.notify("error", R(e, d("tasks_projects.settings.save_failed"))); + } finally { + T.value = !1; + } + } + async function I(e) { + if (!T.value && window.confirm(d("tasks_projects.settings.status_delete_confirm", { name: e.name }))) { + T.value = !0; + try { + await ma(l.client, e.id), l.notify("success", d("tasks_projects.settings.status_deleted", { name: e.name })), P(), await j(); + } catch (e) { + l.notify("error", R(e, d("tasks_projects.settings.delete_failed"))); + } finally { + T.value = !1; + } + } + } + async function te(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 ha(l.client, r.map((e) => e.id)), l.notify("success", d("tasks_projects.settings.status_reordered")); + } catch (e) { + l.notify("error", R(e, d("tasks_projects.settings.reorder_failed"))), await j(); + } finally { + T.value = !1; + } + } return (t, n) => { - let i = b("BaseFormatMoney"), d = b("BaseTable"); - return g(), a("section", Wu, [o("h3", Gu, x(e.title), 1), o("div", Ku, [(g(), r(d, { - key: u.value, - data: e.rows, - columns: l.value, - class: "mt-2" + let l = x("BaseSpinner"), h = x("BaseInput"), v = x("BaseInputGroup"), y = x("BaseSwitch"), j = x("BaseButton"), F = x("BaseBadge"), L = x("BaseIcon"); + return _(), a("div", null, [w.value ? (_(), a("p", Sp, S(C(d)("tasks_projects.settings.forbidden")), 1)) : (_(), a("div", Cp, [g.value ? (_(), a("div", wp, [c(l, { class: "h-4 w-4 text-primary-500" })])) : A.value ? (_(), a("p", Tp, S(C(d)("tasks_projects.settings.no_statuses")), 1)) : (_(), a("ul", Ep, [(_(!0), a(e, null, b(m.value, (t, l) => (_(), a("li", { + key: t.id, + class: "py-3" + }, [E.value === t.id ? (_(), a("div", Dp, [ + c(v, { + label: C(d)("tasks_projects.settings.status_name"), + required: "" + }, { + default: D(() => [c(h, { + modelValue: k.name, + "onUpdate:modelValue": n[0] ||= (e) => k.name = e, + type: "text", + maxlength: "255" + }, null, 8, ["modelValue"])]), + _: 1 + }, 8, ["label"]), + c(v, { label: C(d)("tasks_projects.settings.colour") }, { + default: D(() => [o("div", Op, [(_(), a(e, null, b(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, kp)), 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 = "" + }, S(C(d)("tasks_projects.settings.colour_none")), 1)])]), + _: 1 + }, 8, ["label"]), + o("div", Ap, [o("label", jp, [c(y, { + modelValue: k.is_default, + "onUpdate:modelValue": n[2] ||= (e) => k.is_default = e, + class: "flex" + }, null, 8, ["modelValue"]), s(" " + S(C(d)("tasks_projects.settings.is_default")), 1)]), o("label", Mp, [c(y, { + modelValue: k.is_closed, + "onUpdate:modelValue": n[3] ||= (e) => k.is_closed = e, + class: "flex" + }, null, 8, ["modelValue"]), s(" " + S(C(d)("tasks_projects.settings.is_closed")), 1)])]), + o("div", Np, [c(j, { + variant: "primary", + size: "sm", + disabled: T.value, + onClick: ee + }, { + default: D(() => [s(S(C(d)("tasks_projects.general.save")), 1)]), + _: 1 + }, 8, ["disabled"]), c(j, { + variant: "primary-outline", + size: "sm", + onClick: P + }, { + default: D(() => [s(S(C(d)("tasks_projects.general.cancel")), 1)]), + _: 1 + })]) + ])) : (_(), a("div", Pp, [ + 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", Fp, S(t.name), 1), + t.is_default ? (_(), r(F, { + key: 0, + class: "rounded-full bg-primary-50! text-primary-500!" + }, { + default: D(() => [s(S(C(d)("tasks_projects.settings.is_default")), 1)]), + _: 1 + })) : i("", !0), + t.is_closed ? (_(), r(F, { + key: 1, + class: "rounded-full bg-surface-tertiary! text-muted!" + }, { + default: D(() => [s(S(C(d)("tasks_projects.settings.is_closed")), 1)]), + _: 1 + })) : i("", !0), + o("div", Ip, [ + 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: C(d)("tasks_projects.settings.move_up"), + "aria-label": C(d)("tasks_projects.settings.move_up"), + onClick: (e) => te(l, -1) + }, [c(L, { + name: "ChevronUpIcon", + class: "h-4 w-4" + })], 8, Lp), + 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: C(d)("tasks_projects.settings.move_down"), + "aria-label": C(d)("tasks_projects.settings.move_down"), + onClick: (e) => te(l, 1) + }, [c(L, { + name: "ChevronDownIcon", + class: "h-4 w-4" + })], 8, Rp), + o("button", { + type: "button", + class: "rounded p-1 text-subtle hover:bg-hover hover:text-heading", + title: C(d)("tasks_projects.general.edit"), + "aria-label": C(d)("tasks_projects.general.edit"), + onClick: (e) => M(t) + }, [c(L, { + name: "PencilIcon", + class: "h-4 w-4" + })], 8, zp), + o("button", { + type: "button", + class: "rounded p-1 text-subtle hover:bg-hover hover:text-alert-error-text", + disabled: T.value, + title: C(d)("tasks_projects.general.delete"), + "aria-label": C(d)("tasks_projects.general.delete"), + onClick: (e) => I(t) + }, [c(L, { + name: "TrashIcon", + class: "h-4 w-4" + })], 8, Bp) + ]) + ]))]))), 128))])), O.value ? (_(), a("div", Vp, [ + c(v, { + label: C(d)("tasks_projects.settings.status_name"), + required: "" + }, { + default: D(() => [c(h, { + modelValue: k.name, + "onUpdate:modelValue": n[4] ||= (e) => k.name = e, + type: "text", + maxlength: "255" + }, null, 8, ["modelValue"])]), + _: 1 + }, 8, ["label"]), + c(v, { label: C(d)("tasks_projects.settings.colour") }, { + default: D(() => [o("div", Hp, [(_(), a(e, null, b(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, Up)), 64))])]), + _: 1 + }, 8, ["label"]), + o("div", Wp, [o("label", Gp, [c(y, { + modelValue: k.is_default, + "onUpdate:modelValue": n[5] ||= (e) => k.is_default = e, + class: "flex" + }, null, 8, ["modelValue"]), s(" " + S(C(d)("tasks_projects.settings.is_default")), 1)]), o("label", Kp, [c(y, { + modelValue: k.is_closed, + "onUpdate:modelValue": n[6] ||= (e) => k.is_closed = e, + class: "flex" + }, null, 8, ["modelValue"]), s(" " + S(C(d)("tasks_projects.settings.is_closed")), 1)])]), + o("div", qp, [c(j, { + variant: "primary", + size: "sm", + disabled: T.value, + onClick: ee + }, { + default: D(() => [s(S(C(d)("tasks_projects.general.save")), 1)]), + _: 1 + }, 8, ["disabled"]), c(j, { + variant: "primary-outline", + size: "sm", + onClick: P + }, { + default: D(() => [s(S(C(d)("tasks_projects.general.cancel")), 1)]), + _: 1 + })]) + ])) : g.value ? i("", !0) : (_(), r(j, { + key: 4, + variant: "primary-outline", + size: "sm", + class: "mt-4", + onClick: N }, { - "cell-currency_id": E(({ row: e }) => [e.data.currency_id === null ? (g(), a("span", qu, "-")) : (g(), a("span", Ju, "#" + x(e.data.currency_id), 1))]), - "cell-minutes": E(({ row: e }) => [s(x(S(Ct)(e.data.minutes)), 1)]), - "cell-billable_minutes": E(({ row: e }) => [s(x(S(Ct)(e.data.billable_minutes)), 1)]), - "cell-amount": E(({ row: e }) => [c(i, { amount: e.data.amount }, null, 8, ["amount"])]), - "cell-unbilled_amount": E(({ row: e }) => [c(i, { amount: e.data.unbilled_amount }, null, 8, ["amount"])]), + left: D((e) => [c(L, { + name: "PlusIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: D(() => [s(" " + S(C(d)("tasks_projects.settings.add_status")), 1)]), _: 1 - }, 8, ["data", "columns"]))])]); + }))]))]); }; } -}), Xu = 3; -function Zu(e, t, n = /* @__PURE__ */ new Date()) { - let r = n.getFullYear(), i = n.getMonth(); - switch (e) { - case "THIS_WEEK": { - let e = kt(n, t); - return $u(e, jt(e, 6)); - } - case "LAST_MONTH": return $u(new Date(r, i - 1, 1), new Date(r, i, 0)); - case "THIS_QUARTER": { - let e = Math.floor(i / Xu) * Xu; - return $u(new Date(r, e, 1), new Date(r, e + Xu, 0)); - } - case "THIS_YEAR": return $u(new Date(r, 0, 1), new Date(r, 12, 0)); - default: return $u(new Date(r, i, 1), new Date(r, i + 1, 0)); - } -} -function Qu(e, t) { - return t <= 0 ? 0 : Math.min(100, Math.max(0, Math.round(e / t * 100))); -} -function $u(e, t) { - return { - from: Mt(e), - to: Mt(t) - }; -} -//#endregion -//#region resources/js/pages/ReportsPage.vue?vue&type=script&setup=true&lang.ts -var ed = { - key: 0, - class: "mt-2 text-sm text-muted" -}, td = { class: "flex items-center justify-end space-x-5" }, nd = { class: "mt-4 flex flex-wrap gap-2" }, rd = ["onClick"], id = { - key: 0, - class: "flex justify-center py-16" -}, ad = { - key: 0, - class: "text-xs font-medium tracking-wider text-muted uppercase" -}, od = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, sd = { class: "mt-1 text-2xl font-semibold text-heading" }, cd = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, ld = { class: "mt-1 text-2xl font-semibold text-heading" }, ud = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, dd = { class: "mt-1 text-2xl font-semibold text-heading" }, fd = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, pd = { class: "mt-1 text-2xl font-semibold text-heading" }, md = { class: "mt-4 rounded-xl border border-line-default bg-surface p-5" }, hd = { class: "text-xs font-medium tracking-wider text-muted uppercase" }, gd = { class: "mt-3 flex h-2 w-full overflow-hidden rounded-full bg-surface-tertiary" }, _d = { class: "mt-3 flex flex-wrap gap-6 text-sm" }, vd = { class: "inline-flex items-center text-body" }, yd = { class: "ml-1 font-medium text-heading" }, bd = { class: "ml-1 text-muted" }, xd = { class: "inline-flex items-center text-body" }, Sd = { class: "ml-1 font-medium text-heading" }, Cd = { class: "ml-1 text-muted" }, wd = { - key: 1, - class: "mt-2 text-sm text-subtle" -}, Td = "THIS_MONTH", Ed = /* @__PURE__ */ l({ - __name: "ReportsPage", +}), Yp = { class: "space-y-6" }, Xp = { class: "divide-y divide-line-light" }, Zp = { class: "text-sm text-muted" }, Qp = { class: "text-sm font-medium text-heading" }, $p = { class: "divide-y divide-line-light" }, em = { class: "text-sm text-muted" }, tm = { class: "text-sm font-medium text-heading" }, nm = /* @__PURE__ */ l({ + __name: "TimeSettingsPage", props: { client: { type: [Function, Object] }, notify: { type: Function }, router: {} }, setup(t) { - let l = t, u = de(), d = v(null), m = v([]), _ = v(!0), C = v(Td), w = v(""), T = v(""), D = n(() => [ - { - id: "THIS_WEEK", - label: u("tasks_projects.reports.range.this_week") - }, + let r = B(), i = n(() => xa.settings); + function l(e) { + return r(e ? "tasks_projects.settings.on" : "tasks_projects.settings.off"); + } + let u = n(() => [ { - id: "THIS_MONTH", - label: u("tasks_projects.reports.range.this_month") + key: "rounding_direction", + label: r("tasks_projects.settings.rounding_direction"), + value: r(`tasks_projects.settings.rounding_direction_${i.value.rounding_direction}`) }, { - id: "LAST_MONTH", - label: u("tasks_projects.reports.range.last_month") + key: "rounding_minutes", + label: r("tasks_projects.settings.rounding_increment"), + value: r("tasks_projects.settings.rounding_increment_value", { count: i.value.rounding_minutes }) }, { - id: "THIS_QUARTER", - label: u("tasks_projects.reports.range.this_quarter") + key: "auto_start_tasks", + label: r("tasks_projects.settings.auto_start_tasks"), + value: l(i.value.auto_start_tasks) }, { - id: "THIS_YEAR", - label: u("tasks_projects.reports.range.this_year") + key: "lock_invoiced_tasks", + label: r("tasks_projects.settings.lock_invoiced_tasks"), + value: l(i.value.lock_invoiced_tasks) }, { - id: "CUSTOM", - label: u("tasks_projects.reports.range.custom") - } - ]), O = n(() => d.value?.totals ?? []), k = n(() => new Set(O.value.map((e) => e.currency_id)).size > 1), A = n(() => O.value.length > 0), j = n(() => (d.value?.by_project ?? []).map((e) => ({ - ...e, - id: `${e.project_id ?? "none"}-${e.currency_id ?? "base"}`, - label: e.project_id === null ? u("tasks_projects.reports.tables.no_project") : e.label || `#${e.project_id}` - }))), M = n(() => (d.value?.by_member ?? []).map((e) => ({ - ...e, - id: `${e.user_id ?? "none"}-${e.currency_id ?? "base"}`, - label: ne(e) - }))), N = n(() => (d.value?.by_customer ?? []).map((e) => ({ - ...e, - id: `${e.customer_id ?? "none"}-${e.currency_id ?? "base"}`, - label: e.customer_id === null ? u("tasks_projects.reports.tables.no_customer") : xe(e.customer_id) - }))), P = n(() => re(!0)), F = n(() => re(!1)), I = n(() => P.value + F.value), L = n(() => Qu(P.value, I.value)); - h(() => { - R(Td), te(); - }); - function R(e) { - if (C.value = e, e !== "CUSTOM") { - let t = Zu(e, fr.settings.week_start); - w.value = t.from, T.value = t.to; - } - H(); - } - function z(e) { - w.value = e ? se(e) : "", C.value = "CUSTOM", H(); - } - function B(e) { - T.value = e ? se(e) : "", C.value = "CUSTOM", H(); - } - async function H() { - _.value = !0; - try { - let e = await Iu(l.client, ee()); - d.value = e, e.by_customer.some((e) => e.customer_id !== null) && Se(l.client); - } catch (e) { - d.value = null, l.notify("error", U(e, u("tasks_projects.reports.load_failed"))); - } finally { - _.value = !1; + key: "hide_invoiced_on_board", + label: r("tasks_projects.settings.hide_invoiced_on_board"), + value: l(i.value.hide_invoiced_on_board) } - } - function ee() { - let e = {}; - return w.value !== "" && (e.from = w.value), T.value !== "" && (e.to = T.value), e; - } - async function te() { - try { - m.value = await V(l.client); - } catch {} - } - function ne(e) { - let t = m.value.find((t) => t.id === e.user_id)?.name ?? ""; - return t === "" ? e.label === "" ? e.user_id === null ? u("tasks_projects.reports.tables.unknown_member") : `#${e.user_id}` : e.label : t; - } - function re(e) { - return (d.value?.by_billable ?? []).filter((t) => t.billable === e).reduce((e, t) => e + t.minutes, 0); - } - function ie(e) { - return e === null ? u("tasks_projects.reports.summary.base_currency") : u("tasks_projects.reports.summary.currency", { id: e }); - } - function ae(e) { - return C.value === e.id ? "border-primary-500 bg-primary-50 text-primary-500" : "border-line-default bg-surface text-muted hover:text-heading"; - } - function ce() { - R(Td); - } - return (t, n) => { - let l = b("BaseBreadcrumbItem"), m = b("BaseBreadcrumb"), h = b("BaseIcon"), v = b("BaseButton"), C = b("router-link"), V = b("BasePageHeader"), H = b("BaseDatePicker"), ee = b("BaseInputGroup"), U = b("BaseFilterWrapper"), te = b("BaseSpinner"), ne = b("BaseEmptyPlaceholder"), re = b("BaseFormatMoney"), se = b("BasePage"); - return g(), r(se, null, { - default: E(() => [ - c(V, { title: S(u)("tasks_projects.reports.title") }, { - actions: E(() => [o("div", td, [c(C, { to: "/admin/modules/tasks-projects" }, { - default: E(() => [c(v, { variant: "white" }, { - left: E((e) => [c(h, { - name: "FolderIcon", - class: f(e.class) - }, null, 8, ["class"])]), - default: E(() => [s(" " + x(S(u)("tasks_projects.projects.title")), 1)]), - _: 1 - })]), - _: 1 - }), c(C, { to: "/admin/modules/tasks-projects/board" }, { - default: E(() => [c(v, { variant: "white" }, { - left: E((e) => [c(h, { - name: "ViewColumnsIcon", - class: f(e.class) - }, null, 8, ["class"])]), - default: E(() => [s(" " + x(S(u)("tasks_projects.board.title")), 1)]), - _: 1 - })]), - _: 1 - })])]), - default: E(() => [c(m, null, { - default: E(() => [ - c(l, { - title: S(u)("tasks_projects.general.home"), - to: "/admin/dashboard" - }, null, 8, ["title"]), - c(l, { - title: S(u)("tasks_projects.projects.title"), - to: "/admin/modules/tasks-projects" - }, null, 8, ["title"]), - c(l, { - title: S(u)("tasks_projects.reports.title"), - to: "#", - active: "" - }, null, 8, ["title"]) - ]), - _: 1 - }), d.value ? (g(), a("p", ed, x(S(oe)(d.value.from)) + " – " + x(S(oe)(d.value.to)), 1)) : i("", !0)]), - _: 1 - }, 8, ["title"]), - o("div", nd, [(g(!0), a(e, null, y(D.value, (e) => (g(), a("button", { - key: e.id, - type: "button", - class: f(["rounded-md border px-3 py-1.5 text-sm font-medium", ae(e)]), - onClick: (t) => R(e.id) - }, x(e.label), 11, rd))), 128))]), - c(U, { - show: !0, - "row-on-xl": "", - class: "mt-3", - onClear: ce - }, { - default: E(() => [c(ee, { - label: S(u)("tasks_projects.reports.range.from"), - class: "mt-2 flex-1" - }, { - default: E(() => [c(H, { - "model-value": w.value, - "onUpdate:modelValue": z - }, null, 8, ["model-value"])]), - _: 1 - }, 8, ["label"]), c(ee, { - label: S(u)("tasks_projects.reports.range.to"), - class: "mt-2 flex-1" + ]), d = n(() => [ + "invoice_project_heading", + "invoice_task_description", + "invoice_entry_dates", + "invoice_entry_times", + "invoice_entry_hours", + "invoice_entry_descriptions" + ].map((e) => ({ + key: e, + label: r(`tasks_projects.settings.${e}`), + value: l(i.value[e]) + }))); + return (n, i) => { + let l = x("BaseIcon"), p = x("BaseButton"), m = x("router-link"), h = x("BaseSettingCard"); + return _(), a("div", Yp, [ + c(h, { + title: C(r)("tasks_projects.settings.general_title"), + description: C(r)("tasks_projects.settings.general_description") + }, { + action: D(() => [c(m, { to: C(H).settings }, { + default: D(() => [c(p, { + variant: "primary-outline", + size: "sm" }, { - default: E(() => [c(H, { - "model-value": T.value, - "onUpdate:modelValue": B - }, null, 8, ["model-value"])]), + right: D((e) => [c(l, { + name: "ArrowTopRightOnSquareIcon", + class: f(e.class) + }, null, 8, ["class"])]), + default: D(() => [s(" " + S(C(r)("tasks_projects.settings.open_module_settings")), 1)]), _: 1 - }, 8, ["label"])]), - _: 1 - }), - _.value && d.value === null ? (g(), a("div", id, [c(te, { class: "h-8 w-8 text-primary-500" })])) : A.value ? (g(), a(e, { key: 2 }, [ - (g(!0), a(e, null, y(O.value, (e) => (g(), a("div", { - key: e.currency_id ?? "base", - class: "mt-4 rounded-xl border border-line-default bg-surface p-5" - }, [k.value ? (g(), a("p", ad, x(ie(e.currency_id)), 1)) : i("", !0), o("div", { class: f(["grid grid-cols-2 gap-4 sm:grid-cols-4", k.value ? "mt-3" : ""]) }, [ - o("div", null, [o("p", od, x(S(u)("tasks_projects.reports.summary.logged")), 1), o("p", sd, x(S(Ct)(e.minutes)), 1)]), - o("div", null, [o("p", cd, x(S(u)("tasks_projects.reports.summary.billable")), 1), o("p", ld, x(S(Ct)(e.billable_minutes)), 1)]), - o("div", null, [o("p", ud, x(S(u)("tasks_projects.reports.summary.amount")), 1), o("p", dd, [c(re, { amount: e.amount }, null, 8, ["amount"])])]), - o("div", null, [o("p", fd, x(S(u)("tasks_projects.reports.summary.unbilled")), 1), o("p", pd, [c(re, { amount: e.unbilled_amount }, null, 8, ["amount"])])]) - ], 2)]))), 128)), - o("section", md, [o("p", hd, x(S(u)("tasks_projects.reports.split.title")), 1), I.value > 0 ? (g(), a(e, { key: 0 }, [o("div", gd, [o("div", { - class: "h-2 bg-primary-500", - style: p({ width: `${L.value}%` }) - }, null, 4)]), o("div", _d, [o("span", vd, [ - n[0] ||= o("span", { class: "mr-2 inline-block h-2.5 w-2.5 rounded-full bg-primary-500" }, null, -1), - s(" " + x(S(u)("tasks_projects.reports.split.billable")) + ": ", 1), - o("span", yd, x(S(Ct)(P.value)), 1), - o("span", bd, "(" + x(L.value) + "%)", 1) - ]), o("span", xd, [ - n[1] ||= o("span", { class: "mr-2 inline-block h-2.5 w-2.5 rounded-full bg-surface-tertiary" }, null, -1), - s(" " + x(S(u)("tasks_projects.reports.split.non_billable")) + ": ", 1), - o("span", Sd, x(S(Ct)(F.value)), 1), - o("span", Cd, "(" + x(100 - L.value) + "%)", 1) - ])])], 64)) : (g(), a("p", wd, x(S(u)("tasks_projects.reports.split.nothing")), 1))]), - c(Yu, { - title: S(u)("tasks_projects.reports.tables.by_project"), - "label-heading": S(u)("tasks_projects.reports.tables.project"), - rows: j.value, - "show-currency": k.value - }, null, 8, [ - "title", - "label-heading", - "rows", - "show-currency" - ]), - c(Yu, { - title: S(u)("tasks_projects.reports.tables.by_member"), - "label-heading": S(u)("tasks_projects.reports.tables.member"), - rows: M.value, - "show-currency": k.value - }, null, 8, [ - "title", - "label-heading", - "rows", - "show-currency" - ]), - c(Yu, { - title: S(u)("tasks_projects.reports.tables.by_customer"), - "label-heading": S(u)("tasks_projects.reports.tables.customer"), - rows: N.value, - "show-currency": k.value - }, null, 8, [ - "title", - "label-heading", - "rows", - "show-currency" - ]) - ], 64)) : (g(), r(ne, { - key: 1, - title: S(u)("tasks_projects.reports.empty_title"), - description: S(u)("tasks_projects.reports.empty_description") - }, { - default: E(() => [c(h, { - name: "ChartBarIcon", - class: "mt-5 mb-4 h-16 w-16 text-subtle" })]), _: 1 - }, 8, ["title", "description"])) - ]), - _: 1 - }); + }, 8, ["to"])]), + _: 1 + }, 8, ["title", "description"]), + c(h, { + title: C(r)("tasks_projects.settings.behaviour_title"), + description: C(r)("tasks_projects.settings.behaviour_description") + }, { + default: D(() => [o("dl", Xp, [(_(!0), a(e, null, b(u.value, (e) => (_(), a("div", { + key: e.key, + class: "flex justify-between gap-4 py-2.5" + }, [o("dt", Zp, S(e.label), 1), o("dd", Qp, S(e.value), 1)]))), 128))])]), + _: 1 + }, 8, ["title", "description"]), + c(h, { + title: C(r)("tasks_projects.settings.invoice_title"), + description: C(r)("tasks_projects.settings.invoice_description") + }, { + default: D(() => [o("dl", $p, [(_(!0), a(e, null, b(d.value, (e) => (_(), a("div", { + key: e.key, + class: "flex justify-between gap-4 py-2.5" + }, [o("dt", em, S(e.label), 1), o("dd", tm, S(e.value), 1)]))), 128))])]), + _: 1 + }, 8, ["title", "description"]), + c(h, { + title: C(r)("tasks_projects.settings.statuses_title"), + description: C(r)("tasks_projects.settings.statuses_description") + }, { + default: D(() => [c(Jp, { + client: t.client, + notify: t.notify + }, null, 8, ["client", "notify"])]), + _: 1 + }, 8, ["title", "description"]) + ]); }; } -}), Dd = "tasks-projects"; -function Od(e) { - e.addMessages(Pu), e.registerPage({ - id: "reports", - module: Dd, - path: "reports", - component: jc(e, Ed), - meta: { - ability: `${Dd}:view-own-time`, - title: "tasks_projects.reports.title" - } +}); +//#endregion +//#region resources/js/registrations/time.ts +function rm(e) { + e.addMessages(xp); + let t = (t, n) => { + e.notify(t, n); + }, n = () => { + e.router.push(H.week); + }, r = () => { + let t = G.runningTaskId; + e.router.push(t === null ? H.week : H.task(t)); + }; + e.registerHeaderAction({ + id: `${V}.timer-chip`, + priority: 30, + visible: () => G.running !== null, + component: l({ setup: () => () => d(bp, { + client: e.client, + notify: t, + onOpen: r + }) }) + }), e.registerCompanyLayoutOverlay({ + id: `${V}.quick-start`, + component: l({ setup: () => () => d(mp, { + key: xa.companySession, + client: e.client, + notify: t, + enabled: !xa.adminMode, + onOpenWeek: n + }) }) + }), e.registerCompanySettingsPage({ + id: `${V}.settings`, + title: "tasks_projects.settings.title", + icon: "ClockIcon", + path: V, + priority: 70, + component: we(e, nm) + }), e.on("bootstrap:completed", ({ adminMode: t }) => { + im(e, t); }), e.on("company:changing", () => { - Ce(); + am(); + }), e.on("company:changed", ({ companyId: t }) => { + im(e, t === null); }); } +async function im(e, t) { + if (wa(t), t) { + am(); + return; + } + await Sa(e.client), await G.refresh(e.client); +} +function am() { + G.reset(), Ha(), Ca(); +} //#endregion //#region resources/js/init.ts -var kd = "tasks-projects"; window.InvoiceShelf.booting((e, t, n) => { - n.addMessages(A), n.registerPage({ - id: "projects", - module: kd, - path: "", - component: Ad(n, Le), - meta: { - ability: `${kd}:view-project`, - title: "tasks_projects.projects.title" - } - }), ni(n), Pc(n), Nu(n), Od(n); + n.addMessages(j), Uf(n), ls(n), rm(n), un(n), ic(n); }); -function Ad(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 diff --git a/dist/style.css b/dist/style.css index 70a0446..c0b8320 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-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}.mx-auto{margin-inline:auto}.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-12{height:calc(var(--spacing,.25rem) * 12)}.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-10{width:calc(var(--spacing,.25rem) * 10)}.w-12{width:calc(var(--spacing,.25rem) * 12)}.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}.table-auto{table-layout:auto}.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))}.grid-cols-2{grid-template-columns:repeat(2,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)))}.gap-x-6{column-gap:calc(var(--spacing,.25rem) * 6)}: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)))}.gap-y-3{row-gap:calc(var(--spacing,.25rem) * 3)}: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-default>:not(:last-child)){border-color:var(--color-line-default)}: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-line-strong{border-color:var(--color-line-strong)}.border-primary-400{border-color:var(--color-primary-400)}.border-primary-500{border-color:var(--color-primary-500)}.border-status-red{border-color:var(--color-status-red)}.border-status-yellow{border-color:var(--color-status-yellow)}.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)}.p-6{padding:calc(var(--spacing,.25rem) * 6)}.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)}.pl-4{padding-left: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-base{font-size:var(--text-base,1rem);line-height:var(--tw-leading,var(--text-base--line-height,calc(1.5 / 1)))}.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-xl{font-size:var(--text-xl,1.25rem);line-height:var(--tw-leading,var(--text-xl--line-height,calc(1.75 / 1.25)))}.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\:border-primary-500:hover{border-color:var(--color-primary-500)}.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 not all and (width>=40rem){.max-sm\:hidden{display:none}}@media (width>=40rem){.sm\:w-48{width:calc(var(--spacing,.25rem) * 48)}.sm\:w-56{width:calc(var(--spacing,.25rem) * 56)}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-4{grid-template-columns:repeat(4,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-3{grid-template-columns:repeat(3,minmax(0,1fr))}.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}} +@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}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing,.25rem) * .5)}.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-2{margin-right:calc(var(--spacing,.25rem) * 2)}.mr-3{margin-right:calc(var(--spacing,.25rem) * 3)}.-mb-px{margin-bottom:-1px}.mb-1{margin-bottom:var(--spacing,.25rem)}.mb-3{margin-bottom:calc(var(--spacing,.25rem) * 3)}.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)}.ml-auto{margin-left:auto}.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-12{height:calc(var(--spacing,.25rem) * 12)}.h-16{height:calc(var(--spacing,.25rem) * 16)}.max-h-48{max-height:calc(var(--spacing,.25rem) * 48)}.min-h-20{min-height:calc(var(--spacing,.25rem) * 20)}.min-h-40{min-height:calc(var(--spacing,.25rem) * 40)}.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-10{width:calc(var(--spacing,.25rem) * 10)}.w-12{width:calc(var(--spacing,.25rem) * 12)}.w-16{width:calc(var(--spacing,.25rem) * 16)}.w-64{width:calc(var(--spacing,.25rem) * 64)}.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}.min-w-44{min-width:calc(var(--spacing,.25rem) * 44)}.min-w-48{min-width:calc(var(--spacing,.25rem) * 48)}.min-w-full{min-width:100%}.flex-1{flex:1}.shrink-0{flex-shrink:0}.table-auto{table-layout:auto}.animate-pulse{animation:var(--animate-pulse,pulse 2s cubic-bezier(.4, 0, .6, 1) infinite)}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,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-1\.5{gap:calc(var(--spacing,.25rem) * 1.5)}.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)))}.gap-x-6{column-gap:calc(var(--spacing,.25rem) * 6)}: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)))}.gap-y-3{row-gap:calc(var(--spacing,.25rem) * 3)}: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-default>:not(:last-child)){border-color:var(--color-line-default)}: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-r{border-right-style:var(--tw-border-style);border-right-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-line-strong{border-color:var(--color-line-strong)}.border-primary-200{border-color:var(--color-primary-200)}.border-primary-400{border-color:var(--color-primary-400)}.border-primary-500{border-color:var(--color-primary-500)}.border-status-red{border-color:var(--color-status-red)}.border-status-yellow{border-color:var(--color-status-yellow)}.border-transparent{border-color:#0000}.bg-alert-error-bg{background-color:var(--color-alert-error-bg)}.bg-alert-success-bg\!{background-color:var(--color-alert-success-bg)!important}.bg-alert-warning-bg{background-color:var(--color-alert-warning-bg)}.bg-alert-warning-bg\!{background-color:var(--color-alert-warning-bg)!important}.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-1\.5{padding:calc(var(--spacing,.25rem) * 1.5)}.p-2{padding:calc(var(--spacing,.25rem) * 2)}.p-3{padding:calc(var(--spacing,.25rem) * 3)}.p-5{padding:calc(var(--spacing,.25rem) * 5)}.p-6{padding:calc(var(--spacing,.25rem) * 6)}.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-2{padding-bottom:calc(var(--spacing,.25rem) * 2)}.pb-3{padding-bottom:calc(var(--spacing,.25rem) * 3)}.pb-4{padding-bottom:calc(var(--spacing,.25rem) * 4)}.pl-4{padding-left: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-base{font-size:var(--text-base,1rem);line-height:var(--tw-leading,var(--text-base--line-height,calc(1.5 / 1)))}.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-xl{font-size:var(--text-xl,1.25rem);line-height:var(--tw-leading,var(--text-xl--line-height,calc(1.75 / 1.25)))}.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-success-text\!{color:var(--color-alert-success-text)!important}.text-alert-warning-text{color:var(--color-alert-warning-text)}.text-alert-warning-text\!{color:var(--color-alert-warning-text)!important}.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-primary-600{color:var(--color-primary-600)}.text-primary-700{color:var(--color-primary-700)}.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}.opacity-60{opacity:.6}.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)}}.last\:border-r-0:last-child{border-right-style:var(--tw-border-style);border-right-width:0}@media (hover:hover){.hover\:border-line-strong:hover{border-color:var(--color-line-strong)}.hover\:border-primary-500:hover{border-color:var(--color-primary-500)}.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 not all and (width>=40rem){.max-sm\:hidden{display:none}}@media (width>=40rem){.sm\:w-48{width:calc(var(--spacing,.25rem) * 48)}.sm\:w-56{width:calc(var(--spacing,.25rem) * 56)}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-4{grid-template-columns:repeat(4,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-3{grid-template-columns:repeat(3,minmax(0,1fr))}.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/board.ts b/resources/js/api/board.ts index 573f84e..7cc24f1 100644 --- a/resources/js/api/board.ts +++ b/resources/js/api/board.ts @@ -5,7 +5,14 @@ import type { Paginated, Wrapped } from '@/types/api' import type { BoardColumn, BoardParams } from '@/types/board' import type { Project } from '@/types/project' import type { ProjectMember, ProjectMemberInput } from '@/types/project-member' -import type { Task, TaskInput, TaskListParams, TaskMoveInput } from '@/types/task' +import type { + Task, + TaskBulkInput, + TaskBulkResult, + TaskInput, + TaskListParams, + TaskMoveInput, +} from '@/types/task' import type { TaskStatus } from '@/types/task-status' import type { TimeEntry, TimeEntryListParams } from '@/types/time-entry' @@ -20,6 +27,10 @@ export const BOARD_API = { tasks: `${BASE}/tasks`, task: (id: number): string => `${BASE}/tasks/${id}`, moveTask: (id: number): string => `${BASE}/tasks/${id}/move`, + startTask: (id: number): string => `${BASE}/tasks/${id}/start`, + stopTask: (id: number): string => `${BASE}/tasks/${id}/stop`, + taskTimeLog: (id: number): string => `${BASE}/tasks/${id}/time-log`, + bulkTasks: `${BASE}/tasks/bulk`, taskStatuses: `${BASE}/task-statuses`, timeEntries: `${BASE}/time-entries`, projectMembers: (projectId: number): string => `${BASE}/projects/${projectId}/members`, @@ -73,6 +84,66 @@ export async function deleteTask(client: AxiosInstance, id: number): Promise { + const { data } = await client.get>(BOARD_API.task(id)) + + return data.data +} + +/** + * Put the caller's clock on a task. + * + * Answers 409 `timer_already_running` when their timer is on another task, + * which is a question for the caller rather than a failure: the run control + * offers to stop the other one first. + */ +export async function startTask( + client: AxiosInstance, + id: number, + description: string | null = null, +): Promise { + const body = description === null ? {} : { description } + const { data } = await client.post>(BOARD_API.startTask(id), body) + + return data.data +} + +/** Close the caller's running entry on a task. 409 `timer_mismatch` if it moved. */ +export async function stopTask(client: AxiosInstance, id: number): Promise { + const { data } = await client.post>(BOARD_API.stopTask(id)) + + return data.data +} + +/** + * Every entry logged against one task, running first and then newest. + * + * A caller who may not see other members' time gets their own rows, so the + * grid renders either way and never has to ask which case it is in. + */ +export async function fetchTaskTimeLog(client: AxiosInstance, id: number): Promise { + const { data } = await client.get>(BOARD_API.taskTimeLog(id)) + + return data.data ?? [] +} + +/** + * Apply one action to a selection of tasks. + * + * The endpoint is partial by design: it reports how many it changed and names + * the ones it refused, so a locked task in the selection does not sink the + * rest of it. + */ +export async function bulkTasks( + client: AxiosInstance, + input: TaskBulkInput, +): Promise { + const { data } = await client.post(BOARD_API.bulkTasks, input) + + return { updated: data?.updated ?? 0, failed: data?.failed ?? [] } +} + /** * Drop a task between two neighbours of a column. * diff --git a/resources/js/components/AllTimeTable.vue b/resources/js/components/AllTimeTable.vue index 1c1b3ef..cd0e7a2 100644 --- a/resources/js/components/AllTimeTable.vue +++ b/resources/js/components/AllTimeTable.vue @@ -8,7 +8,6 @@ import { formatDate, toDateString } from '@/support/format' import { useTranslate } from '@/support/i18n' import { formatDuration, localDateOf } from '@/support/time' import type { CompanyMember } from '@/types/member' -import type { Project } from '@/types/project' import type { TimeEntry, TimeEntryListParams } from '@/types/time-entry' type NotifyType = 'success' | 'error' | 'warning' | 'info' @@ -31,7 +30,15 @@ const props = defineProps<{ client: AxiosInstance notify: (type: NotifyType, message: string) => void members: CompanyMember[] - projects: Project[] + /** + * Who and what the screen above is filtered to. + * + * The member and project pickers live on the Tasks header now, shared by + * every view, so the table follows them rather than offering a second pair + * that could disagree with the first. + */ + memberId: number | null + projectId: number | null /** Bumped by the page whenever an entry was saved elsewhere. */ reloadToken: number }>() @@ -46,49 +53,21 @@ const t = useTranslate() const tableRef = ref<{ refresh: (preservePage?: boolean) => void } | null>(null) const filters = reactive<{ - memberId: number | null - projectId: number | null from: string to: string billing: BillingFilter }>({ - memberId: null, - projectId: null, from: '', to: '', billing: 'ALL', }) -const memberOptions = computed(() => [ - { id: 0, label: t('tasks_projects.time.filters.any_member') }, - ...props.members.map((member) => ({ id: member.id, label: member.name })), -]) - -const projectOptions = computed(() => [ - { id: 0, label: t('tasks_projects.time.filters.any_project') }, - ...props.projects.map((project) => ({ id: project.id, label: project.name })), -]) - const billingOptions = computed(() => [ { id: 'ALL', label: t('tasks_projects.time.filters.all') }, { id: 'BILLED', label: t('tasks_projects.time.billed') }, { id: 'UNBILLED', label: t('tasks_projects.time.unbilled') }, ]) -const memberOption = computed