diff --git a/.gitignore b/.gitignore index 95ca378..bf162d7 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ backend/public/* # Личные файлы deploy.js -deploy.php \ No newline at end of file +deploy.php +.vscode \ No newline at end of file diff --git a/backend/api/task.php b/backend/api/task.php index fc2f8b1..61d2a8f 100644 --- a/backend/api/task.php +++ b/backend/api/task.php @@ -95,6 +95,14 @@ if ($method === 'POST') { RestApi::response($result); } + // Установка статуса архивации задачи + if ($action === 'set_archive') { + $id = $data['id'] ?? null; + $archive = $data['archive'] ?? 1; + $result = Task::setArchive($id, $archive); + RestApi::response($result); + } + // Метод не указан if (!$action) { RestApi::response(['success' => false, 'error' => 'Укажите метод'], 400); @@ -103,8 +111,16 @@ if ($method === 'POST') { if ($method === 'GET') { // Получение всех задач + // ?archive=0 (неархивные, по умолчанию), ?archive=1 (архивные), ?archive=all (все) + $archive = $_GET['archive'] ?? 0; + if ($archive === 'all') { + $archive = null; + } else { + $archive = (int)$archive; + } + $task = new Task(); - $tasks = $task->getAll(); + $tasks = $task->getAll($archive); RestApi::response(['success' => true, 'data' => $tasks]); } diff --git a/backend/app/class/enity/class_task.php b/backend/app/class/enity/class_task.php index 6088399..58ac66e 100644 --- a/backend/app/class/enity/class_task.php +++ b/backend/app/class/enity/class_task.php @@ -15,6 +15,7 @@ class Task extends BaseEntity { public $title; public $descript; public $descript_full; + public $archive; // Валидация данных protected function validate() { @@ -65,6 +66,7 @@ class Task extends BaseEntity { 'title' => $this->title, 'descript' => $this->descript ?: null, 'descript_full' => $this->descript_full ?: null, + 'archive' => 0, 'date_create' => date('Y-m-d H:i:s'), 'file_img' => '[]' ]); @@ -175,7 +177,13 @@ class Task extends BaseEntity { } // Получение всех задач - public function getAll() { + // $archive: 0 = неархивные, 1 = архивные, null = все + public function getAll($archive = 0) { + $where = []; + if ($archive !== null) { + $where['archive'] = $archive ? 1 : 0; + } + $tasks = Database::select($this->db_name, [ 'id', 'id_department', @@ -188,8 +196,9 @@ class Task extends BaseEntity { 'file_img', 'title', 'descript', - 'descript_full' - ]); + 'descript_full', + 'archive' + ], $where); // Декодируем JSON и получаем avatar_url из accounts return array_map(function($task) { @@ -243,6 +252,29 @@ class Task extends BaseEntity { } return $task; } + + // Установка статуса архивации задачи (только для задач в колонке 4) + public static function setArchive($id, $archive = 1) { + // Проверка что задача существует + $task = self::check_task($id); + + // Архивировать можно только задачи в колонке 4 + if ($archive && $task['column_id'] != 4) { + RestApi::response([ + 'success' => false, + 'errors' => ['column' => 'Архивировать можно только задачи из колонки "Готово"'] + ], 400); + } + + // Обновляем archive + Database::update('cards_task', [ + 'archive' => $archive ? 1 : 0 + ], [ + 'id' => $id + ]); + + return ['success' => true, 'archive' => $archive ? 1 : 0]; + } } ?> \ No newline at end of file diff --git a/front_vue/src/api.js b/front_vue/src/api.js index 1a6100e..50f755c 100644 --- a/front_vue/src/api.js +++ b/front_vue/src/api.js @@ -70,7 +70,8 @@ export const columnsApi = { // ==================== CARDS ==================== export const cardsApi = { - getAll: () => request('/api/task', { credentials: 'include' }), + // archive: 0 = неархивные (по умолчанию), 1 = архивные, 'all' = все + getAll: (archive = 0) => request(`/api/task${archive !== 0 ? `?archive=${archive}` : ''}`, { credentials: 'include' }), updateOrder: (id, column_id, to_index) => request('/api/task', { method: 'POST', credentials: 'include', @@ -94,6 +95,12 @@ export const cardsApi = { credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'delete', id }) + }), + setArchive: (id, archive = 1) => request('/api/task', { + method: 'POST', + credentials: 'include', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ action: 'set_archive', id, archive }) }) } diff --git a/front_vue/src/components/Board.vue b/front_vue/src/components/Board.vue index f41dbcf..97cc42f 100644 --- a/front_vue/src/components/Board.vue +++ b/front_vue/src/components/Board.vue @@ -10,6 +10,7 @@ @drop-card="handleDropCard" @open-task="(card) => emit('open-task', { card, columnId: column.id })" @create-task="emit('create-task', column.id)" + @archive-task="archiveTask" /> @@ -238,7 +239,20 @@ const deleteTask = async (cardId, columnId) => { } } -defineExpose({ saveTask, deleteTask }) +const archiveTask = async (cardId) => { + // Архивируем на сервере + const result = await cardsApi.setArchive(cardId, 1) + + if (result.success) { + // Удаляем из локального списка (задача уходит в архив) + const index = localCards.value.findIndex(c => c.id === cardId) + if (index !== -1) { + localCards.value.splice(index, 1) + } + } +} + +defineExpose({ saveTask, deleteTask, archiveTask }) diff --git a/front_vue/src/components/Column.vue b/front_vue/src/components/Column.vue index eac88c7..633ad36 100644 --- a/front_vue/src/components/Column.vue +++ b/front_vue/src/components/Column.vue @@ -30,6 +30,7 @@ :departments="departments" :labels="labels" @click="emit('open-task', card)" + @archive="emit('archive-task', $event)" /> @@ -56,7 +57,7 @@ const props = defineProps({ } }) -const emit = defineEmits(['drop-card', 'open-task', 'create-task']) +const emit = defineEmits(['drop-card', 'open-task', 'create-task', 'archive-task']) const refreshIcons = () => { if (window.lucide) { diff --git a/front_vue/src/components/TaskPanel.vue b/front_vue/src/components/TaskPanel.vue index 20a916c..e66d0ba 100644 --- a/front_vue/src/components/TaskPanel.vue +++ b/front_vue/src/components/TaskPanel.vue @@ -1,327 +1,186 @@ diff --git a/front_vue/src/components/ui/FileUploader.vue b/front_vue/src/components/ui/FileUploader.vue new file mode 100644 index 0000000..8568151 --- /dev/null +++ b/front_vue/src/components/ui/FileUploader.vue @@ -0,0 +1,454 @@ + + + + + diff --git a/front_vue/src/components/ui/FormField.vue b/front_vue/src/components/ui/FormField.vue new file mode 100644 index 0000000..f7f8f52 --- /dev/null +++ b/front_vue/src/components/ui/FormField.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/front_vue/src/components/ui/ImagePreview.vue b/front_vue/src/components/ui/ImagePreview.vue new file mode 100644 index 0000000..0470da8 --- /dev/null +++ b/front_vue/src/components/ui/ImagePreview.vue @@ -0,0 +1,256 @@ + + + + + diff --git a/front_vue/src/components/ui/RichTextEditor.vue b/front_vue/src/components/ui/RichTextEditor.vue new file mode 100644 index 0000000..cc97893 --- /dev/null +++ b/front_vue/src/components/ui/RichTextEditor.vue @@ -0,0 +1,324 @@ + + + + + diff --git a/front_vue/src/components/ui/SelectDropdown.vue b/front_vue/src/components/ui/SelectDropdown.vue new file mode 100644 index 0000000..c4cff71 --- /dev/null +++ b/front_vue/src/components/ui/SelectDropdown.vue @@ -0,0 +1,369 @@ + + + + + diff --git a/front_vue/src/components/ui/SlidePanel.vue b/front_vue/src/components/ui/SlidePanel.vue new file mode 100644 index 0000000..d3aeb1a --- /dev/null +++ b/front_vue/src/components/ui/SlidePanel.vue @@ -0,0 +1,282 @@ + + + + + diff --git a/front_vue/src/components/ui/TagsSelect.vue b/front_vue/src/components/ui/TagsSelect.vue new file mode 100644 index 0000000..7243a75 --- /dev/null +++ b/front_vue/src/components/ui/TagsSelect.vue @@ -0,0 +1,121 @@ + + + + + diff --git a/front_vue/src/components/ui/TextInput.vue b/front_vue/src/components/ui/TextInput.vue new file mode 100644 index 0000000..53cb79e --- /dev/null +++ b/front_vue/src/components/ui/TextInput.vue @@ -0,0 +1,68 @@ + + + + + diff --git a/front_vue/src/views/MainApp.vue b/front_vue/src/views/MainApp.vue index a8231f9..e1f1905 100644 --- a/front_vue/src/views/MainApp.vue +++ b/front_vue/src/views/MainApp.vue @@ -73,12 +73,13 @@ @close="closePanel" @save="handleSaveTask" @delete="handleDeleteTask" + @archive="handleArchiveTask" />