deal/src/components/deal/DealModal.vue
2026-07-05 13:21:43 +03:00

1862 lines
62 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="deal-modal addDeal">
<p style="color: #757575">
В данном окне вы можете объединить стороны сделки, проставить условия, ответственных сотрудников и добавить документы.
</p>
<form class="form addDeal__form" @submit.prevent>
<div class="form__item flex flex-col gap-1 w-full">
<label for="title">Название сделки:</label>
<InputText
type="text"
id="title"
placeholder="Введите название сделки"
:class="{ 'p-invalid': errors.title }"
v-model="deal.title"
class="w-full"
/>
<small v-if="errors.title" class="p-error">{{ errorMessages.title }}</small>
</div>
<div class="parties_deal flex w-full gap-6">
<div class="side_deal w-1/2">
<div class="form__item flex flex-col gap-1 w-full mb-3">
<label>Укажите сторону 1:</label>
<Select
v-model="deal.side_one"
:options="selectedRequisitionSideOne ? sideOneOptions : fullSidesList"
:class="{ 'p-invalid': errors.side_one }"
:disabled="isEditing"
>
<template #dropdownicon>
<DropdownIcon />
</template>
</Select>
<small v-if="errors.side_one" class="p-error">{{ errorMessages.side_one }}</small>
</div>
<div class="form__item flex flex-col gap-1 w-full mb-3">
<label>Заявка стороны 1:</label>
<InputText
v-if="selectedRequisitionSideOne"
:value="selectedRequisitionSideOne.name"
disabled
class="w-full bg-gray-100"
/>
</div>
<div class="form__item flex flex-col gap-1 w-full mb-3">
<RequisitionDetails v-if="selectedRequisitionSideOne" :deal="deal" :requisition="selectedRequisitionSideOne" :clientsList="clientsList" side="sideOne" @closeDealRequisition="handleCloseDealRequisition" />
</div>
<div class="form__item flex flex-col gap-1 w-full" v-if="selectedRequisitionSideOne && (selectedRequisitionSideOne.type?.heir || selectedRequisitionSideOne.type_id) === 2">
<!-- Селект объекта скрыт, объект подтягивается из заявки
<label>Выберите объект:</label>
<Select
v-model="selectedObjectSideOneId"
:options="objectsListSideOne"
optionLabel="name"
optionValue="id"
placeholder="Выберите объект"
:disabled="isEditing"
>
<template #dropdownicon>
<DropdownIcon />
</template>
</Select>
-->
</div>
<div class="form__item flex flex-col gap-1 w-full" v-if="selectedRequisitionSideOne && (selectedRequisitionSideOne.type?.heir || selectedRequisitionSideOne.type_id) === 2">
<ObjectDetails v-if="selectedObjectSideOne" :deal="deal" :object="selectedObjectSideOne" side="sideOne" @closeDealObject="handleCloseDealObject" />
</div>
</div>
<div class="side_deal w-1/2">
<div class="form__item flex flex-col gap-1 w-full mb-3">
<label for="side2">Укажите сторону 2:</label>
<Select
v-model="deal.side_two"
:options="sideTwoOptions"
:class="{ 'p-invalid': errors.side_two }"
>
<template #dropdownicon>
<DropdownIcon />
</template>
</Select>
<small v-if="errors.side_two" class="p-error">{{ errorMessages.side_two }}</small>
</div>
<div class="form__item flex flex-col gap-1 w-full mb-3">
<label>Заявка стороны 2:</label>
<Select
v-model="selectedRequisitionSideTwoId"
:options="requisitionsList"
optionLabel="name"
optionValue="id"
placeholder="Выберите заявку"
:filter="true"
:class="{ 'p-invalid': errors.req_side_two }"
@filter="filterRequisitionsList"
:virtualScrollerOptions="{ itemSize: 40 }"
>
<template #dropdownicon>
<DropdownIcon />
</template>
</Select>
<small v-if="errors.req_side_two" class="p-error">{{ errorMessages.req_side_two }}</small>
</div>
<div class="form__item flex flex-col gap-1 w-full mb-3">
<RequisitionDetails v-if="selectedRequisitionSideTwo" :deal="deal" :requisition="selectedRequisitionSideTwo" :clientsList="clientsList" side="sideTwo" @closeDealRequisition="handleCloseDealRequisition" />
</div>
<!-- Селект объекта скрыт, объект подтягивается из заявки
<div class="form__item flex flex-col gap-1 w-full" v-if="selectedRequisitionSideTwo && (selectedRequisitionSideTwo.type?.heir || selectedRequisitionSideTwo.type_id) === 2">
<label for="nameHome">Выберите объект:</label>
<Select
v-model="selectedObjectSideTwoId"
:options="objectsListSideTwo"
optionLabel="name"
optionValue="id"
placeholder="Выберите объект"
>
<template #dropdownicon>
<DropdownIcon />
</template>
</Select>
</div>
-->
<div class="form__item" v-if="selectedRequisitionSideTwo && (selectedRequisitionSideTwo.type?.heir || selectedRequisitionSideTwo.type_id) === 2">
<ObjectDetails v-if="selectedObjectSideTwo" :deal="deal" :object="selectedObjectSideTwo" side="sideTwo" @closeDealObject="handleCloseDealObject" />
</div>
</div>
</div>
<h3>Ответственные сотрудники за сделку</h3>
<div class="flex w-full flex-wrap">
<EmployeeCard v-for="employee in selectedEmployees" :key="employee.id" :employee="employee" :url="url" :locked="isWhoWork(employee.id)" @remove="removeEmployee" />
<div class="sm:w-1/2 md:w-1/3 lg:w-1/4 pr-4 mb-4">
<button @click="openModalDealManager" class="w-full h-full p-4 rounded flex flex-col items-center justify-center add-employee-button">
<img src="@/shared/assets/icons/plus-green.svg" alt="Добавить сотрудника">
<p>Добавить сотрудника</p>
</button>
</div>
</div>
<div class="flex w-full gap-6">
<div class="deal_value w-1/2">
<h3 class="mb-3">Стоимость сделки и комиссионные</h3>
<div class="flex">
<div class="w-1/2 mr-4">
<div class="form__item flex flex-col gap-1 w-full mb-3">
<label for="deal_contract_price">Стоимость в договоре:</label>
<InputNumber
v-model="deal.contract_price"
id="deal_contract_price"
placeholder="Введите стоимость"
:mode="'decimal'"
:useGrouping="true"
:locale="'ru-RU'"
/>
</div>
<div class="form__item flex flex-col gap-1 w-full mb-3">
<label for="deal_commission_buyer">Комиссионное вознаграждение с покупателя/арендатора:</label>
<InputNumber
v-model="deal.commission_buyer"
id="deal_commission_buyer"
placeholder="Введите комиссионное вознаграждение"
:mode="'decimal'"
:useGrouping="true"
:locale="'ru-RU'"
/>
</div>
<div class="form__item flex flex-col gap-1 w-full">
<label for="deal_agent_commission">Комиссия агента:</label>
<InputNumber
v-model="deal.agent_commission"
id="deal_agent_commission"
placeholder="Введите комиссию агента"
:mode="'decimal'"
:useGrouping="true"
:locale="'ru-RU'"
/>
</div>
</div>
<div class="w-1/2">
<div class="form__item flex flex-col gap-1 w-full mb-3">
<label for="deal_object_price">Цена объекта:</label>
<InputNumber
v-model="deal.object_price"
id="deal_object_price"
placeholder="Введите цену объекта"
:mode="'decimal'"
:useGrouping="true"
:locale="'ru-RU'"
/>
</div>
<div class="form__item flex flex-col gap-1 w-full mb-3">
<label for="deal_commission_seller">Комиссионное вознаграждение с продавца/арендодателя:</label>
<InputNumber
v-model="deal.commission_seller"
id="deal_commission_seller"
placeholder="Введите комиссионное вознаграждение"
:mode="'decimal'"
:useGrouping="true"
:locale="'ru-RU'"
/>
</div>
<div class="form__item flex flex-col gap-1 w-full">
<label for="deal_total_commission">Комиссионное вознаграждение Итого:</label>
<InputNumber
v-model="deal.total_commission"
id="deal_total_commission"
placeholder="Введите комиссионное вознаграждение"
:mode="'decimal'"
:useGrouping="true"
:locale="'ru-RU'"
/>
</div>
</div>
</div>
</div>
<div class="deal_additional_details w-1/2">
<h3 class="mb-3">Дополнительные данные по сделке</h3>
<div class="flex w-full">
<div class="w-1/2 mr-4">
<div class="form__item flex flex-col gap-1 w-full mb-3">
<label for="deal_sellers_count">Количество продавцов:</label>
<InputNumber
v-model="deal.sellers_count"
id="deal_sellers_count"
/>
</div>
<div class="form__item flex flex-col gap-1 w-full mb-3">
<label for="deal_proxy_sale">Продажа по доверенности:</label>
<div class="deal_proxy_sale">
<label class="mr-4">
<input type="radio" v-model="deal.proxy_sale" :value="true" name="deal_proxy_sale" />
Да
</label>
<label>
<input type="radio" v-model="deal.proxy_sale" :value="false" name="deal_proxy_sale" />
Нет
</label>
</div>
</div>
<div class="form__item flex flex-col gap-1 w-full mb-3">
<label for="deal_e_registration">Электронная регистрация:</label>
<div class="deal_e_registration">
<label class="mr-4">
<input type="radio" v-model="deal.e_registration" :value="true" name="deal_e_registration" />
Да
</label>
<label>
<input type="radio" v-model="deal.e_registration" :value="false" name="deal_e_registration" />
Нет
</label>
</div>
</div>
<div class="form__item flex flex-col gap-1 w-full">
<label for="deal_object_mortgaged">Объект в залоге:</label>
<div class="deal_object_mortgaged">
<label class="mr-4">
<input type="radio" v-model="deal.mortgaged" :value="true" name="deal_object_mortgaged" />
Да
</label>
<label>
<input type="radio" v-model="deal.mortgaged" :value="false" name="deal_object_mortgaged" />
Нет
</label>
</div>
</div>
<div v-if="deal.mortgaged">
<div class="form__item flex flex-col gap-1 w-full">
<label for="deal_mortgagee">Залогодержатель:</label>
<InputText
v-model="deal.mortgagee"
id="deal_mortgagee"
/>
</div>
<div class="form__item flex flex-col gap-1 w-full">
<label for="deal_debt_amount">Сумма долга:</label>
<InputNumber
v-model="deal.debt_amount"
id="deal_debt_amount"
placeholder="Введите сумму долга"
:mode="'decimal'"
:useGrouping="true"
:locale="'ru-RU'"
/>
</div>
</div>
</div>
<div class="w-1/2">
<div class="form__item flex flex-col gap-1 w-full mb-3">
<label for="deal_buyers_count">Количество покупателей:</label>
<InputNumber
v-model="deal.buyers_count"
id="deal_buyers_count"
/>
</div>
<div class="form__item flex flex-col gap-1 w-full mb-3">
<label for="has_counter_agent">Есть встречный агент:</label>
<div class="has_counter_agent">
<label class="mr-4">
<input type="radio" v-model="deal.has_counter_agent" :value="true" name="has_counter_agent" />
Да
</label>
<label>
<input type="radio" v-model="deal.has_counter_agent" :value="false" name="has_counter_agent" />
Нет
</label>
</div>
</div>
<div v-if="deal.has_counter_agent">
<div class="form__item flex flex-col gap-1 w-full">
<label for="deal_counter_agent_name">ФИО встречного агента:</label>
<InputText
v-model="deal.counter_agent_name"
id="deal_counter_agent_name"
/>
</div>
<div class="form__item flex flex-col gap-1 w-full">
<label for="deal_counter_agent_commission">Комиссия встречному агенту:</label>
<InputNumber
v-model="deal.counter_agent_commission"
id="deal_counter_agent_commission"
:mode="'decimal'"
:useGrouping="true"
:locale="'ru-RU'"
/>
</div>
<div class="form__item flex flex-col gap-1 w-full">
<label for="deal_counter_agent_phone">Телефон встречного агента:</label>
<InputText
v-model="deal.counter_agent_phone"
id="deal_counter_agent_phone"
/>
</div>
</div>
<div class="form__item flex flex-col gap-1 w-full">
<label for="deal_date">Планируемая дата сделки:</label>
<DatePicker
v-model="deal.deal_date"
id="deal_date"
placeholder="Выберите дату сделки"
:showTime="true"
hourFormat="24"
dateFormat="dd.mm.yy"
:showIcon="true"
:showOtherMonths="true"
:selectOtherMonths="true"
class="w-full"
:class="{ 'p-invalid': errors.deal_date }"
/>
<small v-if="errors.deal_date" class="p-error">{{ errorMessages.deal_date }}</small>
</div>
</div>
</div>
</div>
</div>
<h3>Документы</h3>
<div class="flex w-full flex-wrap">
<!-- <DocAddModal v-if="showModalDealDoc" :deal="deal" @close="closeModalDealDoc" @select="addDoc" />-->
<DocsCard
v-for="doc in selectedDocs"
:key="doc.id"
:doc="doc"
:urlLaravel="urlLaravel"
@remove="removeDoc"
/>
<div class="w-full sm:w-1/2 md:w-1/3 lg:w-1/4">
<button @click="openModalDealDoc" class="w-full h-full p-6 rounded flex flex-col items-center justify-center add-deal-doc-button">
<img src="@/shared/assets/icons/plus-green.svg" alt="Добавить документ" class="w-6 h-6">
<p>Добавить документ</p>
</button>
</div>
</div>
<template v-if="isEditing">
<h3>Составить договор</h3>
<div class="flex w-full flex-wrap">
<div class="w-full sm:w-1/2 md:w-1/3 lg:w-1/4 p-2">
<button @click="openModalDealContract" class="w-full h-full p-5 rounded flex flex-col items-center justify-center add-contract-button">
<img src="@/shared/assets/icons/plus-green.svg" alt="Составить договор">
<p>Составить договор</p>
</button>
</div>
</div>
<div class="flex w-full flex-wrap">
<InnerDocCard
v-for="innerDoc in deal.inner_docs"
:key="innerDoc.id"
:innerDoc="innerDoc"
@remove="removeInnerDoc"
class="w-full sm:w-1/2 md:w-1/3 lg:w-1/4 p-2"
/>
</div>
</template>
<div class="form-footer mt-6" style="display: flex; justify-content: center; width: 100%;">
<MyButton
:theme="'green'"
type="button"
class="submit mt-7.5"
style="background: linear-gradient(to bottom, rgba(67, 160, 71, 1) 0%, rgba(56, 142, 60, 1) 100%); color: #fff; padding: 10px 130px; font-size: 18px; border: none; border-radius: 3px; cursor: pointer; font-weight: bold;"
@click="saveDeal"
>
Сохранить
</MyButton>
</div>
</form>
<ConfirmDialog></ConfirmDialog>
<Toast />
</div>
</template>
<script setup>
import MyButton from "@/shared/UI/MyButton.vue";
import {computed, inject, onMounted, ref, watch, nextTick } from "vue";
import {useLaravelApi} from '@/composables/useLaravelApi';
//import useDeal from '@/composables/useDeal';
import {useToast} from "primevue/usetoast";
import {useDialog} from 'primevue/usedialog';
import ConfirmDialog from 'primevue/confirmdialog';
import Toast from "primevue/toast";
import InputText from "primevue/inputtext";
import InputNumber from "primevue/inputnumber";
import DropdownIcon from "@/shared/assets/icons/dropdown-icon.vue";
import dayjs from 'dayjs';
import ru from 'dayjs/locale/ru';
import RequisitionDetails from '@/components/deal/addEdit/RequisitionDetails.vue';
import ObjectDetails from "@/components/deal/addEdit/ObjectDetails.vue";
import EmployeeAddModal from '@/components/deal/addEdit/EmployeeAddModal.vue';
import EmployeeCard from '@/components/deal/addEdit/EmployeeCard.vue';
import DocAddModal from '@/components/deal/addEdit/DocAddModal.vue';
import DocsCard from '@/components/deal/addEdit/DocsCard.vue';
import InnerDocAddModal from "@/components/deal/addEdit/InnerDocAddModal.vue";
import InnerDocCard from "@/components/deal/addEdit/InnerDocCard.vue";
const toast = useToast();
const showError = (message) => {
toast.add({
severity: 'error',
summary: 'Ошибка',
detail: message,
life: 3000
});
};
const props = defineProps({
dealId: Number,
isOpen: Boolean
});
const dialog = useDialog();
const dialogRef = inject('dialogRef');
const newDealReqId = dialogRef.value.data?.newDealReqId;
const dialogRefDealId = dialogRef.value.data?.deal_id;
let dealId = dialogRefDealId || null;
const isEditing = ref(!!dialogRefDealId);
const isSideOneLoaded = ref(false);
const { data: dealData, fetchData: fetchDealData } = useLaravelApi();
const { data: employeesData, fetchData: fetchEmployeesData } = useLaravelApi();
const { data: requisitionsListData, fetchData: fetchRequisitionsListData } = useLaravelApi();
const { data: requisitionSideOneDetails, fetchData: fetchRequisitionSideOneDetails } = useLaravelApi();
const { data: requisitionSideTwoDetails, fetchData: fetchRequisitionSideTwoDetails } = useLaravelApi();
/*Объекты*/
const { data: objectsListData, fetchData: fetchObjectsListData } = useLaravelApi();
const objectsListSideOne = ref([]);
const objectsListSideTwo = ref([]);
const { data: objectSideOneDetails, fetchData: fetchObjectSideOneDetails } = useLaravelApi();
const { data: objectSideTwoDetails, fetchData: fetchObjectSideTwoDetails } = useLaravelApi();
// Клиенты
const { data: clientsListData, fetchData: fetchClientsListData } = useLaravelApi();
const clientsList = ref([]);
// Сотрудники
const { data: employeesListData, fetchData: fetchEmployeesListData } = useLaravelApi();
const employeesList = ref([]);
//Документы
const { data: documentsData, fetchData: fetchDocumentsData } = useLaravelApi();
const docsList = ref([]); // Список документов
async function loadRequisition(reqId, side) {
if (!reqId) return;
console.log('loadRequisition:' + reqId)
try {
let response;
if (side === 'sideOne') {
response = await fetchRequisitionSideOneDetails(`/requisitions/${reqId}`);
} else {
response = await fetchRequisitionSideTwoDetails(`/requisitions/${reqId}`);
}
if (response.data) {
if (side === 'sideOne') {
selectedRequisitionSideOneId.value = reqId;
selectedRequisitionSideOne.value = response.data;
if (response.data.object_id) {
selectedObjectSideOneId.value = response.data.object_id;
}
//Установка клиента в deal
if (response.data.client && typeof response.data.client === 'object') {
deal.value.side_one_client_main = response.data.client; // кладём как массив
} else {
deal.value.side_one_client_main = null;
}
console.log('Загружены основной клиент стороны 1:', deal.value.side_one_client_main);
} else {
selectedRequisitionSideTwoId.value = reqId;
selectedRequisitionSideTwo.value = response.data;
if (response.data.object_id) {
selectedObjectSideTwoId.value = response.data.object_id;
}
// Установка клиента в deal
if (response.data.client && typeof response.data.client === 'object') {
deal.value.side_two_client_main = response.data.client;
} else {
deal.value.side_two_client_main = null;
}
console.log('Загружены клиенты стороны 2:', deal.value.side_two_clients);
}
return response.data;
}
} catch (error) {
console.error(`Ошибка загрузки заявки ${reqId}:`, error);
showError(`Не удалось загрузить заявку ${reqId}`);
}
return null;
}
watch(() => props.dealId, (newId) => {
if (newId) {
console.log('При редактировании сделки ID сделки:', newId);
dealId = newId;
isEditing.value = true;
if (newId) {
loadDealData(newId);
} else {
resetForm();
}
}
});
watch(() => newDealReqId, async (newVal) => {
console.log('При создании сделки из этапа ID заявки:', newVal);
if (!newVal) return;
try {
const reqData = await loadRequisition(newVal, 'sideOne');
if (reqData && reqData.name) {
deal.value.title = `Сделка по заявке: "${reqData.name}"`;
}
selectedRequisitionSideOneId.value = Number(newVal);
deal.value.side_one = getSideByRequisitionType(reqData.type_id, reqData.type_name);
isSideOneLoaded.value = true;
} catch (error) {
console.error('Ошибка загрузки заявки:', error);
showError('Не удалось загрузить заявку');
}
}, { immediate: true });
async function loadRequisitionsForDeal(dealData) {
await Promise.all([
dealData.side_one_requisition_id && loadRequisition(dealData.side_one_requisition_id, 'sideOne'),
dealData.side_two_requisition_id && loadRequisition(dealData.side_two_requisition_id, 'sideTwo')
]);
}
async function loadObjectsForDeal(dealData) {
await Promise.all([
dealData.side_one_object_id && fetchObjectSideOneDetails(`/objects/${dealData.side_one_object_id}`),
dealData.side_two_object_id && fetchObjectSideTwoDetails(`/objects/${dealData.side_two_object_id}`)
]);
}
async function loadDealData(dealId) {
console.log('Loading deal data for ID:', dealId);
// Валидация входных данных
if (!dealId || isNaN(dealId)) {
showError('Некорректный ID сделки');
return;
}
try {
// Параллельная загрузка основных данных
const [dealRes, employeesRes] = await Promise.all([
fetchDealData(`/deals/${dealId}`),
fetchEmployeesData(`/deals/${dealId}/employees`)
]);
// Проверка успешности ответа
if (!dealRes.data?.success) {
throw new Error(dealRes.data?.message || 'Не удалось загрузить данные сделки');
}
// Обновление состояния сделки
const dealData = dealRes.data.data;
Object.assign(deal.value, dealData);
// Форматирование даты
if (dealData.deal_date) {
dayjs.locale(ru);
deal.value.deal_date = dayjs(dealData.deal_date, 'YYYY-MM-DD HH:mm:ss').toDate();
}
const [requisitionRes, objectRes, documentsRes] = await Promise.all([
loadRequisitionsForDeal(dealData),
loadObjectsForDeal(dealData),
fetchDocumentsData('/documents', {
params: { target_id: dealId, target_type: 10 }
})
]);
console.log('Документы:',documentsRes.data);
// Загружаем документы в selectedDocs
if (documentsRes.data?.data?.length) {
selectedDocs.value = documentsRes.data.data.map(doc => ({
id: doc.id,
name: doc.name,
file: doc.file,
created_at: doc.created_at
}));
} else {
selectedDocs.value = [];
}
// Обработка сотрудников
if (employeesRes.data) {
manualEmployees.value = employeesRes.data.map(emp => ({
...emp,
fullName: `${emp.last_name} ${emp.first_name} ${emp.middle_name}` || emp.fio,
}));
}
} catch (error) {
console.error('Ошибка загрузки сделки:', error);
showError(String(error?.message ?? 'Неизвестная ошибка'));
}
}
// Функция сброса формы
function resetForm() {
deal.value = {
title: '',
side_one: '',
side_two: '',
side_one_requisition_id: null,
side_two_requisition_id: null,
side_one_object_id: null,
side_two_object_id: null,
contract_price: null,
object_price: null,
commission_buyer: null,
commission_seller: null,
agent_commission: null,
total_commission: null,
sellers_count: null,
buyers_count: null,
proxy_sale: false,
e_registration: false,
mortgaged: false,
mortgagee: '',
debt_amount: null,
has_counter_agent: false,
counter_agent_name: '',
counter_agent_commission: null,
counter_agent_phone: '',
deal_date: null,
};
requisitionEmployees.value = [];
manualEmployees.value = [];
selectedRequisitionSideOneId.value = null;
selectedRequisitionSideTwoId.value = null;
selectedObjectSideOneId.value = null;
selectedObjectSideTwoId.value = null;
}
const url = import.meta.env.VITE_API_URL;
const urlLaravel = import.meta.env.VITE_LARAVEL_URL
const deal = ref({
title: '',
side_one: '',
side_two: '',
side_one_requisition_id: null,
side_two_requisition_id: null,
side_one_object_id: null,
side_two_object_id: null,
//side_one_clients: null,
//side_two_clients: null,
contract_price: null,
object_price: null,
commission_buyer: null,
commission_seller: null,
agent_commission: null,
total_commission: null,
sellers_count: null,
buyers_count: null,
proxy_sale: false,
e_registration: false,
mortgaged: false,
mortgagee: '',
debt_amount: null,
has_counter_agent: false,
counter_agent_name: '',
counter_agent_commission: null,
counter_agent_phone: '',
deal_date: null,
});
const deal_sides_list = ['Покупатель', 'Продавец', 'Арендодатель', 'Арендатор', 'Застройщик'];
const originalRequisitionsList = ref([]);
const requisitionsList = ref([]);
const selectedRequisitionSideOneId = ref(null);
const selectedRequisitionSideOne = ref(null);
const selectedRequisitionSideTwoId = ref(null);
const selectedRequisitionSideTwo = ref(null);
const selectedObjectSideOneId = ref(null);
const selectedObjectSideOne = ref(null);
const selectedObjectSideTwoId = ref(null);
const selectedObjectSideTwo = ref(null);
const errors = ref({
title: false,
side_one: false,
side_two: false,
req_side_two: false,
deal_date: false
});
const errorMessages = ref({
title: '',
side_one: '',
side_two: '',
req_side_two: '',
deal_date: ''
});
watch(() => deal.value.title, () => {
if (errors.value.title) {
errors.value.title = false;
errorMessages.value.title = '';
}
});
watch(() => deal.value.side_one, () => {
if (errors.value.side_one) {
errors.value.side_one = false;
errorMessages.value.side_one = '';
}
});
watch(() => deal.value.side_two, () => {
if (errors.value.side_two) {
errors.value.side_two = false;
errorMessages.value.side_two = '';
}
});
watch(() => deal.value.deal_date, () => {
if (errors.value.deal_date) {
errors.value.deal_date = false;
errorMessages.value.deal_date = '';
}
});
watch(selectedRequisitionSideTwoId, () => {
if (errors.value.req_side_two) {
errors.value.req_side_two = false;
errorMessages.value.req_side_two = '';
}
});
const validateForm = () => {
let isValid = true;
// Валидация названия сделки
if (!deal.value.title) {
errors.value.title = true;
errorMessages.value.title = 'Название сделки обязательно';
isValid = false;
} else if (deal.value.title.length < 3) {
errors.value.title = true;
errorMessages.value.title = 'Минимум 3 символа';
isValid = false;
} else {
errors.value.title = false;
errorMessages.value.title = '';
}
// Валидация стороны 1
if (!deal.value.side_one) {
errors.value.side_one = true;
errorMessages.value.side_one = 'Выберите сторону 1';
isValid = false;
} else {
errors.value.side_one = false;
errorMessages.value.side_one = '';
}
// Валидация стороны 2
if (!deal.value.side_two) {
errors.value.side_two = true;
errorMessages.value.side_two = 'Выберите сторону 2';
isValid = false;
} else {
errors.value.side_two = false;
errorMessages.value.side_two = '';
}
// Валидация заявки стороны 2
if (!selectedRequisitionSideTwoId.value) {
errors.value.req_side_two = true;
errorMessages.value.req_side_two = 'Выберите заявку стороны 2';
isValid = false;
} else {
errors.value.req_side_two = false;
errorMessages.value.req_side_two = '';
}
// Валидация даты сделки
if (!deal.value.deal_date) {
errors.value.deal_date = true;
errorMessages.value.deal_date = 'Укажите дату сделки';
isValid = false;
} else {
errors.value.deal_date = false;
errorMessages.value.deal_date = '';
}
return isValid;
};
onMounted(async () => {
console.log('[DealModal] dialogRef.data:', dialogRef.value.data)
dealId = dialogRef.value.data?.deal_id
try {
// Загрузка справочников
const [objects, clients, employees, requisitions] = await Promise.all([
fetchObjectsListData(`/objects?fields=short`),
fetchClientsListData('/clients?fields=short&no_pagination=1'),
fetchEmployeesListData('/users?fields=short&no_pagination=1'),
fetchRequisitionsListData('/requisitions?no_pagination=1&exclude_linked_for_deal=1' + (dealId ? '&exclude_linked_for_deal_id=' + dealId : ''))
]);
objectsListSideOne.value = objects.data?.data || [];
objectsListSideTwo.value = objects.data?.data || [];
clientsList.value = clients.data?.data || [];
employeesList.value = employees.data?.data || [];
const reqList = requisitions.data?.data?.map(item => ({
id: item.id,
name: item.name || `Заявка ${item.id}`,
type_id: item.type_id
})) || [];
originalRequisitionsList.value = reqList;
requisitionsList.value = reqList.filter(req => req.id !== newDealReqId.value);
if (isEditing.value && dealId) {
await loadDealData(dealId);
// Фильтруем список заявок для стороны 2 по типу стороны 1
if (selectedRequisitionSideOne.value) {
const sideOneTypeId = selectedRequisitionSideOne.value.type?.heir || selectedRequisitionSideOne.value.type_id;
let filtered;
if (sideOneTypeId === 2) {
filtered = reqList.filter(req => req.type_id !== 2);
} else {
filtered = reqList.filter(req => req.type_id === 2);
}
requisitionsList.value = filtered.filter(req => req.id !== selectedRequisitionSideOneId.value);
}
} else if (isSideOneLoaded) {
const sideOneTypeId = selectedRequisitionSideOne.value.type?.heir || selectedRequisitionSideOne.value.type_id;
let filtered;
if (sideOneTypeId === 2) {
filtered = reqList.filter(req => req.type_id !== 2);
} else {
filtered = reqList.filter(req => req.type_id === 2);
}
requisitionsList.value = filtered.filter(req => req.id !== selectedRequisitionSideOneId.value);
} else {
requisitionsList.value = reqList.filter(req => req.id !== selectedRequisitionSideOneId.value);
}
} catch (error) {
console.error('Ошибка загрузки:', error);
showError('Не удалось загрузить данные');
}
})
// Получение деталей выбранной заявки 1 стороны
watch(selectedRequisitionSideOneId, async (newId, oldId) => {
if (newId != null) {
// Убираем who_work старой заявки стороны 1 из requisitionEmployees
if (oldId && selectedRequisitionSideOne.value) {
const oldWhoWork = selectedRequisitionSideOne.value.who_work;
if (oldWhoWork) {
const otherSideWhoWork = selectedRequisitionSideTwo.value?.who_work;
if (oldWhoWork !== otherSideWhoWork) {
requisitionEmployees.value = requisitionEmployees.value.filter(e => e.id !== oldWhoWork);
}
}
}
console.log('watch selectedRequisitionSideOneId:' + newId)
try {
await fetchRequisitionSideOneDetails(`/requisitions/${newId}`);
if (requisitionSideOneDetails.value) {
selectedRequisitionSideOne.value = requisitionSideOneDetails.value;
deal.value.side_one_requisition_id = newId;
// Добавляем who_work заявки стороны 1
if (requisitionSideOneDetails.value.who_work) {
await addResponsibleEmployee(requisitionSideOneDetails.value.who_work);
}
// Автовыбор стороны 1 (Арендодатель/Продавец) по operation_type объекта
let operationType = null;
if (!isEditing.value && requisitionSideOneDetails.value.object_id) {
selectedObjectSideOneId.value = requisitionSideOneDetails.value.object_id;
await fetchObjectSideOneDetails(`/objects/${requisitionSideOneDetails.value.object_id}`);
const objData = objectSideOneDetails.value;
if (objData && objData.success && objData.data?.operation_type !== undefined) {
operationType = objData.data.operation_type;
deal.value.side_one = operationType === 0 ? 'Арендодатель' : 'Продавец';
}
}
// Автовыбор стороны 2 (Арендатор/Покупатель) по operation_type объекта
if (!isEditing.value && operationType !== null && !deal.value.side_two) {
deal.value.side_two = operationType === 0 ? 'Арендатор' : 'Покупатель';
}
}
} catch (err) {
console.error('Ошибка при загрузке данных:', err);
}
}
});
// Получение деталей выбранной заявки 2 стороны
watch(selectedRequisitionSideTwoId, async (newId, oldId) => {
if (oldId && newId !== oldId) {
selectedObjectSideTwoId.value = null;
selectedObjectSideTwo.value = null;
deal.value.side_two_object_id = null;
// Убираем who_work старой заявки стороны 2 из requisitionEmployees
if (oldId && selectedRequisitionSideTwo.value) {
const oldWhoWork = selectedRequisitionSideTwo.value.who_work;
if (oldWhoWork) {
// Не удаляем если этот сотрудник — who_work другой стороны
const otherSideWhoWork = selectedRequisitionSideOne.value?.who_work;
if (oldWhoWork !== otherSideWhoWork) {
requisitionEmployees.value = requisitionEmployees.value.filter(e => e.id !== oldWhoWork);
}
}
}
}
if (newId) {
await fetchRequisitionSideTwoDetails(`/requisitions/${newId}`);
if (requisitionSideTwoDetails.value) {
selectedRequisitionSideTwo.value = requisitionSideTwoDetails.value;
deal.value.side_two_requisition_id = newId;
if (requisitionSideTwoDetails.value.object_id) {
selectedObjectSideTwoId.value = requisitionSideTwoDetails.value.object_id;
}
// Добавляем who_work заявки стороны 2 в selectedEmployees
if (requisitionSideTwoDetails.value.who_work) {
await addResponsibleEmployee(requisitionSideTwoDetails.value.who_work);
}
}
// Установка клиента в deal для стороны 2
if (requisitionSideTwoDetails.value?.client && typeof requisitionSideTwoDetails.value.client === 'object') {
deal.value.side_two_client_main = requisitionSideTwoDetails.value.client;
} else {
deal.value.side_two_client_main = null;
}
console.log('Загружены основной клиент стороны 2:', deal.value.side_two_client_main);
} else {
selectedRequisitionSideTwo.value = null;
}
});
//Поиск в списке заявок
// async function filterRequisitionsList(event) {
// const query = event.filter || '';
// try {
// const response = await fetchRequisitionsListData(`/requisitions?search=${encodeURIComponent(query)}`);
// requisitionsList.value = response.data.data || [];
// } catch (error) {
// console.error('Ошибка поиска заявок:', error);
// }
// }
async function filterRequisitionsList(event) {
const query = event.value ?? event.filter ?? '';
if (query === '') {
requisitionsList.value = originalRequisitionsList.value.filter(req => req.id !== selectedRequisitionSideOneId.value);
return;
}
try {
const response = await fetchRequisitionsListData(`/requisitions?search=${encodeURIComponent(query)}&exclude_linked_for_deal=1` + (dealId ? '&exclude_linked_for_deal_id=' + dealId : ''));
const items = (response.data.data || response.data.list || []).map(r => ({
...r,
name: r.name + ' (ID: ' + r.id + ')',
}));
requisitionsList.value = items;
} catch (error) {
console.error('Ошибка поиска заявок:', error);
requisitionsList.value = [...originalRequisitionsList.value];
}
}
// Получение деталей выбранного объекта 1 стороны
watch(selectedObjectSideOneId, async (newId) => {
console.log('[DealModal] watch selectedObjectSideOneId:', newId);
if (newId) {
await fetchObjectSideOneDetails(`/objects/${newId}`);
console.log('[DealModal] objectSideOneDetails:', objectSideOneDetails.value);
if (objectSideOneDetails.value && objectSideOneDetails.value.success) {
selectedObjectSideOne.value = objectSideOneDetails.value.data;
deal.value.side_one_object_id = newId;
const exists = objectsListSideOne.value.some(o => o.id === newId);
if (!exists && objectSideOneDetails.value.data) {
objectsListSideOne.value.push({ id: newId, name: objectSideOneDetails.value.data.nazv || `Объект ${newId}` });
}
}
} else {
selectedObjectSideOne.value = null;
}
});
// Получение деталей выбранного объекта 2 стороны
watch(selectedObjectSideTwoId, async (newId) => {
if (newId) {
await fetchObjectSideTwoDetails(`/objects/${newId}`);
if (objectSideTwoDetails.value && objectSideTwoDetails.value.success) {
selectedObjectSideTwo.value = objectSideTwoDetails.value.data;
deal.value.side_two_object_id = newId;
const exists = objectsListSideTwo.value.some(o => o.id === newId);
if (!exists && objectSideTwoDetails.value.data) {
objectsListSideTwo.value.push({ id: newId, name: objectSideTwoDetails.value.data.nazv || `Объект ${newId}` });
}
}
} else {
selectedObjectSideTwo.value = null;
}
});
// Логика для управления модальным окном и выбранными сотрудниками
const requisitionEmployees = ref([]);
const manualEmployees = ref([]);
const selectedEmployees = computed(() => {
const sideOneWhoWork = selectedRequisitionSideOne.value?.who_work;
const sideOneEmp = sideOneWhoWork ? requisitionEmployees.value.filter(e => e.id === sideOneWhoWork) : [];
const otherRequisitionEmps = requisitionEmployees.value.filter(e => !sideOneWhoWork || e.id !== sideOneWhoWork);
return [...sideOneEmp, ...otherRequisitionEmps, ...manualEmployees.value];
});
const isWhoWork = (employeeId) => {
return requisitionEmployees.value.some(e => e.id === employeeId);
};
// Исключаем who_work заявок из списка выбора сотрудников
const filteredEmployeesList = computed(() => {
const excludeIds = requisitionEmployees.value.map(e => e.id);
if (excludeIds.length === 0) return employeesList.value;
return employeesList.value.filter(emp => !excludeIds.includes(emp.id));
});
const openModalDealManager = () => {
console.log('[openModalDealManager] Открываем EmployeeAddModal через DynamicDialog')
dialog.open(EmployeeAddModal, {
props: {
header: 'Выберите сотрудников',
style: { width: '30vw' },
modal: true,
pt: {
header: {
style: {
padding: '0.75rem 1rem'
}
}
}
},
data: {
employees: filteredEmployeesList.value,
selected: manualEmployees.value
},
onClose: (opt) => {
console.log('[DynamicDialog] EmployeeAddModal закрыт:', opt?.data)
if (opt?.data) {
manualEmployees.value = opt.data;
}
}
})
};
const removeEmployee = (employeeId) => {
if (isWhoWork(employeeId)) return;
manualEmployees.value = manualEmployees.value.filter(e => e.id !== employeeId);
};
const selectedDocs = ref([]);
const openModalDealDoc = () => {
console.log('[openModalDealDoc] Открываем DocAddModal через DynamicDialog')
dialog.open(DocAddModal, {
props: {
header: 'Добавление документа',
style: { width: '50vw' },
modal: true,
pt: {
header: {
style: {
padding: '0.75rem 1rem'
}
}
}
},
data: {
deal: deal.value
},
onClose: (opt) => {
console.log('[DynamicDialog] Закрыто с данными:', opt?.data)
if (opt?.data) {
console.log('opt', opt?.data)
addDoc(opt.data);
//selectedDocs.value.push(opt.data)
}
}
})
}
const addDoc = (doc) => {
console.log('[DealModal] addDoc вызван с:', doc);
// ✅ Проверка, что doc — объект и имеет id и name
if (!doc || typeof doc !== 'object' || !doc.id || !doc.name) {
console.error('[DealModal] addDoc: получен невалидный doc:', doc);
return;
}
// Проверяем, что объект документа содержит обязательные поля
if (doc && doc.id && doc.name && doc.file) {
// Проверяем, что документ еще не добавлен
const isDuplicate = selectedDocs.value.some((d) => d.id === doc.id);
if (!isDuplicate) {
selectedDocs.value.push(doc); // Добавляем документ в список
console.log('[DealModal] Документ добавлен в selectedDocs');
}
} else {
console.error('Документ не содержит обязательных полей:', doc);
}
//closeModalDealDoc(); // Закрываем модальное окно
};
// Удалить документ
const removeDoc = async (docId) => {
const doc = selectedDocs.value.find(d => d.id === docId);
selectedDocs.value = selectedDocs.value.filter(d => d.id !== docId);
try {
const { fetchData } = useLaravelApi();
await fetchData(`/documents/${docId}`, {
method: 'PUT',
data: { soft_delete: true },
});
toast.add({
severity: 'success',
summary: 'Документ удалён',
detail: doc ? `«${doc.name}» удалён` : 'Документ удалён',
life: 3000
});
} catch (error) {
toast.add({
severity: 'warn',
summary: 'Документ убран из списка',
detail: 'Не удалось удалить на сервере, будет удалён при сохранении',
life: 3000
});
}
};
// Обработка закрытия заявки
const handleCloseDealRequisition = (side) => {
if (side === 'sideOne') {
selectedRequisitionSideOneId.value = null
selectedRequisitionSideOne.value = null; // Очищаем заявку для стороны 1
deal.value.side_one_requisition_id = null;
} else if (side === 'sideTwo') {
selectedRequisitionSideTwoId.value = null
selectedRequisitionSideTwo.value = null; // Очищаем заявку для стороны 2
deal.value.side_two_requisition_id = null;
}
};
// Закрытие ObjectDetails
const handleCloseDealObject = (side) => {
if (side === 'sideOne') {
selectedObjectSideOneId.value = null; // Очищаем выбранный объект
selectedObjectSideOne.value = null; // Очищаем данные объекта
deal.value.side_one_object_id = null;
} else if (side === 'sideTwo') {
selectedObjectSideTwoId.value = null; // Очищаем выбранный объект
selectedObjectSideTwo.value = null; // Очищаем данные объекта
deal.value.side_two_object_id = null;
}
};
const openModalDealContract = async () => {
if (!deal.value.id) {
return;
}
dialog.open(InnerDocAddModal, {
props: {
header: 'Составить договор',
pt: {
header: {
style: {
padding: '0.75rem 1rem',
borderBottom: 'none'
}
},
},
modal: true,
},
data: {
deal: deal.value,
},
onClose: (options) => {
if (options?.data) {
handleNewInnerDoc(options.data);
}
},
});
};
// Обработка нового договора
const handleNewInnerDoc = (newInnerDoc) => {
console.log('Новый innerDoc получен:', newInnerDoc);
if (!newInnerDoc || typeof newInnerDoc !== 'object' || !newInnerDoc.id || !newInnerDoc.name) {
console.error('[DealModal] handleNewInnerDoc: получен невалидный newInnerDoc:', newInnerDoc);
return;
}
if (!deal.value.inner_docs) {
console.warn('Массив inner_docs не существует в deal. Создаём новый массив.');
deal.value.inner_docs = [];
}
console.log('Добавляем новый договор в deal.inner_docs');
deal.value.inner_docs.push(newInnerDoc);
console.log('Обновлённый список inner_docs:', deal.value.inner_docs);
};
// Удаление документа
const { fetchData: deleteData } = useLaravelApi();
const removeInnerDoc = async (docId) => {
console.log('[DealModal] deleteInnerDoc:', docId);
try {
await deleteData(`/inner-docs/${docId}`, {
method: 'DELETE'
});
// Удаляем документ из локального состояния
deal.value.inner_docs = deal.value.inner_docs.filter(doc => doc.id !== docId);
} catch (error) {
console.error('Ошибка при удалении документа:', error);
throw error;
}
};
const scrollToFirstError = () => {
const firstErrorField = document.querySelector('.p-invalid');
if (firstErrorField) {
firstErrorField.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
firstErrorField.classList.add('error-highlight');
setTimeout(() => {
firstErrorField.classList.remove('error-highlight');
}, 2000);
}
};
// Сохранение сделки
const saveDeal = async () => {
// Проверяем валидацию перед сохранением
if (!validateForm()) {
await nextTick();
scrollToFirstError();
toast.add({
severity: 'error',
summary: 'Ошибка',
detail: 'Заполните все обязательные поля',
life: 3000,
});
return null;
}
const { fetchData } = useLaravelApi();
dayjs.locale(ru);
const formattedDate = dayjs(deal.value.deal_date).format('YYYY-MM-DD HH:mm:ss');
// Подготовка данных клиентов
// const sideOneClients = deal.value.side_one_clients || [];
// const sideTwoClients = deal.value.side_two_clients || [];
const payload = {
...deal.value,
deal_date: formattedDate,
employees: selectedEmployees.value.map(employee => employee.id),
documents: selectedDocs.value.map(doc => doc.id),
};
try {
const url = isEditing.value ? `/deals/${dealId}` : '/deals';
const method = isEditing.value ? 'PUT' : 'POST';
const response = await fetchData(url, {
method,
data: payload,
});
if (response.data.success) {
const docCount = selectedDocs.value.length;
const docText = docCount > 0 ? ` (${docCount} док.)` : '';
toast.add({
severity: 'success',
summary: 'Успех',
detail: String(response.data.message || (isEditing.value ? 'Сделка обновлена' : 'Сделка создана') + docText),
life: 3000,
});
// // Обновляем локальное состояние сделки
// if (response.data.deal) {
// //deal.value = response.data.deal;
// const rawDeal = response.data.deal;
//
// // Конвертируем строки в Date, чтобы избежать падений в computed
// if (rawDeal.deal_date) {
// rawDeal.deal_date = dayjs(rawDeal.deal_date, [
// 'YYYY-MM-DD HH:mm:ss',
// 'DD.MM.YYYY HH:mm'
// ], true).toDate();
// }
// if (rawDeal.created_at) {
// rawDeal.created_at = new Date(rawDeal.created_at);
// }
// if (rawDeal.updated_at) {
// rawDeal.updated_at = new Date(rawDeal.updated_at);
// }
//
// deal.value = rawDeal;
// }
if (response.data.deal) {
const rawDeal = { ...response.data.deal };
// Конвертируем строки в Date
if (rawDeal.deal_date) {
rawDeal.deal_date = dayjs(rawDeal.deal_date, [
'YYYY-MM-DD HH:mm:ss',
'DD.MM.YYYY HH:mm'
], true).toDate();
}
if (rawDeal.created_at) {
rawDeal.created_at = new Date(rawDeal.created_at);
}
if (rawDeal.updated_at) {
rawDeal.updated_at = new Date(rawDeal.updated_at);
}
// Вместо перезаписи — обновляем поля
Object.assign(deal.value, rawDeal);
}
if (!isEditing.value && deal.value.id) {
isEditing.value = true;
dealId = deal.value.id;
toast.add({
severity: 'info',
summary: 'Сделка сохранена',
detail: 'Теперь вы можете составить договор',
life: 5000,
});
}
console.log('cat deal', response.data)
return deal.value;
} else {
new Error(response.data.message || 'Ошибка при сохранении сделки');
}
} catch (error) {
toast.add({
severity: 'error',
summary: 'Ошибка',
detail: String(error?.message ?? 'Неизвестная ошибка'),
life: 3000,
});
return null;
}
};
const getSideByRequisitionType = (typeId) => {
if (typeId === 2) {
return 'Продавец';
}
return 'Покупатель';
};
// Список сторон в зависимости от типа заявки
const sideOneOptions = computed(() => {
if (!selectedRequisitionSideOne.value) return [];
const typeId = selectedRequisitionSideOne.value.type?.heir || selectedRequisitionSideOne.value.type_id;
if (typeId === 2) {
return ['Продавец', 'Арендодатель', 'Застройщик'];
}
return ['Покупатель', 'Арендатор'];
});
const sideTwoOptions = computed(() => {
const sideOne = deal.value.side_one;
if (!sideOne) return [];
// Если сторона 1 - продавец/арендодатель/застройщик
if (sideOne === 'Продавец' || sideOne === 'Арендодатель' || sideOne === 'Застройщик') {
return ['Покупатель', 'Арендатор'];
}
// Если сторона 1 - покупатель/арендатор
if (sideOne === 'Покупатель' || sideOne === 'Арендатор') {
return ['Продавец', 'Арендодатель', 'Застройщик'];
}
return [];
});
// Полный список для случаев, когда заявка не выбрана
const fullSidesList = ['Покупатель', 'Продавец', 'Арендодатель', 'Арендатор', 'Застройщик'];
// Локальное состояние
const isOpen = ref(props.isOpen);
// Синхронизация с props
watch(() => props.isOpen, (newVal) => {
isOpen.value = newVal;
});
const emit = defineEmits(['close', 'added']);
const closeModel = () => {
if (window.isDealOpen !== undefined) window.isDealOpen = false;
document.body.classList.remove('lock');
const backdrop = document.querySelector('.modal-backdrop');
if (backdrop) backdrop.remove();
emit('close');
};
async function addResponsibleEmployee(userId) {
if (!employeesList.value || employeesList.value.length === 0) {
console.warn('Список сотрудников не загружен!');
return;
}
const employee = employeesList.value.find(emp => emp.id === userId);
if (!employee) return;
if (requisitionEmployees.value.some(e => e.id === userId)) return;
const empData = {
...employee,
fullName: `${employee.last_name} ${employee.first_name}` || employee.fio,
};
// Если сотрудник уже в manualEmployees — переносим в requisitionEmployees
const manualIdx = manualEmployees.value.findIndex(e => e.id === userId);
if (manualIdx >= 0) {
manualEmployees.value.splice(manualIdx, 1);
}
requisitionEmployees.value = [...requisitionEmployees.value, empData];
}
defineExpose({ closeModel });
</script>
<style lang="scss">
.addDeal {
width: 100%;
height: auto;
.p-inputtext {
width: 100%;
font-size: 1rem;
color: #000;
background:#fff;
height: 36px;
line-height: 10px;
}
button {
background: transparent;
border:none;
}
.ml-2 {
margin-left: calc(var(--spacing) * 2);
}
.p-datepicker-trigger {
color: #64A853;
border: 1px solid #64A853;
height: 36px;
}
.p-datepicker-trigger:hover {
background: transparent;
color: #64A853;
}
&__form {
width: 100%;
height: auto;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
margin-top: 30px;
row-gap: 20px;
/* Убираем стрелочки вверх/вниз для Chrome, Safari, Edge, Opera */
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Убираем стрелочки вверх/вниз для Firefox */
input[type="number"] {
appearance: textfield;
-moz-appearance: textfield;
-webkit-appearance: textfield;
}
input[type="radio"] {
appearance: none;
-webkit-appearance: none;
display: inline-block;
width: 21px;
height: 21px;
border: 1px solid #64A853;
border-radius: 50%;
position: relative;
cursor: pointer;
}
input[type="radio"]:checked::before {
content: "";
display: inline-block;
width: 12px;
height: 12px;
background-color: #64A853;
border-radius: 50%;
position: absolute;
top: 4px;
left: 4px;
}
h3 {
font-family: 'Lato', sans-serif;
font-style: normal;
font-weight: 400;
font-size: 28px;
line-height: 34px;
display: flex;
align-items: center;
color: #000000;
}
.add-employee-button {
border: 1px dashed #64A853;
border-radius: 5px;
height: 100%;
}
.add-contract-button {
border: 1px dashed #64A853;
border-radius: 5px;
height: 100%;
}
.add-deal-doc-button {
border: 1px dashed #64A853;
border-radius: 5px;
height: 100%;
}
.form__item {
.p-invalid {
border-color: #e24c4c !important;
box-shadow: 0 0 0 1px #e24c4c;
}
.p-error {
color: #e24c4c;
}
.error-highlight {
animation: shake 0.4s ease;
box-shadow: 0 0 0 2px #e24c4c;
}
label {
font-size: 16px;
font-weight: 400;
color: #757575;
}
.item__content {
width: 100%;
height: auto;
display: flex;
justify-content: flex-start;
align-items: flex-start;
column-gap: 16px;
row-gap: 16px;
}
.item__text {
font-size: 16px;
}
.item__btns {
width: 100%;
height: auto;
display: flex;
justify-content: flex-start;
align-items: center;
column-gap: 8px;
}
}
.small__item {
grid-template-columns: 1fr 276px;
}
}
.flex-grap-group {
display: flex;
flex-direction: column;
row-gap: 20px;
}
.p-dropdown {
width: 100% !important;
}
.add-deal-contract-button {
background: linear-gradient(180deg, #429F46 0%, #388E3C 100%), #FFFFFF;
border-radius: 3px;
color: #FFFFFF;
padding: 15px;
font-family: 'Lato', sans-serif;
font-style: normal;
font-weight: 500;
font-size: 18px;
}
.p-dropdown-panel .p-dropdown-items .p-dropdown-item {
max-width: 380px;
overflow: hidden;
text-overflow: ellipsis;
}
.dealAddDoc.modal {
display: flex;
justify-content: center;
align-items: center;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
.modal-content {
position: absolute;
background-color: #fff;
padding: 20px;
border-radius: 10px;
width: 80%;
max-width: 500px;
}
.close {
position: absolute;
top: 10px;
right: 20px;
font-size: 24px;
cursor: pointer;
}
button {
color: #FFFFFF;
background: linear-gradient(180deg, #429F46 0%, #388E3C 100%), #FFFFFF;
}
}
.dealAddContract {
.add-contract-title {
label {
font-family: 'Lato', sans-serif;
font-style: normal;
font-weight: 400;
font-size: 18px;
line-height: 22px;
color: #757575;
}
label::placeholder {
color: #000;
}
}
}
.simple-button.settings {
font-size: 22px;
margin-top: 0;
}
.simple-button {
font-size: 22px;
}
}
.p-dropdown:not(.p-disabled).p-focus {
box-shadow: none;
}
.p-inputtext:enabled:hover {
border-color: #64A853;
}
.p-inputtext:enabled:focus {
box-shadow: none;
border-color: #64A853;
}
.p-dropdown:not(.p-disabled):hover {
border-color: #64A853;
}
.p-dropdown:not(.p-disabled):focus {
border-color: #64A853;
box-shadow: none;
}
.p-dropdown-panel .p-dropdown-items .p-dropdown-item.p-highlight {
color: #64A853;
background: #F5F5F5;
}
.p-checkbox .p-checkbox-box.p-highlight {
border-color: #64A853;
background: #64A853;
}
.p-multiselect-panel .p-multiselect-items .p-multiselect-item.p-highlight {
color: #000000;
background: #f2f2f2;
}
.p-multiselect:not(.p-disabled).p-focus {
box-shadow: none;
}
.p-multiselect-panel .p-multiselect-items .p-multiselect-item.p-highlight.p-focus {
box-shadow: none;
background: #f2f2f2;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
20% { transform: translateX(-4px); }
40% { transform: translateX(4px); }
60% { transform: translateX(-4px); }
80% { transform: translateX(4px); }
}
</style>