first commit

This commit is contained in:
exercict
2026-07-14 08:10:11 +04:00
commit fdfb0dd62e
60 changed files with 9930 additions and 0 deletions
+367
View File
@@ -0,0 +1,367 @@
<?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>