Files
CRM-IKZ/app/Views/tasks/board.php
T
2026-07-14 08:19:56 +04:00

718 lines
36 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
$baseUrl = '/boards/' . urlencode((string)$board['code']);
$queryBase = [
'status' => $filters['status'] ?? '',
'priority' => $filters['priority'] ?? '',
'assignee_id' => $filters['assignee_id'] ?? '',
'search' => $filters['search'] ?? '',
];
$tableUrl = $baseUrl . '?view=table';
$kanbanUrl = $baseUrl . '?view=kanban';
$cardsUrl = $baseUrl . '?view=cards';
$deadlineRank = function (array $task): int {
$deadline = taskDeadlineState($task['planned_at'] ?? null, $task['status'] ?? null);
return match ($deadline['code'] ?? '') {
'red' => 1,
'yellow' => 2,
'green' => 3,
default => 4,
};
};
usort($tasks, function (array $a, array $b) use ($deadlineRank): int {
$rankA = $deadlineRank($a);
$rankB = $deadlineRank($b);
if ($rankA !== $rankB) {
return $rankA <=> $rankB;
}
return strtotime((string)($a['planned_at'] ?? '9999-12-31')) <=> strtotime((string)($b['planned_at'] ?? '9999-12-31'));
});
if (!empty($columns)) {
foreach ($columns as $statusCode => $column) {
usort($columns[$statusCode]['tasks'], function (array $a, array $b) use ($deadlineRank): int {
$rankA = $deadlineRank($a);
$rankB = $deadlineRank($b);
if ($rankA !== $rankB) {
return $rankA <=> $rankB;
}
return strtotime((string)($a['planned_at'] ?? '9999-12-31')) <=> strtotime((string)($b['planned_at'] ?? '9999-12-31'));
});
}
}
?>
<!-- <div class="alert alert-info mb-3">-->
<!-- view = --><?php //= htmlspecialchars((string)$view) ?>
<!-- </div>-->
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center gap-2 mb-3">
<div>
<h1 class="h3 mb-0"><?= htmlspecialchars((string)$board['name']) ?></h1>
<div class="d-flex flex-wrap gap-2 mt-2">
<span class="badge text-bg-secondary">
Всего: <?= (int)$stats['TOTAL'] ?>
</span>
<span class="badge text-bg-secondary">
Новые: <?= (int)$stats['NEW'] ?>
</span>
<span class="badge text-bg-primary">
В работе: <?= (int)$stats['IN_PROGRESS'] ?>
</span>
<span class="badge text-bg-warning text-dark">
На проверке: <?= (int)$stats['REVIEW'] ?>
</span>
<span class="badge text-bg-success">
Завершено: <?= (int)$stats['DONE'] ?>
</span>
<span class="badge text-bg-danger">
Просрочено: <?= (int)$stats['OVERDUE'] ?>
</span>
</div>
<div class="text-muted small">Код: <?= htmlspecialchars((string)$board['code']) ?></div>
</div>
<div class="d-flex gap-2 flex-wrap">
<a href="<?= htmlspecialchars($tableUrl) ?>" class="btn <?= $view === 'table' ? 'btn-primary' : 'btn-outline-primary' ?>">Таблица</a>
<a href="<?= htmlspecialchars($kanbanUrl) ?>" class="btn <?= $view === 'kanban' ? 'btn-primary' : 'btn-outline-primary' ?>">Канбан</a>
<a href="<?= htmlspecialchars($cardsUrl) ?>" class="btn <?= $view === 'cards' ? 'btn-primary' : 'btn-outline-primary' ?>">Плитки</a>
<a href="/tasks/create?board_code=<?= urlencode((string)$board['code']) ?>" class="btn btn-success">+ Новая задача</a>
</div>
<?php if (!empty($canManageBoard)): ?>
<button class="btn btn-primary"
type="button"
data-bs-toggle="offcanvas"
data-bs-target="#boardSettingsCanvas"
aria-controls="boardSettingsCanvas">
Редактировать доску
</button>
<?php endif; ?>
</div>
<div class="card shadow-sm mb-3">
<div class="card-body">
<form method="get" action="<?= htmlspecialchars($baseUrl) ?>" class="row g-2 align-items-end">
<input type="hidden" name="view" value="<?= htmlspecialchars((string)$view) ?>">
<div class="col-12 col-md-3">
<label class="form-label">Статус</label>
<select name="status" class="form-select">
<option value="">Все</option>
<option value="NEW" <?= ($filters['status'] ?? '') === 'NEW' ? 'selected' : '' ?>>Новая</option>
<option value="IN_PROGRESS" <?= ($filters['status'] ?? '') === 'IN_PROGRESS' ? 'selected' : '' ?>>В работе</option>
<option value="REVIEW" <?= ($filters['status'] ?? '') === 'REVIEW' ? 'selected' : '' ?>>На проверке</option>
<option value="DONE" <?= ($filters['status'] ?? '') === 'DONE' ? 'selected' : '' ?>>Закрыта</option>
<option value="CANCELED" <?= ($filters['status'] ?? '') === 'CANCELED' ? 'selected' : '' ?>>Отменена</option>
<option value="OVERDUE" <?= ($filters['status'] ?? '') === 'OVERDUE' ? 'selected' : '' ?>>Просрочена</option>
</select>
</div>
<div class="col-12 col-md-2">
<label class="form-label">Приоритет</label>
<select name="priority" class="form-select">
<option value="">Все</option>
<option value="LOW" <?= ($filters['priority'] ?? '') === 'LOW' ? 'selected' : '' ?>>Низкий</option>
<option value="MEDIUM" <?= ($filters['priority'] ?? '') === 'MEDIUM' ? 'selected' : '' ?>>Средний</option>
<option value="HIGH" <?= ($filters['priority'] ?? '') === 'HIGH' ? 'selected' : '' ?>>Высокий</option>
<option value="CRITICAL" <?= ($filters['priority'] ?? '') === 'CRITICAL' ? 'selected' : '' ?>>Критический</option>
</select>
</div>
<div class="col-12 col-md-3">
<label class="form-label">Ответственный</label>
<select name="assignee_id" class="form-select">
<option value="">Все</option>
<?php foreach ($users as $u): ?>
<option value="<?= (int)$u['id'] ?>" <?= (int)($filters['assignee_id'] ?? 0) === (int)$u['id'] ? 'selected' : '' ?>>
<?= htmlspecialchars((string)($u['display_name'] ?: $u['login'])) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-12 col-md-3">
<label class="form-label">Поиск</label>
<input type="text" name="search" value="<?= htmlspecialchars((string)($filters['search'] ?? '')) ?>" class="form-control" placeholder="Наименование, заказ, заявитель...">
</div>
<div class="col-12 col-md-auto">
<button type="submit" class="btn btn-primary">Применить</button>
<a href="<?= htmlspecialchars($baseUrl . '?view=' . urlencode((string)$view)) ?>" class="btn btn-outline-secondary">Сбросить</a>
</div>
</form>
</div>
</div>
<?php if ($view === 'table'): ?>
<div class="card shadow-sm">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th>ID</th>
<th>CRM ID</th>
<th>Постановщик</th>
<th>Ответственный</th>
<th>Наименование</th>
<th>Статус</th>
<th>Приоритет</th>
<th>Комментарии</th>
<th>Дата план</th>
</tr>
</thead>
<tbody>
<?php foreach ($tasks as $task): ?>
<?php
$deadline = taskDeadlineState($task['planned_at'] ?? null, $task['status'] ?? null);
$commentCount = (int)($task['comment_count'] ?? 0);
$lastCommentId = (int)($task['last_comment_id'] ?? 0);
$lastReadCommentId = (int)($task['last_read_comment_id'] ?? 0);
$isUnread = $commentCount > 0 && $lastCommentId > $lastReadCommentId;
$badgeClass = $isUnread ? 'bg-primary' : 'bg-secondary';
$statusClass = match ($task['status']) {
'NEW' => 'bg-secondary',
'IN_PROGRESS' => 'bg-primary',
'REVIEW' => 'bg-warning text-dark',
'DONE' => 'bg-success',
'CANCELED' => 'bg-dark',
'OVERDUE' => 'bg-danger',
default => 'bg-secondary',
};
$priorityClass = match ($task['priority']) {
'LOW' => 'bg-light text-dark',
'MEDIUM' => 'bg-secondary',
'HIGH' => 'bg-warning text-dark',
'CRITICAL' => 'bg-danger',
default => 'bg-secondary',
};
?>
<tr class="<?= htmlspecialchars($deadline['class'] ?? '') ?>">
<td><?= (int)$task['id'] ?></td>
<td><?= htmlspecialchars((string)$task['crm_id']) ?></td>
<td><?= htmlspecialchars((string)($task['creator_name'] ?? '')) ?></td>
<td><?= htmlspecialchars((string)($task['assignee_name'] ?? '')) ?></td>
<td>
<a href="/tasks/show?id=<?= (int)$task['id'] ?>"
data-task-modal
data-task-id="<?= (int)$task['id'] ?>"
class="text-decoration-none">
<?= htmlspecialchars((string)$task['name']) ?>
</a>
</td>
<td>
<span class="badge <?= $statusClass ?>">
<?= htmlspecialchars(task_status_label((string)$task['status'])) ?>
</span>
</td>
<td>
<span class="badge <?= $priorityClass ?>">
<?= htmlspecialchars(task_priority_label((string)$task['priority'])) ?>
</span>
</td>
<td>
<span
id="comment-badge-<?= (int)$task['id'] ?>"
class="badge <?= $badgeClass ?>"
data-task-id="<?= (int)$task['id'] ?>"
data-last-read-comment-id="<?= $lastReadCommentId ?>"
data-last-comment-id="<?= $lastCommentId ?>"
>
<?= $commentCount ?>
</span>
</td>
<td>
<?php if (!empty($task['planned_at'])): ?>
<div class="small text-muted mt-1">
<?= htmlspecialchars(date('d.m.Y H:i', strtotime((string)$task['planned_at']))) ?>
<?php if ($deadline): ?>
<span class="badge deadline-badge-<?= htmlspecialchars($deadline['code']) ?> ms-1">
<?= htmlspecialchars($deadline['text']) ?>
</span>
<?php endif; ?>
</div>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($tasks)): ?>
<tr>
<td colspan="8" class="text-center text-muted py-4">Нет задач</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<?php elseif ($view === 'cards'): ?>
<div class="row" id="cardsContainer">
<?php foreach ($tasks as $task): ?>
<?php
$deadline = taskDeadlineState($task['planned_at'] ?? null, $task['status'] ?? null);
$commentCount = (int)($task['comment_count'] ?? 0);
$lastCommentId = (int)($task['last_comment_id'] ?? 0);
$lastReadCommentId = (int)($task['last_read_comment_id'] ?? 0);
$isUnread = $commentCount > 0 && $lastCommentId > $lastReadCommentId;
$statusBadgeClass = match ($task['status']) {
'NEW' => 'bg-secondary text-white',
'IN_PROGRESS' => 'bg-info text-white',
'REVIEW' => 'bg-warning text-dark',
'DONE' => 'bg-success text-white',
'CANCELED' => 'bg-dark text-white',
'OVERDUE' => 'bg-danger text-white',
default => 'bg-secondary text-white',
};
$cardBorderClass = match ($task['status']) {
'NEW' => 'border-secondary',
'IN_PROGRESS' => 'border-info',
'REVIEW' => 'border-warning',
'DONE' => 'border-success',
'CANCELED' => 'border-dark',
'OVERDUE' => 'border-danger',
default => 'border-secondary',
};
$priorityClass = match ($task['priority']) {
'LOW' => 'bg-light text-dark',
'MEDIUM' => 'bg-primary text-white',
'HIGH' => 'bg-warning text-dark',
'CRITICAL' => 'bg-danger text-white',
default => 'bg-primary text-white',
};
?>
<div class="col-12 col-md-6 col-xl-3 my-3 task-card"
data-task-id="<?= (int)$task['id'] ?>"
data-status="<?= htmlspecialchars((string)$task['status']) ?>">
<div class="card h-100 shadow-sm <?= $cardBorderClass ?> <?= htmlspecialchars($deadline['class'] ?? '') ?>"
data-task-modal
data-task-id="<?= (int)$task['id'] ?>"
style="cursor: pointer; border-width: 2px;">
<div class="card-body">
<p class="card-title fw-bold mb-2">
<?= htmlspecialchars((string)($task['crm_id'] ?: ('Заявка #' . $task['id']))) ?>
</p>
<p class="card-text mb-3">
<?= htmlspecialchars((string)$task['name']) ?>
</p>
<div class="fw-bold mb-1 small">
Постановщик: <?= htmlspecialchars((string)($task['creator_name'] ?? '')) ?>
</div>
<div class="fw-bold mb-1 small">
Ответственный:
<?= htmlspecialchars((string)($task['assignee_name'] ?? 'Не назначен')) ?>
</div>
<?php if (!empty($task['supplier'])): ?>
<div class="small text-muted mb-1">
Поставщик: <?= htmlspecialchars((string)$task['supplier']) ?>
</div>
<?php endif; ?>
<div class="plane mb-1 small text-muted">
<strong>Дата план:</strong>
<?= !empty($task['planned_at']) ? htmlspecialchars(date('d.m.Y H:i', strtotime((string)$task['planned_at']))) : '—' ?>
<?php if ($deadline): ?>
<span class="badge deadline-badge-<?= htmlspecialchars($deadline['code']) ?> ms-1">
<?= htmlspecialchars($deadline['text']) ?>
</span>
<?php endif; ?>
</div>
<div class="fact mb-2 small text-muted">
<strong>Дата факт:</strong> <?= htmlspecialchars((string)($task['completed_at'] ?? '')) ?>
</div>
<div class="fact mb-2 small text-muted">
<div><strong>Дата постановки:</strong> <?= htmlspecialchars((string)($task['task_created_at'] ?? '')) ?></div>
</div>
<div class="d-flex justify-content-between align-items-center mt-3 pt-2 border-top flex-wrap gap-2">
<span class="badge <?= $priorityClass ?> rounded-pill">
Приоритет: <?= htmlspecialchars(task_priority_label((string)$task['priority'])) ?>
</span>
<span
id="comment-badge-<?= (int)$task['id'] ?>"
class="badge <?= $isUnread ? 'bg-primary' : 'bg-secondary' ?>"
data-task-id="<?= (int)$task['id'] ?>"
data-last-read-comment-id="<?= $lastReadCommentId ?>"
data-last-comment-id="<?= $lastCommentId ?>"
>
<span class="comment-count"><?= $commentCount ?></span>
</span>
<span class="badge <?= $statusBadgeClass ?>">
<?= htmlspecialchars(task_status_label((string)$task['status'])) ?>
</span>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
<?php if (empty($tasks)): ?>
<div class="col-12">
<div class="alert alert-light border text-muted">
Нет задач
</div>
</div>
<?php endif; ?>
</div>
<style>
#cardsContainer .task-card .card {
transition: transform .15s ease, box-shadow .15s ease;
}
#cardsContainer .task-card .card:hover {
transform: translateY(-2px);
box-shadow: 0 .5rem 1rem rgba(0,0,0,.12) !important;
}
#cardsContainer .card-title {
font-size: .95rem;
}
#cardsContainer .card-text {
min-height: 48px;
font-size: .95rem;
}
</style>
<?php else: ?>
<div class="kanban-wrapper">
<div class="kanban-board">
<?php foreach ($columns as $statusCode => $column): ?>
<div class="kanban-column" data-status="<?= htmlspecialchars($statusCode) ?>">
<div class="kanban-column-header <?= htmlspecialchars($column['header_class']) ?>">
<div class="fw-semibold"><?= htmlspecialchars($column['title']) ?></div>
<span class="badge text-bg-light kanban-count"><?= count($column['tasks']) ?></span>
</div>
<div class="kanban-column-body dropzone" data-status="<?= htmlspecialchars($statusCode) ?>">
<?php if (empty($column['tasks'])): ?>
<div class="kanban-empty">Нет задач</div>
<?php endif; ?>
<?php foreach ($column['tasks'] as $task): ?>
<?php
$deadline = taskDeadlineState($task['planned_at'] ?? null, $task['status'] ?? null);
?>
<?php
$commentCount = (int)($task['comment_count'] ?? 0);
$lastCommentId = (int)($task['last_comment_id'] ?? 0);
$lastReadCommentId = (int)($task['last_read_comment_id'] ?? 0);
$isUnread = $commentCount > 0 && $lastCommentId > $lastReadCommentId;
$badgeClass = $isUnread ? 'bg-primary' : 'bg-secondary';
$priorityClass = match ($task['priority']) {
'LOW' => 'text-bg-light',
'MEDIUM' => 'text-bg-secondary',
'HIGH' => 'text-bg-warning',
'CRITICAL' => 'text-bg-danger',
default => 'text-bg-secondary',
};
$isDone = (($task['status'] ?? '') === 'DONE');
?>
<div class="kanban-card <?= htmlspecialchars($deadline['class'] ?? '') ?>"
draggable="true"
data-task-id="<?= (int)$task['id'] ?>"
data-current-status="<?= htmlspecialchars((string)$task['status']) ?>">
<div class="d-flex justify-content-between align-items-start gap-2 mb-2">
<a href="/tasks/show?id=<?= (int)$task['id'] ?>"
data-task-modal
data-task-id="<?= (int)$task['id'] ?>"
class="kanban-card-title text-decoration-none">
<?= htmlspecialchars((string)$task['name']) ?>
</a>
<span class="badge <?= $priorityClass ?>">
<?= htmlspecialchars(task_priority_label((string)$task['priority'])) ?>
</span>
</div>
<?php if (!empty($task['description'])): ?>
<div class="kanban-card-desc mb-2">
<?= htmlspecialchars(mb_strimwidth((string)$task['description'], 0, 140, '...')) ?>
</div>
<?php endif; ?>
<div class="small text-muted mb-2">
<div><strong>CRM:</strong> <?= htmlspecialchars((string)$task['crm_id']) ?></div>
<?php if (!empty($task['creator_name'])): ?>
<div><strong>Постановщик:</strong> <?= htmlspecialchars((string)$task['creator_name']) ?></div>
<?php endif; ?>
<?php if (!empty($task['assignee_name'])): ?>
<div><strong>Ответственный:</strong> <?= htmlspecialchars((string)$task['assignee_name']) ?></div>
<?php endif; ?>
<?php if (!empty($task['planned_at'])): ?>
<div>
<strong>Дата план:</strong>
<?= htmlspecialchars(date('d.m.Y H:i', strtotime((string)$task['planned_at']))) ?>
<?php if ($deadline): ?>
<span class="badge deadline-badge-<?= htmlspecialchars($deadline['code']) ?> ms-1">
<?= htmlspecialchars($deadline['text']) ?>
</span>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<div class="d-flex justify-content-between align-items-center">
<span
id="comment-badge-<?= (int)$task['id'] ?>"
class="badge <?= $badgeClass ?>"
data-task-id="<?= (int)$task['id'] ?>"
data-last-read-comment-id="<?= $lastReadCommentId ?>"
data-last-comment-id="<?= $lastCommentId ?>"
>
Комментарии: <span class="comment-count"><?= $commentCount ?></span>
</span>
<span class="badge <?= $isDone ? 'text-bg-success' : 'text-bg-light' ?>">
<?= $isDone ? 'Выполнено' : 'Не выполнено' ?>
</span>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<style>
.kanban-wrapper { overflow-x: auto; padding-bottom: 8px; }
.kanban-board { display: flex; gap: 16px; align-items: flex-start; min-width: max-content; }
.kanban-column { width: 320px; background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 14px; display: flex; flex-direction: column; max-height: calc(100vh - 260px); }
.kanban-column-header { padding: 12px 14px; border-bottom: 1px solid rgba(255,255,255,.15); display: flex; justify-content: space-between; align-items: center; border-top-left-radius: 14px; border-top-right-radius: 14px; position: sticky; top: 0; z-index: 2; color: #fff; }
.kanban-header-new { background: #6c757d; }
.kanban-header-progress { background: #0d6efd; }
.kanban-header-review { background: #fd7e14; }
.kanban-header-done { background: #198754; }
.kanban-header-canceled { background: #495057; }
.kanban-header-overdue { background: #dc3545; }
.kanban-column-body { padding: 12px; overflow-y: auto; min-height: 120px; transition: background-color .2s ease; }
.kanban-column-body.drag-over { background: #e9f2ff; }
.kanban-card { background: #fff; border: 1px solid #e9ecef; border-radius: 12px; padding: 12px; box-shadow: 0 1px 2px rgba(0,0,0,.04); margin-bottom: 12px; cursor: grab; }
.kanban-card.deadline-green {
background: #d1e7dd !important;
border: 2px solid #198754 !important;
}
.kanban-card.deadline-yellow {
background: #fff3cd !important;
border: 2px solid #ffc107 !important;
}
.kanban-card.deadline-red {
background: #f8d7da !important;
border: 2px solid #dc3545 !important;
}
.deadline-badge-green {
background: #198754;
}
.deadline-badge-yellow {
background: #ffc107;
color: #000;
}
.deadline-badge-red {
background: #dc3545;
}
.kanban-card:last-child { margin-bottom: 0; }
.kanban-card.dragging { opacity: .55; transform: rotate(1deg); }
.kanban-card-title { font-weight: 600; color: #212529; line-height: 1.3; }
.kanban-card-title:hover { color: #0d6efd; }
.kanban-card-desc { color: #6c757d; font-size: 0.92rem; line-height: 1.35; }
.kanban-empty { color: #6c757d; text-align: center; padding: 20px 10px; border: 1px dashed #ced4da; border-radius: 10px; background: #fff; }
.offcanvas{--bs-offcanvas-width: 1000px;};
@media (max-width: 768px) { .kanban-column { width: 280px; } }
</style>
<?php endif; ?>
<?php if (!empty($canManageBoard)): ?>
<div class="offcanvas offcanvas-end" tabindex="-1" id="boardSettingsCanvas" style="width: 720px;">
<div class="offcanvas-header">
<h5 class="offcanvas-title">Редактирование доски: <?= htmlspecialchars($board['name']) ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas"></button>
</div>
<div class="offcanvas-body">
<form method="post" action="/admin/boards/save-all">
<input type="hidden" name="board_id" value="<?= (int)$board['id'] ?>">
<div class="accordion" id="boardSettingsAccordion">
<!-- ОСНОВНОЕ -->
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button" data-bs-toggle="collapse" data-bs-target="#mainBlock">
Основное
</button>
</h2>
<div id="mainBlock" class="accordion-collapse collapse show">
<div class="accordion-body">
<input type="text" name="name" class="form-control mb-2"
value="<?= htmlspecialchars($board['name']) ?>" placeholder="Название">
<input type="text" name="code" class="form-control mb-2"
value="<?= htmlspecialchars($board['code']) ?>" placeholder="Код">
<textarea name="description" class="form-control mb-2"><?= htmlspecialchars($board['description'] ?? '') ?></textarea>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="is_active"
<?= !empty($board['is_active']) ? 'checked' : '' ?>>
<label class="form-check-label">Активна</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="show_on_home"
<?= !empty($board['show_on_home']) ? 'checked' : '' ?>>
<label class="form-check-label">Показывать на главной</label>
</div>
</div>
</div>
</div>
<!-- GOOGLE -->
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" data-bs-toggle="collapse" data-bs-target="#googleBlock" type="button">
Google Sheets
</button>
</h2>
<div id="googleBlock" class="accordion-collapse collapse">
<div class="accordion-body">
<input type="text"
name="sheet_url"
class="form-control mb-2"
value="<?= htmlspecialchars((string)($boardSource['spreadsheet_url'] ?? '')) ?>"
placeholder="Ссылка на таблицу">
<input type="text"
name="sheet_name"
class="form-control mb-2"
value="<?= htmlspecialchars((string)($boardSource['sheet_name'] ?? '')) ?>"
placeholder="Название листа">
<select name="sync_mode" class="form-control">
<option value="import" <?= (($boardSource['sync_mode'] ?? '') === 'import') ? 'selected' : '' ?>>Импорт</option>
<option value="export" <?= (($boardSource['sync_mode'] ?? '') === 'export') ? 'selected' : '' ?>>Экспорт</option>
<option value="import_export" <?= (($boardSource['sync_mode'] ?? 'import_export') === 'import_export') ? 'selected' : '' ?>>Импорт + экспорт</option>
</select>
<div class="form-check mt-2">
<input class="form-check-input"
type="checkbox"
name="sheet_is_active"
id="sheet_is_active"
<?= !empty($boardSource['is_active']) ? 'checked' : '' ?>>
<label class="form-check-label" for="sheet_is_active">Интеграция активна</label>
</div>
</div>
</div>
</div>
<!-- КАСТОМНЫЕ ПОЛЯ -->
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" data-bs-toggle="collapse" data-bs-target="#fieldsBlock" type="button">
Кастомные поля
</button>
</h2>
<div id="fieldsBlock" class="accordion-collapse collapse">
<div class="accordion-body">
<?php if (!empty($boardFields)): ?>
<?php foreach ($boardFields as $field): ?>
<div class="border p-2 mb-2">
<input type="hidden" name="fields[<?= $field['id'] ?>][id]" value="<?= $field['id'] ?>">
<input type="text"
name="fields[<?= $field['id'] ?>][name]"
value="<?= htmlspecialchars($field['name']) ?>"
class="form-control mb-1">
<select name="fields[<?= $field['id'] ?>][type]" class="form-control mb-1">
<option value="text" <?= $field['field_type']=='text'?'selected':'' ?>>Текст</option>
<option value="date" <?= $field['field_type']=='date'?'selected':'' ?>>Дата</option>
</select>
<div class="form-check">
<input type="checkbox"
name="fields[<?= $field['id'] ?>][delete]"
class="form-check-input">
<label class="form-check-label text-danger">Удалить</label>
</div>
</div>
<?php endforeach; ?>
<?php else: ?>
<div class="text-muted">Кастомных полей пока нет</div>
<?php endif; ?>
<hr>
<!-- ДОБАВИТЬ НОВОЕ -->
<input type="text" name="new_fields[][name]" class="form-control mb-2" placeholder="Новое поле">
</div>
</div>
</div>
</div>
<div class="mt-3">
<button type="submit" class="btn btn-success w-100">
💾 Сохранить всё
</button>
</div>
</form>
</div>
</div>
<?php endif; ?>
<script>
document.addEventListener('DOMContentLoaded', function () {
console.log('bootstrap =', window.bootstrap);
console.log('offcanvas el =', document.getElementById('boardSettingsCanvas'));
});
</script>