@wisemen/wise-crm-web 2.0.0-alpha.3 → 2.0.0-alpha.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # @wisemen/wise-crm-web
2
+
3
+ Vue 3 CRM package: business, individual and deal overviews, detail views, dialogs and widgets.
4
+
5
+ ## Extending the detail views
6
+
7
+ The business and individual detail views can be extended with content from the host app in two places.
8
+
9
+ ### Custom tabs
10
+
11
+ Tabs next to the detail sidebar are routes, registered through `useWiseCrmRoutes`. At least one tab is required for businesses and individuals. The tab component reads the entity uuid from the route params (`businessUuid` / `individualUuid` / `dealUuid`).
12
+
13
+ ```ts
14
+ useWiseCrmRoutes({
15
+ businessTabRoutes: [
16
+ {
17
+ label: computed<string>(() => t('crm.business.tabs.information')),
18
+ route: {
19
+ name: 'crm-business-information',
20
+ path: 'information',
21
+ component: (): Component => import('./CrmBusinessInformationTabView.vue'),
22
+ },
23
+ },
24
+ ],
25
+ dealTabRoutes: [...],
26
+ individualTabRoutes: [...],
27
+ })
28
+ ```
29
+
30
+ ### Custom sidebar sections
31
+
32
+ Accordion sections rendered at the end of the sidebar's Overview tab, after the built-in sections. They show in both the detail view and the quick-view dialog. Register them through the `business` / `individual` options of `useWiseCrmConfig().init()`:
33
+
34
+ ```ts
35
+ import type { WiseCrmSidebarSection } from '@wisemen/wise-crm-web'
36
+
37
+ useWiseCrmConfig().init({
38
+ // ...
39
+ options: {
40
+ business: {
41
+ sidebarSections: [
42
+ {
43
+ icon: ShoppingCart01Icon,
44
+ key: 'orders',
45
+ label: computed<string>(() => t('crm.business.sidebar.orders')),
46
+ component: defineAsyncComponent(() => import('./BusinessOrdersSidebarSection.vue')),
47
+ },
48
+ ],
49
+ },
50
+ individual: {
51
+ sidebarSections: [...],
52
+ },
53
+ // ...
54
+ },
55
+ })
56
+ ```
57
+
58
+ | Field | Description |
59
+ | -------------- | --------------------------------------------------------------------------------------------------------------------------------- |
60
+ | `key` | Unique per entity; used as the accordion item value. |
61
+ | `label` | Section header, a `ComputedRef<string>` so it follows the locale. |
62
+ | `icon` | Section header icon. |
63
+ | `component` | Section body. |
64
+ | `useIsVisible` | Optional. Receives the component's props as a `ComputedRef` and returns a `ComputedRef<boolean>`; the section is hidden while it is `false`. |
65
+
66
+ The component receives only the entity uuid. It fetches its own data and renders its own loading and empty state. Sections are open by default.
67
+
68
+ ```vue
69
+ <script setup lang="ts">
70
+ import type { WiseCrmBusinessUuid } from '@wisemen/wise-crm-web'
71
+
72
+ const props = defineProps<{
73
+ businessUuid: WiseCrmBusinessUuid
74
+ }>()
75
+ </script>
76
+ ```
77
+
78
+ Individual sections receive `individualUuid: WiseCrmIndividualUuid` instead. A component with the wrong props is a type error.
79
+
80
+ `useIsVisible` is called once in the sidebar's setup, so it can use composables such as queries. Use it to hide a section that has nothing to show for this entity, rather than rendering an empty body under its header:
81
+
82
+ ```ts
83
+ {
84
+ key: 'parent',
85
+ // ...
86
+ useIsVisible: (props) => useBusinessHasParent(computed(() => props.value.businessUuid)),
87
+ }
88
+ ```
89
+
90
+ A section that becomes visible after the sidebar has mounted still opens by default.
@@ -1,8 +1,8 @@
1
1
  import { defineComponent as n, openBlock as o, createBlock as r, unref as s, withCtx as a, createVNode as u } from "vue";
2
2
  import { useRouteParams as m } from "@vueuse/router";
