feat(companies): add company creation flow (#14402)

This commit is contained in:
Sojan Jose
2026-05-08 11:46:33 +05:30
committed by GitHub
parent 53b2a517d7
commit 10597863d7
8 changed files with 222 additions and 4 deletions
@@ -2,6 +2,7 @@
import Input from 'dashboard/components-next/input/Input.vue';
import Icon from 'dashboard/components-next/icon/Icon.vue';
import CompanySortMenu from './components/CompanySortMenu.vue';
import CompanyMoreActions from './components/CompanyMoreActions.vue';
defineProps({
showSearch: { type: Boolean, default: true },
@@ -11,7 +12,7 @@ defineProps({
activeOrdering: { type: String, default: '' },
});
const emit = defineEmits(['search', 'update:sort']);
const emit = defineEmits(['search', 'update:sort', 'create']);
</script>
<template>
@@ -48,6 +49,7 @@ const emit = defineEmits(['search', 'update:sort']);
:active-ordering="activeOrdering"
@update:sort="emit('update:sort', $event)"
/>
<CompanyMoreActions @create="emit('create')" />
</div>
</div>
</div>
@@ -0,0 +1,45 @@
<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';
const emit = defineEmits(['create']);
const { t } = useI18n();
const showActionsDropdown = ref(false);
const menuItems = [
{
label: t('COMPANIES.ACTIONS.CREATE'),
action: 'create',
value: 'create',
icon: 'i-lucide-plus',
},
];
const handleAction = ({ action }) => {
if (action === 'create') emit('create');
showActionsDropdown.value = false;
};
</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="menuItems"
class="ltr:right-0 rtl:left-0 mt-1 w-52 top-full"
@action="handleAction($event)"
/>
</div>
</template>
@@ -12,7 +12,12 @@ defineProps({
showPaginationFooter: { type: Boolean, default: true },
});
const emit = defineEmits(['update:currentPage', 'update:sort', 'search']);
const emit = defineEmits([
'update:currentPage',
'update:sort',
'search',
'create',
]);
const updateCurrentPage = page => {
emit('update:currentPage', page);
@@ -31,6 +36,7 @@ const updateCurrentPage = page => {
:active-ordering="activeOrdering"
@search="emit('search', $event)"
@update:sort="emit('update:sort', $event)"
@create="emit('create')"
/>
<main class="flex-1 px-6 overflow-y-auto">
<div class="w-full mx-auto max-w-5xl py-4">
@@ -0,0 +1,110 @@
<script setup>
import { computed, reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import Button from 'dashboard/components-next/button/Button.vue';
import Dialog from 'dashboard/components-next/dialog/Dialog.vue';
import Input from 'dashboard/components-next/input/Input.vue';
import TextArea from 'dashboard/components-next/textarea/TextArea.vue';
defineProps({
isLoading: { type: Boolean, default: false },
});
const emit = defineEmits(['create']);
const { t } = useI18n();
const dialogRef = ref(null);
const form = reactive({ name: '', domain: '', description: '' });
const isFormInvalid = computed(() => !form.name.trim());
const resetForm = () => {
form.name = '';
form.domain = '';
form.description = '';
};
const handleConfirm = () => {
if (isFormInvalid.value) return;
emit('create', {
name: form.name.trim(),
domain: form.domain.trim() || null,
description: form.description.trim() || null,
});
};
const closeDialog = () => {
dialogRef.value?.close();
};
const onSuccess = () => {
resetForm();
closeDialog();
};
defineExpose({ dialogRef, onSuccess });
</script>
<template>
<Dialog
ref="dialogRef"
width="3xl"
overflow-y-auto
@confirm="handleConfirm"
@close="resetForm"
>
<div class="flex flex-col gap-6">
<div class="flex flex-col items-start gap-2">
<span class="py-1 text-sm font-medium text-n-slate-12">
{{ t('COMPANIES.CREATE.TITLE') }}
</span>
<div class="grid w-full grid-cols-1 gap-4 sm:grid-cols-2">
<Input
v-model="form.name"
:placeholder="t('COMPANIES.DETAIL.PROFILE.FIELDS.NAME')"
:disabled="isLoading"
custom-input-class="h-8 !pt-1 !pb-1 [&:not(.error,.focus)]:!outline-transparent"
autofocus
/>
<Input
v-model="form.domain"
:placeholder="t('COMPANIES.DETAIL.PROFILE.FIELDS.DOMAIN')"
:disabled="isLoading"
custom-input-class="h-8 !pt-1 !pb-1 [&:not(.error,.focus)]:!outline-transparent"
/>
</div>
</div>
<TextArea
v-model="form.description"
:placeholder="t('COMPANIES.DETAIL.PROFILE.DESCRIPTION_PLACEHOLDER')"
:disabled="isLoading"
:max-length="280"
class="w-full"
show-character-count
auto-height
/>
</div>
<template #footer>
<div class="flex items-center justify-between w-full gap-3">
<Button
:label="t('DIALOG.BUTTONS.CANCEL')"
variant="link"
type="reset"
class="h-10 hover:!no-underline hover:text-n-brand"
@click="closeDialog"
/>
<Button
:label="t('COMPANIES.CREATE.ACTIONS.SAVE')"
color="blue"
type="submit"
:disabled="isFormInvalid || isLoading"
:is-loading="isLoading"
/>
</div>
</template>
</Dialog>
</template>
@@ -169,7 +169,7 @@ const handleContactSelect = contactId => {
</script>
<template>
<div class="flex flex-col gap-6 px-6 pb-6 pt-1">
<div class="flex flex-col gap-6 px-6 pb-8 pt-1">
<div v-if="!selectedContact" class="flex flex-col gap-4">
<div class="flex flex-col gap-2">
<label class="text-base text-n-slate-12">
@@ -346,7 +346,7 @@ const handleContactSelect = contactId => {
:current-page="currentPage"
:total-items="totalContacts"
:items-per-page="15"
class="px-0 before:hidden"
class="!px-0 before:hidden"
@update:current-page="emit('update:currentPage', $event)"
/>
</div>