diff --git a/backend/app/class/enity/class_project.php b/backend/app/class/enity/class_project.php
index fd166db..a1da35a 100644
--- a/backend/app/class/enity/class_project.php
+++ b/backend/app/class/enity/class_project.php
@@ -377,6 +377,11 @@ class Project extends BaseEntity {
return ['success' => false, 'errors' => ['access' => 'Нет прав на управление отделами']];
}
+ // Валидация имени
+ if (!$name || trim($name) === '') {
+ return ['success' => false, 'errors' => ['name' => 'Укажите название отдела']];
+ }
+
// Получаем максимальный order_id для проекта
$maxOrder = (int)(Database::max('departments', 'order_id', ['id_project' => $project_id]) ?? 0);
$newOrderId = $maxOrder + 1;
@@ -413,6 +418,11 @@ class Project extends BaseEntity {
return ['success' => false, 'errors' => ['access' => 'Нет прав на управление отделами']];
}
+ // Валидация имени (если передано)
+ if ($name !== null && trim($name) === '') {
+ return ['success' => false, 'errors' => ['name' => 'Укажите название отдела']];
+ }
+
$updateData = [];
if ($name !== null) $updateData['name_departments'] = $name;
if ($color !== null) $updateData['color'] = $color;
diff --git a/front_vue/src/components/ProjectPanel.vue b/front_vue/src/components/ProjectPanel.vue
index 76f4b0a..08048f2 100644
--- a/front_vue/src/components/ProjectPanel.vue
+++ b/front_vue/src/components/ProjectPanel.vue
@@ -307,6 +307,9 @@ const deleteColumnMessage = ref('')
const departmentToDelete = ref(null)
const deleteDepartmentMessage = ref('')
+// Отделы к удалению (ID существующих отделов для удаления при сохранении)
+const departmentsToDelete = ref([])
+
// Drag state for columns
const dragIndex = ref(null)
const dragOverIndex = ref(null)
@@ -495,16 +498,20 @@ const addDepartment = () => {
const confirmDeleteDepartment = async (index) => {
const department = form.value.departments[index]
+
+ // Для новых отделов (ещё не на сервере) — удаляем сразу без диалога
+ if (department.tempId && !department.id) {
+ form.value.departments.splice(index, 1)
+ nextTick(refreshIcons)
+ return
+ }
+
+ // Для существующих отделов — показываем диалог с предупреждением
departmentToDelete.value = index
- // Для существующих отделов проверяем количество задач
- if (department.id) {
- const count = await store.getDepartmentTasksCount(department.id)
- if (count > 0) {
- deleteDepartmentMessage.value = `В отделе ${count} задач.
Задачи не удалятся, но потеряют привязку к отделу.`
- } else {
- deleteDepartmentMessage.value = 'Отдел будет удалён.'
- }
+ const count = await store.getDepartmentTasksCount(department.id)
+ if (count > 0) {
+ deleteDepartmentMessage.value = `В отделе ${count} задач.
Задачи не удалятся, но потеряют привязку к отделу.`
} else {
deleteDepartmentMessage.value = 'Отдел будет удалён.'
}
@@ -516,15 +523,12 @@ const confirmDeleteDepartmentAction = async () => {
const index = departmentToDelete.value
const department = form.value.departments[index]
- // Если отдел уже существует на сервере — удаляем через API
+ // Если отдел существует на сервере — добавляем в список для удаления при сохранении
if (department.id) {
- const result = await store.deleteDepartment(department.id)
- if (!result.success) {
- throw new Error(result.errors?.department || 'Ошибка удаления')
- }
+ departmentsToDelete.value.push(department.id)
}
- // Удаляем из формы
+ // Удаляем из формы (локально)
form.value.departments.splice(index, 1)
departmentToDelete.value = null
@@ -768,15 +772,35 @@ const handleSave = async () => {
try {
if (isNew.value) {
+ // Проверяем что у всех отделов есть имена (для нового проекта тоже)
+ const emptyDeptNew = form.value.departments.find(d => !d.name_departments?.trim())
+ if (emptyDeptNew) {
+ toast.error('Укажите название для всех отделов')
+ return
+ }
+
// Создание проекта
const result = await store.createProject(form.value.name)
if (result.success) {
- // Обновляем колонки если есть изменения
- // Дефолтные колонки уже созданы сервером
- // Здесь можно добавить логику для кастомизации колонок при создании
+ const newProjectId = result.id
+
+ // Создаём отделы для нового проекта
+ for (const department of form.value.departments) {
+ if (department.tempId) {
+ const deptResult = await store.addDepartmentToProject(
+ newProjectId,
+ department.name_departments,
+ department.color
+ )
+ if (!deptResult.success) {
+ toast.error(deptResult.errors?.name || 'Ошибка создания отдела')
+ }
+ }
+ }
+
toast.success('Проект создан')
- emit('saved', result.id)
+ emit('saved', newProjectId)
emit('close')
} else {
toast.error('Ошибка создания проекта')
@@ -816,6 +840,23 @@ const handleSave = async () => {
await store.reorderColumns(ids)
}
+ // Удаляем отделы, помеченные на удаление
+ for (const deptId of departmentsToDelete.value) {
+ const result = await store.deleteDepartment(deptId)
+ if (!result.success) {
+ toast.error(result.errors?.department || result.errors?.access || 'Ошибка удаления отдела')
+ return
+ }
+ }
+ departmentsToDelete.value = []
+
+ // Проверяем что у всех отделов есть имена
+ const emptyDepartment = form.value.departments.find(d => !d.name_departments?.trim())
+ if (emptyDepartment) {
+ toast.error('Укажите название для всех отделов')
+ return
+ }
+
// Обрабатываем отделы
for (const department of form.value.departments) {
if (department.tempId && !department.id) {
@@ -824,6 +865,9 @@ const handleSave = async () => {
if (result.success) {
department.id = result.id
delete department.tempId
+ } else {
+ toast.error(result.errors?.name || result.errors?.access || 'Ошибка создания отдела')
+ return
}
} else if (department.id) {
// Проверяем изменения в существующем отделе
@@ -832,7 +876,11 @@ const handleSave = async () => {
const nameChanged = department.name_departments !== original.name_departments
const colorChanged = department.color !== original.color
if (nameChanged || colorChanged) {
- await store.updateDepartment(department.id, department.name_departments, department.color)
+ const result = await store.updateDepartment(department.id, department.name_departments, department.color)
+ if (!result.success) {
+ toast.error(result.errors?.name || result.errors?.access || 'Ошибка обновления отдела')
+ return
+ }
}
}
}
@@ -903,6 +951,7 @@ watch(() => props.show, async (newVal) => {
if (newVal) {
isSaving.value = false
tempIdCounter = 0
+ departmentsToDelete.value = []
if (props.project) {
// Редактирование — загружаем данные
diff --git a/front_vue/src/stores/projects.js b/front_vue/src/stores/projects.js
index 9909f68..22ca3a3 100644
--- a/front_vue/src/stores/projects.js
+++ b/front_vue/src/stores/projects.js
@@ -661,6 +661,13 @@ export const useProjectsStore = defineStore('projects', () => {
await projectsApi.updateDepartmentsOrder(currentProjectId.value, ids)
}
+ // Добавление отдела в конкретный проект (для создания нового проекта)
+ const addDepartmentToProject = async (projectId, name, color = '#6366f1') => {
+ const result = await projectsApi.addDepartment(projectId, name, color)
+ // Не добавляем в локальный state — при переключении на проект данные загрузятся
+ return result
+ }
+
return {
// Состояние
projects,
@@ -714,6 +721,7 @@ export const useProjectsStore = defineStore('projects', () => {
setReadyColumn,
// CRUD отделов
addDepartment,
+ addDepartmentToProject,
updateDepartment,
getDepartmentTasksCount,
deleteDepartment,