@open-commune/web 0.1.3

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.
Files changed (132) hide show
  1. package/README.md +41 -0
  2. package/app.config.ts +10 -0
  3. package/app.vue +13 -0
  4. package/assets/css/main.css +13 -0
  5. package/components/data/display/row/data-avatar.vue +23 -0
  6. package/components/data/display/row/data-badge.vue +34 -0
  7. package/components/data/display/row/data-color.vue +17 -0
  8. package/components/data/display/row/data-display-badge-publish-status.vue +29 -0
  9. package/components/data/display/row/data-label.vue +37 -0
  10. package/components/data/display/row/data-last-update.vue +45 -0
  11. package/components/data/display/row/data-spacer.vue +4 -0
  12. package/components/data/display/row/data-summary.vue +44 -0
  13. package/components/data/display/row/data-text.vue +14 -0
  14. package/components/data/display/row/data-toggle.vue +16 -0
  15. package/components/data/display/row.vue +111 -0
  16. package/components/data/lists/data-list.vue +85 -0
  17. package/components/data/lists/data-lists-table.vue +155 -0
  18. package/components/elements/avatars/avatar-circular.vue +68 -0
  19. package/components/elements/avatars/avatar-with-text.vue +47 -0
  20. package/components/elements/badges/badge.vue +85 -0
  21. package/components/elements/buttons/primary.vue +32 -0
  22. package/components/elements/buttons/secondary.vue +35 -0
  23. package/components/elements/buttons/tertiary.vue +25 -0
  24. package/components/elements/dropdowns/dots.vue +75 -0
  25. package/components/elements/media/image.vue +82 -0
  26. package/components/elements/notifications/default.vue +90 -0
  27. package/components/forms/handlers/handler-collections.vue +189 -0
  28. package/components/forms/handlers/handler-publication-status.vue +193 -0
  29. package/components/forms/handlers/handler-records.vue +138 -0
  30. package/components/forms/handlers/handler-roles.vue +176 -0
  31. package/components/forms/handlers/handler-schedules.vue +218 -0
  32. package/components/forms/handlers/handler-steps.vue +147 -0
  33. package/components/forms/handlers/handler-tokens.vue +79 -0
  34. package/components/forms/inputs/input-color-picker.vue +159 -0
  35. package/components/forms/inputs/input-editor.vue +363 -0
  36. package/components/forms/inputs/input-files-media.vue +150 -0
  37. package/components/forms/inputs/input-files.vue +98 -0
  38. package/components/forms/inputs/input-json.vue +65 -0
  39. package/components/forms/inputs/input-location.vue +179 -0
  40. package/components/forms/inputs/input-text-area.vue +65 -0
  41. package/components/forms/inputs/input-text.vue +122 -0
  42. package/components/forms/inputs/input-toggle.vue +125 -0
  43. package/components/forms/inputs/media-section/media-editor.vue +95 -0
  44. package/components/forms/layouts/form.vue +203 -0
  45. package/components/forms/layouts/layout.vue +47 -0
  46. package/components/forms/layouts/modal.vue +96 -0
  47. package/components/forms/layouts/navigation.vue +48 -0
  48. package/components/forms/layouts/section.vue +118 -0
  49. package/components/forms/selects/select-collection-enum.vue +195 -0
  50. package/components/forms/selects/select-date.vue +245 -0
  51. package/components/forms/selects/select-enum.vue +186 -0
  52. package/components/forms/selects/select-record.vue +274 -0
  53. package/components/layout/headings/headings-collection.vue +29 -0
  54. package/components/layout/headings/headings-record.vue +33 -0
  55. package/components/layout/navigations/primary/dropdown-publish.vue +147 -0
  56. package/components/layout/navigations/primary.vue +132 -0
  57. package/components/layout/notifications/notifications-list.vue +19 -0
  58. package/composables/core/domains/use-definitions.ts +57 -0
  59. package/composables/core/domains/use-entries.ts +62 -0
  60. package/composables/core/domains/use-media.ts +82 -0
  61. package/composables/core/use-core.ts +12 -0
  62. package/composables/use-ability.js +38 -0
  63. package/composables/use-models.js +27 -0
  64. package/composables/use-session.ts +80 -0
  65. package/composables/use-timer.js +62 -0
  66. package/composables/use-trpc.js +17 -0
  67. package/editor/index.ts +44 -0
  68. package/editor/views/CalloutView.vue +22 -0
  69. package/editor/views/DetailsContentView.vue +11 -0
  70. package/editor/views/DetailsSummaryView.vue +11 -0
  71. package/editor/views/DetailsView.vue +72 -0
  72. package/editor/views/EmbedView.vue +53 -0
  73. package/editor/views/FileView.vue +79 -0
  74. package/editor/views/ImageView.vue +56 -0
  75. package/editor/views/UploadView.vue +71 -0
  76. package/layouts/default.vue +21 -0
  77. package/layouts/entrance.vue +10 -0
  78. package/layouts/record.vue +21 -0
  79. package/middleware/00.auth.global.js +16 -0
  80. package/middleware/01.instance.global.js +21 -0
  81. package/middleware/03.verify-permissions.global.js +11 -0
  82. package/middleware/04.prefetch-page.global.js +18 -0
  83. package/middleware/05.headings-page.global.js +25 -0
  84. package/models/_commun.js +243 -0
  85. package/models/_factory.js +34 -0
  86. package/models/collection.js +87 -0
  87. package/models/entrance.js +42 -0
  88. package/models/media.js +82 -0
  89. package/models/organization.js +92 -0
  90. package/models/record.js +165 -0
  91. package/models/role.js +66 -0
  92. package/models/token.js +56 -0
  93. package/models/user.js +97 -0
  94. package/nuxt.config.ts +151 -0
  95. package/package.json +71 -0
  96. package/pages/[collection]/[record]/index.vue +88 -0
  97. package/pages/[collection]/index.vue +55 -0
  98. package/pages/_lab/editor.vue +149 -0
  99. package/pages/auth/callback.vue +31 -0
  100. package/pages/index/index.vue +85 -0
  101. package/pages/index/password/define/[token].vue +79 -0
  102. package/pages/index/password/recover/index.vue +72 -0
  103. package/pages/index/welcome/[token].vue +90 -0
  104. package/pages/index.vue +44 -0
  105. package/pages/management/index.vue +15 -0
  106. package/pages/management/members.vue +176 -0
  107. package/pages/management/organizations.vue +192 -0
  108. package/pages/management/roles.vue +183 -0
  109. package/pages/overview.vue +15 -0
  110. package/pages/settings.vue +197 -0
  111. package/plugins/01.session.client.ts +5 -0
  112. package/plugins/02.vue-query.ts +25 -0
  113. package/public/favicon.ico +0 -0
  114. package/public/logo-black.svg +1 -0
  115. package/public/logo-white.svg +1 -0
  116. package/server/api/trpc/[...trpc].ts +28 -0
  117. package/server/api/v1/content/deployment.get.ts +45 -0
  118. package/server/api/v1/content/records.get.ts +12 -0
  119. package/server/middleware/0.security.ts +19 -0
  120. package/server/middleware/1.cors.ts +31 -0
  121. package/server/middleware/2.session.ts +20 -0
  122. package/server/middleware/3.api-token.ts +20 -0
  123. package/server/plugins/core.ts +8 -0
  124. package/server/routes/_tasks/[name].post.ts +20 -0
  125. package/server/routes/health.get.ts +6 -0
  126. package/server/tasks/deploy.ts +26 -0
  127. package/server/types.d.ts +8 -0
  128. package/server/utils/core.ts +18 -0
  129. package/store/layout/headings.js +21 -0
  130. package/store/layout/modal.js +80 -0
  131. package/store/layout/notifications.js +52 -0
  132. package/store/layout/workspace.js +80 -0
