@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.
- package/README.md +41 -0
- package/app.config.ts +10 -0
- package/app.vue +13 -0
- package/assets/css/main.css +13 -0
- package/components/data/display/row/data-avatar.vue +23 -0
- package/components/data/display/row/data-badge.vue +34 -0
- package/components/data/display/row/data-color.vue +17 -0
- package/components/data/display/row/data-display-badge-publish-status.vue +29 -0
- package/components/data/display/row/data-label.vue +37 -0
- package/components/data/display/row/data-last-update.vue +45 -0
- package/components/data/display/row/data-spacer.vue +4 -0
- package/components/data/display/row/data-summary.vue +44 -0
- package/components/data/display/row/data-text.vue +14 -0
- package/components/data/display/row/data-toggle.vue +16 -0
- package/components/data/display/row.vue +111 -0
- package/components/data/lists/data-list.vue +85 -0
- package/components/data/lists/data-lists-table.vue +155 -0
- package/components/elements/avatars/avatar-circular.vue +68 -0
- package/components/elements/avatars/avatar-with-text.vue +47 -0
- package/components/elements/badges/badge.vue +85 -0
- package/components/elements/buttons/primary.vue +32 -0
- package/components/elements/buttons/secondary.vue +35 -0
- package/components/elements/buttons/tertiary.vue +25 -0
- package/components/elements/dropdowns/dots.vue +75 -0
- package/components/elements/media/image.vue +82 -0
- package/components/elements/notifications/default.vue +90 -0
- package/components/forms/handlers/handler-collections.vue +189 -0
- package/components/forms/handlers/handler-publication-status.vue +193 -0
- package/components/forms/handlers/handler-records.vue +138 -0
- package/components/forms/handlers/handler-roles.vue +176 -0
- package/components/forms/handlers/handler-schedules.vue +218 -0
- package/components/forms/handlers/handler-steps.vue +147 -0
- package/components/forms/handlers/handler-tokens.vue +79 -0
- package/components/forms/inputs/input-color-picker.vue +159 -0
- package/components/forms/inputs/input-editor.vue +363 -0
- package/components/forms/inputs/input-files-media.vue +150 -0
- package/components/forms/inputs/input-files.vue +98 -0
- package/components/forms/inputs/input-json.vue +65 -0
- package/components/forms/inputs/input-location.vue +179 -0
- package/components/forms/inputs/input-text-area.vue +65 -0
- package/components/forms/inputs/input-text.vue +122 -0
- package/components/forms/inputs/input-toggle.vue +125 -0
- package/components/forms/inputs/media-section/media-editor.vue +95 -0
- package/components/forms/layouts/form.vue +203 -0
- package/components/forms/layouts/layout.vue +47 -0
- package/components/forms/layouts/modal.vue +96 -0
- package/components/forms/layouts/navigation.vue +48 -0
- package/components/forms/layouts/section.vue +118 -0
- package/components/forms/selects/select-collection-enum.vue +195 -0
- package/components/forms/selects/select-date.vue +245 -0
- package/components/forms/selects/select-enum.vue +186 -0
- package/components/forms/selects/select-record.vue +274 -0
- package/components/layout/headings/headings-collection.vue +29 -0
- package/components/layout/headings/headings-record.vue +33 -0
- package/components/layout/navigations/primary/dropdown-publish.vue +147 -0
- package/components/layout/navigations/primary.vue +132 -0
- package/components/layout/notifications/notifications-list.vue +19 -0
- package/composables/core/domains/use-definitions.ts +57 -0
- package/composables/core/domains/use-entries.ts +62 -0
- package/composables/core/domains/use-media.ts +82 -0
- package/composables/core/use-core.ts +12 -0
- package/composables/use-ability.js +38 -0
- package/composables/use-models.js +27 -0
- package/composables/use-session.ts +80 -0
- package/composables/use-timer.js +62 -0
- package/composables/use-trpc.js +17 -0
- package/editor/index.ts +44 -0
- package/editor/views/CalloutView.vue +22 -0
- package/editor/views/DetailsContentView.vue +11 -0
- package/editor/views/DetailsSummaryView.vue +11 -0
- package/editor/views/DetailsView.vue +72 -0
- package/editor/views/EmbedView.vue +53 -0
- package/editor/views/FileView.vue +79 -0
- package/editor/views/ImageView.vue +56 -0
- package/editor/views/UploadView.vue +71 -0
- package/layouts/default.vue +21 -0
- package/layouts/entrance.vue +10 -0
- package/layouts/record.vue +21 -0
- package/middleware/00.auth.global.js +16 -0
- package/middleware/01.instance.global.js +21 -0
- package/middleware/03.verify-permissions.global.js +11 -0
- package/middleware/04.prefetch-page.global.js +18 -0
- package/middleware/05.headings-page.global.js +25 -0
- package/models/_commun.js +243 -0
- package/models/_factory.js +34 -0
- package/models/collection.js +87 -0
- package/models/entrance.js +42 -0
- package/models/media.js +82 -0
- package/models/organization.js +92 -0
- package/models/record.js +165 -0
- package/models/role.js +66 -0
- package/models/token.js +56 -0
- package/models/user.js +97 -0
- package/nuxt.config.ts +151 -0
- package/package.json +71 -0
- package/pages/[collection]/[record]/index.vue +88 -0
- package/pages/[collection]/index.vue +55 -0
- package/pages/_lab/editor.vue +149 -0
- package/pages/auth/callback.vue +31 -0
- package/pages/index/index.vue +85 -0
- package/pages/index/password/define/[token].vue +79 -0
- package/pages/index/password/recover/index.vue +72 -0
- package/pages/index/welcome/[token].vue +90 -0
- package/pages/index.vue +44 -0
- package/pages/management/index.vue +15 -0
- package/pages/management/members.vue +176 -0
- package/pages/management/organizations.vue +192 -0
- package/pages/management/roles.vue +183 -0
- package/pages/overview.vue +15 -0
- package/pages/settings.vue +197 -0
- package/plugins/01.session.client.ts +5 -0
- package/plugins/02.vue-query.ts +25 -0
- package/public/favicon.ico +0 -0
- package/public/logo-black.svg +1 -0
- package/public/logo-white.svg +1 -0
- package/server/api/trpc/[...trpc].ts +28 -0
- package/server/api/v1/content/deployment.get.ts +45 -0
- package/server/api/v1/content/records.get.ts +12 -0
- package/server/middleware/0.security.ts +19 -0
- package/server/middleware/1.cors.ts +31 -0
- package/server/middleware/2.session.ts +20 -0
- package/server/middleware/3.api-token.ts +20 -0
- package/server/plugins/core.ts +8 -0
- package/server/routes/_tasks/[name].post.ts +20 -0
- package/server/routes/health.get.ts +6 -0
- package/server/tasks/deploy.ts +26 -0
- package/server/types.d.ts +8 -0
- package/server/utils/core.ts +18 -0
- package/store/layout/headings.js +21 -0
- package/store/layout/modal.js +80 -0
- package/store/layout/notifications.js +52 -0
- 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">​</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>
|