/
Codename-Nik
/
WebApp
Обзор
Документация
Войти
/
Codename-Nik
/
WebApp
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
frontend/src/components/TaskForm.vue
272 строки
7 KB
Nikolay Pokhodnya
first commit
03 ноя 2025, 19:47
03 ноя 2025, 19:47
a9fbdee
Код
Авторство
О чём код?
<template> <v-card :max-width="600" class="mx-auto"> <v-card-title class="d-flex align-center"> <v-icon :icon="editMode ? 'mdi-pencil' : 'mdi-plus'" class="mr-2"></v-icon> {{ editMode ? 'Редактирование задачи' : 'Новая задача' }} </v-card-title> <v-divider></v-divider> <v-card-text class="pt-4"> <v-form @submit.prevent="handleSubmit"> <v-text-field v-model="form.title" label="Название задачи *" variant="outlined" required :error-messages="errors.title" class="mb-4" ></v-text-field> <v-textarea v-model="form.description" label="Описание" variant="outlined" rows="3" :error-messages="errors.description" class="mb-4" ></v-textarea> <v-row class="mb-4"> <v-col cols="12" sm="6"> <v-select v-model="form.status" label="Статус" :items="statusOptions" variant="outlined" :error-messages="errors.status" ></v-select> </v-col> <v-col cols="12" sm="6"> <v-select v-model="form.priority" label="Приоритет" :items="priorityOptions" variant="outlined" :error-messages="errors.priority" ></v-select> </v-col> </v-row> <v-row class="mb-4"> <v-col cols="12" sm="6"> <v-select v-model="form.project" label="Проект" :items="projectOptions" item-title="name" item-value="id" variant="outlined" clearable :loading="projects.isLoading" :error-messages="errors.project" > <template v-slot:item="{ props, item }"> <v-list-item v-bind="props"> <template v-slot:prepend> <v-icon :color="item.raw.color">mdi-folder</v-icon> </template> </v-list-item> </template> </v-select> </v-col> <v-col cols="12" sm="6"> <v-text-field v-model="form.due_date" label="Срок выполнения" type="datetime-local" variant="outlined" :error-messages="errors.due_date" ></v-text-field> </v-col> </v-row> <v-select v-model="form.assigned_to" label="Назначить на" :items="userOptions" item-title="full_name" item-value="id" variant="outlined" clearable class="mb-4" > <template v-slot:item="{ props, item }"> <v-list-item v-bind="props"> <template v-slot:prepend> <v-avatar size="32" class="mr-2"> <v-icon>mdi-account</v-icon> </v-avatar> </template> </v-list-item> </template> </v-select> <v-alert v-if="isOverdue" type="warning" variant="tonal" class="mb-4" > <div class="d-flex align-center"> <v-icon icon="mdi-alert" class="mr-2"></v-icon> Срок выполнения истек </div> </v-alert> <v-alert v-if="tasks.error" type="error" variant="tonal" class="mb-4" > {{ tasks.error }} </v-alert> <v-card-actions class="px-0"> <v-spacer></v-spacer> <v-btn @click="$emit('cancel')" variant="text" :disabled="tasks.isLoading" > Отмена </v-btn> <v-btn type="submit" color="primary" variant="flat" :loading="tasks.isLoading" :disabled="!isFormValid" > <v-icon :icon="editMode ? 'mdi-content-save' : 'mdi-plus'" class="mr-2"></v-icon> {{ editMode ? 'Сохранить' : 'Создать' }} </v-btn> </v-card-actions> </v-form> </v-card-text> </v-card> </template> <script setup lang="ts"> import { reactive, computed, onMounted, watch } from 'vue' import { useTasksStore } from '@/stores/tasks' import { useProjectsStore } from '@/stores/projects' import type { TaskCreateData } from '@/types/tasks' interface Props { task?: TaskCreateData & { id?: number } } interface Emits { (e: 'submit', data: TaskCreateData): void (e: 'cancel'): void } const props = defineProps<Props>() const emit = defineEmits<Emits>() const tasks = useTasksStore() const projects = useProjectsStore() const editMode = computed(() => !!props.task?.id) const form = reactive<TaskCreateData>({ title: props.task?.title || '', description: props.task?.description || '', status: props.task?.status || 'todo', priority: props.task?.priority || 'medium', due_date: props.task?.due_date || null, project: props.task?.project || null, assigned_to: props.task?.assigned_to || null }) const errors = reactive<{ [key: string]: string }>({}) const statusOptions = [ { title: '📝 To Do', value: 'todo' }, { title: '🔄 In Progress', value: 'in_progress' }, { title: '👀 Review', value: 'review' }, { title: '✅ Done', value: 'done' } ] const priorityOptions = [ { title: '🟢 Low', value: 'low' }, { title: '🟡 Medium', value: 'medium' }, { title: '🔴 High', value: 'high' }, { title: '⚫ Urgent', value: 'urgent' } ] const projectOptions = computed(() => { return projects.projects.map(project => ({ ...project, name: `${project.name} (${project.task_count || 0} задач)` })) }) const userOptions = computed(() => [ { id: 1, full_name: 'Иван Иванов', username: 'ivanov' }, { id: 2, full_name: 'Петр Петров', username: 'petrov' }, { id: 3, full_name: 'Мария Сидорова', username: 'sidorova' } ]) const isFormValid = computed(() => { return form.title.trim().length > 0 }) const isOverdue = computed(() => { if (!form.due_date) return false const dueDate = new Date(form.due_date) const now = new Date() return dueDate < now }) const handleSubmit = () => { Object.keys(errors).forEach(key => delete errors[key]) if (!form.title.trim()) { errors.title = 'Название задачи обязательно' notifications.error('Заполните название задачи') return } if (form.title.length < 3) { errors.title = 'Название должно содержать минимум 3 символа' return } if (form.due_date && new Date(form.due_date) < new Date()) { errors.due_date = 'Срок выполнения не может быть в прошлом' return } emit('submit', { ...form }) } onMounted(() => { if (projects.projects.length === 0) { projects.fetchProjects() } }) watch(() => props.task, (newTask) => { if (newTask) { Object.assign(form, newTask) } }, { immediate: true }) watch(() => form.title, () => { if (errors.title) delete errors.title }) watch(() => form.due_date, () => { if (errors.due_date) delete errors.due_date }) </script> <style scoped> .v-card { border-radius: 12px; } </style>