@@ -0,0 +1,203 @@
1
+ <template>
2
+ <div v-show="attributes.length" class="space-y-4">
3
+ <div
4
+ v-for="({ property, component, componentOptions }, i) in attributes"
5
+ :key="i"
6
+ class="w-full"
7
+ >
8
+ <handler-collections v-if="component === 'handler-collections'" v-bind="componentOptions" :read-only="componentOptions.readOnly || loading" />
9
+ <handler-records v-if="component === 'handler-records'" v-bind="componentOptions" :read-only="componentOptions.readOnly || loading" />
10
+ <handler-tokens v-if="component === 'handler-tokens'" v-bind="componentOptions" :read-only="componentOptions.readOnly || loading" />
11
+ <handler-roles
12
+ v-if="component === 'handler-roles'"
13
+ :value="_get(property)"
14
+ v-bind="componentOptions"
15
+ :read-only="componentOptions.readOnly || loading"
16
+ @change="_update(property, $event)"
17
+ />
18
+ <handler-schedules
19
+ v-if="component === 'handler-schedules'"
20
+ :value="_get(property)"
21
+ v-bind="componentOptions"
22
+ :read-only="componentOptions.readOnly || loading"
23
+ @change="_update(property, $event)"
24
+ />
25
+ <handler-steps
26
+ v-if="component === 'handler-steps'"
27
+ :value="_get(property)"
28
+ v-bind="componentOptions"
29
+ :read-only="componentOptions.readOnly || loading"
30
+ @change="_update(property, $event)"
31
+ />
32
+ <input-color-picker
33
+ v-if="component === 'input-color-picker'"
34
+ v-bind="componentOptions"
35
+ :value="_get(property)"
36
+ :read-only="componentOptions.readOnly || loading"
37
+ @change="_update(property, $event)"
38
+ />
39
+ <input-files-media
40
+ v-if="component === 'input-files-media'"
41
+ v-bind="componentOptions"
42
+ :value="_get(property)"
43
+ :read-only="componentOptions.readOnly || loading"
44
+ @change="_update(property, $event)"
45
+ />
46
+ <input-json
47
+ v-if="component === 'input-json'"
48
+ v-bind="componentOptions"
49
+ :value="_get(property)"
50
+ :read-only="componentOptions.readOnly || loading"
51
+ @change="_update(property, $event)"
52
+ />
53
+ <input-text
54
+ v-if="component === 'input-text'"
55
+ v-bind="componentOptions"
56
+ :value="_get(property)"
57
+ :read-only="componentOptions.readOnly || loading"
58
+ @change="_update(property, $event)"
59
+ />
60
+ <input-text-area
61
+ v-if="component === 'input-text-area'"
62
+ v-bind="componentOptions"
63
+ :value="_get(property)"
64
+ :read-only="componentOptions.readOnly || loading"
65
+ @change="_update(property, $event)"
66
+ />
67
+ <input-WYSIWYG
68
+ v-if="component === 'input-wysiwyg'"
69
+ v-bind="componentOptions"
70
+ :value="_get(property)"
71
+ :read-only="componentOptions.readOnly || loading"
72
+ @change="_update(property, $event)"
73
+ />
74
+ <input-toggle
75
+ v-if="component === 'input-toggle'"
76
+ v-bind="componentOptions"
77
+ :value="_get(property)"
78
+ :read-only="componentOptions.readOnly || loading"
79
+ @change="_update(property, $event)"
80
+ />
81
+ <input-location
82
+ v-if="component === 'input-location'"
83
+ v-bind="componentOptions"
84
+ :value="_get(property)"
85
+ :read-only="componentOptions.readOnly || loading"
86
+ @change="_update(property, $event)"
87
+ />
88
+ <select-enum
89
+ v-if="component === 'select-enum'"
90
+ v-bind="componentOptions"
91
+ :value="_get(property)"
92
+ :read-only="componentOptions.readOnly || loading"
93
+ @change="_update(property, $event)"
94
+ />
95
+ <select-collection-enum
96
+ v-if="component === 'select-collection-enum'"
97
+ v-bind="componentOptions"
98
+ :value="_get(property)"
99
+ :read-only="componentOptions.readOnly || loading"
100
+ @change="_update(property, $event)"
101
+ />
102
+ <select-record
103
+ v-if="component === 'select-label' || component === 'select-record'"
104
+ v-bind="componentOptions"
105
+ :value="_get(property)"
106
+ :read-only="componentOptions.readOnly || loading"
107
+ @change="_update(property, $event)"
108
+ />
109
+ <!--
110
+ <select-model
111
+ v-if="component === 'select-model'"
112
+ v-bind="componentOptions"
113
+ :value="_get(property)"
114
+ :read-only="componentOptions.readOnly || loading"
115
+ @change="_update(property, $event)"
116
+ /> -->
117
+ </div>
118
+ </div>
119
+ </template>
120
+
121
+ <script setup>
122
+ import handlerCollections from '~/components/forms/handlers/handler-collections'
123
+ import handlerRecords from '~/components/forms/handlers/handler-records'
124
+ import handlerRoles from '~/components/forms/handlers/handler-roles'
125
+ import handlerSchedules from '~/components/forms/handlers/handler-schedules'
126
+ import handlerTokens from '~/components/forms/handlers/handler-tokens'
127
+ import handlerSteps from '~/components/forms/handlers/handler-steps'
128
+ import inputColorPicker from '~/components/forms/inputs/input-color-picker'
129
+ import inputJson from '~/components/forms/inputs/input-json'
130
+ import inputLocation from '~/components/forms/inputs/input-location'
131
+ import inputFilesMedia from '~/components/forms/inputs/input-files-media'
132
+ import inputText from '~/components/forms/inputs/input-text'
133
+ import inputTextArea from '~/components/forms/inputs/input-text-area'
134
+ import inputWYSIWYG from '~/components/forms/inputs/input-editor'
135
+ // import inputTimeSchedules from '@/components/forms/attributes/input-time-schedules'
136
+ import inputToggle from '~/components/forms/inputs/input-toggle'
137
+ import selectCollectionEnum from '~/components/forms/selects/select-collection-enum'
138
+ import selectEnum from '~/components/forms/selects/select-enum'
139
+ import selectRecord from '~/components/forms/selects/select-record'
140
+ // import selectModel from '@/components/forms/attributes/select-model'
141
+
142
+ // Prepare emitters
143
+ const $emitters = defineEmits(['change'])
144
+
145
+ // Prepare props
146
+ const $props = defineProps({
147
+ loading: {
148
+ type: Boolean,
149
+ default: false
150
+ },
151
+ attributes: {
152
+ type: Array,
153
+ default: () => []
154
+ },
155
+ record: {
156
+ type: Object,
157
+ default: () => ({})
158
+ }
159
+ })
160
+
161
+ // Prepare reactive data
162
+ const updateRecord = ref({})
163
+
164
+ // Prepare methods
165
+ const _get = (key) => {
166
+ const path = key.split('.');
167
+ let value = $props.record;
168
+
169
+ for (let i = 0; i < path.length; i++) {
170
+ const key = path[i];
171
+ value = value[key];
172
+
173
+ // If the nested property doesn't exist, return undefined
174
+ if (value === undefined) {
175
+ return undefined;
176
+ }
177
+ }
178
+
179
+ return value;
180
+ }
181
+
182
+ const _update = (key, value) => {
183
+ const path = key.split('.');
184
+ let targetObject = updateRecord.value;
185
+
186
+ for (let i = 0; i < path.length - 1; i++) {
187
+ const key = path[i];
188
+ targetObject = targetObject[key] = targetObject[key] || {};
189
+ }
190
+
191
+ const lastKey = path[path.length - 1];
192
+ targetObject[lastKey] = value;
193
+
194
+ $emitters('change', updateRecord.value)
195
+ }
196
+
197
+ // Prepare mounted
198
+ onMounted(() => {
199
+ if ($props.record?._id) {
200
+ Object.assign(updateRecord.value, { _id: $props.record._id })
201
+ }
202
+ })
203
+ </script>
@@ -0,0 +1,47 @@
1
+ <template>
2
+ <div class="lg:grid lg:grid-flow-row lg:auto-rows-max lg:grid-cols-12 lg:gap-x-5">
3
+ <navigation
4
+ v-if="tabs.length"
5
+ :selection="selectedTab"
6
+ :tabs="tabs"
7
+ class="px-2 sm:px-6 lg:py-0 lg:px-0 lg:col-span-2"
8
+ @select="_changeTab"
9
+ />
10
+ <div class="space-y-6 sm:px-6 lg:px-0 lg:col-span-10">
11
+ <forms-section
12
+ v-for="({ componentOptions }, i) in currentSections"
13
+ :key="`form-layout-${i}`"
14
+ v-bind="{ record, ...componentOptions }"
15
+ />
16
+ </div>
17
+ </div>
18
+ </template>
19
+
20
+ <script setup>
21
+ import navigation from '~/components/forms/layouts/navigation'
22
+ import formsSection from '~/components/forms/layouts/section'
23
+
24
+ // Prepare props
25
+ const $props = defineProps({
26
+ record: {
27
+ type: Object,
28
+ default: () => ({})
29
+ },
30
+ layout: {
31
+ type: Array,
32
+ default: () => []
33
+ }
34
+ })
35
+
36
+ // Prepare reactive data
37
+ const selectedTab = ref('')
38
+
39
+ // Prepare computed
40
+ const tabs = computed(() => $props.layout.map(({ title, icon }) => ({ title, icon })))
41
+ const currentSections = computed(() => $props.layout.find(({ title }) => title === selectedTab.value)?.sections || [])
42
+
43
+ // Prepare methods
44
+ const _changeTab = (tab) => {
45
+ selectedTab.value = tab
46
+ }
47
+ </script>
@@ -0,0 +1,96 @@
1
+ <template>
2
+ <transition
3
+ enter-active-class="ease-out duration-200"
4
+ enter-class="opacity-0"
5
+ enter-to-class="opacity-100"
6
+ leave-active-class="ease-in duration-200"
7
+ leave-class="opacity-100"
8
+ leave-to-class="opacity-0"
9
+ >
10
+ <div v-if="display" class="fixed z-20 inset-0 overflow-y-auto" @keyup.escape="modalStore.close">
11
+ <div class="flex items-end justify-center min-h-screen text-center sm:block">
12
+ <div class="fixed inset-0 transition-opacity" aria-hidden="true">
13
+ <!-- `bg-inverted/50` et non `bg-gray-600 bg-opacity-50` : Tailwind v4 a
14
+ SUPPRIMÉ les utilitaires `bg-opacity-*`. Ignoré, il laissait le
15
+ voile à 100 % — un écran opaque bleu-gris qui masquait tout.
16
+ Le panneau, lui, porte `relative z-10` : sans position, un
17
+ élément ne peint pas au-dessus d'un frère positionné, même
18
+ placé après lui dans le document. Le voile passait par-dessus
19
+ la modale, qui paraissait grisée en second plan. -->
20
+ <div class="absolute inset-0 bg-inverted/50 transition-opacity" aria-hidden="true" />
21
+ </div>
22
+
23
+ <!-- This element is to trick the browser into centering the modal contents. -->
24
+ <span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
25
+ <transition
26
+ enter-active-class="ease-out duration-300"
27
+ enter-class="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
28
+ enter-to-class="opacity-100 translate-y-0 sm:scale-100"
29
+ leave-active-class="ease-in duration-200"
30
+ leave-class="opacity-100 translate-y-0 sm:scale-100"
31
+ leave-to-class="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
32
+ >
33
+ <div
34
+ v-if="display"
35
+ class="relative z-10 inline-block align-bottom bg-white sm:max-w-4xl w-full rounded-lg px-4 pt-5 pb-4 text-left shadow-xl transform transition-all sm:my-8 sm:align-middle sm:p-6"
36
+ role="dialog"
37
+ aria-modal="true"
38
+ aria-labelledby="modal-headline"
39
+ >
40
+ <div>
41
+ <div class="my-3 text-center sm:mt-5">
42
+ <h3 id="modal-headline" class="text-lg leading-6 font-medium text-gray-900">
43
+ {{ title }}
44
+ </h3>
45
+ <div class="mt-2">
46
+ <p class="text-sm text-gray-500">
47
+ {{ subTitle }}
48
+ </p>
49
+ </div>
50
+ </div>
51
+ <form-default
52
+ v-if="attributes.length"
53
+ :record="record"
54
+ :attributes="attributes"
55
+ @change="modalStore.updateRecord"
56
+ />
57
+ </div>
58
+ <div class="mt-5 sm:mt-6 sm:grid sm:grid-cols-2 sm:gap-3 sm:grid-flow-row-dense">
59
+ <button-secondary
60
+ v-if="closeButton"
61
+ :label="closeButton"
62
+ @click="modalStore.close"
63
+ />
64
+ <button-primary
65
+ v-if="acceptButton"
66
+ :label="acceptButton"
67
+ @click="modalStore.submit"
68
+ />
69
+ </div>
70
+ </div>
71
+ </transition>
72
+ </div>
73
+ </div>
74
+ </transition>
75
+ </template>
76
+
77
+ <script setup>
78
+ import formDefault from '~/components/forms/layouts/form'
79
+ import buttonPrimary from '~/components/elements/buttons/primary'
80
+ import buttonSecondary from '~/components/elements/buttons/secondary'
81
+ import { useModalStore } from '~/store/layout/modal'
82
+
83
+ const modalStore = useModalStore()
84
+
85
+ // Prepare emits
86
+ defineEmits(['keyup.escape'])
87
+
88
+ // Prepare computed
89
+ const display = computed(() => modalStore.display)
90
+ const title = computed(() => modalStore.title)
91
+ const subTitle = computed(() => modalStore.subTitle)
92
+ const attributes = computed(() => modalStore.attributes)
93
+ const record = computed(() => modalStore.record)
94
+ const acceptButton = computed(() => modalStore.acceptButton)
95
+ const closeButton = computed(() => modalStore.closeButton)
96
+ </script>
@@ -0,0 +1,48 @@
1
+
2
+ <template>
3
+ <nav class="space-y-1" aria-label="Sidebar">
4
+ <div
5
+ v-for="({ title }) in tabs"
6
+ :key="title"
7
+ :class="selection === title ? 'bg-gray-200 text-gray-900 font-normal' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900 font-light'"
8
+ class="flex items-center px-3 py-2 text-sm rounded-md cursor-pointer"
9
+ @click="_select(title)"
10
+ >
11
+ <span class="truncate">
12
+ {{ title }}
13
+ </span>
14
+ </div>
15
+ </nav>
16
+ </template>
17
+
18
+ <script setup>
19
+ // Prepre emitters
20
+ const $emitters = defineEmits(['select'])
21
+
22
+ // Prepare props
23
+ const $props = defineProps({
24
+ selection: {
25
+ type: String,
26
+ default: ''
27
+ },
28
+ tabs: {
29
+ type: Array,
30
+ default: () => []
31
+ }
32
+ })
33
+
34
+ // Prepare methods
35
+ const _select = (tab) => {
36
+ if (tab) {
37
+ $emitters('select', tab)
38
+ }
39
+ }
40
+
41
+ // Prepare mounted
42
+ onMounted(() => {
43
+ if (!$props.selection) {
44
+ const [{ title: first }] = $props.tabs
45
+ _select(first)
46
+ }
47
+ })
48
+ </script>
@@ -0,0 +1,118 @@
1
+ <template>
2
+ <form class="border border-gray-200 sm:rounded-md" @submit.prevent="">
3
+ <div class="py-6 px-4 space-y-6 sm:p-6">
4
+ <div>
5
+ <h3 v-show="title" class="text-lg leading-6 font-medium text-gray-900">
6
+ {{ title }}
7
+ </h3>
8
+ <p v-show="description" class="mt-1 text-sm text-gray-500">
9
+ {{ description }}
10
+ </p>
11
+ </div>
12
+ <form-default
13
+ v-if="attributes.length"
14
+ :record="record"
15
+ :attributes="attributes"
16
+ :loading="isLoading"
17
+ @change="updateRecord = $event"
18
+ />
19
+ </div>
20
+ <div
21
+ v-show="hint || (attributes.length && !noAction)"
22
+ class="flex flex-row items-center px-4 py-3 bg-gray-50 border-t border-gray-200 text-right sm:px-6"
23
+ :class="hint ? 'justify-between' : 'justify-end'"
24
+ >
25
+ <p v-show="hint" class="text-sm text-gray-600">
26
+ {{ hint }}
27
+ </p>
28
+ <button-primary
29
+ v-show="attributes.length && !noAction"
30
+ :label="formButtonLabel"
31
+ :loading="isLoading"
32
+ @click="_save"
33
+ />
34
+ </div>
35
+ </form>
36
+ </template>
37
+
38
+ <script setup>
39
+ import { useNotificationsStore } from '~/store/layout/notifications'
40
+ import formDefault from '~/components/forms/layouts/form'
41
+ import buttonPrimary from '~/components/elements/buttons/primary'
42
+
43
+ const $models = useModels()
44
+ const $route = useRoute()
45
+ const $router = useRouter()
46
+ const notificationsStore = useNotificationsStore()
47
+
48
+ // Prepare emits
49
+ const $emitters = defineEmits(['change'])
50
+
51
+ // Define props
52
+ const $props = defineProps({
53
+ record: {
54
+ type: Object,
55
+ default: () => ({})
56
+ },
57
+ title: {
58
+ type: String,
59
+ default: ''
60
+ },
61
+ description: {
62
+ type: String,
63
+ default: ''
64
+ },
65
+ attributes: {
66
+ type: Array,
67
+ default: () => []
68
+ },
69
+ hint: {
70
+ type: String,
71
+ default: ''
72
+ },
73
+ formButtonLabel: {
74
+ type: String,
75
+ default: 'Enregistrer'
76
+ },
77
+ noAction: {
78
+ type: Boolean,
79
+ default: false
80
+ }
81
+ })
82
+
83
+ // Prepare reactive data
84
+ const isLoading = ref(false)
85
+ const updateRecord = ref({})
86
+
87
+ // Prepare computed
88
+ const isEditing = computed(() => Object.keys($props.record).includes('_id'))
89
+ const currentCollection = computed(() => $route.params.collection)
90
+ const currentModel = computed(() => $route.meta.preFetch?.[0])
91
+
92
+ // Prepare methods
93
+ const _save = async () => {
94
+ if (!$props.noAction) {
95
+ isLoading.value = true
96
+ try {
97
+ const { _id, ...record } = updateRecord.value
98
+ if (isEditing.value) {
99
+ await $models[currentModel.value].update($props.record._id, record, currentCollection.value)
100
+ } else {
101
+ const data = await $models[currentModel.value].create(record, currentCollection.value)
102
+ if (!isEditing.value) {
103
+ const splittedRoute = $route.path.split('/')
104
+ splittedRoute.pop()
105
+ $router.replace([...splittedRoute, data._id].join('/'))
106
+ }
107
+ }
108
+ notificationsStore.add({ title: `${$props.title} à jour` })
109
+ } catch (err) {
110
+ console.log(err)
111
+ } finally {
112
+ setTimeout(() => { isLoading.value = false }, 500)
113
+ }
114
+ } else {
115
+ $emitters('change', updateRecord.value)
116
+ }
117
+ }
118
+ </script>