@fy-/fws-vue 0.0.1
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/components/fws/CmsArticleBoxed.vue +113 -0
- package/components/fws/CmsArticleSingle.vue +115 -0
- package/components/fws/DataTable.vue +260 -0
- package/components/fws/FilterData.vue +179 -0
- package/components/fws/UserFlow.vue +305 -0
- package/components/ssr/ClientOnly.ts +12 -0
- package/components/ui/DefaultBreadcrumb.vue +75 -0
- package/components/ui/DefaultConfirm.vue +69 -0
- package/components/ui/DefaultDateSelection.vue +56 -0
- package/components/ui/DefaultInput.vue +243 -0
- package/components/ui/DefaultLoader.vue +49 -0
- package/components/ui/DefaultModal.vue +90 -0
- package/components/ui/DefaultPaging.vue +212 -0
- package/components/ui/transitions/CollapseTransition.vue +30 -0
- package/components/ui/transitions/ExpandTransition.vue +32 -0
- package/components/ui/transitions/FadeTransition.vue +17 -0
- package/components/ui/transitions/ScaleTransition.vue +35 -0
- package/components/ui/transitions/SlideTransition.vue +127 -0
- package/components.d.ts +22 -0
- package/env.d.ts +6 -0
- package/event-bus.ts +14 -0
- package/index.ts +121 -0
- package/package.json +43 -0
- package/rest.ts +72 -0
- package/seo.ts +114 -0
- package/ssr.ts +97 -0
- package/stores/rest.ts +24 -0
- package/stores/serverRouter.ts +49 -0
- package/stores/user.ts +81 -0
- package/style.css +42 -0
- package/templating.ts +79 -0
- package/translations.ts +29 -0
- package/types.d.ts +4 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, onMounted } from "vue";
|
|
3
|
+
import type { KlbFlowData, KlbUserFlowField } from "@fy-/fws-types";
|
|
4
|
+
import { useRoute, useRouter } from "vue-router";
|
|
5
|
+
import { useTranslation } from "../../translations";
|
|
6
|
+
import { useEventBus } from "../../event-bus";
|
|
7
|
+
import { useUserStore } from "../../stores/user";
|
|
8
|
+
import { useRest, APIResult } from "../../rest";
|
|
9
|
+
import DefaultInput from "../ui/DefaultInput.vue";
|
|
10
|
+
import { ClientOnly } from "../ssr/ClientOnly";
|
|
11
|
+
const rest = useRest();
|
|
12
|
+
interface UserFlow extends APIResult {
|
|
13
|
+
data: KlbFlowData;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const props = withDefaults(
|
|
17
|
+
defineProps<{
|
|
18
|
+
returnDefault?: string;
|
|
19
|
+
forceAction?: string;
|
|
20
|
+
onSuccess?: Function;
|
|
21
|
+
}>(),
|
|
22
|
+
{
|
|
23
|
+
mode: "klb",
|
|
24
|
+
returnDefault: "/",
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
const isExternalUrl = (url: string) => {
|
|
28
|
+
return url.startsWith("http://") || url.startsWith("https://");
|
|
29
|
+
};
|
|
30
|
+
type paramsType = {
|
|
31
|
+
initial: boolean;
|
|
32
|
+
oauth?: string;
|
|
33
|
+
};
|
|
34
|
+
const store = useUserStore();
|
|
35
|
+
const route = useRoute();
|
|
36
|
+
const router = useRouter();
|
|
37
|
+
const eventBus = useEventBus();
|
|
38
|
+
const returnTo = ref<string>(props.returnDefault);
|
|
39
|
+
const responseMessage = ref<string | null>(null);
|
|
40
|
+
const responseError = ref<APIResult>();
|
|
41
|
+
const responseReq = ref<string[]>([]);
|
|
42
|
+
const responseFields = ref<Array<KlbUserFlowField>>([]);
|
|
43
|
+
const response = ref<UserFlow>();
|
|
44
|
+
const hasOauth = ref<boolean>(false);
|
|
45
|
+
const fieldsError = ref<Record<string, any>>({});
|
|
46
|
+
const pwdRecoverMailSent = ref<boolean>(false);
|
|
47
|
+
const inputs = ref<InstanceType<typeof DefaultInput>[]>([]);
|
|
48
|
+
const translate = useTranslation();
|
|
49
|
+
|
|
50
|
+
const formData = ref<Record<string, any>>({
|
|
51
|
+
return_to: props.returnDefault,
|
|
52
|
+
session: null,
|
|
53
|
+
action: props.forceAction ? props.forceAction : undefined,
|
|
54
|
+
});
|
|
55
|
+
const completed = ref(false);
|
|
56
|
+
|
|
57
|
+
const doTrigger = async (field: any) => {
|
|
58
|
+
// eslint-disable-next-line
|
|
59
|
+
|
|
60
|
+
const _res = await eval(
|
|
61
|
+
`const _rest = rest; ${field.info.Button_Extra.trigger}`,
|
|
62
|
+
);
|
|
63
|
+
if (_res.ethereum) {
|
|
64
|
+
formData.value.ethereum = _res.ethereum;
|
|
65
|
+
responseReq.value = [];
|
|
66
|
+
await userFlow();
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const userFlow = async (params: paramsType = { initial: false }) => {
|
|
70
|
+
eventBus.emit("login-loading", true);
|
|
71
|
+
fieldsError.value = {};
|
|
72
|
+
responseError.value = undefined;
|
|
73
|
+
|
|
74
|
+
if (params.initial === false) {
|
|
75
|
+
let hasError = false;
|
|
76
|
+
responseReq.value.forEach((field) => {
|
|
77
|
+
if (!formData.value[field] || formData.value[field] == "") {
|
|
78
|
+
fieldsError.value[field] = translate("vuelidate_validator_req");
|
|
79
|
+
hasError = true;
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
if (hasError) {
|
|
83
|
+
eventBus.emit("login-loading", false);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
hasOauth.value = false;
|
|
88
|
+
|
|
89
|
+
if (params.oauth) {
|
|
90
|
+
formData.value.oauth2 = params.oauth;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (route.query.return_to && typeof route.query.return_to == "string") {
|
|
94
|
+
returnTo.value = route.query.return_to
|
|
95
|
+
? route.query.return_to
|
|
96
|
+
: props.returnDefault;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!formData.value.session) {
|
|
100
|
+
formData.value.session = route.query.session
|
|
101
|
+
? route.query.session
|
|
102
|
+
: undefined;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
formData.value.return_to = returnTo.value;
|
|
106
|
+
response.value = (await rest("User:flow", "POST", formData.value).catch(
|
|
107
|
+
(err: APIResult) => {
|
|
108
|
+
if (err.token && err.token == "invalid_request_token") {
|
|
109
|
+
window.location.reload();
|
|
110
|
+
}
|
|
111
|
+
responseError.value = err;
|
|
112
|
+
if (responseError.value.param) {
|
|
113
|
+
fieldsError.value[responseError.value.param] =
|
|
114
|
+
responseError.value.token;
|
|
115
|
+
}
|
|
116
|
+
eventBus.emit("login-loading", false);
|
|
117
|
+
return;
|
|
118
|
+
},
|
|
119
|
+
)) as UserFlow;
|
|
120
|
+
if (response.value?.result == "success") {
|
|
121
|
+
if (props.onSuccess) {
|
|
122
|
+
await props.onSuccess();
|
|
123
|
+
}
|
|
124
|
+
if (response.value.data.complete == true && response.value.data.user) {
|
|
125
|
+
store.setUser(response.value.data.user);
|
|
126
|
+
if (isExternalUrl(returnTo.value)) {
|
|
127
|
+
window.location.href = returnTo.value;
|
|
128
|
+
} else {
|
|
129
|
+
const routeExists = router.resolve(returnTo.value);
|
|
130
|
+
if (routeExists.matched.length != 0) router.push(returnTo.value);
|
|
131
|
+
else window.location.href = returnTo.value;
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (response.value.data.url) {
|
|
136
|
+
window.location.href = response.value.data.url;
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (response.value.data.Redirect && response.value.data.complete) {
|
|
140
|
+
router.push("/");
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
formData.value = {
|
|
144
|
+
session: response.value.data.session,
|
|
145
|
+
};
|
|
146
|
+
inputs.value = [];
|
|
147
|
+
responseFields.value = response.value.data.fields;
|
|
148
|
+
if (response.value.data.message)
|
|
149
|
+
responseMessage.value = response.value.data.message;
|
|
150
|
+
responseReq.value = response.value.data.req;
|
|
151
|
+
responseFields.value.forEach((field) => {
|
|
152
|
+
if (field.type == "oauth2") {
|
|
153
|
+
hasOauth.value = true;
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
setTimeout(() => {
|
|
157
|
+
if (inputs.value.length > 0 && inputs.value[inputs.value.length - 1])
|
|
158
|
+
inputs.value[inputs.value.length - 1].focus();
|
|
159
|
+
}, 300);
|
|
160
|
+
} else {
|
|
161
|
+
//
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
eventBus.emit("login-loading", false);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
onMounted(async () => {
|
|
168
|
+
await userFlow({ initial: true });
|
|
169
|
+
});
|
|
170
|
+
</script>
|
|
171
|
+
<template>
|
|
172
|
+
<ClientOnly>
|
|
173
|
+
<form
|
|
174
|
+
@submit.prevent="userFlow()"
|
|
175
|
+
v-if="!completed"
|
|
176
|
+
class="fws-login w-full"
|
|
177
|
+
>
|
|
178
|
+
<!--<FyLoader id="klblogin" />-->
|
|
179
|
+
<div class="w-full">
|
|
180
|
+
<h2
|
|
181
|
+
class="text-lg text-fv-neutral-700 dark:text-fv-neutral-300"
|
|
182
|
+
v-if="responseMessage"
|
|
183
|
+
>
|
|
184
|
+
{{ responseMessage }}
|
|
185
|
+
</h2>
|
|
186
|
+
<template v-if="responseFields && responseFields.length > 0">
|
|
187
|
+
<template v-for="field of responseFields" :key="field.label">
|
|
188
|
+
<h3
|
|
189
|
+
v-if="field.type == 'label'"
|
|
190
|
+
class="pt-2 pb-1 text-sm text-fv-neutral-500 dark:text-fv-neutral-400"
|
|
191
|
+
:class="
|
|
192
|
+
field.style == 'error'
|
|
193
|
+
? 'text-sm my-2 p-1 font-semibold text-red-800 dark:text-red-300'
|
|
194
|
+
: ''
|
|
195
|
+
"
|
|
196
|
+
>
|
|
197
|
+
<a :href="field.link" v-if="field.link" class="fws-link mb-3">{{
|
|
198
|
+
field.label
|
|
199
|
+
}}</a>
|
|
200
|
+
<span class="mb-2" v-else>{{ field.label }}</span>
|
|
201
|
+
</h3>
|
|
202
|
+
|
|
203
|
+
<template v-if="field.cat == 'input'">
|
|
204
|
+
<template
|
|
205
|
+
v-if="
|
|
206
|
+
field.type == 'text' ||
|
|
207
|
+
field.type == 'password' ||
|
|
208
|
+
field.type == 'email'
|
|
209
|
+
"
|
|
210
|
+
>
|
|
211
|
+
<DefaultInput
|
|
212
|
+
v-if="field.name"
|
|
213
|
+
:id="field.name"
|
|
214
|
+
:label="field.label"
|
|
215
|
+
class="mt-3"
|
|
216
|
+
:placeholder="field.name == 'name' ? 'John Doe' : field.label"
|
|
217
|
+
:error="fieldsError[field.name]"
|
|
218
|
+
:type="field.type"
|
|
219
|
+
ref="inputs"
|
|
220
|
+
v-model="formData[field.name]"
|
|
221
|
+
:req="responseReq.includes(field.name)"
|
|
222
|
+
/>
|
|
223
|
+
</template>
|
|
224
|
+
</template>
|
|
225
|
+
<template v-if="field.type == 'checkbox'">
|
|
226
|
+
<DefaultInput
|
|
227
|
+
v-if="field.name"
|
|
228
|
+
:id="field.name"
|
|
229
|
+
class="mt-3"
|
|
230
|
+
:label="field.label"
|
|
231
|
+
:error="fieldsError[field.name]"
|
|
232
|
+
:type="field.type"
|
|
233
|
+
v-model:checkbox-value="formData[field.name]"
|
|
234
|
+
:req="responseReq.includes(field.name)"
|
|
235
|
+
:link-icon="field.link"
|
|
236
|
+
/>
|
|
237
|
+
</template>
|
|
238
|
+
</template>
|
|
239
|
+
<div
|
|
240
|
+
class="text-sm my-2 p-1 font-semibold text-red-800 dark:text-red-300"
|
|
241
|
+
v-if="responseError && responseError.token"
|
|
242
|
+
>
|
|
243
|
+
{{ $t(responseError.token) }}
|
|
244
|
+
</div>
|
|
245
|
+
<div v-if="responseReq.includes('password') && 0" class="reset-pwd">
|
|
246
|
+
<a
|
|
247
|
+
href="javascript:void(0)"
|
|
248
|
+
@click="
|
|
249
|
+
() => {
|
|
250
|
+
eventBus.emit('ResetPasswordModal', true);
|
|
251
|
+
pwdRecoverMailSent = false;
|
|
252
|
+
}
|
|
253
|
+
"
|
|
254
|
+
>{{ $t("recover_pwd_link") }}</a
|
|
255
|
+
>
|
|
256
|
+
</div>
|
|
257
|
+
<button class="btn primary medium mt-4">
|
|
258
|
+
{{ $t("cta_login_next") }}
|
|
259
|
+
</button>
|
|
260
|
+
<template v-if="hasOauth">
|
|
261
|
+
<div
|
|
262
|
+
class="relative flex items-center justify-center w-full mt-4 mb-2"
|
|
263
|
+
>
|
|
264
|
+
<div
|
|
265
|
+
class="h-px bg-neutral-300 dark:bg-neutral-600 inset-x-0 absolute"
|
|
266
|
+
></div>
|
|
267
|
+
<div
|
|
268
|
+
class="bg-white dark:bg-neutral-700 fws-helper-text px-4 relative"
|
|
269
|
+
>
|
|
270
|
+
{{ $t("or_text_label") }}
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
<div
|
|
274
|
+
class="flex items-center justify-center shadow py-2 rounded bg-fv-neutral-50 dark:bg-fv-neutral-800"
|
|
275
|
+
>
|
|
276
|
+
<template v-for="field of responseFields" :key="field.id">
|
|
277
|
+
<a
|
|
278
|
+
@click="
|
|
279
|
+
() => {
|
|
280
|
+
if (field.info.Button_Extra?.trigger) {
|
|
281
|
+
doTrigger(field);
|
|
282
|
+
} else {
|
|
283
|
+
userFlow({ initial: true, oauth: field.id });
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
"
|
|
287
|
+
v-if="field.type && field.type == 'oauth2' && field.button"
|
|
288
|
+
href="javascript:void(0);"
|
|
289
|
+
>
|
|
290
|
+
<img
|
|
291
|
+
:key="`${field.label}oauth`"
|
|
292
|
+
class="h-12 w-12 block p-2 mr-3 rounded-full border-4 shadow hover:border"
|
|
293
|
+
:alt="field.info.Name"
|
|
294
|
+
:src="field.button.logo"
|
|
295
|
+
:style="`background: ${field.button['background-color']}`"
|
|
296
|
+
/>
|
|
297
|
+
</a>
|
|
298
|
+
</template>
|
|
299
|
+
</div>
|
|
300
|
+
</template>
|
|
301
|
+
</template>
|
|
302
|
+
</div>
|
|
303
|
+
</form>
|
|
304
|
+
</ClientOnly>
|
|
305
|
+
</template>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ref, onMounted, defineComponent } from "vue";
|
|
2
|
+
|
|
3
|
+
export const ClientOnly = defineComponent({
|
|
4
|
+
__name: "ClientOnly",
|
|
5
|
+
setup(_, { slots }) {
|
|
6
|
+
const show = ref(false);
|
|
7
|
+
onMounted(() => {
|
|
8
|
+
show.value = true;
|
|
9
|
+
});
|
|
10
|
+
return () => (show.value && slots.default ? slots.default() : null);
|
|
11
|
+
},
|
|
12
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { HomeIcon, ChevronRightIcon } from "@heroicons/vue/24/solid";
|
|
3
|
+
import type { BreadcrumbLink } from "../../types";
|
|
4
|
+
withDefaults(
|
|
5
|
+
defineProps<{
|
|
6
|
+
nav: BreadcrumbLink[];
|
|
7
|
+
showHome: Boolean;
|
|
8
|
+
}>(),
|
|
9
|
+
{
|
|
10
|
+
nav: () => [],
|
|
11
|
+
showHome: () => true,
|
|
12
|
+
},
|
|
13
|
+
);
|
|
14
|
+
</script>
|
|
15
|
+
<template>
|
|
16
|
+
<ol
|
|
17
|
+
class="inline-flex items-center flex-wrap"
|
|
18
|
+
itemscope
|
|
19
|
+
itemtype="https://schema.org/BreadcrumbList"
|
|
20
|
+
>
|
|
21
|
+
<template v-for="(item, index) in nav" :key="`bc_${index.toString()}`">
|
|
22
|
+
<li class="'inline-flex items-center'">
|
|
23
|
+
<ChevronRightIcon
|
|
24
|
+
v-if="index != 0"
|
|
25
|
+
:class="
|
|
26
|
+
item.to
|
|
27
|
+
? index == 0
|
|
28
|
+
? 'w-4 h-4 mr-2 inline-block'
|
|
29
|
+
: 'w-5 h-5 text-fv-neutral-400 inline-block mx-0.5 md:mx-1.5'
|
|
30
|
+
: 'w-5 h-5 text-fv-neutral-400 inline-block mx-0.5 md:mx-1.5'
|
|
31
|
+
"
|
|
32
|
+
/>
|
|
33
|
+
|
|
34
|
+
<router-link
|
|
35
|
+
:to="item.to"
|
|
36
|
+
v-if="item.to"
|
|
37
|
+
itemprop="itemListElement"
|
|
38
|
+
itemtype="https://schema.org/ListItem"
|
|
39
|
+
itemscope
|
|
40
|
+
:class="
|
|
41
|
+
item.to
|
|
42
|
+
? index == 0
|
|
43
|
+
? 'text-xs font-medium text-fv-neutral-700 hover:text-fv-neutral-900 dark:text-fv-neutral-200 dark:hover:text-white'
|
|
44
|
+
: 'text-xs font-medium text-fv-neutral-700 hover:text-fv-neutral-900 dark:text-fv-neutral-200 dark:hover:text-white'
|
|
45
|
+
: ''
|
|
46
|
+
"
|
|
47
|
+
>
|
|
48
|
+
<HomeIcon
|
|
49
|
+
:class="
|
|
50
|
+
item.to
|
|
51
|
+
? index == 0
|
|
52
|
+
? 'w-4 h-4 mr-2 inline-block'
|
|
53
|
+
: 'w-4 h-4 text-fv-neutral-400 inline-block mx-0.5 md:mx-1.5'
|
|
54
|
+
: 'w-4 h-4 text-fv-neutral-400 inline-block mx-0.5 md:mx-1.5'
|
|
55
|
+
"
|
|
56
|
+
v-if="showHome && index === 0"
|
|
57
|
+
/>
|
|
58
|
+
<span itemprop="name">{{ item.name }}</span>
|
|
59
|
+
<meta itemprop="position" :content="(index + 1).toString()" />
|
|
60
|
+
<meta itemprop="url" :content="item.to" />
|
|
61
|
+
</router-link>
|
|
62
|
+
<span
|
|
63
|
+
class="text-xs font-medium text-fv-neutral-500 dark:text-fv-neutral-200"
|
|
64
|
+
v-else
|
|
65
|
+
itemprop="itemListElement"
|
|
66
|
+
itemtype="https://schema.org/ListItem"
|
|
67
|
+
itemscope
|
|
68
|
+
>
|
|
69
|
+
<span itemprop="name">{{ item.name }}</span>
|
|
70
|
+
<meta itemprop="position" :content="(index + 1).toString()" />
|
|
71
|
+
</span>
|
|
72
|
+
</li>
|
|
73
|
+
</template>
|
|
74
|
+
</ol>
|
|
75
|
+
</template>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, onMounted, onUnmounted } from "vue";
|
|
3
|
+
import { ExclamationCircleIcon } from "@heroicons/vue/24/solid";
|
|
4
|
+
import { useEventBus } from "../../event-bus";
|
|
5
|
+
import DefaultModal from "./DefaultModal.vue";
|
|
6
|
+
|
|
7
|
+
const eventBus = useEventBus();
|
|
8
|
+
const title = ref<string | null>(null);
|
|
9
|
+
const desc = ref<string | null>(null);
|
|
10
|
+
const onConfirm = ref<Function | null>(null);
|
|
11
|
+
interface ConfirmModalData {
|
|
12
|
+
title: string;
|
|
13
|
+
desc: string;
|
|
14
|
+
onConfirm: Function;
|
|
15
|
+
}
|
|
16
|
+
const _onConfirm = async () => {
|
|
17
|
+
if (onConfirm.value) {
|
|
18
|
+
await onConfirm.value();
|
|
19
|
+
}
|
|
20
|
+
resetConfirm();
|
|
21
|
+
};
|
|
22
|
+
const resetConfirm = () => {
|
|
23
|
+
title.value = null;
|
|
24
|
+
desc.value = null;
|
|
25
|
+
onConfirm.value = null;
|
|
26
|
+
eventBus.emit("confirmModal", false);
|
|
27
|
+
};
|
|
28
|
+
const showConfirm = (data: ConfirmModalData) => {
|
|
29
|
+
title.value = data.title;
|
|
30
|
+
desc.value = data.desc;
|
|
31
|
+
onConfirm.value = data.onConfirm;
|
|
32
|
+
eventBus.emit("confirmModal", true);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
onMounted(() => {
|
|
36
|
+
eventBus.on("resetConfirm", resetConfirm);
|
|
37
|
+
eventBus.on("showConfirm", showConfirm);
|
|
38
|
+
});
|
|
39
|
+
onUnmounted(() => {
|
|
40
|
+
eventBus.off("resetConfirm", resetConfirm);
|
|
41
|
+
eventBus.off("showConfirm", showConfirm);
|
|
42
|
+
});
|
|
43
|
+
</script>
|
|
44
|
+
<template>
|
|
45
|
+
<DefaultModal id="confirm" :title="title ? title : ''">
|
|
46
|
+
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
|
47
|
+
<div class="p-6 text-center">
|
|
48
|
+
<ExclamationCircleIcon
|
|
49
|
+
class="mx-auto mb-4 text-gray-400 w-12 h-12 dark:text-gray-200"
|
|
50
|
+
/>
|
|
51
|
+
|
|
52
|
+
<h3 class="mb-5 text-lg font-normal text-gray-500 dark:text-gray-400">
|
|
53
|
+
{{ desc ? desc : title }}
|
|
54
|
+
</h3>
|
|
55
|
+
<div class="flex justify-between gap-3 mt-4">
|
|
56
|
+
<button class="btn danger defaults" @click="_onConfirm()">
|
|
57
|
+
{{ $t("confirm_modal_cta_confirm") }}
|
|
58
|
+
</button>
|
|
59
|
+
<button
|
|
60
|
+
class="btn neutral defaults"
|
|
61
|
+
@click="$eventBus.emit('confirmModal', false)"
|
|
62
|
+
>
|
|
63
|
+
{{ $t("confirm_modal_cta_cancel") }}
|
|
64
|
+
</button>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</DefaultModal>
|
|
69
|
+
</template>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { DefaultInput } from "../..";
|
|
4
|
+
interface DateInterval {
|
|
5
|
+
$between: [any, any];
|
|
6
|
+
}
|
|
7
|
+
const props = withDefaults(
|
|
8
|
+
defineProps<{
|
|
9
|
+
mode?: "interval" | "single";
|
|
10
|
+
modelValue?: DateInterval;
|
|
11
|
+
id: string;
|
|
12
|
+
label?: string;
|
|
13
|
+
}>(),
|
|
14
|
+
{
|
|
15
|
+
mode: "single",
|
|
16
|
+
modelValue: () => {
|
|
17
|
+
return { $between: [undefined, undefined] };
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const emit = defineEmits(["update:modelValue"]);
|
|
23
|
+
|
|
24
|
+
const model = computed({
|
|
25
|
+
get: () => props.modelValue,
|
|
26
|
+
set: (items) => {
|
|
27
|
+
emit("update:modelValue", items);
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
</script>
|
|
31
|
+
<template>
|
|
32
|
+
<div v-if="mode == 'interval' && model">
|
|
33
|
+
<div class="flex flex-col md:flex-row">
|
|
34
|
+
<DefaultInput
|
|
35
|
+
v-model="model.$between[0]"
|
|
36
|
+
type="date"
|
|
37
|
+
:id="`${id}_start`"
|
|
38
|
+
class="w-full"
|
|
39
|
+
:label="`${label} (${$t('date_selection_start')})`"
|
|
40
|
+
/>
|
|
41
|
+
<div class="md:mx-2 flex items-center justify-center">
|
|
42
|
+
<div>↭</div>
|
|
43
|
+
</div>
|
|
44
|
+
<DefaultInput
|
|
45
|
+
v-model="model.$between[1]"
|
|
46
|
+
type="date"
|
|
47
|
+
class="w-full"
|
|
48
|
+
:id="`${id}_end`"
|
|
49
|
+
:label="`${label} (${$t('date_selection_end')})`"
|
|
50
|
+
/>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
<div v-else>
|
|
54
|
+
<DefaultInput v-model="model.$between[0]" type="date" :id="id" />
|
|
55
|
+
</div>
|
|
56
|
+
</template>
|