Inline manager order editing
This commit is contained in:
@@ -20,12 +20,16 @@ const props = defineProps<{
|
|||||||
mode?: 'readonly' | 'manager-pricing' | 'cart';
|
mode?: 'readonly' | 'manager-pricing' | 'cart';
|
||||||
editable?: boolean;
|
editable?: boolean;
|
||||||
unitPriceDrafts?: Record<string, string>;
|
unitPriceDrafts?: Record<string, string>;
|
||||||
|
editablePriceItemIds?: string[];
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
framed?: boolean;
|
framed?: boolean;
|
||||||
|
pricePlaceholder?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'update:unit-price': [payload: { itemId: string; value: string }];
|
'update:unit-price': [payload: { itemId: string; value: string }];
|
||||||
|
'activate:unit-price': [itemId: string];
|
||||||
|
'finish:unit-price': [itemId: string];
|
||||||
increment: [itemId: string];
|
increment: [itemId: string];
|
||||||
decrement: [itemId: string];
|
decrement: [itemId: string];
|
||||||
remove: [itemId: string];
|
remove: [itemId: string];
|
||||||
@@ -86,6 +90,18 @@ function updateUnitPrice(itemId: string, event: Event) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function activateUnitPrice(itemId: string) {
|
||||||
|
if (!isPricingMode.value || props.disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('activate:unit-price', itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishUnitPrice(itemId: string) {
|
||||||
|
emit('finish:unit-price', itemId);
|
||||||
|
}
|
||||||
|
|
||||||
function incrementItem(itemId: string) {
|
function incrementItem(itemId: string) {
|
||||||
emit('increment', itemId);
|
emit('increment', itemId);
|
||||||
}
|
}
|
||||||
@@ -110,6 +126,8 @@ const mode = computed(() => props.mode ?? (props.editable ? 'manager-pricing' :
|
|||||||
const isPricingMode = computed(() => mode.value === 'manager-pricing');
|
const isPricingMode = computed(() => mode.value === 'manager-pricing');
|
||||||
const isCartMode = computed(() => mode.value === 'cart');
|
const isCartMode = computed(() => mode.value === 'cart');
|
||||||
const isFramed = computed(() => props.framed ?? true);
|
const isFramed = computed(() => props.framed ?? true);
|
||||||
|
const editablePriceItemIds = computed(() => new Set(props.editablePriceItemIds ?? []));
|
||||||
|
const displayPricePlaceholder = computed(() => props.pricePlaceholder ?? '—');
|
||||||
|
|
||||||
function mapParameterEntries(source: Record<string, unknown> | null | undefined): ItemParameter[] {
|
function mapParameterEntries(source: Record<string, unknown> | null | undefined): ItemParameter[] {
|
||||||
if (!source || typeof source !== 'object') {
|
if (!source || typeof source !== 'object') {
|
||||||
@@ -187,18 +205,26 @@ function itemParameters(item: OrderItemView) {
|
|||||||
<div class="mt-4 md:mt-0">
|
<div class="mt-4 md:mt-0">
|
||||||
<p class="mb-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-[#6a8a76] md:hidden">Цена</p>
|
<p class="mb-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-[#6a8a76] md:hidden">Цена</p>
|
||||||
<input
|
<input
|
||||||
v-if="isPricingMode"
|
v-if="isPricingMode && editablePriceItemIds.has(item.id)"
|
||||||
:value="unitPriceDrafts?.[item.id] ?? ''"
|
:value="unitPriceDrafts?.[item.id] ?? ''"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
placeholder="Например, 125.50"
|
placeholder="Например, 125.50"
|
||||||
|
:data-unit-price-input="item.id"
|
||||||
class="w-full rounded-2xl bg-[#f3f5f4] px-4 py-3 text-sm text-[#123824] outline-none transition focus:shadow-[0_0_0_3px_rgba(19,153,87,0.12)] disabled:cursor-not-allowed disabled:bg-[#f4f8f5]"
|
class="w-full rounded-2xl bg-[#f3f5f4] px-4 py-3 text-sm text-[#123824] outline-none transition focus:shadow-[0_0_0_3px_rgba(19,153,87,0.12)] disabled:cursor-not-allowed disabled:bg-[#f4f8f5]"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
@input="updateUnitPrice(item.id, $event)"
|
@input="updateUnitPrice(item.id, $event)"
|
||||||
|
@blur="finishUnitPrice(item.id)"
|
||||||
|
@keydown.enter="finishUnitPrice(item.id)"
|
||||||
>
|
>
|
||||||
<p v-else class="text-sm font-semibold text-[#123824]">
|
<p
|
||||||
{{ formatPrice(currentUnitPrice(item)) || '—' }}
|
v-else
|
||||||
|
class="text-sm font-semibold text-[#123824]"
|
||||||
|
:class="isPricingMode && !disabled ? 'cursor-pointer transition hover:text-[#0d854a]' : ''"
|
||||||
|
@dblclick="activateUnitPrice(item.id)"
|
||||||
|
>
|
||||||
|
{{ formatPrice(currentUnitPrice(item)) || displayPricePlaceholder }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useMutation, useQuery } from '@vue/apollo-composable';
|
import { useMutation, useQuery } from '@vue/apollo-composable';
|
||||||
import {
|
import {
|
||||||
|
CompleteOrderDocument,
|
||||||
|
Decision,
|
||||||
|
ManagerFinalizeOrderDocument,
|
||||||
ManagerSetOrderOfferDocument,
|
ManagerSetOrderOfferDocument,
|
||||||
OrderDetailDocument,
|
OrderDetailDocument,
|
||||||
StartOrderWorkDocument,
|
StartOrderWorkDocument,
|
||||||
@@ -21,18 +24,31 @@ const route = useRoute();
|
|||||||
const orderId = computed(() => String(route.params.id || ''));
|
const orderId = computed(() => String(route.params.id || ''));
|
||||||
|
|
||||||
type ManagerOrderItem = NonNullable<OrderDetailQuery['order']>;
|
type ManagerOrderItem = NonNullable<OrderDetailQuery['order']>;
|
||||||
|
type StatusAction = 'APPROVE' | 'REJECT' | 'START' | 'COMPLETE';
|
||||||
|
type StatusActionOption = {
|
||||||
|
value: StatusAction;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
|
||||||
const orderQuery = useQuery(OrderDetailDocument, () => ({
|
const orderQuery = useQuery(OrderDetailDocument, () => ({
|
||||||
id: orderId.value,
|
id: orderId.value,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const completeOrderMutation = useMutation(CompleteOrderDocument);
|
||||||
|
const finalizeOrderMutation = useMutation(ManagerFinalizeOrderDocument);
|
||||||
const startWorkMutation = useMutation(StartOrderWorkDocument);
|
const startWorkMutation = useMutation(StartOrderWorkDocument);
|
||||||
const setOfferMutation = useMutation(ManagerSetOrderOfferDocument);
|
const setOfferMutation = useMutation(ManagerSetOrderOfferDocument);
|
||||||
|
|
||||||
const itemPriceDrafts = reactive<Record<string, string>>({});
|
const itemPriceDrafts = reactive<Record<string, string>>({});
|
||||||
const deliveryTermsDraft = ref('');
|
const deliveryTermsDraft = ref('');
|
||||||
const deliveryFeeDraft = ref('');
|
const deliveryFeeDraft = ref('');
|
||||||
|
const editingPriceItemId = ref<string | null>(null);
|
||||||
|
const editingDeliveryTerms = ref(false);
|
||||||
|
const editingDeliveryFee = ref(false);
|
||||||
|
const editingStatus = ref(false);
|
||||||
|
const statusActionDraft = ref<StatusAction | ''>('');
|
||||||
const autosavePending = ref(false);
|
const autosavePending = ref(false);
|
||||||
|
const statusMutationPending = ref(false);
|
||||||
let autosaveTimer: ReturnType<typeof setTimeout> | null = null;
|
let autosaveTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
const currentOrder = computed<ManagerOrderItem | null>(() =>
|
const currentOrder = computed<ManagerOrderItem | null>(() =>
|
||||||
@@ -44,6 +60,12 @@ const currentOrderCode = computed(() => formatOrderCode(currentOrder.value?.code
|
|||||||
watch(
|
watch(
|
||||||
currentOrder,
|
currentOrder,
|
||||||
(order) => {
|
(order) => {
|
||||||
|
editingPriceItemId.value = null;
|
||||||
|
editingDeliveryTerms.value = false;
|
||||||
|
editingDeliveryFee.value = false;
|
||||||
|
editingStatus.value = false;
|
||||||
|
statusActionDraft.value = '';
|
||||||
|
|
||||||
for (const key of Object.keys(itemPriceDrafts)) {
|
for (const key of Object.keys(itemPriceDrafts)) {
|
||||||
delete itemPriceDrafts[key];
|
delete itemPriceDrafts[key];
|
||||||
}
|
}
|
||||||
@@ -84,6 +106,58 @@ const canEditOffer = computed(() => (
|
|||||||
currentOrder.value != null
|
currentOrder.value != null
|
||||||
&& ['NEW', 'MANAGER_PROCESSING', 'WAITING_DOUBLE_CONFIRM', 'CONFIRMED'].includes(currentOrder.value.status)
|
&& ['NEW', 'MANAGER_PROCESSING', 'WAITING_DOUBLE_CONFIRM', 'CONFIRMED'].includes(currentOrder.value.status)
|
||||||
));
|
));
|
||||||
|
const activePriceItemIds = computed(() => (
|
||||||
|
editingPriceItemId.value ? [editingPriceItemId.value] : []
|
||||||
|
));
|
||||||
|
const managerStatusSummary = computed(() => {
|
||||||
|
if (!currentOrder.value) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['NEW', 'MANAGER_PROCESSING'].includes(currentOrder.value.status)) {
|
||||||
|
return 'Заполните цены по позициям и логистике. После этого заказ можно перевести в предложение.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['WAITING_DOUBLE_CONFIRM', 'CONFIRMED'].includes(currentOrder.value.status)) {
|
||||||
|
return 'Предложение уже собрано. Следующий шаг для менеджера: перевести заказ в работу или остановить его.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentOrder.value.status === 'IN_PROGRESS') {
|
||||||
|
return 'Заказ уже в работе. После исполнения его можно завершить.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentOrder.value.status === 'COMPLETED') {
|
||||||
|
return 'Заказ завершён и больше не редактируется.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Заказ остановлен. Если нужно продолжить работу, сначала поправьте условия и затем снова переведите его в предложение.';
|
||||||
|
});
|
||||||
|
const statusActions = computed<StatusActionOption[]>(() => {
|
||||||
|
if (!currentOrder.value) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const options: StatusActionOption[] = [];
|
||||||
|
|
||||||
|
if (offerReady.value && ['NEW', 'MANAGER_PROCESSING', 'MANAGER_BLOCKED', 'CLIENT_REJECTED', 'MANAGER_REJECTED'].includes(currentOrder.value.status)) {
|
||||||
|
options.push({ value: 'APPROVE', label: 'Предложение' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['NEW', 'MANAGER_PROCESSING', 'WAITING_DOUBLE_CONFIRM', 'CONFIRMED', 'MANAGER_BLOCKED'].includes(currentOrder.value.status)) {
|
||||||
|
options.push({ value: 'REJECT', label: 'Отклонен' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['WAITING_DOUBLE_CONFIRM', 'CONFIRMED'].includes(currentOrder.value.status)) {
|
||||||
|
options.push({ value: 'START', label: 'В работе' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentOrder.value.status === 'IN_PROGRESS') {
|
||||||
|
options.push({ value: 'COMPLETE', label: 'Завершен' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
});
|
||||||
|
const canEditStatus = computed(() => statusActions.value.length > 0);
|
||||||
|
|
||||||
const offerSignature = computed(() => {
|
const offerSignature = computed(() => {
|
||||||
if (!currentOrder.value) {
|
if (!currentOrder.value) {
|
||||||
@@ -144,6 +218,11 @@ async function refetchOrder() {
|
|||||||
await orderQuery.refetch({ id: orderId.value });
|
await orderQuery.refetch({ id: orderId.value });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function focusElement(selector: string) {
|
||||||
|
await nextTick();
|
||||||
|
document.querySelector<HTMLInputElement | HTMLSelectElement>(selector)?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
async function saveOffer() {
|
async function saveOffer() {
|
||||||
if (!currentOrder.value || !offerReady.value || !canEditOffer.value) {
|
if (!currentOrder.value || !offerReady.value || !canEditOffer.value) {
|
||||||
return;
|
return;
|
||||||
@@ -169,14 +248,99 @@ async function saveOffer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function startOrder() {
|
async function openPriceEditor(itemId: string) {
|
||||||
if (!currentOrder.value) {
|
if (!canEditOffer.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
editingPriceItemId.value = itemId;
|
||||||
|
await focusElement(`[data-unit-price-input="${itemId}"]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closePriceEditor(itemId: string) {
|
||||||
|
if (editingPriceItemId.value === itemId) {
|
||||||
|
editingPriceItemId.value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openDeliveryTermsEditor() {
|
||||||
|
if (!canEditOffer.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
editingDeliveryTerms.value = true;
|
||||||
|
await focusElement('[data-delivery-terms-input]');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDeliveryTermsEditor() {
|
||||||
|
editingDeliveryTerms.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openDeliveryFeeEditor() {
|
||||||
|
if (!canEditOffer.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
editingDeliveryFee.value = true;
|
||||||
|
await focusElement('[data-delivery-fee-input]');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDeliveryFeeEditor() {
|
||||||
|
editingDeliveryFee.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openStatusEditor() {
|
||||||
|
if (!canEditStatus.value || statusMutationPending.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
editingStatus.value = true;
|
||||||
|
statusActionDraft.value = '';
|
||||||
|
await focusElement('[data-manager-status-select]');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeStatusEditor() {
|
||||||
|
if (!statusMutationPending.value) {
|
||||||
|
editingStatus.value = false;
|
||||||
|
statusActionDraft.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function applyStatusAction() {
|
||||||
|
if (!currentOrder.value || !statusActionDraft.value) {
|
||||||
|
closeStatusEditor();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
statusMutationPending.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (statusActionDraft.value === 'APPROVE') {
|
||||||
|
await finalizeOrderMutation.mutate({
|
||||||
|
orderId: currentOrder.value.id,
|
||||||
|
decision: Decision.Approve,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (statusActionDraft.value === 'REJECT') {
|
||||||
|
await finalizeOrderMutation.mutate({
|
||||||
|
orderId: currentOrder.value.id,
|
||||||
|
decision: Decision.Reject,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (statusActionDraft.value === 'START') {
|
||||||
await startWorkMutation.mutate({ orderId: currentOrder.value.id });
|
await startWorkMutation.mutate({ orderId: currentOrder.value.id });
|
||||||
|
}
|
||||||
|
else if (statusActionDraft.value === 'COMPLETE') {
|
||||||
|
await completeOrderMutation.mutate({ orderId: currentOrder.value.id });
|
||||||
|
}
|
||||||
|
|
||||||
await refetchOrder();
|
await refetchOrder();
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
|
statusMutationPending.value = false;
|
||||||
|
closeStatusEditor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
[offerReady, offerSignature, publishedSignature],
|
[offerReady, offerSignature, publishedSignature],
|
||||||
@@ -216,11 +380,57 @@ watch(
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<OrdersOrderStatusTimelineCard
|
<div class="surface-card rounded-3xl p-5">
|
||||||
:status="currentOrder.status"
|
<div class="flex flex-wrap items-start justify-between gap-4">
|
||||||
:created-at="currentOrder.createdAt"
|
<div class="space-y-2">
|
||||||
audience="manager"
|
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Статус заказа</p>
|
||||||
/>
|
|
||||||
|
<label v-if="editingStatus && canEditStatus" class="block">
|
||||||
|
<select
|
||||||
|
v-model="statusActionDraft"
|
||||||
|
data-manager-status-select
|
||||||
|
class="w-full min-w-[220px] rounded-2xl bg-[#f3f5f4] px-4 py-3 text-sm font-semibold text-[#123824] outline-none transition focus:shadow-[0_0_0_3px_rgba(19,153,87,0.12)]"
|
||||||
|
:disabled="statusMutationPending"
|
||||||
|
@change="void applyStatusAction()"
|
||||||
|
@blur="closeStatusEditor"
|
||||||
|
>
|
||||||
|
<option value="" disabled>Выберите статус</option>
|
||||||
|
<option
|
||||||
|
v-for="option in statusActions"
|
||||||
|
:key="option.value"
|
||||||
|
:value="option.value"
|
||||||
|
>
|
||||||
|
{{ option.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
|
type="button"
|
||||||
|
class="rounded-2xl px-0 py-0 text-left"
|
||||||
|
:class="canEditStatus ? 'cursor-pointer' : 'cursor-default'"
|
||||||
|
@dblclick="void openStatusEditor()"
|
||||||
|
>
|
||||||
|
<OrdersOrderStatusBadge :status="currentOrder.status" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="text-sm text-[#5c7b69]">
|
||||||
|
{{
|
||||||
|
statusMutationPending
|
||||||
|
? 'Обновляем статус...'
|
||||||
|
: canEditStatus
|
||||||
|
? 'Двойной клик по статусу, чтобы изменить.'
|
||||||
|
: 'Статус уже финальный.'
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="mt-4 max-w-2xl text-sm leading-6 text-[#355947]">
|
||||||
|
{{ managerStatusSummary }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-xl font-bold text-[#123824]">Состав заказа</h2>
|
<h2 class="text-xl font-bold text-[#123824]">Состав заказа</h2>
|
||||||
@@ -228,11 +438,15 @@ watch(
|
|||||||
class="mt-4"
|
class="mt-4"
|
||||||
:items="currentOrder.items"
|
:items="currentOrder.items"
|
||||||
:calculation-payload="currentOrder.calculationPayload"
|
:calculation-payload="currentOrder.calculationPayload"
|
||||||
:editable="true"
|
mode="manager-pricing"
|
||||||
:unit-price-drafts="itemPriceDrafts"
|
:unit-price-drafts="itemPriceDrafts"
|
||||||
|
:editable-price-item-ids="activePriceItemIds"
|
||||||
:disabled="!canEditOffer"
|
:disabled="!canEditOffer"
|
||||||
:framed="false"
|
:framed="false"
|
||||||
|
price-placeholder="Рассчитывается"
|
||||||
@update:unit-price="({ itemId, value }) => { itemPriceDrafts[itemId] = value; }"
|
@update:unit-price="({ itemId, value }) => { itemPriceDrafts[itemId] = value; }"
|
||||||
|
@activate:unit-price="void openPriceEditor($event)"
|
||||||
|
@finish:unit-price="closePriceEditor($event)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -246,31 +460,53 @@ watch(
|
|||||||
<div class="grid gap-4 md:grid-cols-2">
|
<div class="grid gap-4 md:grid-cols-2">
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Комментарий по доставке</p>
|
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Комментарий по доставке</p>
|
||||||
<p>{{ orderDeliveryStateText(draftDeliveryTerms) }}</p>
|
<label v-if="editingDeliveryTerms" class="form-control">
|
||||||
<label class="form-control">
|
|
||||||
<input
|
<input
|
||||||
v-model="deliveryTermsDraft"
|
v-model="deliveryTermsDraft"
|
||||||
|
data-delivery-terms-input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Например, доставка до склада 2-3 дня"
|
placeholder="Например, доставка до склада 2-3 дня"
|
||||||
class="input input-bordered manager-field w-full rounded-2xl bg-white"
|
class="input input-bordered manager-field w-full rounded-2xl bg-white"
|
||||||
:disabled="!canEditOffer"
|
:disabled="!canEditOffer"
|
||||||
|
@blur="closeDeliveryTermsEditor"
|
||||||
|
@keydown.enter="closeDeliveryTermsEditor"
|
||||||
>
|
>
|
||||||
</label>
|
</label>
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
|
type="button"
|
||||||
|
class="w-full rounded-2xl px-0 py-0 text-left text-sm text-[#123824]"
|
||||||
|
:class="canEditOffer ? 'cursor-pointer transition hover:text-[#0d854a]' : 'cursor-default'"
|
||||||
|
@dblclick="void openDeliveryTermsEditor()"
|
||||||
|
>
|
||||||
|
{{ orderDeliveryStateText(draftDeliveryTerms) }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Стоимость логистики</p>
|
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Стоимость логистики</p>
|
||||||
<p>{{ orderLogisticsStateText(draftDeliveryFee) }}</p>
|
<label v-if="editingDeliveryFee" class="form-control">
|
||||||
<label class="form-control">
|
|
||||||
<input
|
<input
|
||||||
v-model="deliveryFeeDraft"
|
v-model="deliveryFeeDraft"
|
||||||
|
data-delivery-fee-input
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
placeholder="Например, 3000"
|
placeholder="Например, 3000"
|
||||||
class="input input-bordered manager-field w-full rounded-2xl bg-white"
|
class="input input-bordered manager-field w-full rounded-2xl bg-white"
|
||||||
:disabled="!canEditOffer"
|
:disabled="!canEditOffer"
|
||||||
|
@blur="closeDeliveryFeeEditor"
|
||||||
|
@keydown.enter="closeDeliveryFeeEditor"
|
||||||
>
|
>
|
||||||
</label>
|
</label>
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
|
type="button"
|
||||||
|
class="w-full rounded-2xl px-0 py-0 text-left text-sm text-[#123824]"
|
||||||
|
:class="canEditOffer ? 'cursor-pointer transition hover:text-[#0d854a]' : 'cursor-default'"
|
||||||
|
@dblclick="void openDeliveryFeeEditor()"
|
||||||
|
>
|
||||||
|
{{ orderLogisticsStateText(draftDeliveryFee) }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -279,22 +515,16 @@ watch(
|
|||||||
<p class="text-sm text-[#5c7b69]">
|
<p class="text-sm text-[#5c7b69]">
|
||||||
{{
|
{{
|
||||||
offerTotal == null
|
offerTotal == null
|
||||||
? 'Итог по заказу посчитается автоматически после заполнения всех цен.'
|
? 'Итог по заказу появится после заполнения всех цен.'
|
||||||
: autosavePending
|
: autosavePending
|
||||||
? `Сохраняем и отправляем клиенту: ${formatPrice(offerTotal)}`
|
? `Сохраняем: ${formatPrice(offerTotal)}`
|
||||||
: `Предложение отправится клиенту автоматически: ${formatPrice(offerTotal)}`
|
: `Текущий итог: ${formatPrice(offerTotal)}`
|
||||||
}}
|
}}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="['WAITING_DOUBLE_CONFIRM', 'CONFIRMED'].includes(currentOrder.status)" class="surface-card rounded-3xl p-5">
|
<p v-if="canEditOffer" class="text-xs font-semibold uppercase tracking-[0.14em] text-[#6a8a76]">
|
||||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
Двойной клик по цене или условиям, чтобы изменить.
|
||||||
<div>
|
</p>
|
||||||
<h2 class="text-xl font-bold text-[#123824]">Следующий шаг</h2>
|
|
||||||
<p class="mt-1 text-sm text-[#5c7b69]">Когда всё согласовано, менеджер просто переводит заказ в работу.</p>
|
|
||||||
</div>
|
|
||||||
<button class="btn btn-accent rounded-full border-0 px-5" @click="startOrder">Пустить в работу</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user