367 lines
16 KiB
PHP
367 lines
16 KiB
PHP
<?php
|
||
function crm_format_datetime(?string $value): string
|
||
{
|
||
$value = trim((string)$value);
|
||
if ($value === '') return '—';
|
||
|
||
$timestamp = strtotime($value);
|
||
return $timestamp === false ? htmlspecialchars($value) : date('d.m.Y H:i', $timestamp);
|
||
}
|
||
|
||
function crm_datetime_local(?string $value): string
|
||
{
|
||
$value = trim((string)$value);
|
||
if ($value === '') return '';
|
||
|
||
$timestamp = strtotime($value);
|
||
return $timestamp === false ? '' : date('Y-m-d\TH:i', $timestamp);
|
||
}
|
||
|
||
function crm_hide_custom_field(array $field): bool
|
||
{
|
||
$fieldName = mb_strtolower(trim((string)($field['name'] ?? '')));
|
||
$fieldCode = mb_strtolower(trim((string)($field['code'] ?? '')));
|
||
|
||
return in_array($fieldName, [
|
||
'дата создания',
|
||
'дата постановки',
|
||
'дата план',
|
||
'дата факта',
|
||
'дата факт',
|
||
], true) || in_array($fieldCode, [
|
||
'task_created_at',
|
||
'planned_at',
|
||
'completed_at',
|
||
'date_created',
|
||
'created_date',
|
||
], true);
|
||
}
|
||
?>
|
||
|
||
<div class="modal-header row">
|
||
<h5 class="modal-title fs-5 col-9">
|
||
<?= htmlspecialchars((string)$task['name']) ?>
|
||
</h5>
|
||
|
||
<div class="col-3 d-flex gap-2 align-items-center">
|
||
<?php if (!empty($canEditTask)): ?>
|
||
<button type="button" class="btn btn-outline-primary btn-sm" id="taskEditBtn">
|
||
Редактировать задачу
|
||
</button>
|
||
<?php endif; ?>
|
||
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal-body">
|
||
|
||
<form id="taskEditForm" method="post" action="/tasks/update">
|
||
<input type="hidden" name="task_id" value="<?= (int)$task['id'] ?>">
|
||
|
||
<div class="row g-3 mb-3">
|
||
<div class="col-md-6">
|
||
<strong>CRM ID:</strong> <?= htmlspecialchars((string)$task['crm_id']) ?>
|
||
</div>
|
||
|
||
<div class="col-md-6">
|
||
<strong>Постановщик:</strong> <?= htmlspecialchars((string)($task['creator_name'] ?? '')) ?>
|
||
</div>
|
||
|
||
<div class="col-md-6">
|
||
<strong>Ответственный:</strong> <?= htmlspecialchars((string)($task['assignee_name'] ?? '')) ?>
|
||
</div>
|
||
|
||
<div class="col-md-6">
|
||
<strong>Статус:</strong> <?= htmlspecialchars(task_status_label((string)$task['status'])) ?>
|
||
</div>
|
||
|
||
<div class="col-md-6">
|
||
<strong>Приоритет:</strong> <?= htmlspecialchars(task_priority_label((string)$task['priority'])) ?>
|
||
</div>
|
||
|
||
<div class="col-md-6">
|
||
<strong>Дата постановки:</strong>
|
||
<span class="task-view-value"><?= crm_format_datetime($task['task_created_at'] ?? null) ?></span>
|
||
|
||
<input
|
||
type="datetime-local"
|
||
name="task_created_at"
|
||
class="form-control task-edit-field d-none mt-1"
|
||
value="<?= htmlspecialchars(crm_datetime_local($task['task_created_at'] ?? null)) ?>"
|
||
<?= empty($canEditTask) ? 'disabled' : '' ?>
|
||
>
|
||
</div>
|
||
|
||
<div class="col-md-6">
|
||
<strong>Дата план:</strong>
|
||
<span class="task-view-value"><?= crm_format_datetime($task['planned_at'] ?? null) ?></span>
|
||
|
||
<input
|
||
type="datetime-local"
|
||
name="planned_at"
|
||
class="form-control task-edit-field d-none mt-1"
|
||
value="<?= htmlspecialchars(crm_datetime_local($task['planned_at'] ?? null)) ?>"
|
||
<?= empty($canEditTask) ? 'disabled' : '' ?>
|
||
>
|
||
</div>
|
||
|
||
<div class="col-md-6">
|
||
<strong>Дата факт:</strong>
|
||
<?= crm_format_datetime($task['completed_at'] ?? null) ?>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mb-4">
|
||
<label class="form-label fw-bold">Наименование</label>
|
||
|
||
<div class="task-view-value border rounded p-3 bg-light">
|
||
<?= htmlspecialchars((string)$task['name']) ?>
|
||
</div>
|
||
|
||
<input
|
||
type="text"
|
||
name="name"
|
||
class="form-control task-edit-field d-none"
|
||
value="<?= htmlspecialchars((string)$task['name']) ?>"
|
||
<?= empty($canEditTask) ? 'disabled' : '' ?>
|
||
>
|
||
</div>
|
||
|
||
<div class="mb-4">
|
||
<label class="form-label fw-bold">Описание задачи</label>
|
||
|
||
<div class="task-view-value border rounded p-3 bg-light">
|
||
<?= nl2br(htmlspecialchars((string)($task['description'] ?? ''))) ?>
|
||
</div>
|
||
|
||
<textarea
|
||
name="description"
|
||
class="form-control task-edit-field d-none"
|
||
rows="4"
|
||
<?= empty($canEditTask) ? 'disabled' : '' ?>
|
||
><?= htmlspecialchars((string)($task['description'] ?? '')) ?></textarea>
|
||
</div>
|
||
|
||
<?php if (!empty($customFields)): ?>
|
||
<div class="mb-4">
|
||
<label class="form-label fw-bold">Поля доски</label>
|
||
|
||
<div class="row g-3">
|
||
<?php foreach ($customFields as $field): ?>
|
||
<?php if (crm_hide_custom_field($field)) continue; ?>
|
||
|
||
<div class="col-md-6">
|
||
<div class="border rounded p-3 bg-light h-100">
|
||
<label class="fw-semibold mb-1">
|
||
<?= htmlspecialchars((string)$field['name']) ?>
|
||
</label>
|
||
|
||
<div class="task-view-value">
|
||
<?= trim((string)($field['value'] ?? '')) !== ''
|
||
? nl2br(htmlspecialchars((string)$field['value']))
|
||
: '<span class="text-muted">—</span>' ?>
|
||
</div>
|
||
|
||
<input
|
||
type="text"
|
||
name="custom_field_<?= (int)$field['id'] ?>"
|
||
class="form-control task-edit-field d-none"
|
||
value="<?= htmlspecialchars((string)($field['value'] ?? '')) ?>"
|
||
<?= empty($canEditTask) ? 'disabled' : '' ?>
|
||
>
|
||
</div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php if (!empty($canEditTask)): ?>
|
||
<div class="mt-4 mb-4 d-none" id="taskEditActions">
|
||
<button type="submit" class="btn btn-success">
|
||
Сохранить
|
||
</button>
|
||
|
||
<button type="button" class="btn btn-secondary" id="taskCancelEditBtn">
|
||
Отмена
|
||
</button>
|
||
</div>
|
||
<?php endif; ?>
|
||
</form>
|
||
|
||
<div class="mb-4">
|
||
<label class="form-label fw-bold">Доска</label>
|
||
<div class="border rounded p-3 bg-light">
|
||
<div><strong>Название:</strong> <?= htmlspecialchars((string)($task['board_name'] ?? '')) ?></div>
|
||
<div><strong>Код:</strong> <?= htmlspecialchars((string)($task['board_code'] ?? '')) ?></div>
|
||
|
||
<?php if (!empty($task['board_description'])): ?>
|
||
<div class="mt-2">
|
||
<strong>Описание:</strong><br>
|
||
<?= nl2br(htmlspecialchars((string)$task['board_description'])) ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
|
||
<?php if (!empty($canEditTask)): ?>
|
||
<hr>
|
||
|
||
<h6 class="mb-3">Управление статусом</h6>
|
||
|
||
<div class="d-flex flex-wrap gap-2 mb-4">
|
||
<?php
|
||
$statuses = [
|
||
'NEW' => ['Новая', 'btn-outline-secondary'],
|
||
'IN_PROGRESS' => ['В работу', 'btn-outline-primary'],
|
||
'REVIEW' => ['На проверку', 'btn-outline-warning'],
|
||
'CANCELED' => ['Отменить', 'btn-outline-dark'],
|
||
'DONE' => ['Завершить', 'btn-success fw-semibold'],
|
||
];
|
||
?>
|
||
|
||
<?php foreach ($statuses as $statusCode => [$statusLabel, $btnClass]): ?>
|
||
<form method="post" action="/tasks/update-status" class="d-inline js-task-status-form" data-task-id="<?= (int)$task['id'] ?>">
|
||
<input type="hidden" name="task_id" value="<?= (int)$task['id'] ?>">
|
||
<input type="hidden" name="status" value="<?= htmlspecialchars($statusCode) ?>">
|
||
<button type="submit" class="btn <?= htmlspecialchars($btnClass) ?> btn-sm">
|
||
<?= htmlspecialchars($statusLabel) ?>
|
||
</button>
|
||
</form>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
|
||
<hr>
|
||
|
||
<h6 class="mb-3">Файлы</h6>
|
||
|
||
<form method="post" action="/tasks/files/upload" enctype="multipart/form-data" class="mb-4 js-task-file-upload-form" data-task-id="<?= (int)$task['id'] ?>">
|
||
<input type="hidden" name="task_id" value="<?= (int)$task['id'] ?>">
|
||
|
||
<div class="row g-2 align-items-end">
|
||
<div class="col-md-8">
|
||
<label class="form-label">Загрузить файл</label>
|
||
<input type="file" name="file" class="form-control" required>
|
||
</div>
|
||
|
||
<div class="col-md-4">
|
||
<button type="submit" class="btn btn-primary w-100">Загрузить файл</button>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
<?php endif; ?>
|
||
|
||
<div class="files-list mb-4">
|
||
<?php if (empty($files)): ?>
|
||
<div class="text-muted">Файлов пока нет.</div>
|
||
<?php else: ?>
|
||
<div class="row g-3">
|
||
<?php foreach ($files as $file): ?>
|
||
<div class="col-12 col-md-6">
|
||
<div class="border rounded p-3 h-100">
|
||
<?php if (!empty($file['is_image'])): ?>
|
||
<div class="mb-3 text-center">
|
||
<img
|
||
src="/tasks/files/view?id=<?= (int)$file['id'] ?>"
|
||
alt="<?= htmlspecialchars((string)$file['original_name']) ?>"
|
||
class="img-fluid rounded border file-preview-image"
|
||
style="max-height: 180px; object-fit: cover; cursor: pointer;"
|
||
data-image-modal
|
||
data-image-src="/tasks/files/view?id=<?= (int)$file['id'] ?>"
|
||
data-image-title="<?= htmlspecialchars((string)$file['original_name']) ?>"
|
||
>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<div class="fw-semibold mb-1">
|
||
<?= htmlspecialchars((string)$file['original_name']) ?>
|
||
</div>
|
||
|
||
<div class="small text-muted">
|
||
Размер: <?= number_format(((int)$file['file_size']) / 1024, 1, '.', ' ') ?> KB
|
||
</div>
|
||
|
||
<div class="small text-muted mb-3">
|
||
Загрузил:
|
||
<?= htmlspecialchars((string)($file['display_name'] ?: $file['login'] ?: 'Пользователь')) ?>
|
||
— <?= htmlspecialchars((string)$file['created_at']) ?>
|
||
</div>
|
||
|
||
<div class="d-flex gap-2 flex-wrap">
|
||
<?php if (!empty($file['is_image'])): ?>
|
||
<button
|
||
type="button"
|
||
class="btn btn-sm btn-outline-primary"
|
||
data-image-modal
|
||
data-image-src="/tasks/files/view?id=<?= (int)$file['id'] ?>"
|
||
data-image-title="<?= htmlspecialchars((string)$file['original_name']) ?>"
|
||
>
|
||
Открыть
|
||
</button>
|
||
<?php endif; ?>
|
||
|
||
<a href="/tasks/files/download?id=<?= (int)$file['id'] ?>" class="btn btn-sm btn-outline-secondary">
|
||
Скачать
|
||
</a>
|
||
|
||
<?php if (!empty($canEditTask)): ?>
|
||
<form method="post"
|
||
action="/tasks/files/delete"
|
||
class="d-inline js-task-file-delete-form"
|
||
data-task-id="<?= (int)$task['id'] ?>"
|
||
onsubmit="return confirm('Удалить файл?');">
|
||
<input type="hidden" name="id" value="<?= (int)$file['id'] ?>">
|
||
<button type="submit" class="btn btn-sm btn-outline-danger">Удалить</button>
|
||
</form>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<hr>
|
||
|
||
<h6 class="mb-3">Комментарии</h6>
|
||
|
||
<form method="post" action="/tasks/comment/store" class="mb-4 js-task-comment-form" data-task-id="<?= (int)$task['id'] ?>">
|
||
<input type="hidden" name="task_id" value="<?= (int)$task['id'] ?>">
|
||
|
||
<div class="mb-3">
|
||
<textarea name="text" class="form-control" rows="3" placeholder="Введите комментарий..." required></textarea>
|
||
</div>
|
||
|
||
<button type="submit" class="btn btn-primary">Добавить комментарий</button>
|
||
</form>
|
||
|
||
<div class="comments-list">
|
||
<?php if (empty($comments)): ?>
|
||
<div class="text-muted">Комментариев пока нет.</div>
|
||
<?php else: ?>
|
||
<?php foreach ($comments as $comment): ?>
|
||
<div class="border rounded p-3 mb-3">
|
||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||
<strong>
|
||
<?= htmlspecialchars((string)($comment['display_name'] ?: $comment['login'] ?: 'Пользователь')) ?>
|
||
</strong>
|
||
|
||
<small class="text-muted">
|
||
<?= htmlspecialchars((string)$comment['created_at']) ?>
|
||
</small>
|
||
</div>
|
||
|
||
<div>
|
||
<?= nl2br(htmlspecialchars((string)$comment['text'])) ?>
|
||
</div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
|
||
</div>
|