3
- import { _ as p } from "./index-D4yr-qw2.js";
4
- import { aV as c } from "./uuid.util-B9bkU4IX.js";
5
- import { _ } from "./CrmDetailView.vue_vue_type_script_setup_true_lang-CONdU2gO.js";
3
+ import { _ as p } from "./index-DIg0NJqw.js";
4
+ import { bq as c } from "./uuid.util-DIPbDJ-m.js";
5
+ import { _ } from "./CrmDetailView.vue_vue_type_script_setup_true_lang-B6S4_83Z.js";
6
6
  const x = /* @__PURE__ */ n({
7
7
  __name: "BusinessDetailView",
8
8
  props: {
@@ -1,7 +1,7 @@
1
- import { defineComponent as v, ref as h, unref as e, openBlock as d, createElementBlock as u, createElementVNode as t, renderSlot as x, createVNode as r, withCtx as f, Fragment as y, renderList as b, createBlock as g, toValue as m, createCommentVNode as w } from "vue";
1
+ import { defineComponent as v, ref as h, unref as e, openBlock as d, createElementBlock as u, createElementVNode as t, renderSlot as x, createVNode as r, withCtx as f, Fragment as y, renderList as g, createBlock as w, toValue as m, createCommentVNode as b } from "vue";
2
2
  import { onKeyStroke as C } from "@vueuse/core";
3
3
  import { UITabsRouterLink as k, UITabsRouterLinkItem as R, UIIconButton as B } from "@wisemen/vue-core-design-system";
4
- import { a1 as E, V as I, X as V, ab as T } from "./index-D4yr-qw2.js";
4
+ import { d as E, f as T, T as I, a6 as V } from "./index-DIg0NJqw.js";
5
5
  import { useRoute as A, RouterView as H } from "vue-router";
6
6
  class U {
7
7
  /**
@@ -19,14 +19,14 @@ class U {
19
19
  return !1;
20
20
  }
21
21
  }
22
- const $ = { key: 0 }, j = { class: "flex h-dvh w-dvw flex-1 overflow-hidden" }, z = { class: "flex h-full min-w-100 flex-col" }, L = { class: "flex size-full flex-1 justify-center overflow-hidden bg-secondary" }, N = { class: "flex w-full flex-col" }, S = { class: "grid w-full grid-cols-3 p-lg" }, X = { class: "flex items-center justify-center" }, D = { class: "flex items-center justify-end" }, F = { class: "size-full overflow-y-auto" }, O = /* @__PURE__ */ v({
22
+ const $ = { key: 0 }, j = { class: "flex h-dvh w-dvw flex-1 overflow-hidden" }, z = { class: "flex h-full min-w-100 flex-col" }, L = { class: "flex size-full flex-1 justify-center overflow-hidden bg-secondary" }, N = { class: "flex w-full flex-col" }, S = { class: "grid w-full grid-cols-3 p-lg" }, D = { class: "flex items-center justify-center" }, F = { class: "flex items-center justify-end" }, K = { class: "size-full overflow-y-auto" }, P = /* @__PURE__ */ v({
23
23
  __name: "CrmDetailView",
24
24
  props: {
25
25
  entityType: {},
26
26
  tabs: {}
27
27
  },
28
28
  setup(i) {
29
- const a = i, o = h(null), c = E(), _ = I(), p = A();
29
+ const a = i, o = h(null), c = E(), _ = T(), p = A();
30
30
  return C("Escape", () => {
31
31
  const l = o.value?.$el;
32
32
  l && U.parentHasAriaHidden(l) || c.goToReturnRoute();
@@ -35,7 +35,7 @@ const $ = { key: 0 }, j = { class: "flex h-dvh w-dvw flex-1 overflow-hidden" },
35
35
  t("div", z, [
36
36
  x(l.$slots, "sidebar")
37
37
  ]),
38
- r(V, {
38
+ r(I, {
39
39
  color: e(c).getEntityConfig(i.entityType)?.color ?? null,
40
40
  class: "relative flex flex-1 flex-col overflow-hidden"
41
41
  }, {
@@ -44,10 +44,10 @@ const $ = { key: 0 }, j = { class: "flex h-dvh w-dvw flex-1 overflow-hidden" },
44
44
  t("div", N, [
45
45
  t("div", S, [
46
46
  n[1] || (n[1] = t("div", null, null, -1)),
47
- t("div", X, [
47
+ t("div", D, [
48
48
  r(e(k), { variant: "button-border" }, {
49
49
  default: f(() => [
50
- (d(!0), u(y, null, b(a.tabs, (s) => (d(), g(e(R), {
50
+ (d(!0), u(y, null, g(a.tabs, (s) => (d(), w(e(R), {
51
51
  key: m(s.label),
52
52
  label: m(s.label),
53
53
  to: {
@@ -59,21 +59,21 @@ const $ = { key: 0 }, j = { class: "flex h-dvh w-dvw flex-1 overflow-hidden" },
59
59
  _: 1
60
60
  })
61
61
  ]),
62
- t("div", D, [
62
+ t("div", F, [
63
63
  r(e(B), {
64
64
  ref_key: "closeButtonRef",
65
65
  ref: o,
66
66
  "class-config": {
67
67
  root: "rounded-md shadow-lg"
68
68
  },
69
- icon: e(T),
69
+ icon: e(V),
70
70
  label: "Close",
71
71
  variant: "secondary",
72
72
  onClick: n[0] || (n[0] = (s) => e(c).goToReturnRoute())
73
73
  }, null, 8, ["icon"])
74
74
  ])
75
75
  ]),
76
- t("div", F, [
76
+ t("div", K, [
77
77
  r(e(H))
78
78
  ])
79
79
  ])
@@ -82,9 +82,9 @@ const $ = { key: 0 }, j = { class: "flex h-dvh w-dvw flex-1 overflow-hidden" },
82
82
  _: 1
83
83
  }, 8, ["color"])
84
84
  ])
85
- ])) : w("", !0);
85
+ ])) : b("", !0);
86
86
  }
87
87
  });
88
88
  export {
89
- O as _
89
+ P as _
90
90
  };
@@ -1,88 +1,88 @@
1
- import { defineComponent as A, openBlock as t, createElementBlock as f, createElementVNode as n, createVNode as a, withCtx as s, toDisplayString as m, unref as e, createBlock as l, createCommentVNode as _, Fragment as v, renderList as g, normalizeClass as b, resolveDynamicComponent as k, computed as I } from "vue";
2
- import { useRouteParams as M } from "@vueuse/router";
1
+ import { defineComponent as C, openBlock as t, createElementBlock as f, createElementVNode as s, createVNode as a, withCtx as n, toDisplayString as m, unref as e, createBlock as o, createCommentVNode as _, Fragment as g, renderList as v, normalizeClass as b, resolveDynamicComponent as k, computed as M } from "vue";
2
+ import { useRouteParams as I } from "@vueuse/router";
3
3
  import { useOffsetInfiniteQuery as L } from "@wisemen/vue-core-api-utils";
4
- import { M as T, D as p, a as w, b as C, C as S, c as V, u as j, A as U } from "./index-D4yr-qw2.js";
5
- import { UIEmptyState as $ } from "@wisemen/vue-core-design-system";
6
- import { AnimatePresence as q, Motion as z } from "motion-v";
7
- import { AccordionRoot as B, AccordionItem as E, AccordionHeader as F, AccordionTrigger as H, AccordionContent as N } from "reka-ui";
8
- import { useI18n as P } from "vue-i18n";
9
- function Q(r) {
4
+ import { M as T, D as p, g as w, a as A, m as S, b as V, u as j, A as Q } from "./index-DIg0NJqw.js";
5
+ import { UIEmptyState as U } from "@wisemen/vue-core-design-system";
6
+ import { AnimatePresence as $, Motion as q } from "motion-v";
7
+ import { AccordionRoot as z, AccordionItem as B, AccordionHeader as E, AccordionTrigger as F, AccordionContent as H } from "reka-ui";
8
+ import { useI18n as N } from "vue-i18n";
9
+ function P(r) {
10
10
  return L("mailIndex", {
11
11
  params: {
12
12
  queryParams: r.params
13
13
  },
14
- queryFn: (o) => T.getAll({
14
+ queryFn: (l) => T.getAll({
15
15
  filters: r.params.filters.value,
16
- pagination: o
16
+ pagination: l
17
17
  })
18
18
  });
19
19
  }
20
- const O = { class: "p-xl" }, R = { class: "overflow-hidden rounded-xl border border-secondary bg-primary shadow-lg" }, G = { class: "text-md font-medium" }, J = { class: "relative" }, K = { class: "sr-only" }, W = { class: "flex min-w-0 flex-1 items-center gap-sm" }, X = { class: "truncate" }, Y = { class: "flex items-center gap-sm" }, Z = { class: "flex flex-col items-end text-sm text-tertiary" }, ee = { class: "flex flex-col" }, te = ["innerHTML"], ne = /* @__PURE__ */ A({
20
+ const O = { class: "p-xl" }, R = { class: "overflow-hidden rounded-xl border border-secondary bg-primary shadow-lg" }, G = { class: "text-md font-medium" }, J = { class: "relative" }, K = { class: "sr-only" }, W = { class: "flex min-w-0 flex-1 items-center gap-sm" }, X = { class: "truncate" }, Y = { class: "pointer-events-none flex items-center gap-sm" }, Z = { class: "flex flex-col items-end text-sm text-tertiary" }, ee = { class: "flex flex-col" }, te = ["innerHTML"], se = /* @__PURE__ */ C({
21
21
  __name: "DealCommunicationMailList",
22
22
  props: {
23
23
  mails: {}
24
24
  },
25
25
  setup(r) {
26
- const o = r, c = P();
26
+ const l = r, c = N();
27
27
  function u(d) {
28
28
  return j().toDateTime(d.createdAt);
29
29
  }
30
- function h(d) {
30
+ function x(d) {
31
31
  return d.to.join(", ");
32
32
  }
33
33
  return (d, ie) => (t(), f("div", O, [
34
- n("div", R, [
34
+ s("div", R, [
35
35
  a(p, null, {
36
- default: s(() => [
37
- n("p", G, m(e(c).t("wise_crm.deal.communication.mail_list.title")), 1)
36
+ default: n(() => [
37
+ s("p", G, m(e(c).t("wise_crm.deal.communication.mail_list.title")), 1)
38
38
  ]),
39
39
  _: 1
40
40
  }),
41
- o.mails.length === 0 ? (t(), l(e($), {
41
+ l.mails.length === 0 ? (t(), o(e(U), {
42
42
  key: 0,
43
43
  icon: e(w),
44
44
  title: e(c).t("wise_crm.deal.communication.mail_list.no_communication_sent"),
45
45
  description: e(c).t("wise_crm.deal.communication.mail_list.create_quote_instruction"),
46
46
  class: "h-120"
47
47
  }, null, 8, ["icon", "title", "description"])) : _("", !0),
48
- o.mails.length > 0 ? (t(), l(e(B), {
48
+ l.mails.length > 0 ? (t(), o(e(z), {
49
49
  key: 1,
50
50
  collapsible: !0,
51
51
  type: "multiple",
52
52
  class: "flex flex-col gap-0"
53
53
  }, {
54
- default: s(() => [
55
- (t(!0), f(v, null, g(o.mails, (i, D) => (t(), l(e(E), {
54
+ default: n(() => [
55
+ (t(!0), f(g, null, v(l.mails, (i, D) => (t(), o(e(B), {
56
56
  key: i.uuid,
57
57
  value: i.uuid
58
58
  }, {
59
- default: s(({ open: y }) => [
60
- a(e(F), null, {
61
- default: s(() => [
62
- n("div", J, [
63
- a(e(H), { class: "absolute inset-0 rounded-none ring-brand-500 ring-offset-1 duration-200 outline-none focus-visible:ring-2" }, {
64
- default: s(() => [
65
- n("span", K, m(i.subject), 1)
59
+ default: n(({ open: y }) => [
60
+ a(e(E), null, {
61
+ default: n(() => [
62
+ s("div", J, [
63
+ a(e(F), { class: "absolute inset-0 rounded-none ring-brand-500 ring-offset-1 duration-200 outline-none focus-visible:ring-2" }, {
64
+ default: n(() => [
65
+ s("span", K, m(i.subject), 1)
66
66
  ]),
67
67
  _: 2
68
68
  }, 1024),
69
- n("div", {
69
+ s("div", {
70
70
  class: b([{
71
- "border-b border-secondary": D < o.mails.length - 1
71
+ "border-b border-secondary": D < l.mails.length - 1
72
72
  }, "px-2xl py-lg"])
73
73
  }, [
74
- a(C, { justify: "between" }, {
75
- default: s(() => [
76
- n("div", W, [
77
- (t(), l(k(e(w)), { class: "size-4 shrink-0 text-secondary" })),
78
- n("p", X, m(i.subject), 1)
74
+ a(A, { justify: "between" }, {
75
+ default: n(() => [
76
+ s("div", W, [
77
+ (t(), o(k(e(w)), { class: "size-4 shrink-0 text-secondary" })),
78
+ s("p", X, m(i.subject), 1)
79
79
  ]),
80
- n("div", Y, [
81
- n("div", Z, [
82
- n("span", null, m(h(i)), 1),
83
- n("span", null, m(u(i)), 1)
80
+ s("div", Y, [
81
+ s("div", Z, [
82
+ s("span", null, m(x(i)), 1),
83
+ s("span", null, m(u(i)), 1)
84
84
  ]),
85
- (t(), l(k(e(S)), {
85
+ (t(), o(k(e(S)), {
86
86
  class: b([{
87
87
  "-rotate-180": y
88
88
  }, "size-4 shrink-0 text-secondary duration-300"])
@@ -96,14 +96,14 @@ const O = { class: "p-xl" }, R = { class: "overflow-hidden rounded-xl border bor
96
96
  ]),
97
97
  _: 2
98
98
  }, 1024),
99
- a(e(q), { initial: !1 }, {
100
- default: s(() => [
101
- y ? (t(), l(e(N), {
99
+ a(e($), { initial: !1 }, {
100
+ default: n(() => [
101
+ y ? (t(), o(e(H), {
102
102
  key: 0,
103
103
  class: "overflow-clip"
104
104
  }, {
105
- default: s(() => [
106
- a(e(z), {
105
+ default: n(() => [
106
+ a(e(q), {
107
107
  initial: {
108
108
  opacity: 0,
109
109
  height: 0,
@@ -127,15 +127,15 @@ const O = { class: "p-xl" }, R = { class: "overflow-hidden rounded-xl border bor
127
127
  type: "spring"
128
128
  }
129
129
  }, {
130
- default: s(() => [
131
- n("div", ee, [
132
- i.attachments.length > 0 ? (t(), l(p, { key: 0 }, {
133
- default: s(() => [
134
- a(C, null, {
135
- default: s(() => [
136
- (t(!0), f(v, null, g(i.attachments, (x) => (t(), l(V, {
137
- key: x.uuid,
138
- attachment: x
130
+ default: n(() => [
131
+ s("div", ee, [
132
+ i.attachments.length > 0 ? (t(), o(p, { key: 0 }, {
133
+ default: n(() => [
134
+ a(A, { class: "flex-wrap" }, {
135
+ default: n(() => [
136
+ (t(!0), f(g, null, v(i.attachments, (h) => (t(), o(V, {
137
+ key: h.uuid,
138
+ attachment: h
139
139
  }, null, 8, ["attachment"]))), 128))
140
140
  ]),
141
141
  _: 2
@@ -144,9 +144,9 @@ const O = { class: "p-xl" }, R = { class: "overflow-hidden rounded-xl border bor
144
144
  _: 2
145
145
  }, 1024)) : _("", !0),
146
146
  a(p, null, {
147
- default: s(() => [
148
- n("div", {
149
- class: "prose max-w-full py-3xl pb-xl",
147
+ default: n(() => [
148
+ s("div", {
149
+ class: "prose prose-sm max-w-full py-3xl pb-xl",
150
150
  innerHTML: i.text
151
151
  }, null, 8, te)
152
152
  ]),
@@ -171,23 +171,23 @@ const O = { class: "p-xl" }, R = { class: "overflow-hidden rounded-xl border bor
171
171
  ])
172
172
  ]));
173
173
  }
174
- }), se = {
174
+ }), ne = {
175
175
  key: 0,
176
176
  class: "p-xl"
177
- }, _e = /* @__PURE__ */ A({
177
+ }, _e = /* @__PURE__ */ C({
178
178
  __name: "DealCommunicationTabView",
179
179
  setup(r) {
180
- const o = M("dealUuid"), c = I(() => ({
181
- dealUuid: o.value
182
- })), u = Q({
180
+ const l = I("dealUuid"), c = M(() => ({
181
+ dealUuid: l.value
182
+ })), u = P({
183
183
  params: {
184
184
  filters: c
185
185
  }
186
186
  });
187
- return (h, d) => (t(), f("div", null, [
188
- e(u).isLoading.value ? (t(), f("div", se, [
189
- a(U, { class: "h-150 w-full" })
190
- ])) : e(u).result.value.isOk() ? (t(), l(ne, {
187
+ return (x, d) => (t(), f("div", null, [
188
+ e(u).isLoading.value ? (t(), f("div", ne, [
189
+ a(Q, { class: "h-150 w-full" })
190
+ ])) : e(u).result.value.isOk() ? (t(), o(se, {
191
191
  key: 1,
192
192
  mails: e(u).result.value.getValue().data
193
193
  }, null, 8, ["mails"])) : _("", !0)