contactstudiocstools 1.0.224
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 +94 -0
- package/dist/module.cjs +5 -0
- package/dist/module.d.ts +7 -0
- package/dist/module.json +5 -0
- package/dist/module.mjs +72 -0
- package/dist/runtime/components/Atom.Alert.vue +46 -0
- package/dist/runtime/components/Atom.Auth.vue +37 -0
- package/dist/runtime/components/Atom.BannerChatEmpty.vue +18 -0
- package/dist/runtime/components/Atom.BannerPage404.vue +28 -0
- package/dist/runtime/components/Atom.BannerPageUnauthorized.vue +18 -0
- package/dist/runtime/components/Atom.Breadcrumb.vue +26 -0
- package/dist/runtime/components/Atom.ChatContact.vue +136 -0
- package/dist/runtime/components/Atom.ChatContactSchedule.vue +87 -0
- package/dist/runtime/components/Atom.ChatMessageFooter.vue +25 -0
- package/dist/runtime/components/Atom.DarkMode.vue +67 -0
- package/dist/runtime/components/Atom.DraggableWindow.vue +102 -0
- package/dist/runtime/components/Atom.Dropdown.vue +9 -0
- package/dist/runtime/components/Atom.DropdownSearchable.vue +25 -0
- package/dist/runtime/components/Atom.Fetch.vue +46 -0
- package/dist/runtime/components/Atom.Field.vue +43 -0
- package/dist/runtime/components/Atom.FieldDate.vue +19 -0
- package/dist/runtime/components/Atom.FieldNumber.vue +19 -0
- package/dist/runtime/components/Atom.FieldPhone.vue +92 -0
- package/dist/runtime/components/Atom.FieldSelect.vue +28 -0
- package/dist/runtime/components/Atom.FieldSelectMultiple.vue +49 -0
- package/dist/runtime/components/Atom.FieldText.vue +19 -0
- package/dist/runtime/components/Atom.FieldTextarea.vue +41 -0
- package/dist/runtime/components/Atom.Loading.vue +80 -0
- package/dist/runtime/components/Atom.Notification.vue +48 -0
- package/dist/runtime/components/Atom.Ringtone.vue +23 -0
- package/dist/runtime/components/Atom.SelectTreeField.vue +49 -0
- package/dist/runtime/components/Atom.Snapshot.vue +33 -0
- package/dist/runtime/components/Atom.Tabs.vue +60 -0
- package/dist/runtime/components/Molecule.ChatMessageFile.vue +102 -0
- package/dist/runtime/components/Molecule.ChatMessageOption.vue +85 -0
- package/dist/runtime/components/Molecule.ChatMessageText.vue +36 -0
- package/dist/runtime/components/Molecule.ClientHistory.vue +62 -0
- package/dist/runtime/components/Molecule.DropdownDDI.vue +333 -0
- package/dist/runtime/components/Molecule.FieldGroup.vue +73 -0
- package/dist/runtime/components/Molecule.FieldSelectMultiple.vue +19 -0
- package/dist/runtime/components/Molecule.File.vue +84 -0
- package/dist/runtime/components/Molecule.SelectTreeSearchable.vue +126 -0
- package/dist/runtime/components/Molecule.Status.vue +154 -0
- package/dist/runtime/components/Molecule.TimeDaily.vue +9 -0
- package/dist/runtime/components/Organism.Attachments.vue +139 -0
- package/dist/runtime/components/Organism.ChatMessages.vue +31 -0
- package/dist/runtime/components/Organism.ChatRoom.vue +342 -0
- package/dist/runtime/components/Organism.ChatSchedule.vue +110 -0
- package/dist/runtime/components/Organism.ClientHistoryTable.vue +85 -0
- package/dist/runtime/components/Organism.ClientHistoryTimeline.vue +77 -0
- package/dist/runtime/components/Organism.FAQ.vue +88 -0
- package/dist/runtime/components/Organism.Form.vue +67 -0
- package/dist/runtime/components/Organism.FormMailing.vue +112 -0
- package/dist/runtime/components/Organism.HeaderMain.vue +79 -0
- package/dist/runtime/components/Organism.Manifestation.vue +146 -0
- package/dist/runtime/components/Organism.Nav.vue +27 -0
- package/dist/runtime/components/Organism.NavMain.vue +187 -0
- package/dist/runtime/components/Organism.PageContainer.vue +22 -0
- package/dist/runtime/components/Organism.Schedule.vue +170 -0
- package/dist/runtime/components/Organism.Tabulation.vue +237 -0
- package/dist/runtime/components/types/dto.d.ts +16 -0
- package/dist/runtime/components/types/dto.mjs +236 -0
- package/dist/runtime/components/types/helpers.d.ts +39 -0
- package/dist/runtime/components/types/helpers.mjs +295 -0
- package/dist/runtime/components/types/index.d.ts +4 -0
- package/dist/runtime/components/types/index.mjs +4 -0
- package/dist/runtime/components/types/types.d.ts +198 -0
- package/dist/runtime/components/types/types.mjs +35 -0
- package/dist/runtime/index.css +1 -0
- package/dist/runtime/plugins/clickOutside.d.ts +2 -0
- package/dist/runtime/plugins/clickOutside.mjs +16 -0
- package/dist/runtime/plugins/emitter.d.ts +2 -0
- package/dist/runtime/plugins/emitter.mjs +17 -0
- package/dist/runtime/public/192x192.png +0 -0
- package/dist/runtime/public/404.svg +1 -0
- package/dist/runtime/public/512x512.png +0 -0
- package/dist/runtime/public/chat.svg +138 -0
- package/dist/runtime/public/chatbg.png +0 -0
- package/dist/runtime/public/dev-sw.d.ts +0 -0
- package/dist/runtime/public/dev-sw.mjs +0 -0
- package/dist/runtime/public/empty.svg +1 -0
- package/dist/runtime/public/loading.svg +1 -0
- package/dist/runtime/public/messages.svg +1 -0
- package/dist/runtime/public/privacy.svg +1 -0
- package/dist/runtime/public/ringtone.mp3 +0 -0
- package/dist/runtime/public/security.svg +188 -0
- package/dist/runtime/public/snapshot.d.ts +15 -0
- package/dist/runtime/public/snapshot.mjs +77 -0
- package/dist/runtime/public/unauthorized.svg +1 -0
- package/dist/types.d.ts +10 -0
- package/package.json +50 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
v-if="visible"
|
|
4
|
+
class="draggable absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 flex flex-col overflow-hidden scrollbar z-50 shadow-lg rounded-md !bg-white dark:!bg-slate-900 chatbg"
|
|
5
|
+
>
|
|
6
|
+
<header class="dragzone flex flex-col sticky top-0 z-10 bg-slate-700">
|
|
7
|
+
<ul class="tabs pl-2 mt-3 whitespace-nowrap">
|
|
8
|
+
<li class="tab active bg-white dark:bg-slate-800 normal-case">
|
|
9
|
+
Histórico de Conversas
|
|
10
|
+
</li>
|
|
11
|
+
<li class="tab flex-1 justify-end">
|
|
12
|
+
<i
|
|
13
|
+
class="bi-x-lg text-error text-sm leading-3 px-2 pb-2"
|
|
14
|
+
@click="hide"
|
|
15
|
+
/>
|
|
16
|
+
</li>
|
|
17
|
+
</ul>
|
|
18
|
+
</header>
|
|
19
|
+
<article
|
|
20
|
+
class="p-2 h-full overflow-auto scrollbar z-10 border dark:border-slate-800"
|
|
21
|
+
>
|
|
22
|
+
<div class="flex justify-center">
|
|
23
|
+
<span
|
|
24
|
+
class="text-center text- my-3 badge badge-outline-secondary w-fit bg-white dark:bg-slate-900"
|
|
25
|
+
>{{ title }}</span>
|
|
26
|
+
</div>
|
|
27
|
+
<slot />
|
|
28
|
+
</article>
|
|
29
|
+
<footer class="dragzone cursor-pointer w-full h-12 bg-slate-700 z-10" />
|
|
30
|
+
</div>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<script setup lang="ts">
|
|
34
|
+
import { ref } from "vue";
|
|
35
|
+
|
|
36
|
+
// data
|
|
37
|
+
const visible = ref<boolean>(false);
|
|
38
|
+
const title = ref<string>("");
|
|
39
|
+
|
|
40
|
+
// methods
|
|
41
|
+
function show(text: string = "") {
|
|
42
|
+
visible.value = true;
|
|
43
|
+
title.value = text;
|
|
44
|
+
|
|
45
|
+
setTimeout(() => {
|
|
46
|
+
const draggable = document.querySelector(".draggable");
|
|
47
|
+
for (const dragzone of document.querySelectorAll(".dragzone")) {
|
|
48
|
+
dragElement(draggable, dragzone);
|
|
49
|
+
}
|
|
50
|
+
}, 500);
|
|
51
|
+
}
|
|
52
|
+
function hide() {
|
|
53
|
+
visible.value = false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const dragElement = (element: any, dragzone: any) => {
|
|
57
|
+
let pos1 = 0,
|
|
58
|
+
pos2 = 0,
|
|
59
|
+
pos3 = 0,
|
|
60
|
+
pos4 = 0;
|
|
61
|
+
const dragMouseUp = () => {
|
|
62
|
+
document.onmouseup = null;
|
|
63
|
+
document.onmousemove = null;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const dragMouseMove = (event: any) => {
|
|
67
|
+
event.preventDefault();
|
|
68
|
+
pos1 = pos3 - event.clientX;
|
|
69
|
+
pos2 = pos4 - event.clientY;
|
|
70
|
+
pos3 = event.clientX;
|
|
71
|
+
pos4 = event.clientY;
|
|
72
|
+
element.style.top = `${element.offsetTop - pos2}px`;
|
|
73
|
+
element.style.left = `${element.offsetLeft - pos1}px`;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const dragMouseDown = (event: any) => {
|
|
77
|
+
event.preventDefault();
|
|
78
|
+
|
|
79
|
+
pos3 = event.clientX;
|
|
80
|
+
pos4 = event.clientY;
|
|
81
|
+
|
|
82
|
+
document.onmouseup = dragMouseUp;
|
|
83
|
+
document.onmousemove = dragMouseMove;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
dragzone.onmousedown = dragMouseDown;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// expose
|
|
90
|
+
defineExpose({ show, hide });
|
|
91
|
+
</script>
|
|
92
|
+
|
|
93
|
+
<style scoped>
|
|
94
|
+
.chatbg::after {
|
|
95
|
+
content: "";
|
|
96
|
+
width: 100%;
|
|
97
|
+
height: 100%;
|
|
98
|
+
position: absolute;
|
|
99
|
+
background: url(../public/chatbg.png);
|
|
100
|
+
background-size: cover;
|
|
101
|
+
}
|
|
102
|
+
</style>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<ul class="dropdown">
|
|
3
|
+
<form class="field-group mb-2">
|
|
4
|
+
<input
|
|
5
|
+
:value="modelValue"
|
|
6
|
+
type="search"
|
|
7
|
+
placeholder="Pesquise"
|
|
8
|
+
class="input bg-slate-100 dark:bg-slate-700"
|
|
9
|
+
@input=" $emit('update:modelValue', ($event.target as HTMLInputElement).value)"
|
|
10
|
+
>
|
|
11
|
+
</form>
|
|
12
|
+
<slot />
|
|
13
|
+
</ul>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script setup lang="ts">
|
|
17
|
+
interface IProps {
|
|
18
|
+
modelValue?: string;
|
|
19
|
+
}
|
|
20
|
+
defineProps<IProps>();
|
|
21
|
+
|
|
22
|
+
defineEmits(["update:modelValue"]);
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<style scoped></style>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<slot />
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup lang="ts">
|
|
6
|
+
import { useFetch, UseFetchOptions, useNuxtApp } from "#app"
|
|
7
|
+
|
|
8
|
+
// props
|
|
9
|
+
interface IProps {
|
|
10
|
+
url: string
|
|
11
|
+
success: Function
|
|
12
|
+
options?: UseFetchOptions<any>
|
|
13
|
+
error?: Function
|
|
14
|
+
finally?: Function
|
|
15
|
+
}
|
|
16
|
+
const props = defineProps<IProps>()
|
|
17
|
+
|
|
18
|
+
// app
|
|
19
|
+
const { $emit } = useNuxtApp()
|
|
20
|
+
|
|
21
|
+
await new Promise(resolve=>setTimeout(resolve, 2000))
|
|
22
|
+
|
|
23
|
+
async function load(): Promise<void> {
|
|
24
|
+
const { data, error, refresh } = await useFetch(props.url, props.options)
|
|
25
|
+
|
|
26
|
+
if (error.value && props.error) {
|
|
27
|
+
if (error.value.statusCode === 401) {
|
|
28
|
+
$emit("auth:logout")
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
props.error({ error: error.value, refresh })
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
props.success(data.value)
|
|
37
|
+
|
|
38
|
+
if (props.finally) {
|
|
39
|
+
props.finally({ data: data.value, error: error.value, refresh })
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await load()
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<style scoped></style>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="getComponentByType()"
|
|
4
|
+
:field="field"
|
|
5
|
+
/>
|
|
6
|
+
</template>
|
|
7
|
+
|
|
8
|
+
<script setup lang="ts">
|
|
9
|
+
import { resolveComponent } from "vue";
|
|
10
|
+
import { EFieldTypes, IField } from "./types";
|
|
11
|
+
|
|
12
|
+
// props
|
|
13
|
+
interface IProps {
|
|
14
|
+
type?: EFieldTypes;
|
|
15
|
+
field?: IField
|
|
16
|
+
}
|
|
17
|
+
const props = defineProps<IProps>();
|
|
18
|
+
|
|
19
|
+
// method
|
|
20
|
+
function getComponentByType() {
|
|
21
|
+
if (props.type === EFieldTypes.date) {
|
|
22
|
+
return resolveComponent("AtomFieldDate");
|
|
23
|
+
}
|
|
24
|
+
if (props.type === EFieldTypes.text) {
|
|
25
|
+
return resolveComponent("AtomFieldText");
|
|
26
|
+
}
|
|
27
|
+
if (props.type === EFieldTypes.phone) {
|
|
28
|
+
return resolveComponent("AtomFieldPhone");
|
|
29
|
+
}
|
|
30
|
+
if (props.type === EFieldTypes.number) {
|
|
31
|
+
return resolveComponent("AtomFieldNumber");
|
|
32
|
+
}
|
|
33
|
+
if (props.type === EFieldTypes.textarea) {
|
|
34
|
+
return resolveComponent("AtomFieldTextarea");
|
|
35
|
+
}
|
|
36
|
+
if (props.type === EFieldTypes.select) {
|
|
37
|
+
return resolveComponent("AtomFieldSelect");
|
|
38
|
+
}
|
|
39
|
+
return resolveComponent("AtomFieldText");
|
|
40
|
+
}
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<style scoped></style>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<input
|
|
3
|
+
:value="modelValue"
|
|
4
|
+
type="date"
|
|
5
|
+
class="input w-1"
|
|
6
|
+
@input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)"
|
|
7
|
+
>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup lang="ts">
|
|
11
|
+
interface IProps {
|
|
12
|
+
modelValue?: string;
|
|
13
|
+
}
|
|
14
|
+
defineProps<IProps>();
|
|
15
|
+
|
|
16
|
+
defineEmits(["update:modelValue"]);
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<style scoped></style>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<input
|
|
3
|
+
:value="modelValue"
|
|
4
|
+
type="number w-1"
|
|
5
|
+
class="input"
|
|
6
|
+
@input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)"
|
|
7
|
+
>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup lang="ts">
|
|
11
|
+
interface IProps {
|
|
12
|
+
modelValue?: string;
|
|
13
|
+
}
|
|
14
|
+
defineProps<IProps>();
|
|
15
|
+
|
|
16
|
+
defineEmits(["update:modelValue"]);
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<style scoped></style>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="input-group">
|
|
3
|
+
<MoleculeDropdownDDI
|
|
4
|
+
ref="MoleculeDropdownDDIRef"
|
|
5
|
+
v-model="ddi"
|
|
6
|
+
class="addon"
|
|
7
|
+
:disabled="field?.disabled || loading"
|
|
8
|
+
/>
|
|
9
|
+
<input
|
|
10
|
+
v-model="phone"
|
|
11
|
+
:disabled="field?.disabled || loading"
|
|
12
|
+
type="tel"
|
|
13
|
+
class="input w-1"
|
|
14
|
+
@blur="verifyNumberHasDDI"
|
|
15
|
+
>
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script setup lang="ts">
|
|
20
|
+
import { ref, reactive, onMounted, watch } from "vue";
|
|
21
|
+
import { getDDI, IField, IDDI, getDefaultDDI, sleep } from "./types";
|
|
22
|
+
import { useNuxtApp } from "#app";
|
|
23
|
+
import MoleculeDropdownDDI from "./Molecule.DropdownDDI.vue";
|
|
24
|
+
|
|
25
|
+
// props
|
|
26
|
+
interface IProps {
|
|
27
|
+
modelValue?: any;
|
|
28
|
+
field?: IField;
|
|
29
|
+
}
|
|
30
|
+
const props = defineProps<IProps>();
|
|
31
|
+
|
|
32
|
+
// mounted
|
|
33
|
+
onMounted(setByNumber);
|
|
34
|
+
|
|
35
|
+
// data
|
|
36
|
+
const { $listen } = useNuxtApp();
|
|
37
|
+
const MoleculeDropdownDDIRef = ref<InstanceType<
|
|
38
|
+
typeof MoleculeDropdownDDI
|
|
39
|
+
> | null>(null);
|
|
40
|
+
const loading = ref<boolean>(false);
|
|
41
|
+
const ddi = reactive<IDDI>(getDefaultDDI());
|
|
42
|
+
const phone = ref<string>(props.modelValue);
|
|
43
|
+
|
|
44
|
+
// methods
|
|
45
|
+
async function setByNumber() {
|
|
46
|
+
if (!/^[+][0-9]{9,}$/.test(phone.value)) return;
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
loading.value = true;
|
|
50
|
+
const { DDI, national_number } = await getDDI(phone.value);
|
|
51
|
+
|
|
52
|
+
MoleculeDropdownDDIRef.value?.set(DDI);
|
|
53
|
+
phone.value = national_number;
|
|
54
|
+
updatePhone();
|
|
55
|
+
} finally {
|
|
56
|
+
loading.value = false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function updatePhone() {
|
|
60
|
+
emit("update:modelValue", getPhoneWithDDI());
|
|
61
|
+
}
|
|
62
|
+
function getPhoneWithDDI() {
|
|
63
|
+
return `${ddi.ddi}${phone.value}`;
|
|
64
|
+
}
|
|
65
|
+
async function verifyNumberHasDDI() {
|
|
66
|
+
if(!phone.value) return
|
|
67
|
+
|
|
68
|
+
if (phone.value[0]?.includes("+")) {
|
|
69
|
+
await setByNumber();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
updatePhone();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
watch(() => ddi.ddi, updatePhone);
|
|
76
|
+
|
|
77
|
+
// emits
|
|
78
|
+
const emit = defineEmits(["update:modelValue"]);
|
|
79
|
+
$listen("form:append", async () => {
|
|
80
|
+
await sleep(0);
|
|
81
|
+
phone.value = props.modelValue;
|
|
82
|
+
verifyNumberHasDDI();
|
|
83
|
+
});
|
|
84
|
+
</script>
|
|
85
|
+
|
|
86
|
+
<style scoped>
|
|
87
|
+
.input-phone {
|
|
88
|
+
width: 100%;
|
|
89
|
+
height: 100%;
|
|
90
|
+
outline: none;
|
|
91
|
+
}
|
|
92
|
+
</style>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<select
|
|
3
|
+
:value="modelValue"
|
|
4
|
+
class="input w-1"
|
|
5
|
+
@input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)"
|
|
6
|
+
>
|
|
7
|
+
<option
|
|
8
|
+
v-for="({ label, value }, i) in field?.options"
|
|
9
|
+
:key="i"
|
|
10
|
+
:value="value"
|
|
11
|
+
v-text="label"
|
|
12
|
+
/>
|
|
13
|
+
</select>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script setup lang="ts">
|
|
17
|
+
import type { IField } from "./types";
|
|
18
|
+
|
|
19
|
+
interface IProps {
|
|
20
|
+
field: IField;
|
|
21
|
+
modelValue?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
defineProps<IProps>();
|
|
25
|
+
|
|
26
|
+
defineEmits(["update:modelValue"]);
|
|
27
|
+
</script>
|
|
28
|
+
<style scoped></style>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<label class="input max-h-none flex cursor-pointer justify-between">
|
|
3
|
+
<span
|
|
4
|
+
v-if="optionsIsEmpty"
|
|
5
|
+
class="pl-2"
|
|
6
|
+
>Selecione</span>
|
|
7
|
+
|
|
8
|
+
<div class="flex flex-wrap">
|
|
9
|
+
<span
|
|
10
|
+
v-for="(option, i) in options"
|
|
11
|
+
:key="i"
|
|
12
|
+
class="badge badge-soft-secondary mx-1 my-1 text-sm leading-3"
|
|
13
|
+
@click="del(option)"
|
|
14
|
+
>
|
|
15
|
+
<span v-text="option.label" />
|
|
16
|
+
<i class="bi-x-circle-fill text-red-500 ml-2" />
|
|
17
|
+
</span>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<i class="bi-chevron-down ml-4" />
|
|
21
|
+
</label>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<script setup lang="ts">
|
|
25
|
+
import { computed } from "vue";
|
|
26
|
+
import { IFieldSelectOption, IFieldSelectOptions } from "./types";
|
|
27
|
+
|
|
28
|
+
// props
|
|
29
|
+
interface IProps {
|
|
30
|
+
options: IFieldSelectOptions;
|
|
31
|
+
}
|
|
32
|
+
const props = defineProps<IProps>();
|
|
33
|
+
|
|
34
|
+
// emits
|
|
35
|
+
interface IEmits {
|
|
36
|
+
(e: "delete", option: IFieldSelectOption): void;
|
|
37
|
+
}
|
|
38
|
+
const emit = defineEmits<IEmits>();
|
|
39
|
+
|
|
40
|
+
// computed
|
|
41
|
+
const optionsIsEmpty = computed<boolean>(() => !props.options.length);
|
|
42
|
+
|
|
43
|
+
// methods
|
|
44
|
+
function del(option: IFieldSelectOption): void {
|
|
45
|
+
emit("delete", option);
|
|
46
|
+
}
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<style scoped></style>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<input
|
|
3
|
+
:value="modelValue"
|
|
4
|
+
type="text"
|
|
5
|
+
class="input w-1"
|
|
6
|
+
@input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)"
|
|
7
|
+
>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup lang="ts">
|
|
11
|
+
interface IProps {
|
|
12
|
+
modelValue?: string;
|
|
13
|
+
}
|
|
14
|
+
defineProps<IProps>();
|
|
15
|
+
|
|
16
|
+
defineEmits(["update:modelValue"]);
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<style scoped></style>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="input-group">
|
|
3
|
+
<textarea
|
|
4
|
+
:value="modelValue"
|
|
5
|
+
class="input rounded-tr-md rounded-br-md w-1"
|
|
6
|
+
@input="
|
|
7
|
+
$emit('update:modelValue', ($event.target as HTMLInputElement).value)
|
|
8
|
+
"
|
|
9
|
+
/>
|
|
10
|
+
|
|
11
|
+
<!-- maxlength -->
|
|
12
|
+
<span
|
|
13
|
+
v-if="field?.maxlength"
|
|
14
|
+
class="absolute top-0 right-0 text-[11px] text-secondary"
|
|
15
|
+
>
|
|
16
|
+
{{ counter({ maxlength: field.maxlength, value: modelValue }) }}
|
|
17
|
+
</span>
|
|
18
|
+
</div>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup lang="ts">
|
|
22
|
+
import { computed } from "vue";
|
|
23
|
+
import type { IField } from "./types";
|
|
24
|
+
|
|
25
|
+
interface IProps {
|
|
26
|
+
modelValue?: string;
|
|
27
|
+
field?: IField;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
defineProps<IProps>();
|
|
31
|
+
|
|
32
|
+
// computed
|
|
33
|
+
const counter = computed<Function>(() => ({ value, maxlength }: any) => {
|
|
34
|
+
return `${value?.length ?? 0}/${maxlength}`;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// emits
|
|
38
|
+
defineEmits(["update:modelValue"]);
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<style></style>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="absolute left-0 top-0 w-full h-screen z-20 overflow-hidden">
|
|
3
|
+
<header
|
|
4
|
+
class="absolute flex items-center w-full h-16 bg-slate-800 z-10 pointer-events-none"
|
|
5
|
+
>
|
|
6
|
+
<img
|
|
7
|
+
src="../public/192x192.png"
|
|
8
|
+
class="w-8 min-w-2rem m-4"
|
|
9
|
+
alt="ContactStudio Logo"
|
|
10
|
+
>
|
|
11
|
+
|
|
12
|
+
<div>
|
|
13
|
+
<div class="w-96 h-3 bg-slate-700 rounded-full animate-pulse" />
|
|
14
|
+
<div
|
|
15
|
+
class="w-36 h-2 bg-slate-700 mt-2 rounded-full animate-pulse"
|
|
16
|
+
/>
|
|
17
|
+
</div>
|
|
18
|
+
</header>
|
|
19
|
+
<nav
|
|
20
|
+
class="sidenav flex flex-col justify-between !transition-none absolute w-[60px] h-full pt-[72px] max-sm:hidden pointer-events-none"
|
|
21
|
+
>
|
|
22
|
+
<article>
|
|
23
|
+
<ul class="list sidenav-item-full">
|
|
24
|
+
<li class="item sidenav-item h-9">
|
|
25
|
+
<i
|
|
26
|
+
class="bi-exclamation-triangle-fill leading-none text-base text-warn -ml-1"
|
|
27
|
+
/>
|
|
28
|
+
</li>
|
|
29
|
+
</ul>
|
|
30
|
+
<div class="divider" />
|
|
31
|
+
<ul
|
|
32
|
+
v-for="i in 3"
|
|
33
|
+
:key="i"
|
|
34
|
+
class="list sidenav-item-full mb-2"
|
|
35
|
+
>
|
|
36
|
+
<li
|
|
37
|
+
class="item sidenav-item bg-slate-200 dark:bg-slate-800 h-9 animate-pulse"
|
|
38
|
+
/>
|
|
39
|
+
</ul>
|
|
40
|
+
<div class="divider" />
|
|
41
|
+
<ul class="list sidenav-item-full mb-2">
|
|
42
|
+
<li
|
|
43
|
+
class="item sidenav-item bg-slate-200 dark:bg-slate-800 h-9 animate-pulse"
|
|
44
|
+
/>
|
|
45
|
+
</ul>
|
|
46
|
+
</article>
|
|
47
|
+
<aside>
|
|
48
|
+
<ul class="list sidenav-item-full mb-2">
|
|
49
|
+
<li
|
|
50
|
+
class="item sidenav-item bg-slate-200 dark:bg-slate-800 h-9 animate-pulse"
|
|
51
|
+
/>
|
|
52
|
+
</ul>
|
|
53
|
+
<div class="divider" />
|
|
54
|
+
<ul class="list sidenav-item-full mb-2">
|
|
55
|
+
<li class="item sidenav-item">
|
|
56
|
+
<i
|
|
57
|
+
class="bi-box-arrow-in-left text-lg leading-none mr-5 text-error"
|
|
58
|
+
/>
|
|
59
|
+
</li>
|
|
60
|
+
</ul>
|
|
61
|
+
</aside>
|
|
62
|
+
</nav>
|
|
63
|
+
<article class="absolute w-full h-screen p-24 max-sm:px-10 pointer-events-none">
|
|
64
|
+
<aside class="bg-slate-200 dark:bg-slate-800 w-full h-24 rounded-lg animate-pulse" />
|
|
65
|
+
|
|
66
|
+
<div
|
|
67
|
+
class="mt-8 flex max-sm:flex-wrap w-full gap-8"
|
|
68
|
+
style="height: calc(100vh - 300px);"
|
|
69
|
+
>
|
|
70
|
+
<aside class="bg-slate-200 dark:bg-slate-800 w-full sm:w-1/4 h-1/4 rounded-lg animate-pulse" />
|
|
71
|
+
<aside class="bg-slate-200 dark:bg-slate-800 w-full sm:w-2/4 h-1/2 rounded-lg animate-pulse" />
|
|
72
|
+
<aside class="bg-slate-200 dark:bg-slate-800 w-full sm:w-1/4 h-1/3 rounded-lg animate-pulse" />
|
|
73
|
+
</div>
|
|
74
|
+
</article>
|
|
75
|
+
</section>
|
|
76
|
+
</template>
|
|
77
|
+
|
|
78
|
+
<script setup lang="ts"></script>
|
|
79
|
+
|
|
80
|
+
<style scoped></style>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div />
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup lang="ts">
|
|
6
|
+
import { onMounted, ref } from "vue";
|
|
7
|
+
import { useNuxtApp, useRuntimeConfig } from "#app";
|
|
8
|
+
|
|
9
|
+
// data
|
|
10
|
+
const { BASE_URL } = useRuntimeConfig().public;
|
|
11
|
+
const { $listen } = useNuxtApp();
|
|
12
|
+
|
|
13
|
+
// mounted
|
|
14
|
+
onMounted(async () => {
|
|
15
|
+
const filename = process.dev ? "dev-sw.js" : "sw.js";
|
|
16
|
+
sw.value = await navigator.serviceWorker.register(`${BASE_URL}/${filename}`);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// data
|
|
20
|
+
const sw = ref<ServiceWorkerRegistration | null>(null);
|
|
21
|
+
|
|
22
|
+
// methods
|
|
23
|
+
async function show({ title, message, image }: any): Promise<void> {
|
|
24
|
+
if (!("serviceWorker" in navigator) || !("PushManager" in window)) {
|
|
25
|
+
console.error("Browser does not support notifications");
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!sw.value) return;
|
|
30
|
+
|
|
31
|
+
const permission = await Notification.requestPermission();
|
|
32
|
+
|
|
33
|
+
if (permission !== "granted") return;
|
|
34
|
+
|
|
35
|
+
sw.value.showNotification(title, {
|
|
36
|
+
body: message,
|
|
37
|
+
icon: image ?? `${BASE_URL}/192x192.png`,
|
|
38
|
+
vibrate: [200, 100, 200],
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// emits
|
|
43
|
+
$listen("notification:show", ({ title, message, image }: any) => {
|
|
44
|
+
show({ title, message, image });
|
|
45
|
+
});
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<style scoped></style>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div />
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup lang="ts">
|
|
6
|
+
import sound from "../public/ringtone.mp3";
|
|
7
|
+
import { useNuxtApp } from "#app";
|
|
8
|
+
|
|
9
|
+
const { $listen } = useNuxtApp();
|
|
10
|
+
|
|
11
|
+
$listen("ringtone:play", (tone?: string) => {
|
|
12
|
+
const audio = new Audio(tone ?? sound);
|
|
13
|
+
audio.oncanplaythrough = () => {
|
|
14
|
+
audio.play().catch(() => {
|
|
15
|
+
window.addEventListener("click", () => {
|
|
16
|
+
audio.play();
|
|
17
|
+
}, { once: true });
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<style scoped></style>
|