start tasks module
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
{% extends 'layouts/base.twig' %}
|
||||
|
||||
{% block title %}{{ title }} — Бизнес.Точка{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="h3 mb-0">{{ title }}</h1>
|
||||
<div class="btn-group">
|
||||
<a href="{{ base_url('/tasks') }}" class="btn btn-outline-secondary">
|
||||
<i class="fa-solid fa-list me-2"></i>Список
|
||||
</a>
|
||||
<a href="{{ base_url('/tasks/kanban') }}" class="btn btn-outline-primary">
|
||||
<i class="fa-solid fa-table-columns me-2"></i>Канбан
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Статистика #}
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-3">
|
||||
<div class="card h-100 border-0 shadow-sm">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title text-muted mb-0">Всего</h5>
|
||||
<h2 class="mb-0">{{ stats.total }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card h-100 border-0 shadow-sm">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title text-muted mb-0">Выполнено</h5>
|
||||
<h2 class="mb-0 text-success">{{ stats.completed }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card h-100 border-0 shadow-sm">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title text-muted mb-0">В ожидании</h5>
|
||||
<h2 class="mb-0 text-primary">{{ stats.pending }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card h-100 border-0 shadow-sm">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title text-muted mb-0">Просрочено</h5>
|
||||
<h2 class="mb-0 text-danger">{{ stats.overdue }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||||
<div class="btn-group">
|
||||
<a href="{{ base_url('/tasks/calendar?month=' ~ prevMonth) }}" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="fa-solid fa-chevron-left"></i>
|
||||
</a>
|
||||
<a href="{{ base_url('/tasks/calendar?month=' ~ nextMonth) }}" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="fa-solid fa-chevron-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
<h5 class="mb-0">{{ monthName }}</h5>
|
||||
<a href="{{ base_url('/tasks/calendar') }}" class="btn btn-outline-secondary btn-sm">
|
||||
Сегодня
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="calendar-container">
|
||||
{# Дни недели #}
|
||||
<div class="calendar-weekdays mb-2">
|
||||
{% for day in ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'] %}
|
||||
<div class="calendar-weekday text-center text-muted fw-bold" style="flex: 1;">{{ day }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{# Календарная сетка #}
|
||||
<div class="calendar-grid">
|
||||
{% set firstDay = firstDayOfWeek %}
|
||||
{% set daysInMonth = daysInMonth %}
|
||||
|
||||
{# Пустые ячейки до первого дня #}
|
||||
{% for i in 0..(firstDay - 1) %}
|
||||
<div class="calendar-day p-2 border bg-light"></div>
|
||||
{% endfor %}
|
||||
|
||||
{# Дни месяца #}
|
||||
{% for day in 1..daysInMonth %}
|
||||
{% set dateStr = currentMonth ~ '-' ~ (day < 10 ? '0' ~ day : day) %}
|
||||
{% set isToday = dateStr == today %}
|
||||
{% set isPast = dateStr < today %}
|
||||
{% set dayEvents = eventsByDate[dateStr]|default([]) %}
|
||||
|
||||
<div class="calendar-day p-2 border {% if isToday %}bg-primary bg-opacity-10{% endif %}">
|
||||
<div class="d-flex justify-content-between align-items-start mb-1">
|
||||
<span class="calendar-day-number fw-bold {% if isToday %}text-primary{% endif %}">{{ day }}</span>
|
||||
{% if dayEvents|length > 0 %}
|
||||
<span class="badge bg-primary" style="font-size: 0.6rem;">{{ dayEvents|length }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="calendar-events">
|
||||
{% for event in dayEvents|slice(0, 3) %}
|
||||
<a href="{{ base_url(event.url) }}"
|
||||
class="calendar-event d-block text-decoration-none mb-1 px-1 py-1 rounded small"
|
||||
style="font-size: 0.75rem; background-color: {{ event.column_color }}20; border-left: 3px solid {{ event.column_color }}; color: #333;"
|
||||
title="{{ event.title }}">
|
||||
<i class="fa-solid fa-circle me-1" style="font-size: 0.5rem; color: {{ event.column_color }};"></i>
|
||||
{{ event.title|length > 15 ? event.title|slice(0, 15) ~ '...' : event.title }}
|
||||
{% if event.priority == 'urgent' or event.priority == 'high' %}
|
||||
<i class="fa-solid fa-flag text-danger ms-1" style="font-size: 0.5rem;"></i>
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
|
||||
{% if dayEvents|length > 3 %}
|
||||
<div class="text-muted small text-center">
|
||||
+{{ dayEvents|length - 3 }} ещё
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{# Пустые ячейки после последнего дня #}
|
||||
{% set remaining = 7 - ((firstDay + daysInMonth) % 7) %}
|
||||
{% if remaining < 7 %}
|
||||
{% for i in 1..remaining %}
|
||||
<div class="calendar-day p-2 border bg-light"></div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.calendar-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
gap: 1px;
|
||||
background-color: #dee2e6;
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.calendar-day {
|
||||
min-height: 100px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.calendar-day.bg-light {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.calendar-event:hover {
|
||||
background-color: {{ event.column_color }}40 !important;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Навигация по месяцам при клике на заголовок
|
||||
document.querySelector('h5.mb-0').style.cursor = 'pointer';
|
||||
document.querySelector('h5.mb-0').addEventListener('click', function() {
|
||||
window.location.href = '{{ base_url('/tasks/calendar') }}';
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,141 @@
|
||||
{% extends 'layouts/base.twig' %}
|
||||
|
||||
{% block title %}{{ title }} — Бизнес.Точка{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="h3 mb-0">{{ title }}</h1>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<form action="{{ actionUrl }}" method="post" id="task-form">
|
||||
{{ csrf_field()|raw }}
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="title" class="form-label">Название *</label>
|
||||
<input type="text" class="form-control" id="title" name="title" required
|
||||
value="{{ task.title|default('') }}">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="description" class="form-label">Описание</label>
|
||||
<textarea class="form-control" id="description" name="description" rows="4">{{ task.description|default('') }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="board_id" class="form-label">Доска</label>
|
||||
<select class="form-select" id="board_id" name="board_id" required>
|
||||
{% for board in boards %}
|
||||
<option value="{{ board.id }}" {{ (task.board_id|default(selectedBoard) == board.id) ? 'selected' : '' }}>
|
||||
{{ board.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="column_id" class="form-label">Статус</label>
|
||||
<select class="form-select" id="column_id" name="column_id" required>
|
||||
{# Заполняется через JS #}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="priority" class="form-label">Приоритет</label>
|
||||
<select class="form-select" id="priority" name="priority">
|
||||
{% for value, label in priorities %}
|
||||
<option value="{{ value }}" {{ (task.priority|default('medium') == value) ? 'selected' : '' }}>
|
||||
{{ label }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="due_date" class="form-label">Срок выполнения</label>
|
||||
<input type="date" class="form-control" id="due_date" name="due_date"
|
||||
value="{{ task.due_date|default('') }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Исполнители</label>
|
||||
<select class="form-select" id="assignees" name="assignees[]" multiple>
|
||||
{% for userId, userName in users %}
|
||||
<option value="{{ userId }}" {% if task.assignees and userId in task.assignees|map(a => a.user_id) %}selected{% endif %}>
|
||||
{{ userName }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div class="form-text">Выберите нескольких исполнителей, удерживая Ctrl/Cmd</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fa-solid fa-check me-2"></i>Сохранить
|
||||
</button>
|
||||
<a href="{{ base_url('/tasks') }}" class="btn btn-outline-secondary">Отмена</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-light">
|
||||
<h5 class="mb-0">Информация</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if task %}
|
||||
<p class="mb-1"><strong>Создано:</strong> {{ task.created_at|date('d.m.Y H:i') }}</p>
|
||||
<p class="mb-1"><strong>Автор:</strong> {{ task.created_by_name|default('—') }}</p>
|
||||
{% if task.completed_at %}
|
||||
<p class="mb-1 text-success"><strong>Завершено:</strong> {{ task.completed_at|date('d.m.Y H:i') }}</p>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<p class="text-muted mb-0">Заполните форму для создания новой задачи</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Загрузка колонок при изменении доски
|
||||
const boardSelect = document.getElementById('board_id');
|
||||
const columnSelect = document.getElementById('column_id');
|
||||
|
||||
function loadColumns(boardId, selectedColumnId) {
|
||||
fetch(`/api/tasks/columns?board_id=${boardId}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
columnSelect.innerHTML = '';
|
||||
data.columns.forEach(column => {
|
||||
const option = document.createElement('option');
|
||||
option.value = column.id;
|
||||
option.textContent = column.name;
|
||||
if (column.id == selectedColumnId) {
|
||||
option.selected = true;
|
||||
}
|
||||
columnSelect.appendChild(option);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const currentColumnId = {{ task.column_id|default(0) }};
|
||||
loadColumns(boardSelect.value, currentColumnId || null);
|
||||
|
||||
boardSelect.addEventListener('change', function() {
|
||||
loadColumns(this.value, null);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,72 @@
|
||||
{% extends 'layouts/base.twig' %}
|
||||
|
||||
{% block title %}{{ title }} — Бизнес.Точка{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="h3 mb-0">{{ title }}</h1>
|
||||
<a href="{{ base_url('/tasks/new') }}" class="btn btn-primary">
|
||||
<i class="fa-solid fa-plus me-2"></i>Создать задачу
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{# Статистика #}
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-3">
|
||||
<div class="card h-100 border-0 shadow-sm">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title text-muted mb-0">Всего</h5>
|
||||
<h2 class="mb-0">{{ stats.total }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card h-100 border-0 shadow-sm">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title text-muted mb-0">Выполнено</h5>
|
||||
<h2 class="mb-0 text-success">{{ stats.completed }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card h-100 border-0 shadow-sm">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title text-muted mb-0">В ожидании</h5>
|
||||
<h2 class="mb-0 text-primary">{{ stats.pending }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card h-100 border-0 shadow-sm">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title text-muted mb-0">Просрочено</h5>
|
||||
<h2 class="mb-0 text-danger">{{ stats.overdue }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Переключатель видов #}
|
||||
<div class="btn-group mb-4" role="group">
|
||||
<a href="{{ base_url('/tasks') }}" class="btn btn-outline-primary active">
|
||||
<i class="fa-solid fa-list me-2"></i>Список
|
||||
</a>
|
||||
<a href="{{ base_url('/tasks/kanban') }}" class="btn btn-outline-primary">
|
||||
<i class="fa-solid fa-table-columns me-2"></i>Канбан
|
||||
</a>
|
||||
<a href="{{ base_url('/tasks/calendar') }}" class="btn btn-outline-primary">
|
||||
<i class="fa-solid fa-calendar me-2"></i>Календарь
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{# Таблица задач #}
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
{{ tableHtml|raw }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ base_url('assets/js/modules/DataTable.js') }}"></script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,199 @@
|
||||
{% extends 'layouts/base.twig' %}
|
||||
|
||||
{% block title %}{{ title }} — Бизнес.Точка{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="h3 mb-0">{{ title }}</h1>
|
||||
<div class="d-flex gap-3 align-items-center">
|
||||
{# Выбор доски #}
|
||||
<select class="form-select" style="width: 200px" onchange="window.location.href = '{{ base_url('/tasks/kanban?board=') }}' + this.value">
|
||||
{% for b in boards %}
|
||||
<option value="{{ b.id }}" {{ board.id == b.id ? 'selected' : '' }}>{{ b.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<div class="btn-group">
|
||||
<a href="{{ base_url('/tasks') }}" class="btn btn-outline-secondary">
|
||||
<i class="fa-solid fa-list me-2"></i>Список
|
||||
</a>
|
||||
<a href="{{ base_url('/tasks/calendar') }}" class="btn btn-outline-primary">
|
||||
<i class="fa-solid fa-calendar me-2"></i>Календарь
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<a href="{{ base_url('/tasks/new?board=' ~ board.id) }}" class="btn btn-primary">
|
||||
<i class="fa-solid fa-plus me-2"></i>Добавить задачу
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Статистика #}
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-3">
|
||||
<div class="card h-100 border-0 shadow-sm">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title text-muted mb-0">Всего</h5>
|
||||
<h2 class="mb-0">{{ stats.total }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card h-100 border-0 shadow-sm">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title text-muted mb-0">Выполнено</h5>
|
||||
<h2 class="mb-0 text-success">{{ stats.completed }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card h-100 border-0 shadow-sm">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title text-muted mb-0">В ожидании</h5>
|
||||
<h2 class="mb-0 text-primary">{{ stats.pending }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card h-100 border-0 shadow-sm">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title text-muted mb-0">Просрочено</h5>
|
||||
<h2 class="mb-0 text-danger">{{ stats.overdue }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Канбан доска #}
|
||||
<div class="kanban-container" style="overflow-x: auto; padding-bottom: 1rem;">
|
||||
<div class="d-flex gap-3" style="min-width: max-content;">
|
||||
{% for column in kanbanColumns %}
|
||||
<div class="kanban-column" style="width: 320px; min-width: 320px;">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header d-flex justify-content-between align-items-center" style="background-color: {{ column.color }}; color: white;">
|
||||
<h6 class="mb-0 fw-bold">{{ column.name }}</h6>
|
||||
<span class="badge bg-light text-dark">{{ column.items|length }}</span>
|
||||
</div>
|
||||
<div class="card-body p-2 kanban-items" data-column-id="{{ column.id }}"
|
||||
style="min-height: 400px; max-height: 600px; overflow-y: auto;">
|
||||
|
||||
{% for item in column.items %}
|
||||
<div class="card mb-2 kanban-item" data-task-id="{{ item.id }}" style="cursor: grab;">
|
||||
<div class="card-body p-3">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<h6 class="card-title mb-0">{{ item.title }}</h6>
|
||||
{% if item.priority == 'urgent' %}
|
||||
<span class="badge bg-danger" style="font-size: 0.6rem;">Срочно</span>
|
||||
{% elseif item.priority == 'high' %}
|
||||
<span class="badge bg-warning text-dark" style="font-size: 0.6rem;">Высокий</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if item.description %}
|
||||
<p class="card-text text-muted small mb-2">
|
||||
{{ item.description|length > 50 ? item.description|slice(0, 50) ~ '...' : item.description }}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
{% if item.due_date %}
|
||||
<small class="text-muted">
|
||||
<i class="fa-regular fa-calendar me-1"></i>
|
||||
{{ item.due_date|date('d.m') }}
|
||||
{% if item.due_date < date('Y-m-d') %}
|
||||
<span class="text-danger">!</span>
|
||||
{% endif %}
|
||||
</small>
|
||||
{% endif %}
|
||||
<a href="{{ base_url('/tasks/' ~ item.id) }}" class="btn btn-sm btn-outline-primary">
|
||||
<i class="fa-solid fa-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{# Кнопка добавления #}
|
||||
<a href="{{ base_url('/tasks/new?board=' ~ board.id ~ '&column=' ~ column.id) }}" class="btn btn-outline-secondary btn-sm w-100 mt-2">
|
||||
<i class="fa-solid fa-plus me-1"></i>Добавить задачу
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="move-task-form" action="{{ base_url('/tasks/move-column') }}" method="post" style="display: none;">
|
||||
{{ csrf_field|raw }}
|
||||
<input type="hidden" name="task_id" id="move-task-id">
|
||||
<input type="hidden" name="column_id" id="move-column-id">
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Drag and drop для Канбана
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const columns = document.querySelectorAll('.kanban-items');
|
||||
let draggedItem = null;
|
||||
|
||||
columns.forEach(column => {
|
||||
column.addEventListener('dragover', function(e) {
|
||||
e.preventDefault();
|
||||
this.style.backgroundColor = '#f8f9fa';
|
||||
});
|
||||
|
||||
column.addEventListener('dragleave', function() {
|
||||
this.style.backgroundColor = '';
|
||||
});
|
||||
|
||||
column.addEventListener('drop', function(e) {
|
||||
e.preventDefault();
|
||||
this.style.backgroundColor = '';
|
||||
|
||||
if (draggedItem && draggedItem.dataset.taskId) {
|
||||
const taskId = draggedItem.dataset.taskId;
|
||||
const newColumnId = this.dataset.columnId;
|
||||
|
||||
// Отправляем запрос на перемещение
|
||||
document.getElementById('move-task-id').value = taskId;
|
||||
document.getElementById('move-column-id').value = newColumnId;
|
||||
|
||||
fetch('{{ base_url('/tasks/move-column') }}', {
|
||||
method: 'POST',
|
||||
body: new FormData(document.getElementById('move-task-form'))
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Перезагружаем страницу для обновления
|
||||
location.reload();
|
||||
} else {
|
||||
alert('Ошибка при перемещении задачи');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('Ошибка при перемещении задачи');
|
||||
});
|
||||
}
|
||||
|
||||
draggedItem = null;
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('.kanban-item').forEach(item => {
|
||||
item.addEventListener('dragstart', function() {
|
||||
draggedItem = this;
|
||||
this.style.opacity = '0.5';
|
||||
});
|
||||
|
||||
item.addEventListener('dragend', function() {
|
||||
this.style.opacity = '1';
|
||||
draggedItem = null;
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,180 @@
|
||||
{% extends 'layouts/base.twig' %}
|
||||
|
||||
{% block title %}{{ title }} — Бизнес.Точка{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="h3 mb-0">{{ title }}</h1>
|
||||
<div class="btn-group">
|
||||
<a href="{{ base_url('/tasks') }}" class="btn btn-outline-secondary">
|
||||
<i class="fa-solid fa-list me-2"></i>Список
|
||||
</a>
|
||||
<a href="{{ base_url('/tasks/kanban') }}" class="btn btn-outline-primary">
|
||||
<i class="fa-solid fa-table-columns me-2"></i>Канбан
|
||||
</a>
|
||||
<a href="{{ base_url('/tasks/calendar') }}" class="btn btn-outline-primary">
|
||||
<i class="fa-solid fa-calendar me-2"></i>Календарь
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-header bg-light d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">
|
||||
{% if task.priority == 'urgent' %}
|
||||
<span class="badge bg-danger me-2">Срочно</span>
|
||||
{% elseif task.priority == 'high' %}
|
||||
<span class="badge bg-warning text-dark me-2">Высокий</span>
|
||||
{% elseif task.priority == 'low' %}
|
||||
<span class="badge bg-secondary me-2">Низкий</span>
|
||||
{% endif %}
|
||||
{{ task.title }}
|
||||
</h5>
|
||||
<div>
|
||||
{% if not task.completed_at %}
|
||||
<form action="{{ base_url('/tasks/' ~ task.id ~ '/complete') }}" method="post" class="d-inline">
|
||||
{{ csrf_field()|raw }}
|
||||
<button type="submit" class="btn btn-success btn-sm">
|
||||
<i class="fa-solid fa-check me-1"></i>Завершить
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<form action="{{ base_url('/tasks/' ~ task.id ~ '/reopen') }}" method="post" class="d-inline">
|
||||
{{ csrf_field()|raw }}
|
||||
<button type="submit" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="fa-solid fa-rotate-left me-1"></i>Вернуть в работу
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<a href="{{ base_url('/tasks/' ~ task.id ~ '/edit') }}" class="btn btn-outline-primary btn-sm">
|
||||
<i class="fa-solid fa-pen me-1"></i>Редактировать
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<p class="mb-1"><strong>Статус:</strong></p>
|
||||
<span class="badge" style="background-color: {{ task.column_color|default('#6B7280') }}">
|
||||
{{ task.column_name|default('—') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<p class="mb-1"><strong>Приоритет:</strong></p>
|
||||
<span class="badge {% if task.priority == 'urgent' %}bg-danger{% elseif task.priority == 'high' %}bg-warning text-dark{% elseif task.priority == 'low' %}bg-secondary{% else %}bg-info{% endif %}">
|
||||
{{ task.priorityLabels[task.priority]|default(task.priority) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if task.description %}
|
||||
<div class="mb-3">
|
||||
<p class="mb-1"><strong>Описание:</strong></p>
|
||||
<p class="text-muted">{{ task.description|nl2br }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if task.assignees %}
|
||||
<div class="mb-3">
|
||||
<p class="mb-1"><strong>Исполнители:</strong></p>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
{% for assignee in task.assignees %}
|
||||
<span class="badge bg-light text-dark border">
|
||||
<i class="fa-solid fa-user me-1"></i>
|
||||
{{ assignee.user_name|default(assignee.user_email) }}
|
||||
{% if assignee.role == 'watcher' %}
|
||||
(наблюдатель)
|
||||
{% endif %}
|
||||
</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Комментарии #}
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-light">
|
||||
<h5 class="mb-0">Комментарии</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted text-center">Комментарии будут доступны в следующей версии</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card border-0 shadow-sm mb-3">
|
||||
<div class="card-header bg-light">
|
||||
<h6 class="mb-0">Детали</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="mb-2">
|
||||
<i class="fa-regular fa-calendar me-2 text-muted"></i>
|
||||
<strong>Срок:</strong>
|
||||
{% if task.due_date %}
|
||||
{{ task.due_date|date('d.m.Y') }}
|
||||
{% if task.due_date < date('now') and not task.completed_at %}
|
||||
<span class="text-danger">(просрочено)</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
не указан
|
||||
{% endif %}
|
||||
</p>
|
||||
<p class="mb-2">
|
||||
<i class="fa-regular fa-user me-2 text-muted"></i>
|
||||
<strong>Автор:</strong> {{ task.created_by_name|default('—') }}
|
||||
</p>
|
||||
<p class="mb-2">
|
||||
<i class="fa-regular fa-clock me-2 text-muted"></i>
|
||||
<strong>Создано:</strong> {{ task.created_at|date('d.m.Y H:i') }}
|
||||
</p>
|
||||
{% if task.completed_at %}
|
||||
<p class="mb-0 text-success">
|
||||
<i class="fa-solid fa-check me-2"></i>
|
||||
<strong>Завершено:</strong> {{ task.completed_at|date('d.m.Y H:i') }}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-light">
|
||||
<h6 class="mb-0">Действия</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-grid gap-2">
|
||||
{% if not task.completed_at %}
|
||||
<form action="{{ base_url('/tasks/' ~ task.id ~ '/complete') }}" method="post">
|
||||
{{ csrf_field()|raw }}
|
||||
<button type="submit" class="btn btn-success w-100">
|
||||
<i class="fa-solid fa-check me-2"></i>Отметить как выполненное
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<form action="{{ base_url('/tasks/' ~ task.id ~ '/reopen') }}" method="post">
|
||||
{{ csrf_field()|raw }}
|
||||
<button type="submit" class="btn btn-outline-secondary w-100">
|
||||
<i class="fa-solid fa-rotate-left me-2"></i>Вернуть в работу
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<a href="{{ base_url('/tasks/' ~ task.id ~ '/edit') }}" class="btn btn-outline-primary">
|
||||
<i class="fa-solid fa-pen me-2"></i>Редактировать
|
||||
</a>
|
||||
<form action="{{ base_url('/tasks/' ~ task.id ~ '/delete') }}" method="post"
|
||||
onsubmit="return confirm('Вы уверены, что хотите удалить задачу?')">
|
||||
{{ csrf_field()|raw }}
|
||||
<button type="submit" class="btn btn-outline-danger w-100">
|
||||
<i class="fa-solid fa-trash me-2"></i>Удалить задачу
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user