feat: Create contact
This commit is contained in:
@@ -1,12 +1,9 @@
|
||||
<script setup>
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import ContactSortMenu from './ContactSortMenu.vue';
|
||||
import ContactMoreActions from './ContactMoreActions.vue';
|
||||
|
||||
defineProps({
|
||||
buttonLabel: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
isDetailView: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
@@ -21,7 +18,7 @@ defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['filter', 'more', 'message', 'update:sort']);
|
||||
const emit = defineEmits(['filter', 'more', 'update:sort']);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -30,6 +27,7 @@ const emit = defineEmits(['filter', 'more', 'message', 'update:sort']);
|
||||
v-if="!isDetailView"
|
||||
icon="i-lucide-list-filter"
|
||||
color="slate"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
@click="emit('filter')"
|
||||
/>
|
||||
@@ -39,13 +37,6 @@ const emit = defineEmits(['filter', 'more', 'message', 'update:sort']);
|
||||
:active-ordering="activeOrdering"
|
||||
@update:sort="emit('update:sort', $event)"
|
||||
/>
|
||||
<Button
|
||||
v-if="!isDetailView"
|
||||
icon="i-lucide-ellipsis-vertical"
|
||||
color="slate"
|
||||
variant="ghost"
|
||||
@click="emit('more')"
|
||||
/>
|
||||
<Button :label="buttonLabel" size="sm" @click="emit('message')" />
|
||||
<ContactMoreActions v-if="!isDetailView" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue';
|
||||
import CreateNewContactDialog from 'dashboard/components-next/Contacts/ContactsForm/CreateNewContactDialog.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const contactMenuItems = [
|
||||
{
|
||||
label: t('CONTACTS_LAYOUT.HEADER.ACTIONS.CONTACT_CREATION.ADD_CONTACT'),
|
||||
action: 'add',
|
||||
value: 'add',
|
||||
icon: 'i-lucide-plus',
|
||||
},
|
||||
{
|
||||
label: t('CONTACTS_LAYOUT.HEADER.ACTIONS.CONTACT_CREATION.EXPORT_CONTACT'),
|
||||
action: 'export',
|
||||
value: 'export',
|
||||
icon: 'i-lucide-upload',
|
||||
},
|
||||
{
|
||||
label: t('CONTACTS_LAYOUT.HEADER.ACTIONS.CONTACT_CREATION.IMPORT_CONTACT'),
|
||||
action: 'import',
|
||||
value: 'import',
|
||||
icon: 'i-lucide-download',
|
||||
},
|
||||
];
|
||||
|
||||
const showActionsDropdown = ref(false);
|
||||
const createNewContactDialogRef = ref(null);
|
||||
|
||||
const handleContactAction = ({ action }) => {
|
||||
if (action === 'add') {
|
||||
createNewContactDialogRef.value?.dialogRef.open();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-on-clickaway="() => (showActionsDropdown = false)" class="relative">
|
||||
<Button
|
||||
icon="i-lucide-ellipsis-vertical"
|
||||
color="slate"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
:class="showActionsDropdown ? 'bg-n-alpha-2' : ''"
|
||||
@click="showActionsDropdown = !showActionsDropdown"
|
||||
/>
|
||||
<DropdownMenu
|
||||
v-if="showActionsDropdown"
|
||||
:menu-items="contactMenuItems"
|
||||
class="right-0 mt-1 w-52 top-full"
|
||||
@action="handleContactAction($event)"
|
||||
/>
|
||||
</div>
|
||||
<CreateNewContactDialog ref="createNewContactDialogRef" />
|
||||
</template>
|
||||
@@ -92,6 +92,7 @@ const handleOrderChange = value => {
|
||||
<Button
|
||||
icon="i-lucide-arrow-down-up"
|
||||
color="slate"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
:class="isMenuOpen ? 'bg-n-alpha-2' : ''"
|
||||
@click="isMenuOpen = !isMenuOpen"
|
||||
|
||||
@@ -12,12 +12,16 @@ import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||
const props = defineProps({
|
||||
contactData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: null,
|
||||
},
|
||||
isDetailsView: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isNewContact: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update']);
|
||||
@@ -76,6 +80,7 @@ const validationRules = {
|
||||
const v$ = useVuelidate(validationRules, state);
|
||||
|
||||
const updateState = () => {
|
||||
if (props.isNewContact) return;
|
||||
const {
|
||||
id,
|
||||
name = '',
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { useStore, useMapGetter } from 'dashboard/composables/store';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import Dialog from 'dashboard/components-next/dialog/Dialog.vue';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import ContactsForm from 'dashboard/components-next/Contacts/ContactsForm/ContactsForm.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const store = useStore();
|
||||
|
||||
const dialogRef = ref(null);
|
||||
const contact = ref(null);
|
||||
|
||||
const uiFlags = useMapGetter('contacts/getUIFlags');
|
||||
const isCreatingContact = computed(() => uiFlags.value.isCreating);
|
||||
|
||||
const createNewContact = contactItem => {
|
||||
contact.value = contactItem;
|
||||
};
|
||||
|
||||
const handleDialogConfirm = async () => {
|
||||
if (!contact.value) return;
|
||||
await store.dispatch('contacts/create', contact.value);
|
||||
dialogRef.value.close();
|
||||
};
|
||||
|
||||
const closeDialog = () => {
|
||||
dialogRef.value.close();
|
||||
};
|
||||
|
||||
defineExpose({ dialogRef });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog ref="dialogRef" max-width="max-w-3xl" @confirm="handleDialogConfirm">
|
||||
<template #form>
|
||||
<ContactsForm is-new-contact @update="createNewContact" />
|
||||
</template>
|
||||
<template #actions>
|
||||
<div class="flex items-center justify-between w-full gap-3">
|
||||
<Button
|
||||
:label="t('DIALOG.BUTTONS.CANCEL')"
|
||||
variant="link"
|
||||
class="h-10 hover:no-underline hover:text-n-brand"
|
||||
@click="closeDialog"
|
||||
/>
|
||||
<Button
|
||||
:label="
|
||||
t('CONTACTS_LAYOUT.HEADER.ACTIONS.CONTACT_CREATION.SAVE_CONTACT')
|
||||
"
|
||||
color="blue"
|
||||
:is-loading="isCreatingContact"
|
||||
@click="handleDialogConfirm"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -2,6 +2,7 @@
|
||||
import { computed, useSlots } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import Input from 'dashboard/components-next/input/Input.vue';
|
||||
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||
import PaginationFooter from 'dashboard/components-next/pagination/PaginationFooter.vue';
|
||||
@@ -121,12 +122,12 @@ const updateCurrentPage = page => {
|
||||
:items="breadcrumbItems"
|
||||
@click="handleBreadcrumbClick"
|
||||
/>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex items-center gap-4">
|
||||
<div v-if="!isDetailView" class="flex items-center gap-2">
|
||||
<Input
|
||||
:placeholder="$t('CONTACTS_LAYOUT.HEADER.SEARCH_PLACEHOLDER')"
|
||||
:custom-input-class="[
|
||||
'h-10 [&:not(.focus)]:!border-transparent bg-n-solid-1 ltr:!pl-8 rtl:!pr-8',
|
||||
'h-8 [&:not(.focus)]:!border-transparent bg-n-solid-1 ltr:!pl-8 !py-1 rtl:!pr-8',
|
||||
]"
|
||||
@input="emit('search', $event.target.value)"
|
||||
>
|
||||
@@ -139,15 +140,15 @@ const updateCurrentPage = page => {
|
||||
</Input>
|
||||
</div>
|
||||
<ContactActions
|
||||
:button-label="buttonLabel"
|
||||
:is-detail-view="isDetailView"
|
||||
:active-sort="activeSort"
|
||||
:active-ordering="activeOrdering"
|
||||
@filter="emit('filter')"
|
||||
@update:sort="emit('sort', $event)"
|
||||
@more="emit('more')"
|
||||
@message="emit('message')"
|
||||
/>
|
||||
<div class="w-px h-4 bg-n-strong" />
|
||||
<Button :label="buttonLabel" size="sm" @click="emit('message')" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,7 @@ defineProps({
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: '',
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
@@ -48,6 +48,10 @@ defineProps({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
maxWidth: {
|
||||
type: String,
|
||||
default: 'max-w-lg',
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['confirm', 'close']);
|
||||
@@ -77,8 +81,11 @@ defineExpose({ open, close });
|
||||
<Teleport to="body">
|
||||
<dialog
|
||||
ref="dialogRef"
|
||||
class="w-full max-w-lg transition-all duration-300 ease-in-out shadow-xl rounded-xl"
|
||||
:class="overflowYAuto ? 'overflow-y-auto' : 'overflow-visible'"
|
||||
class="w-full transition-all duration-300 ease-in-out shadow-xl rounded-xl"
|
||||
:class="[
|
||||
maxWidth,
|
||||
overflowYAuto ? 'overflow-y-auto' : 'overflow-visible',
|
||||
]"
|
||||
:dir="isRTL ? 'rtl' : 'ltr'"
|
||||
@close="close"
|
||||
>
|
||||
@@ -88,7 +95,7 @@ defineExpose({ open, close });
|
||||
class="flex flex-col w-full h-auto gap-6 p-6 overflow-visible text-left align-middle transition-all duration-300 ease-in-out transform bg-n-alpha-3 backdrop-blur-[100px] shadow-xl rounded-xl"
|
||||
@click.stop
|
||||
>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div v-if="title || description" class="flex flex-col gap-2">
|
||||
<h3 class="text-base font-medium leading-6 text-n-slate-12">
|
||||
{{ title }}
|
||||
</h3>
|
||||
@@ -101,25 +108,27 @@ defineExpose({ open, close });
|
||||
<slot name="form">
|
||||
<!-- Form content will be injected here -->
|
||||
</slot>
|
||||
<div class="flex items-center justify-between w-full gap-3">
|
||||
<Button
|
||||
v-if="showCancelButton"
|
||||
variant="faded"
|
||||
color="slate"
|
||||
:label="cancelButtonLabel || t('DIALOG.BUTTONS.CANCEL')"
|
||||
class="w-full"
|
||||
@click="close"
|
||||
/>
|
||||
<Button
|
||||
v-if="showConfirmButton"
|
||||
:color="type === 'edit' ? 'blue' : 'ruby'"
|
||||
:label="confirmButtonLabel || t('DIALOG.BUTTONS.CONFIRM')"
|
||||
class="w-full"
|
||||
:is-loading="isLoading"
|
||||
:disabled="disableConfirmButton || isLoading"
|
||||
@click="confirm"
|
||||
/>
|
||||
</div>
|
||||
<slot name="actions">
|
||||
<div class="flex items-center justify-between w-full gap-3">
|
||||
<Button
|
||||
v-if="showCancelButton"
|
||||
variant="faded"
|
||||
color="slate"
|
||||
:label="cancelButtonLabel || t('DIALOG.BUTTONS.CANCEL')"
|
||||
class="w-full"
|
||||
@click="close"
|
||||
/>
|
||||
<Button
|
||||
v-if="showConfirmButton"
|
||||
:color="type === 'edit' ? 'blue' : 'ruby'"
|
||||
:label="confirmButtonLabel || t('DIALOG.BUTTONS.CONFIRM')"
|
||||
class="w-full"
|
||||
:is-loading="isLoading"
|
||||
:disabled="disableConfirmButton || isLoading"
|
||||
@click="confirm"
|
||||
/>
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
</OnClickOutside>
|
||||
</dialog>
|
||||
|
||||
@@ -396,6 +396,12 @@
|
||||
"CONTACTS": "Contacts"
|
||||
},
|
||||
"ACTIONS": {
|
||||
"CONTACT_CREATION": {
|
||||
"ADD_CONTACT": "Add contact",
|
||||
"EXPORT_CONTACT": "Export contacts",
|
||||
"IMPORT_CONTACT": "Import contacts",
|
||||
"SAVE_CONTACT": "Save contact"
|
||||
},
|
||||
"SORT_BY": {
|
||||
"LABEL": "Sort by",
|
||||
"OPTIONS": {
|
||||
|
||||
Reference in New Issue
Block a user