1096 lines
58 KiB
PHP
1096 lines
58 KiB
PHP
<?php
|
|
require_once 'db/config.php';
|
|
require_once 'WorkflowEngine.php';
|
|
|
|
$pdo = db();
|
|
|
|
$workflowEngine = new WorkflowEngine();
|
|
$matrix = $workflowEngine->getDashboardMatrix();
|
|
|
|
$people = $matrix['people'];
|
|
$instances = $matrix['instances'];
|
|
$all_functions = $matrix['all_functions'];
|
|
$person_functions_map = $matrix['person_functions_map'];
|
|
$bni_groups = $matrix['bni_groups'];
|
|
|
|
// Filter out specific process definitions
|
|
$processes = array_filter($matrix['definitions'], function($process) {
|
|
return !in_array($process['name'], ['Obsluga goscia', 'Przygotowanie spotkania grupy']);
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
$status_colors = [
|
|
'completed' => '#28a745',
|
|
'positive' => '#28a745',
|
|
'in_progress' => '#fd7e14',
|
|
'negative' => '#dc3545',
|
|
'error' => '#dc3545',
|
|
'none' => '#808080',
|
|
'not_started' => '#808080',
|
|
'inactive' => '#808080',
|
|
];
|
|
|
|
|
|
|
|
|
|
?>
|
|
<?php include '_header.php'; ?>
|
|
<?php include '_navbar.php'; ?>
|
|
|
|
<div class="container-fluid">
|
|
<div class="row">
|
|
<?php include '_sidebar.php'; ?>
|
|
|
|
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
|
|
<div id="main-dashboard-view">
|
|
<?php if (isset($_SESSION['success_message'])): ?>
|
|
<div class="alert alert-success alert-dismissible fade show mt-3" role="alert">
|
|
<?= $_SESSION['success_message']; ?>
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
</div>
|
|
<?php unset($_SESSION['success_message']); ?>
|
|
<?php endif; ?>
|
|
|
|
<?php if (isset($_SESSION['error_message'])): ?>
|
|
<div class="alert alert-danger alert-dismissible fade show mt-3" role="alert">
|
|
<?= $_SESSION['error_message']; ?>
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
</div>
|
|
<?php unset($_SESSION['error_message']); ?>
|
|
<?php endif; ?>
|
|
|
|
<div class="d-flex justify-content-between align-items-center pt-3 pb-2 mb-3 border-bottom">
|
|
<h1 class="h2">Dashboard</h1>
|
|
<div class="btn-toolbar mb-2 mb-md-0">
|
|
<div class="btn-group me-2" id="bulk-actions-group" style="display: none;">
|
|
<button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
|
Bulk Actions
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="#" data-bs-toggle="modal" data-bs-target="#bulkStatusModal">Bulk Status Update</a></li>
|
|
<li><a class="dropdown-item" href="#" data-bs-toggle="modal" data-bs-target="#bulkEventModal">Bulk Add Event</a></li>
|
|
<li><a class="dropdown-item" href="#" data-bs-toggle="modal" data-bs-target="#bulkInitModal">Bulk Initialize Instances</a></li>
|
|
</ul>
|
|
</div>
|
|
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#createPersonModal">
|
|
Create Person
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<?php
|
|
// Define process groups
|
|
// Show all processes from the DB directly.
|
|
$inne_procesy_cols = $processes;
|
|
|
|
|
|
// --- Spotkania Columns ---
|
|
// Fetch upcoming meetings for each group
|
|
$today = date('Y-m-d H:i:s');
|
|
$stmt_meetings = $pdo->prepare("
|
|
SELECT bni_groups.id as group_id, bni_groups.name as group_name, MIN(calendar_events.start_datetime) as next_meeting_date
|
|
FROM bni_groups
|
|
LEFT JOIN calendar_event_groups ON bni_groups.id = calendar_event_groups.bni_group_id
|
|
LEFT JOIN calendar_events ON calendar_event_groups.calendar_event_id = calendar_events.id AND calendar_events.start_datetime >= :today
|
|
GROUP BY bni_groups.id
|
|
ORDER BY bni_groups.name
|
|
");
|
|
$stmt_meetings->execute(['today' => $today]);
|
|
$spotkania_cols = $stmt_meetings->fetchAll(PDO::FETCH_ASSOC);
|
|
?>
|
|
|
|
<div class="mb-3">
|
|
<label for="groupFilter" class="form-label">Filter by Group</label>
|
|
<select class="form-select" id="groupFilter">
|
|
<option value="">All Groups</option>
|
|
<?php foreach ($bni_groups as $group): ?>
|
|
<option value="<?= $group['id'] ?>"><?= htmlspecialchars($group['name']) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="table-responsive">
|
|
<?php
|
|
// Find the meeting process ID from the already-fetched definitions
|
|
$meeting_process_id = 'null';
|
|
foreach ($matrix['definitions'] as $definition) {
|
|
if ($definition['name'] === 'Przygotowanie spotkania grupy') {
|
|
$meeting_process_id = $definition['id'];
|
|
break;
|
|
}
|
|
}
|
|
?>
|
|
<table class="table table-bordered table-sm">
|
|
<thead class="table-light">
|
|
<tr class="text-center">
|
|
<th rowspan="2" class="align-middle"><input type="checkbox" id="selectAll"></th>
|
|
<th rowspan="2" class="align-middle">Person</th>
|
|
<?php if (!empty($spotkania_cols)): ?>
|
|
<th id="spotkania-header" colspan="<?= count($spotkania_cols) ?>">Spotkania</th>
|
|
<?php endif; ?>
|
|
<?php if (!empty($inne_procesy_cols)): ?>
|
|
<th colspan="<?= count($inne_procesy_cols) ?>">Inne procesy</th>
|
|
<?php endif; ?>
|
|
</tr>
|
|
<tr class="text-center" id="processes-header-row">
|
|
<?php foreach ($spotkania_cols as $index => $col): ?>
|
|
<th class="process-header-clickable" data-process-id="<?= $meeting_process_id ?>" style="cursor: pointer;" data-group-id="<?= $col['group_id'] ?>" data-col-index="<?= $index ?>">
|
|
<?= htmlspecialchars($col['group_name']) ?><br>
|
|
<small>
|
|
<?= $col['next_meeting_date'] ? date('d.m.Y', strtotime($col['next_meeting_date'])) : 'Brak' ?>
|
|
<?php if($col['next_meeting_date']): ?>
|
|
<i class="bi bi-arrow-right-short expand-meeting" style="cursor: pointer;" data-group-id="<?= $col['group_id'] ?>"></i>
|
|
<?php endif; ?>
|
|
</small>
|
|
</th>
|
|
<?php endforeach; ?>
|
|
<?php foreach ($inne_procesy_cols as $col): ?>
|
|
<th class="process-header-clickable" data-process-id="<?= $col['id'] ?>" style="cursor: pointer;"><?= htmlspecialchars($col['name']) ?></th>
|
|
<?php endforeach; ?>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($people as $person): ?>
|
|
<tr data-group-id="<?= $person['bni_group_id'] ?>">
|
|
<td class="text-center align-middle"><input type="checkbox" class="person-checkbox" name="personIds[]" value="<?= $person['id'] ?>"></td>
|
|
<td class="person-cell">
|
|
<div class="person-main">
|
|
<div class="person-name"><?= htmlspecialchars($person['first_name'] . ' ' . $person['last_name']) ?></div>
|
|
<div class="person-details">
|
|
<span class="d-block"><?= htmlspecialchars($person['company_name']) ?></span>
|
|
<span class="d-block"><?= htmlspecialchars($person['industry'] ?? '') ?></span>
|
|
<span><?= htmlspecialchars(ucfirst($person['role'])) ?></span>
|
|
<?php if ($person['role'] === 'member' && !empty($person['bni_group_name'])): ?>
|
|
<span class="person-group">, Grupa: <?= htmlspecialchars($person['bni_group_name']) ?></span>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
<div class="person-actions">
|
|
<div class="status-dots">
|
|
<?php if ($person['role'] === 'member'): ?>
|
|
<span class="status-dot <?= !empty($person['gains_sheet_path']) ? 'bg-success' : 'bg-danger' ?>" title="GAINS Sheet"></span>
|
|
<span class="status-dot <?= !empty($person['top_wanted_contacts_path']) ? 'bg-success' : 'bg-danger' ?>" title="Top Wanted Contacts"></span>
|
|
<span class="status-dot <?= !empty($person['top_owned_contacts_path']) ? 'bg-success' : 'bg-danger' ?>" title="Top Owned Contacts"></span>
|
|
<?php endif; ?>
|
|
</div>
|
|
<button class="btn btn-sm btn-secondary edit-btn" data-bs-toggle="modal" data-bs-target="#editPersonModal"
|
|
data-person-id="<?= $person['id'] ?>"
|
|
data-person-name="<?= htmlspecialchars($person['first_name'] . ' ' . $person['last_name']) ?>">
|
|
Edit
|
|
</button>
|
|
</div>
|
|
</td>
|
|
|
|
<?php // Spotkania Columns ?>
|
|
<?php foreach ($spotkania_cols as $index => $col): ?>
|
|
<td class="text-center align-middle meeting-cell" data-group-id="<?= $col['group_id'] ?>" data-col-index="<?= $index ?>">
|
|
<?php
|
|
// Placeholder Status: Logic for meeting attendance is not yet defined.
|
|
// Display icon only if the person belongs to the group for that column.
|
|
if ($person['bni_group_id'] == $col['group_id']) {
|
|
$status = 'none'; // Default/placeholder status
|
|
$color = $status_colors[$status] ?? '#808080';
|
|
echo "<span style='width: 20px; height: 20px; display: inline-block; border-radius: 50%; background-color: $color;' title='Status nieokreślony'></span>";
|
|
} else {
|
|
echo ''; // Empty cell if person is not in this group
|
|
}
|
|
?>
|
|
</td>
|
|
<?php endforeach; ?>
|
|
|
|
<?php // Inne Procesy Columns ?>
|
|
<?php foreach ($inne_procesy_cols as $process):
|
|
$instance = $instances[$person['id']][$process['id']] ?? null;
|
|
$lastActivity = $instance && isset($instance['last_activity_at']) ? date('d/m/y', strtotime($instance['last_activity_at'])) : '';
|
|
|
|
// Correctly check eligibility using the WorkflowEngine
|
|
$eligibilityCheck = $workflowEngine->checkEligibility($person['id'], $process['id']);
|
|
$is_eligible = $eligibilityCheck['is_eligible'];
|
|
|
|
$is_active = $process['is_active'] ?? true;
|
|
$modal_target = ''; // Default to not clickable
|
|
$is_clickable = false;
|
|
|
|
if (!$is_active) {
|
|
$status = 'inactive';
|
|
$color = $status_colors['inactive'];
|
|
$title = 'Process inactive';
|
|
} elseif ($instance && isset($instance['id'])) { // Existing instance
|
|
$status = $instance['computed_status'];
|
|
$color = $status_colors[$status] ?? $status_colors['inactive'];
|
|
$title = !empty($instance['computed_reason']) ? $instance['computed_reason'] : ucfirst($status);
|
|
$modal_target = '#instanceModal';
|
|
$is_clickable = true;
|
|
} else { // No instance
|
|
if ($is_eligible) {
|
|
$status = 'not_started';
|
|
$color = $status_colors[$status];
|
|
$title = 'Not Started';
|
|
$modal_target = '#instanceModal';
|
|
$is_clickable = true;
|
|
} else {
|
|
$status = 'ineligible';
|
|
$color = '#e9ecef'; // A light gray color for the circle
|
|
$title = implode(' ', $eligibilityCheck['reasons']); // Use the reason from the engine
|
|
$modal_target = '#instanceModal'; // Still open the modal to show details
|
|
$is_clickable = true;
|
|
}
|
|
}
|
|
?>
|
|
<td class="text-center align-middle"
|
|
<?php if ($is_clickable): ?>
|
|
style="cursor: pointer;"
|
|
data-bs-toggle="modal"
|
|
data-bs-target="<?= $modal_target ?>"
|
|
data-person-id="<?= $person['id'] ?>"
|
|
data-process-id="<?= $process['id'] ?>"
|
|
<?php else: ?>
|
|
style="cursor: not-allowed;"
|
|
<?php endif; ?>
|
|
title="<?= htmlspecialchars($title) ?>">
|
|
<span style="height: 20px; width: 20px; background-color: <?= $color ?>; border-radius: 50%; display: inline-block;"></span>
|
|
<small class="text-muted d-block mt-1"><?= $lastActivity ?></small>
|
|
</td>
|
|
<?php
|
|
endforeach; ?>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<div id="process-detail-view" style="display: none;">
|
|
<!-- This will be populated dynamically -->
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Edit Person Modal -->
|
|
<div class="modal fade" id="editPersonModal" tabindex="-1" aria-labelledby="editPersonModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-fullscreen-xl">
|
|
<div class="modal-content">
|
|
<form id="editPersonForm" action="_update_person.php" method="post" enctype="multipart/form-data">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="editPersonModalLabel">Edytuj osobę</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Zamknij"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div id="editPersonError" class="alert alert-danger" style="display: none;"></div>
|
|
<input type="hidden" name="id" id="editPersonId">
|
|
<div class="row">
|
|
<div class="col-md-4">
|
|
<div class="mb-3">
|
|
<label for="editFirstName" class="form-label">Imię</label>
|
|
<input type="text" class="form-control" id="editFirstName" name="first_name" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="editLastName" class="form-label">Nazwisko</label>
|
|
<input type="text" class="form-control" id="editLastName" name="last_name" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="editPhone" class="form-label">Numer telefonu</label>
|
|
<input type="text" class="form-control" id="editPhone" name="phone">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="editEmail" class="form-label">Email (login)</label>
|
|
<input type="email" class="form-control" id="editEmail" name="email">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="editPassword" class="form-label">Nowe hasło</label>
|
|
<input type="password" class="form-control" id="editPassword" name="password">
|
|
<small class="form-text text-muted">Pozostaw puste, jeśli nie chcesz zmieniać hasła.</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="mb-3">
|
|
<label for="editRole" class="form-label">Rola</label>
|
|
<select class="form-select" id="editRole" name="role" required>
|
|
<option value="admin">Admin</option>
|
|
<option value="member">Członek</option>
|
|
<option value="guest">Gość</option>
|
|
<option value="team_member">Pracownik Biura</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3" id="edit-group-selection-div" style="display: none;">
|
|
<label for="editBniGroup" class="form-label">Grupa</label>
|
|
<select class="form-select" id="editBniGroup" name="bni_group_id">
|
|
<option value="">Wybierz grupę...</option>
|
|
<?php foreach ($bni_groups as $group): ?>
|
|
<option value="<?= $group['id'] ?>"><?= htmlspecialchars($group['name']) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="editRoles" class="form-label">Funkcje</label>
|
|
<select class="form-select" id="editRoles" name="functions[]" multiple size="5">
|
|
<!-- Opcje zostaną wstawione przez JavaScript -->
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="editCompanyName" class="form-label">Nazwa firmy</label>
|
|
<input type="text" class="form-control" id="editCompanyName" name="company_name">
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="mb-3">
|
|
<label for="editNip" class="form-label">NIP</label>
|
|
<input type="text" class="form-control" id="editNip" name="nip">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="editIndustry" class="form-label">Branża</label>
|
|
<input type="text" class="form-control" id="editIndustry" name="industry">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="editCompanySize" class="form-label">Wielkość firmy / Przychody</label>
|
|
<input type="text" class="form-control" id="editCompanySize" name="company_size_revenue">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="editBusinessDescription" class="form-label">Opis działalności</label>
|
|
<textarea class="form-control" id="editBusinessDescription" name="business_description" rows="3"></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row member-only-fields" style="display: none;">
|
|
<hr class="mt-3">
|
|
<div class="col-md-6">
|
|
<h5>Pliki firmowe</h5>
|
|
<div class="mb-3">
|
|
<label for="editCompanyLogo" class="form-label">Logo firmy</label>
|
|
<input class="form-control" type="file" id="editCompanyLogo" name="company_logo">
|
|
<small id="editCompanyLogoPath" class="form-text text-muted"></small>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="editPersonPhoto" class="form-label">Zdjęcie osoby</label>
|
|
<input class="form-control" type="file" id="editPersonPhoto" name="person_photo">
|
|
<small id="editPersonPhotoPath" class="form-text text-muted"></small>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h5>Dokumenty członkowskie</h5>
|
|
<div class="mb-3">
|
|
<label for="editGainsSheet" class="form-label">Arkusz GAINS</label>
|
|
<input class="form-control" type="file" id="editGainsSheet" name="gains_sheet">
|
|
<small id="editGainsSheetPath" class="form-text text-muted"></small>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="editTopWanted" class="form-label">Lista TOP poszukiwanych kontaktów</label>
|
|
<input class="form-control" type="file" id="editTopWanted" name="top_wanted_contacts">
|
|
<small id="editTopWantedPath" class="form-text text-muted"></small>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="editTopOwned" class="form-label">Lista TOP posiadanych kontaktów</label>
|
|
<input class="form-control" type="file" id="editTopOwned" name="top_owned_contacts">
|
|
<small id="editTopOwnedPath" class="form-text text-muted"></small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<hr class="my-3">
|
|
<div id="followUpSummaryContainer"></div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Zamknij</button>
|
|
<button type="submit" class="btn btn-primary">Zapisz zmiany</button>
|
|
<button type="button" class="btn btn-danger ms-auto" id="deleteUserBtn" data-bs-toggle="modal" data-bs-target="#deletePersonModal">Usuń</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Create Person Modal -->
|
|
<div class="modal fade" id="createPersonModal" tabindex="-1" aria-labelledby="createPersonModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-fullscreen-xl">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="createPersonModalLabel">Create Person</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div id="createPersonError" class="alert alert-danger" style="display: none;"></div>
|
|
<form id="createPersonForm" action="_create_person.php" method="post" enctype="multipart/form-data">
|
|
<div class="row">
|
|
<div class="col-md-4">
|
|
<div class="mb-3">
|
|
<label for="createFirstName" class="form-label">First Name <span class="text-danger">*</span></label>
|
|
<input type="text" class="form-control" id="createFirstName" name="first_name" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="createLastName" class="form-label">Last Name <span class="text-danger">*</span></label>
|
|
<input type="text" class="form-control" id="createLastName" name="last_name" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="createPhone" class="form-label">Phone Number</label>
|
|
<input type="text" class="form-control" id="createPhone" name="phone">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="createEmail" class="form-label">Email (Login) <span class="text-danger">*</span></label>
|
|
<input type="email" class="form-control" id="createEmail" name="email" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="createPassword" class="form-label">Password <span class="text-danger">*</span></label>
|
|
<input type="password" class="form-control" id="createPassword" name="password" required>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="mb-3">
|
|
<label for="createRole" class="form-label">Rola</label>
|
|
<select class="form-select" id="createRole" name="role" required>
|
|
<option value="admin">Admin</option>
|
|
<option value="member" selected>Członek</option>
|
|
<option value="guest">Gość</option>
|
|
<option value="team_member">Pracownik Biura</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3" id="create-group-selection-div">
|
|
<label for="createBniGroup" class="form-label">Grupa</label>
|
|
<select class="form-select" id="createBniGroup" name="bni_group_id">
|
|
<option value="">Wybierz grupę...</option>
|
|
<?php foreach ($bni_groups as $group): ?>
|
|
<option value="<?= $group['id'] ?>"><?= htmlspecialchars($group['name']) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="createRoles" class="form-label">Funkcje</label>
|
|
<select class="form-select" id="createRoles" name="functions[]" multiple size="5">
|
|
<?php foreach ($all_functions as $function): ?>
|
|
<option value="<?= $function['id'] ?>"><?= htmlspecialchars($function['name']) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="createCompanyName" class="form-label">Company Name</label>
|
|
<input type="text" class="form-control" id="createCompanyName" name="company_name">
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="mb-3">
|
|
<label for="createNip" class="form-label">NIP</label>
|
|
<input type="text" class="form-control" id="createNip" name="nip">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="createIndustry" class="form-label">Branża</label>
|
|
<input type="text" class="form-control" id="createIndustry" name="industry">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="createCompanySize" class="form-label">Wielkość firmy / Przychody</label>
|
|
<input type="text" class="form-control" id="createCompanySize" name="company_size_revenue">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="createBusinessDescription" class="form-label">Opis działalności</label>
|
|
<textarea class="form-control" id="createBusinessDescription" name="business_description" rows="3"></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row member-only-fields" style="display: none;">
|
|
<hr class="mt-3">
|
|
<div class="col-md-6">
|
|
<h5>Pliki firmowe</h5>
|
|
<div class="mb-3">
|
|
<label for="createCompanyLogo" class="form-label">Logo firmy</label>
|
|
<input class="form-control" type="file" id="createCompanyLogo" name="company_logo">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="createPersonPhoto" class="form-label">Zdjęcie osoby</label>
|
|
<input class="form-control" type="file" id="createPersonPhoto" name="person_photo">
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h5>Dokumenty członkowskie</h5>
|
|
<div class="mb-3">
|
|
<label for="createGainsSheet" class="form-label">Arkusz GAINS</label>
|
|
<input class="form-control" type="file" id="createGainsSheet" name="gains_sheet">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="createTopWanted" class="form-label">Lista TOP poszukiwanych kontaktów</label>
|
|
<input class="form-control" type="file" id="createTopWanted" name="top_wanted_contacts">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="createTopOwned" class="form-label">Lista TOP posiadanych kontaktów</label>
|
|
<input class="form-control" type="file" id="createTopOwned" name="top_owned_contacts">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary mt-3">Create Person</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Delete Person Modal -->
|
|
<div class="modal fade" id="deletePersonModal" tabindex="-1" aria-labelledby="deletePersonModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="deletePersonModalLabel">Potwierdź usunięcie</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Zamknij"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
Czy na pewno chcesz usunąć użytkownika <strong id="personNameToDelete"></strong>? Tej operacji nie można cofnąć.
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Anuluj</button>
|
|
<button type="button" class="btn btn-danger" id="confirmDeleteBtn">Tak, usuń</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<?php include '_footer.php'; ?>
|
|
|
|
<!-- Instance Modal -->
|
|
<div class="modal fade" id="instanceModal" tabindex="-1" aria-labelledby="instanceModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="instanceModalLabel">Process Details</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<!-- Content will be loaded here via AJAX -->
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
// --- Injected PHP Data ---
|
|
const peopleData = <?= json_encode($people) ?>;
|
|
|
|
// --- STATE MANAGEMENT ---
|
|
const meetingsState = {};
|
|
|
|
// --- MODAL LOGIC ---
|
|
const instanceModal = document.getElementById('instanceModal');
|
|
let currentPersonId = null;
|
|
let currentProcessId = null;
|
|
|
|
if (instanceModal) {
|
|
// Event listener for when the modal is about to be shown
|
|
instanceModal.addEventListener('show.bs.modal', function (event) {
|
|
const button = event.relatedTarget;
|
|
currentPersonId = button.getAttribute('data-person-id');
|
|
currentProcessId = button.getAttribute('data-process-id');
|
|
|
|
const modalBody = instanceModal.querySelector('.modal-body');
|
|
const modalTitle = instanceModal.querySelector('.modal-title');
|
|
|
|
modalBody.innerHTML = '<div class="d-flex justify-content-center"><div class="spinner-border" role="status"><span class="visually-hidden">Loading...</span></div></div>';
|
|
modalTitle.textContent = 'Ładowanie...';
|
|
|
|
// Fetch and display the initial modal content
|
|
fetchAndRenderModalContent(currentPersonId, currentProcessId);
|
|
});
|
|
|
|
// Event listener for when the modal has been hidden
|
|
instanceModal.addEventListener('hidden.bs.modal', function () {
|
|
location.reload(); // Reload the main page to reflect any changes
|
|
});
|
|
|
|
// Delegated event listener for all actions within the modal
|
|
instanceModal.addEventListener('click', function(event) {
|
|
const transitionBtn = event.target.closest('.apply-transition-btn');
|
|
if (transitionBtn) {
|
|
handleTransition(transitionBtn);
|
|
return;
|
|
}
|
|
|
|
const noteBtn = event.target.closest('#addNoteBtn');
|
|
if (noteBtn) {
|
|
handleAddNote(noteBtn);
|
|
return;
|
|
}
|
|
|
|
const startBtn = event.target.closest('#startProcessBtn');
|
|
if (startBtn) {
|
|
handleStartProcess(startBtn);
|
|
return;
|
|
}
|
|
});
|
|
|
|
instanceModal.addEventListener('change', function(event) {
|
|
const checkbox = event.target.closest('.task-checkbox-modal');
|
|
if (checkbox) {
|
|
handleCheckboxChange(checkbox);
|
|
return;
|
|
}
|
|
});
|
|
}
|
|
|
|
// --- HELPER FUNCTIONS FOR MODAL ---
|
|
|
|
function fetchAndRenderModalContent(personId, processId) {
|
|
const modalBody = instanceModal.querySelector('.modal-body');
|
|
const modalTitle = instanceModal.querySelector('.modal-title');
|
|
|
|
fetch(`_get_instance_details.php?person_id=${personId}&process_id=${processId}`)
|
|
.then(response => response.text())
|
|
.then(html => {
|
|
modalBody.innerHTML = html;
|
|
const newTitleEl = modalBody.querySelector('#instance-modal-title');
|
|
if (newTitleEl) {
|
|
modalTitle.innerHTML = newTitleEl.innerHTML;
|
|
newTitleEl.remove();
|
|
} else {
|
|
modalTitle.textContent = 'Szczegóły procesu';
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error fetching instance details:', error);
|
|
modalBody.innerHTML = '<p class="text-danger">Wystąpił błąd podczas ładowania danych.</p>';
|
|
modalTitle.textContent = 'Błąd';
|
|
});
|
|
}
|
|
|
|
function handleCheckboxChange(checkbox) {
|
|
const taskCode = checkbox.dataset.taskCode;
|
|
const isChecked = checkbox.checked;
|
|
const instanceId = checkbox.closest('.modal-body').querySelector('[data-instance-id]').dataset.instanceId;
|
|
|
|
fetch('_update_training_checklist_status.php', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
|
|
body: JSON.stringify({
|
|
instance_id: instanceId,
|
|
task_code: taskCode,
|
|
is_checked: isChecked
|
|
})
|
|
})
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error('Network response was not ok');
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
if (!data.success) {
|
|
console.error('Failed to update checklist status:', data.error);
|
|
checkbox.checked = !isChecked; // Revert on failure
|
|
alert('Error updating status: ' + data.error);
|
|
}
|
|
// No reload on success, keep modal open
|
|
})
|
|
.catch(error => {
|
|
console.error('Network or server error during checklist update:', error);
|
|
checkbox.checked = !isChecked; // Revert on network error
|
|
alert('A network error occurred. Please try again.');
|
|
});
|
|
}
|
|
|
|
function handleTransition(button) {
|
|
const instanceId = button.dataset.instanceId;
|
|
const transitionId = button.dataset.transitionId;
|
|
|
|
if (!confirm(`Czy na pewno chcesz wykonać akcję \"${button.textContent.trim()}\"?`)) {
|
|
return;
|
|
}
|
|
|
|
const form = document.getElementById('transition-form');
|
|
const formData = form ? new FormData(form) : new FormData();
|
|
|
|
formData.append('instanceId', instanceId);
|
|
formData.append('transitionId', transitionId);
|
|
|
|
// Add outcome_status based on transitionId for the new workflow
|
|
if (transitionId === 'log_wants_to_join') {
|
|
formData.append('outcome_status', 'wants_to_join');
|
|
} else if (transitionId === 'log_declined') {
|
|
formData.append('outcome_status', 'declined');
|
|
} else if (transitionId === 'log_no_answer') {
|
|
formData.append('outcome_status', 'no_answer');
|
|
} else if (transitionId === 'log_call_later') {
|
|
formData.append('outcome_status', 'call_later');
|
|
}
|
|
|
|
submitRequestAndReloadModal('_apply_transition.php', formData);
|
|
}
|
|
|
|
function handleAddNote(button) {
|
|
const instanceId = button.dataset.instanceId;
|
|
const message = document.getElementById('noteMessage').value;
|
|
|
|
if (!message.trim()) {
|
|
alert('Proszę wpisać treść notatki.');
|
|
return;
|
|
}
|
|
|
|
const formData = new FormData();
|
|
formData.append('instanceId', instanceId);
|
|
formData.append('transitionId', 'note'); // Special transitionId
|
|
formData.append('payload[message]', message);
|
|
submitRequestAndReloadModal('_apply_transition.php', formData);
|
|
}
|
|
|
|
function handleStartProcess(button) {
|
|
const personId = button.dataset.personId;
|
|
const processId = button.dataset.processId;
|
|
|
|
if (!personId || !processId) {
|
|
alert('Missing data for starting process. Please close the modal and try again.');
|
|
return;
|
|
}
|
|
|
|
const formData = new FormData();
|
|
formData.append('person_id', personId);
|
|
formData.append('process_id', processId);
|
|
|
|
submitRequestAndReloadModal('_init_single_instance.php', formData);
|
|
}
|
|
|
|
function submitRequestAndReloadModal(url, formData) {
|
|
const modalBody = instanceModal.querySelector('.modal-body');
|
|
showLoading(modalBody);
|
|
|
|
fetch(url, {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
// If response is not OK, it's an error. Clone the response to read it twice.
|
|
const clone = response.clone();
|
|
return response.json()
|
|
.then(json => {
|
|
// We have a JSON error body, throw a custom error with its details
|
|
const error = new Error(json.error?.message || 'An unkown error occurred.');
|
|
error.correlation_id = json.correlation_id;
|
|
error.response = response; // Attach full response
|
|
throw error;
|
|
})
|
|
.catch(() => {
|
|
// If JSON parsing fails, fall back to the text body
|
|
return clone.text().then(text => {
|
|
const error = new Error(text || 'Network response was not ok and could not parse error body.');
|
|
error.response = response;
|
|
throw error;
|
|
});
|
|
});
|
|
}
|
|
return response.json(); // On success, just parse the JSON
|
|
})
|
|
.then(data => {
|
|
if (data.success) {
|
|
// Reload modal content for the same person/process after successful submission
|
|
fetchAndRenderModalContent(currentPersonId, currentProcessId);
|
|
} else {
|
|
// Handle cases where the server returns 200 OK but with success: false
|
|
const error = new Error(data.message || 'An unknown error occurred.');
|
|
if (data.correlation_id) {
|
|
error.correlation_id = data.correlation_id;
|
|
}
|
|
throw error;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error submitting request:', error);
|
|
|
|
let errorMessage = `<div class="alert alert-danger">`;
|
|
errorMessage += `<strong>Error:</strong> ${error.message}`;
|
|
if (error.correlation_id) {
|
|
errorMessage += `<br><small class="text-muted">Correlation ID: ${error.correlation_id}</small>`;
|
|
}
|
|
errorMessage += `</div>`;
|
|
modalBody.innerHTML = errorMessage;
|
|
});
|
|
}
|
|
|
|
function showLoading(element) {
|
|
element.innerHTML = '<div class="d-flex justify-content-center align-items-center" style="min-height: 200px;"><div class="spinner-border" role="status"><span class="visually-hidden">Loading...</span></div></div>';
|
|
}
|
|
|
|
// --- GROUP FILTER LOGIC ---
|
|
const groupFilter = document.getElementById('groupFilter');
|
|
if (groupFilter) {
|
|
groupFilter.addEventListener('change', function () {
|
|
const selectedGroupId = this.value;
|
|
document.querySelectorAll('tbody tr').forEach(row => {
|
|
const rowGroupId = row.getAttribute('data-group-id');
|
|
row.style.display = (selectedGroupId === '' || rowGroupId === selectedGroupId) ? '' : 'none';
|
|
});
|
|
});
|
|
}
|
|
|
|
// --- EXPAND MEETINGS LOGIC ---
|
|
const headerRow = document.getElementById('processes-header-row');
|
|
if (headerRow) {
|
|
headerRow.addEventListener('click', function (event) {
|
|
const expandBtn = event.target.closest('.expand-meeting');
|
|
if (!expandBtn) return;
|
|
|
|
event.stopPropagation(); // Prevent the click from bubbling up to the process-header-clickable listener
|
|
|
|
const groupId = expandBtn.dataset.groupId;
|
|
expandBtn.style.display = 'none';
|
|
|
|
if (!meetingsState[groupId]) {
|
|
meetingsState[groupId] = { offset: 1 };
|
|
}
|
|
|
|
const limit = 2;
|
|
const offset = meetingsState[groupId].offset;
|
|
meetingsState[groupId].offset += limit;
|
|
|
|
const fetchUrl = `_get_future_meetings.php?bni_group_id=${groupId}&limit=${limit}&offset=${offset}`;
|
|
|
|
fetch(fetchUrl)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.error) {
|
|
console.error('BŁĄD Z SERWERA:', data.error);
|
|
return;
|
|
}
|
|
|
|
if (Array.isArray(data) && data.length > 0) {
|
|
let lastThForGroup = findLastElementForGroup(headerRow, 'th', groupId);
|
|
if (!lastThForGroup) {
|
|
console.error('KRYTYCZNY BŁĄD: Nie można znaleźć nagłówka startowego (TH) dla grupy.');
|
|
return;
|
|
}
|
|
|
|
data.forEach((meeting) => {
|
|
const newTh = document.createElement('th');
|
|
newTh.dataset.groupId = groupId;
|
|
newTh.className = 'text-center';
|
|
|
|
const meetingDate = new Date(meeting.start_datetime);
|
|
const formattedDate = meetingDate.toLocaleDateString('pl-PL', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
|
|
|
const isLastInBatch = false;
|
|
const isLastInGroup = false;
|
|
|
|
let iconHtml = '';
|
|
if (isLastInBatch && !isLastInGroup) {
|
|
iconHtml = ` <i class="bi bi-arrow-right-short expand-meeting" data-group-id="${groupId}" style="cursor: pointer;"></i>`;
|
|
}
|
|
|
|
newTh.innerHTML = `<span class=\"text-muted\">${htmlspecialchars(meeting.group_name || '')}</span><br><small>${formattedDate}${iconHtml}</small>`;
|
|
|
|
lastThForGroup.after(newTh);
|
|
lastThForGroup = newTh;
|
|
});
|
|
|
|
document.querySelectorAll('tbody tr').forEach((personRow) => {
|
|
let lastTdForGroup = findLastElementForGroup(personRow, 'td.meeting-cell', groupId);
|
|
|
|
if (!lastTdForGroup) {
|
|
// Fallback for rows that might not have the initial meeting cell
|
|
const allHeaders = Array.from(headerRow.children);
|
|
const initialTh = findLastElementForGroup(headerRow, 'th', groupId, true);
|
|
const anchorIndex = allHeaders.indexOf(initialTh);
|
|
lastTdForGroup = personRow.children[anchorIndex];
|
|
}
|
|
|
|
if (!lastTdForGroup) {
|
|
return; // Skip row if anchor is still not found
|
|
}
|
|
|
|
data.forEach(() => {
|
|
const newTd = document.createElement('td');
|
|
newTd.dataset.groupId = groupId;
|
|
newTd.className = 'text-center align-middle meeting-cell';
|
|
|
|
if (personRow.dataset.groupId === groupId) {
|
|
newTd.innerHTML = `<span class="badge rounded-circle bg-secondary" style="width: 20px; height: 20px; display: inline-block;" title="Status nieokreślony"></span>`;
|
|
}
|
|
|
|
lastTdForGroup.after(newTd);
|
|
lastTdForGroup = newTd;
|
|
});
|
|
});
|
|
|
|
const spotkaniaHeader = document.getElementById('spotkania-header');
|
|
if (spotkaniaHeader) {
|
|
spotkaniaHeader.colSpan = (spotkaniaHeader.colSpan || 1) + data.length;
|
|
}
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('KRYTYCZNY BŁĄD SIECI LUB PARSOWANIA:', error);
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
|
|
function findLastElementForGroup(parent, selector, groupId, findFirst = false) {
|
|
const elements = parent.querySelectorAll(`${selector}[data-group-id="${groupId}"]`);
|
|
if (elements.length === 0) return null;
|
|
return findFirst ? elements[0] : elements[elements.length - 1];
|
|
}
|
|
|
|
function htmlspecialchars(str) {
|
|
if (typeof str !== 'string') return '';
|
|
return str.replace(/[&<>"]/g, match => ({
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '"'
|
|
}[match]));
|
|
}
|
|
|
|
// --- PROCESS BULK VIEW LOGIC ---
|
|
|
|
const processDetailView = document.getElementById('process-detail-view');
|
|
|
|
function renderProcessDetailView(data) {
|
|
const { process, steps, instances } = data;
|
|
|
|
let tableHtml = `<div class="d-flex justify-content-between align-items-center pt-3 pb-2 mb-3 border-bottom">
|
|
<h1 class="h2">Process: ${htmlspecialchars(process.name)}</h1>
|
|
<button class="btn btn-secondary" id="back-to-dashboard">Back to Dashboard</button>
|
|
</div>`;
|
|
|
|
tableHtml += '<div class="table-responsive"><table class="table table-bordered table-sm">';
|
|
|
|
tableHtml += '<thead class="table-light"><tr><th>Person</th>';
|
|
steps.forEach(step => {
|
|
tableHtml += `<th>${htmlspecialchars(step.name)}</th>`;
|
|
});
|
|
tableHtml += '</tr></thead>';
|
|
|
|
tableHtml += '<tbody>';
|
|
instances.forEach(instance => {
|
|
const person = peopleData.find(p => p.id == instance.person_id);
|
|
if (!person) return;
|
|
|
|
tableHtml += `<tr><td class="person-cell">
|
|
<div class="person-main">
|
|
<div class="person-name">${htmlspecialchars(person.first_name + ' ' + person.last_name)}</div>
|
|
</div>
|
|
</td>`;
|
|
|
|
steps.forEach(step => {
|
|
const stepState = instance.steps.find(s => s.step_id == step.id);
|
|
const status = stepState ? stepState.status : 'pending';
|
|
const statusColors = {
|
|
pending: 'secondary',
|
|
in_progress: 'warning',
|
|
completed: 'success',
|
|
skipped: 'light',
|
|
failed: 'danger'
|
|
};
|
|
const color = statusColors[status] || 'secondary';
|
|
|
|
tableHtml += `<td class="text-center align-middle">
|
|
<span class="badge rounded-circle bg-${color}" style="width: 20px; height: 20px; display: inline-block;" title="${status}"> </span>
|
|
</td>`;
|
|
});
|
|
|
|
tableHtml += '</tr>';
|
|
});
|
|
tableHtml += '</tbody></table></div>';
|
|
|
|
processDetailView.innerHTML = tableHtml;
|
|
|
|
document.getElementById('back-to-dashboard').addEventListener('click', () => {
|
|
processDetailView.style.display = 'none';
|
|
mainDashboardView.style.display = 'block';
|
|
processDetailView.innerHTML = '';
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<!-- Bulk Modals -->
|
|
<!-- Bulk Status Update Modal -->
|
|
<div class="modal fade" id="bulkStatusModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Bulk Status Update</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form action="_bulk_update_status.php" method="post">
|
|
<input type="hidden" name="personIds" id="bulkStatusPersonIds">
|
|
<div class="mb-3">
|
|
<label class="form-label">Process</label>
|
|
<select name="process_id" class="form-select" required>
|
|
<?php foreach($processes as $process): ?>
|
|
<option value="<?= $process['id'] ?>"><?= htmlspecialchars($process['name']) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">New Status</label>
|
|
<select name="status" class="form-select" required>
|
|
<option value="none">None</option>
|
|
<option value="negative">Negative</option>
|
|
<option value="in_progress">In Progress</option>
|
|
<option value="positive">Positive</option>
|
|
</select>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary">Update Status</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bulk Add Event Modal -->
|
|
<div class="modal fade" id="bulkEventModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Bulk Add Event</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form action="_bulk_add_event.php" method="post">
|
|
<input type="hidden" name="personIds" id="bulkEventPersonIds">
|
|
<div class="mb-3">
|
|
<label class="form-label">Process</label>
|
|
<select name="process_id" class="form-select" required>
|
|
<?php foreach($processes as $process): ?>
|
|
<option value="<?= $process['id'] ?>"><?= htmlspecialchars($process['name']) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">Event Description</label>
|
|
<textarea name="description" class="form-control" required></textarea>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary">Add Event</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bulk Initialize Instances Modal -->
|
|
<div class="modal fade" id="bulkInitModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Bulk Initialize Instances</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form action="_bulk_init_instances.php" method="post">
|
|
<input type="hidden" name="personIds" id="bulkInitPersonIds">
|
|
<div class="mb-3">
|
|
<label class="form-label">Process</label> <select name="process_id" class="form-select" required>
|
|
<?php foreach($processes as $process): ?>
|
|
<option value="<?= $process['id'] ?>"><?= htmlspecialchars($process['name']) ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<p>This will initialize this process for all selected people if it is not already initialized.</p>
|
|
<button type="submit" class="btn btn-primary">Initialize</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|