Files
portal/app/resources/js/components/import/UnknownStatusesDialog.vue
T
Дмитрий cdd0237108 fix(портал-мобилка): диалоги на телефоне — на весь экран (fullscreen)
Узкие центрированные модалки на телефоне резали содержимое (ряды кнопок,
вкладки, дни недели, сетку регионов — контент вылезал за края, текст обрезался
слева и справа). Диалоги с богатым содержимым теперь на телефоне открываются
на весь экран (:fullscreen по useLayoutTier + scrollable):
- Проект (создание/редактирование) — NewProjectDialog
- Новая сделка — NewDealDialog
- Пополнение баланса — TopupDialog
- Дни оптом — DaysBulkDialog (те же кнопки дней)
- Регионы оптом — RegionsBulkDialog
- Импорт: сопоставление статусов — UnknownStatusesDialog
Десктоп/планшет — прежний центрированный модал, без изменений.
Тесты диалогов 59/59 (+2 skip) зелёные.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 20:46:47 +03:00

113 lines
4.4 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.
<script setup lang="ts">
/**
* Wizard маппинга неизвестных статусов воронки из CSV-импорта (ТЗ §6.4/§6.6).
*
* Для каждого незамапленного русского статуса пользователь выбирает один из
* 5 slug'ов воронки. Сохранение → POST /api/imports/unknown-statuses/resolve.
*/
import { computed, reactive, ref } from 'vue';
import { resolveUnknownStatuses, type StatusMapping, type UnknownStatus } from '../../api/imports';
import { useLayoutTier } from '../../composables/useLayoutTier';
// Телефон: на весь экран (таблица сопоставления статусов широкая).
const { isPhone } = useLayoutTier();
const props = defineProps<{
modelValue: boolean;
statuses: UnknownStatus[];
}>();
const emit = defineEmits<{
'update:modelValue': [value: boolean];
resolved: [];
}>();
/** 5 статусов воронки (редизайн 2026-05-17). */
const STATUS_OPTIONS: { value: string; title: string }[] = [
{ value: 'new', title: 'Новая сделка' },
{ value: 'viewed', title: 'Просмотрено' },
{ value: 'in_progress', title: 'В работе' },
{ value: 'won', title: 'Сделка' },
{ value: 'lost', title: 'Не реализовано' },
];
const selection = reactive<Record<string, string | null>>({});
const saving = ref(false);
const error = ref<string | null>(null);
const dialogOpen = computed({
get: () => props.modelValue,
set: (v: boolean) => emit('update:modelValue', v),
});
const allMapped = computed(() => props.statuses.length > 0 && props.statuses.every((s) => !!selection[s.status_ru]));
async function save(): Promise<void> {
if (!allMapped.value) {
return;
}
saving.value = true;
error.value = null;
try {
const mappings: StatusMapping[] = props.statuses.map((s) => ({
status_ru: s.status_ru,
slug: selection[s.status_ru] as string,
}));
await resolveUnknownStatuses(mappings);
emit('resolved');
} catch {
error.value = 'Не удалось сохранить маппинг. Повторите попытку.';
} finally {
saving.value = false;
}
}
defineExpose({ selection, save });
</script>
<template>
<v-dialog v-model="dialogOpen" max-width="640" :fullscreen="isPhone" scrollable>
<v-card>
<v-card-title class="text-h6">Маппинг неизвестных статусов</v-card-title>
<v-card-text>
<p class="text-body-2 text-medium-emphasis mb-4">
Эти статусы из CSV не входят в стандартную воронку. Выберите соответствие повторный импорт
применит маппинг автоматически.
</p>
<div v-for="status in statuses" :key="status.id" class="d-flex align-center ga-3 mb-3">
<div class="flex-grow-1">
<strong>{{ status.status_ru }}</strong>
<span class="text-caption text-medium-emphasis ml-2"> ({{ status.occurrences }} шт.) </span>
</div>
<v-select
v-model="selection[status.status_ru]"
:items="STATUS_OPTIONS"
label="Статус воронки"
density="compact"
variant="outlined"
hide-details
style="max-width: 280px"
/>
</div>
<v-alert v-if="error" type="error" variant="tonal" class="mt-2">
{{ error }}
</v-alert>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn variant="text" @click="dialogOpen = false">Отмена</v-btn>
<v-btn
data-test="save-mappings"
color="primary"
variant="flat"
:loading="saving"
:disabled="!allMapped"
@click="save"
>
Сохранить
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>