feat: Create contact

This commit is contained in:
iamsivin
2024-11-08 02:22:22 +05:30
parent ecd8b265bc
commit 8320ecd92b
8 changed files with 174 additions and 41 deletions
@@ -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>
@@ -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 = '',
@@ -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": {