Pierwszy działający proces
This commit is contained in:
parent
ee89357279
commit
524c7007ab
@ -1,4 +1,5 @@
|
||||
<?php
|
||||
// Cache-buster: 1720638682
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
class WorkflowEngine {
|
||||
@ -27,7 +28,7 @@ class WorkflowEngine {
|
||||
|
||||
$instances = [];
|
||||
foreach ($instances_data as $instance) {
|
||||
$instances[$instance['personId']][$instance['processDefinitionId']] = $instance;
|
||||
$instances[$instance['person_id']][$instance['process_definition_id']] = $instance;
|
||||
}
|
||||
|
||||
return [
|
||||
@ -38,33 +39,45 @@ class WorkflowEngine {
|
||||
}
|
||||
|
||||
public function startProcess(string $processCode, int $personId, int $userId): ?int {
|
||||
error_log("startProcess: processCode=$processCode, personId=$personId, userId=$userId");
|
||||
$this->pdo->beginTransaction();
|
||||
try {
|
||||
// 1. Find active process definition by code.
|
||||
$stmt_def = $this->pdo->prepare("SELECT * FROM process_definitions WHERE code = ? AND active = 1");
|
||||
$stmt_def->execute([$processCode]);
|
||||
$definition = $stmt_def->fetch(PDO::FETCH_ASSOC);
|
||||
error_log("startProcess: definition=" . print_r($definition, true));
|
||||
|
||||
if (!$definition) {
|
||||
throw new Exception("Active process definition with code '$processCode' not found.");
|
||||
}
|
||||
// If no process definition is found, check if there is a definition for a checklist
|
||||
$stmt_def = $this->pdo->prepare("SELECT * FROM process_definitions WHERE id = ?");
|
||||
$stmt_def->execute([$processCode]);
|
||||
$definition = $stmt_def->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
$definition_json = json_decode($definition['definition_json'], true);
|
||||
if (!$definition_json || !isset($definition_json['start_node_id'])) {
|
||||
throw new Exception("Process definition is missing start_node_id.");
|
||||
if (!$definition) {
|
||||
throw new Exception("Process definition with code or id '$processCode' not found.");
|
||||
}
|
||||
|
||||
$definition_json = !empty($definition['definition_json']) ? json_decode($definition['definition_json'], true) : [];
|
||||
if (empty($definition_json) || $definition_json['type'] !== 'checklist') {
|
||||
throw new Exception("Process definition with code '$processCode' not found or not a checklist.");
|
||||
}
|
||||
|
||||
// For checklists, there's no start_node_id, so we can proceed with instance creation
|
||||
$startNodeId = null;
|
||||
|
||||
} else {
|
||||
$definition_json = !empty($definition['definition_json']) ? json_decode($definition['definition_json'], true) : [];
|
||||
if (empty($definition_json) || !isset($definition_json['start_node_id'])) {
|
||||
throw new Exception("Process definition is missing start_node_id.");
|
||||
}
|
||||
$startNodeId = $definition_json['start_node_id'];
|
||||
}
|
||||
$startNodeId = $definition_json['start_node_id'];
|
||||
|
||||
// 2. Create a new process instance.
|
||||
$stmt_insert = $this->pdo->prepare(
|
||||
"INSERT INTO process_instances (personId, processDefinitionId, current_node_id, current_status, lastActivityAt) VALUES (?, ?, ?, 'in_progress', NOW())"
|
||||
"INSERT INTO process_instances (person_id, process_definition_id, current_node_id, current_status, lastActivityAt) VALUES (?, ?, ?, 'in_progress', NOW())"
|
||||
);
|
||||
$stmt_insert->execute([$personId, $definition['id'], $startNodeId]);
|
||||
error_log("startProcess: affected rows=" . $stmt_insert->rowCount());
|
||||
$instanceId = $this->pdo->lastInsertId();
|
||||
error_log("startProcess: instanceId=$instanceId");
|
||||
|
||||
// 3. Create a system event for process start.
|
||||
$this->addEvent($instanceId, 'system', 'Process started.', $startNodeId, [], $userId);
|
||||
@ -88,9 +101,9 @@ class WorkflowEngine {
|
||||
}
|
||||
|
||||
$stmt_def = $this->pdo->prepare("SELECT definition_json FROM process_definitions WHERE id = ?");
|
||||
$stmt_def->execute([$instance['processDefinitionId']]);
|
||||
$stmt_def->execute([$instance['process_definition_id']]);
|
||||
$definition_json = $stmt_def->fetchColumn();
|
||||
$definition = json_decode($definition_json, true);
|
||||
$definition = !empty($definition_json) ? json_decode($definition_json, true) : [];
|
||||
|
||||
$currentNodeId = $instance['current_node_id'];
|
||||
$nodeInfo = $definition['nodes'][$currentNodeId] ?? null;
|
||||
@ -157,22 +170,45 @@ class WorkflowEngine {
|
||||
}
|
||||
}
|
||||
|
||||
public function addNote(int $instanceId, string $message, int $userId): bool {
|
||||
$state = $this->getProcessState($instanceId);
|
||||
if (!$state) {
|
||||
// Even if the instance isn't found, we shouldn't crash. Log and return false.
|
||||
error_log("addNote failed: Process instance $instanceId not found.");
|
||||
return false;
|
||||
}
|
||||
$currentNodeId = $state['instance']['current_node_id'];
|
||||
$payload = ['message' => $message];
|
||||
$this->addEvent($instanceId, 'note', $message, $currentNodeId, $payload, $userId);
|
||||
return true;
|
||||
}
|
||||
|
||||
private function addEvent(int $instanceId, string $eventType, string $message, ?string $nodeId, array $payload, int $userId): void {
|
||||
$stmt = $this->pdo->prepare(
|
||||
"INSERT INTO process_events (processInstanceId, eventType, message, node_id, payload_json, createdById) VALUES (?, ?, ?, ?, ?, ?)"
|
||||
"INSERT INTO process_events (processInstanceId, event_type, message, node_id, payload_json, createdBy, createdAt) VALUES (?, ?, ?, ?, ?, ?, NOW())"
|
||||
);
|
||||
$stmt->execute([$instanceId, $eventType, $message, $nodeId, json_encode($payload), $userId]);
|
||||
}
|
||||
|
||||
public function getOrCreateInstanceByDefId(int $personId, int $processDefinitionId, int $userId): ?array {
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM process_instances WHERE personId = ? AND processDefinitionId = ?");
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM process_instances WHERE `person_id` = ? AND `process_definition_id` = ?");
|
||||
$stmt->execute([$personId, $processDefinitionId]);
|
||||
$instance = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$instance) {
|
||||
$stmt_def = $this->pdo->prepare("SELECT code FROM process_definitions WHERE id = ?");
|
||||
// For checklists, the process code is the process definition ID
|
||||
$stmt_def = $this->pdo->prepare("SELECT definition_json FROM process_definitions WHERE id = ?");
|
||||
$stmt_def->execute([$processDefinitionId]);
|
||||
$processCode = $stmt_def->fetchColumn();
|
||||
$definition_json = $stmt_def->fetchColumn();
|
||||
$definition = !empty($definition_json) ? json_decode($definition_json, true) : [];
|
||||
|
||||
if ($definition && isset($definition['type']) && $definition['type'] === 'checklist') {
|
||||
$processCode = (string) $processDefinitionId;
|
||||
} else {
|
||||
$stmt_def = $this->pdo->prepare("SELECT code FROM process_definitions WHERE id = ?");
|
||||
$stmt_def->execute([$processDefinitionId]);
|
||||
$processCode = $stmt_def->fetchColumn();
|
||||
}
|
||||
|
||||
if($processCode) {
|
||||
$instanceId = $this->startProcess($processCode, $personId, $userId);
|
||||
@ -187,7 +223,7 @@ class WorkflowEngine {
|
||||
}
|
||||
|
||||
public function getEvents(int $instanceId): array {
|
||||
$stmt_events = $this->pdo->prepare("SELECT pe.*, p.email as user_email, p.firstName, p.lastName FROM process_events pe JOIN people p ON pe.createdById = p.id WHERE pe.processInstanceId = ? ORDER BY pe.createdAt DESC");
|
||||
$stmt_events = $this->pdo->prepare("SELECT pe.*, p.email as user_email, p.firstName, p.lastName FROM process_events pe JOIN people p ON pe.createdBy = p.id WHERE pe.processInstanceId = ? ORDER BY pe.createdAt DESC");
|
||||
$stmt_events->execute([$instanceId]);
|
||||
return $stmt_events->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
@ -212,4 +248,17 @@ class WorkflowEngine {
|
||||
|
||||
return $transitions;
|
||||
}
|
||||
|
||||
public function getProcessDefinitionNodes(int $processDefinitionId): array {
|
||||
$stmt = $this->pdo->prepare("SELECT definition_json FROM process_definitions WHERE id = ?");
|
||||
$stmt->execute([$processDefinitionId]);
|
||||
$json = $stmt->fetchColumn();
|
||||
|
||||
if (!$json) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$definition = !empty($json) ? json_decode($json, true) : [];
|
||||
return $definition['nodes'] ?? [];
|
||||
}
|
||||
}
|
||||
@ -1,43 +1,67 @@
|
||||
<?php
|
||||
session_start();
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$response = ['success' => false, 'message' => 'Wystąpił nieoczekiwany błąd.'];
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
header('Location: process_dashboard.php');
|
||||
http_response_code(405);
|
||||
$response['message'] = 'Invalid request method.';
|
||||
echo json_encode($response);
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once 'WorkflowEngine.php';
|
||||
|
||||
if (!isset($_POST['instanceId']) || !isset($_POST['transitionId'])) {
|
||||
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'Błąd: Brak wymaganych parametrów.'];
|
||||
header('Location: process_dashboard.php');
|
||||
http_response_code(400);
|
||||
$response['message'] = 'Błąd: Brak wymaganych parametrów.';
|
||||
echo json_encode($response);
|
||||
exit;
|
||||
}
|
||||
|
||||
$instanceId = (int)$_POST['instanceId'];
|
||||
$transitionId = $_POST['transitionId'];
|
||||
$userId = $_SESSION['user_id'] ?? null;
|
||||
$payload = $_POST['payload'] ?? null;
|
||||
$payload = $_POST['payload'] ?? [];
|
||||
|
||||
if (!$userId) {
|
||||
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'Błąd: Sesja wygasła.'];
|
||||
header('Location: login.php');
|
||||
http_response_code(401);
|
||||
$response['message'] = 'Błąd: Sesja wygasła.';
|
||||
echo json_encode($response);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$engine = new WorkflowEngine();
|
||||
$success = $engine->applyTransition($instanceId, $transitionId, $payload, $userId);
|
||||
$success = false;
|
||||
|
||||
if ($transitionId === 'note') {
|
||||
// Special case: Just add a note, don't change state.
|
||||
$message = $payload['message'] ?? '';
|
||||
if (!empty($message)) {
|
||||
$success = $engine->addNote($instanceId, $message, $userId);
|
||||
if ($success) {
|
||||
$response['message'] = 'Notatka została dodana.';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Standard transition logic
|
||||
$success = $engine->applyTransition($instanceId, $transitionId, $payload, $userId);
|
||||
if ($success) {
|
||||
$response['message'] = 'Akcja została wykonana pomyślnie.';
|
||||
}
|
||||
}
|
||||
|
||||
if ($success) {
|
||||
$_SESSION['flash_message'] = ['type' => 'success', 'message' => 'Akcja została wykonana pomyślnie.'];
|
||||
} else {
|
||||
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'Błąd: Nie udało się wykonać akcji.'];
|
||||
$response['success'] = true;
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error applying transition: " . $e->getMessage());
|
||||
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'Wystąpił krytyczny błąd: ' . $e->getMessage()];
|
||||
http_response_code(500);
|
||||
$response['message'] = 'Wystąpił krytyczny błąd: ' . $e->getMessage();
|
||||
}
|
||||
|
||||
header('Location: process_dashboard.php');
|
||||
echo json_encode($response);
|
||||
exit;
|
||||
@ -1,17 +1,15 @@
|
||||
<?php
|
||||
|
||||
require_once 'WorkflowEngine.php';
|
||||
session_start();
|
||||
|
||||
// Security check
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
http_response_code(401);
|
||||
die('Brak autoryzacji');
|
||||
}
|
||||
|
||||
$personId = $_GET['personId'] ?? null;
|
||||
$processDefinitionId = $_GET['processId'] ?? null; // Pulpit wysyła processId, który jest ID definicji
|
||||
|
||||
|
||||
$processDefinitionId = $_GET['processId'] ?? null;
|
||||
|
||||
if (!$personId || !$processDefinitionId) {
|
||||
http_response_code(400);
|
||||
@ -20,73 +18,158 @@ if (!$personId || !$processDefinitionId) {
|
||||
|
||||
$userId = $_SESSION['user_id'];
|
||||
$engine = new WorkflowEngine();
|
||||
$pdo = db();
|
||||
|
||||
// 1. Pobierz lub utwórz instancję
|
||||
// 1. Get or create instance
|
||||
$instance = $engine->getOrCreateInstanceByDefId($personId, $processDefinitionId, $userId);
|
||||
|
||||
if (!$instance) {
|
||||
http_response_code(500);
|
||||
die("Nie można pobrać lub utworzyć instancji procesu. personId: $personId, processDefinitionId: $processDefinitionId, userId: $userId");
|
||||
die("Nie można pobrać lub utworzyć instancji procesu.");
|
||||
}
|
||||
$instanceId = $instance['id'];
|
||||
|
||||
// 2. Pobierz powiązane dane przez silnik
|
||||
$events = $engine->getEvents($instanceId);
|
||||
$availableTransitions = $engine->getAvailableTransitions($instanceId);
|
||||
|
||||
// 3. Pobierz nazwy do wyświetlenia
|
||||
$pdo = db();
|
||||
// 2. Fetch all related data
|
||||
$stmt_person = $pdo->prepare("SELECT firstName, lastName FROM people WHERE id = ?");
|
||||
$stmt_person->execute([$personId]);
|
||||
$person = $stmt_person->fetch();
|
||||
|
||||
$stmt_process = $pdo->prepare("SELECT name FROM process_definitions WHERE id = ?");
|
||||
$stmt_process = $pdo->prepare("SELECT * FROM process_definitions WHERE id = ?");
|
||||
$stmt_process->execute([$processDefinitionId]);
|
||||
$process = $stmt_process->fetch();
|
||||
$definition = $process && $process['definition_json'] ? json_decode($process['definition_json'], true) : null;
|
||||
$isChecklist = ($definition && isset($definition['type']) && $definition['type'] === 'checklist');
|
||||
|
||||
$events = $engine->getEvents($instanceId);
|
||||
|
||||
?>
|
||||
|
||||
<h4><?= htmlspecialchars($person['firstName'] . ' ' . $person['lastName']) ?> - <?= htmlspecialchars($process['name']) ?></h4>
|
||||
<p>Status: <span class="badge bg-secondary"><?= htmlspecialchars($instance['current_status']) ?></span></p>
|
||||
<hr>
|
||||
<!-- Title for the modal, to be grabbed by JS -->
|
||||
<div id="instance-modal-title" class="d-none">
|
||||
<?= htmlspecialchars($person['firstName'] . ' ' . $person['lastName']) ?> - <?= htmlspecialchars($process['name']) ?>
|
||||
</div>
|
||||
|
||||
<h5>Wykonaj akcję</h5>
|
||||
<form action="_apply_transition.php" method="post">
|
||||
<input type="hidden" name="instanceId" value="<?= $instanceId ?>">
|
||||
<div class="mb-3">
|
||||
<label for="transitionSelect" class="form-label">Akcja</label>
|
||||
<select name="transitionId" id="transitionSelect" class="form-select" required>
|
||||
<option value="" disabled selected>-- Wybierz akcję --</option>
|
||||
<?php foreach ($availableTransitions as $transition): ?>
|
||||
<option value="<?= $transition['id'] ?>"><?= htmlspecialchars($transition['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
<option value="note">Dodaj notatkę</option> <!-- Specjalny przypadek -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="payloadMessage" class="form-label">Notatka / Wiadomość (opcjonalnie)</label>
|
||||
<textarea name="payload[message]" id="payloadMessage" class="form-control" rows="3"></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Zatwierdź</button>
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
|
||||
<h5>Historia</h5>
|
||||
<?php if (empty($events)): ?>
|
||||
<p>Brak zdarzeń.</p>
|
||||
<?php else: ?>
|
||||
<ul class="list-group">
|
||||
<?php foreach ($events as $event): ?>
|
||||
<li class="list-group-item">
|
||||
<strong><?= htmlspecialchars(ucfirst(str_replace('_', ' ', $event['event_type']))) ?></strong>
|
||||
<?php if (!empty($event['message'])):
|
||||
$payload = json_decode($event['payload_json'], true);
|
||||
$message = $payload['message'] ?? $event['message'];
|
||||
<?php if ($isChecklist): ?>
|
||||
<?php
|
||||
$tasks = $definition['tasks'] ?? [];
|
||||
$instanceData = $instance && $instance['data_json'] ? json_decode($instance['data_json'], true) : [];
|
||||
?>
|
||||
<div class="checklist-modal-container">
|
||||
<h5>Zadania do wykonania</h5>
|
||||
<div class="checklist-container">
|
||||
<?php foreach ($tasks as $task):
|
||||
$isChecked = !empty($instanceData[$task['code']]);
|
||||
?>
|
||||
<p class="mb-1"><?= htmlspecialchars($message) ?></p>
|
||||
<?php endif; ?>
|
||||
<small class="text-muted">Przez <?= htmlspecialchars($event['firstName'] . ' ' . $event['lastName']) ?> dnia <?= date('d.m.Y, H:i', strtotime($event['createdAt'])) ?></small>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input task-checkbox-modal" type="checkbox" value=""
|
||||
data-task-code="<?= $task['code'] ?>" <?= $isChecked ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" title="<?= htmlspecialchars($task['name']) ?>">
|
||||
<?= htmlspecialchars($task['name']) ?>
|
||||
</label>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php else: ?>
|
||||
<?php
|
||||
$currentNodeId = $instance['current_node_id'];
|
||||
$all_nodes = $engine->getProcessDefinitionNodes($processDefinitionId);
|
||||
$availableTransitions = $engine->getAvailableTransitions($instanceId);
|
||||
|
||||
$available_target_node_ids = array_map(function($t) { return $t['to']; }, $availableTransitions);
|
||||
$available_transitions_map = [];
|
||||
foreach ($availableTransitions as $t) {
|
||||
$available_transitions_map[$t['to']] = $t;
|
||||
}
|
||||
|
||||
$visited_nodes = [];
|
||||
foreach ($events as $event) {
|
||||
if ($event['node_id']) {
|
||||
$visited_nodes[$event['node_id']] = true;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<div class="process-steps-container">
|
||||
<h5>Kroki procesu</h5>
|
||||
<ul class="list-group">
|
||||
<?php foreach ($all_nodes as $nodeId => $node): ?>
|
||||
<?php
|
||||
$is_current = ($currentNodeId === $nodeId);
|
||||
$is_completed = isset($visited_nodes[$nodeId]) && !$is_current;
|
||||
$is_available = in_array($nodeId, $available_target_node_ids);
|
||||
|
||||
$status_icon = '';
|
||||
$li_class = '';
|
||||
$button = '';
|
||||
|
||||
if ($is_current) {
|
||||
$li_class = 'list-group-item-primary';
|
||||
$status_icon = '<i class="bi bi-arrow-right-circle-fill text-primary me-2"></i>';
|
||||
} elseif ($is_completed) {
|
||||
$li_class = 'list-group-item-success';
|
||||
$status_icon = '<i class="bi bi-check-circle-fill text-success me-2"></i>';
|
||||
} else {
|
||||
$li_class = 'text-muted';
|
||||
$status_icon = '<i class="bi bi-lock-fill me-2"></i>';
|
||||
}
|
||||
|
||||
if ($is_available) {
|
||||
$transition = $available_transitions_map[$nodeId];
|
||||
$button = <<<HTML
|
||||
<button class="btn btn-sm btn-primary apply-transition-btn"
|
||||
data-instance-id="{$instanceId}"
|
||||
data-transition-id="{$transition['id']}">
|
||||
{$transition['name']}
|
||||
</button>
|
||||
HTML;
|
||||
}
|
||||
?>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center <?= $li_class ?>">
|
||||
<div>
|
||||
<?= $status_icon ?>
|
||||
<strong><?= htmlspecialchars($node['name']) ?></strong>
|
||||
</div>
|
||||
<?= $button ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="add-note-container">
|
||||
<h5>Dodaj notatkę</h5>
|
||||
<div class="mb-3">
|
||||
<textarea id="noteMessage" class="form-control" rows="2" placeholder="Wpisz treść notatki..."></textarea>
|
||||
</div>
|
||||
<button id="addNoteBtn" class="btn btn-secondary" data-instance-id="<?= $instanceId ?>">Dodaj notatkę</button>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="history-container">
|
||||
<h5>Historia</h5>
|
||||
<?php if (empty($events)): ?>
|
||||
<p>Brak zdarzeń.</p>
|
||||
<?php else: ?>
|
||||
<ul class="list-group">
|
||||
<?php foreach ($events as $event): ?>
|
||||
<li class="list-group-item">
|
||||
<strong><?= htmlspecialchars(ucfirst(str_replace('_', ' ', $event['event_type']))) ?></strong>
|
||||
<?php
|
||||
if (!empty($event['message'])) {
|
||||
$payload = json_decode($event['payload_json'], true);
|
||||
$message = $payload['message'] ?? $event['message'];
|
||||
echo '<p class="mb-1 text-muted fst-italic">' . htmlspecialchars($message) . '</p>';
|
||||
}
|
||||
?>
|
||||
<small class="text-muted">Przez <?= htmlspecialchars($event['firstName'] . ' ' . $event['lastName']) ?> dnia <?= date('d.m.Y, H:i', strtotime($event['created_at'])) ?></small>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
96
_get_process_bulk_details.php
Normal file
96
_get_process_bulk_details.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($_GET['process_id'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Process ID is required.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$process_id = $_GET['process_id'];
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// 1. Get process definition details
|
||||
$stmt_def = $pdo->prepare("SELECT * FROM process_definitions WHERE id = ?");
|
||||
$stmt_def->execute([$process_id]);
|
||||
$process_definition = $stmt_def->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$process_definition) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Process definition not found.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!empty($process_definition['definition_json'])) {
|
||||
$process_definition['definition_json'] = json_decode($process_definition['definition_json'], true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new Exception("Failed to decode process definition JSON. Error: " . json_last_error_msg());
|
||||
}
|
||||
} else {
|
||||
$process_definition['definition_json'] = [];
|
||||
}
|
||||
|
||||
// 2. Get all instances for this process
|
||||
$stmt_instances = $pdo->prepare("SELECT * FROM process_instances WHERE processDefinitionId = ?");
|
||||
$stmt_instances->execute([$process_id]);
|
||||
$instances = $stmt_instances->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$instance_ids = array_map(function($i) { return $i['id']; }, $instances);
|
||||
|
||||
// 3. Get all events for these instances
|
||||
$events = [];
|
||||
if (!empty($instance_ids)) {
|
||||
$placeholders = implode(',', array_fill(0, count($instance_ids), '?'));
|
||||
$stmt_events = $pdo->prepare("SELECT * FROM process_events WHERE processInstanceId IN ($placeholders) ORDER BY createdAt, id");
|
||||
$stmt_events->execute($instance_ids);
|
||||
$all_events = $stmt_events->fetchAll(PDO::FETCH_ASSOC);
|
||||
// Group events by instance_id
|
||||
foreach ($all_events as $event) {
|
||||
$events[$event['processInstanceId']][] = $event;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Get People details
|
||||
$people_ids = array_unique(array_column($instances, 'personId'));
|
||||
$people = [];
|
||||
if (!empty($people_ids)) {
|
||||
$valid_people_ids = array_filter($people_ids, 'is_numeric');
|
||||
|
||||
if (!empty($valid_people_ids)) {
|
||||
$placeholders = implode(',', array_fill(0, count($valid_people_ids), '?'));
|
||||
$stmt_people = $pdo->prepare("SELECT id, firstName, lastName FROM people WHERE id IN ($placeholders)");
|
||||
$stmt_people->execute(array_values($valid_people_ids));
|
||||
$people_results = $stmt_people->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($people_results as $person) {
|
||||
$people[$person['id']] = $person;
|
||||
$people[$person['id']]['name'] = trim($person['firstName'] . ' ' . $person['lastName']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assemble the response
|
||||
// Ensure steps are available, even if the JSON is empty or malformed.
|
||||
$steps = !empty($process_definition['definition_json']['steps']) ? $process_definition['definition_json']['steps'] : [];
|
||||
|
||||
$response = [
|
||||
'process' => $process_definition,
|
||||
'steps' => $steps,
|
||||
'instances' => $instances,
|
||||
'events' => $events,
|
||||
'people' => $people
|
||||
];
|
||||
|
||||
echo json_encode($response);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'A database error occurred.', 'details' => $e->getMessage()]);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'A general error occurred.', 'details' => $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
31
_init_single_instance.php
Normal file
31
_init_single_instance.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'WorkflowEngine.php';
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
$_SESSION['error_message'] = "Authentication required.";
|
||||
header('Location: login.php');
|
||||
exit();
|
||||
}
|
||||
$userId = $_SESSION['user_id'];
|
||||
|
||||
$personId = $_GET['personId'] ?? null;
|
||||
$processDefinitionId = $_GET['processId'] ?? null;
|
||||
|
||||
if (!$personId || !$processDefinitionId) {
|
||||
$_SESSION['error_message'] = "Missing parameters for process initialization.";
|
||||
header('Location: index.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
$engine = new WorkflowEngine();
|
||||
$instance = $engine->getOrCreateInstanceByDefId($personId, $processDefinitionId, $userId);
|
||||
|
||||
if ($instance) {
|
||||
$_SESSION['success_message'] = "Process initialized successfully.";
|
||||
} else {
|
||||
$_SESSION['error_message'] = "Failed to initialize process.";
|
||||
}
|
||||
|
||||
header('Location: index.php');
|
||||
exit();
|
||||
81
_update_training_checklist_status.php
Normal file
81
_update_training_checklist_status.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['error' => 'Method Not Allowed']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$inputJSON = file_get_contents('php://input');
|
||||
$input = json_decode($inputJSON, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Invalid JSON']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$instanceId = $input['instance_id'] ?? null;
|
||||
$taskCode = $input['task_code'] ?? null;
|
||||
$isChecked = $input['is_checked'] ?? null;
|
||||
|
||||
if (!$instanceId || !$taskCode || $isChecked === null) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Missing required parameters: instance_id, task_code, is_checked']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// Get current data_json
|
||||
$stmt = $pdo->prepare("SELECT data_json FROM process_instances WHERE id = ?");
|
||||
$stmt->execute([$instanceId]);
|
||||
$currentDataJson = $stmt->fetchColumn();
|
||||
|
||||
$data = $currentDataJson ? json_decode($currentDataJson, true) : [];
|
||||
|
||||
// Update the specific task status
|
||||
$data[$taskCode] = (bool)$isChecked;
|
||||
$newDataJson = json_encode($data);
|
||||
|
||||
// Save new data_json and update timestamp
|
||||
$stmt = $pdo->prepare("UPDATE process_instances SET data_json = ?, lastActivityAt = CURRENT_TIMESTAMP WHERE id = ?");
|
||||
$success = $stmt->execute([$newDataJson, $instanceId]);
|
||||
|
||||
if ($success) {
|
||||
// Calculate progress
|
||||
$stmt = $pdo->prepare("SELECT pd.definition_json FROM process_definitions pd JOIN process_instances pi ON pd.id = pi.process_definition_id WHERE pi.id = ?");
|
||||
$stmt->execute([$instanceId]);
|
||||
$definitionJson = $stmt->fetchColumn();
|
||||
$definition = json_decode($definitionJson, true);
|
||||
$totalTasks = count($definition['tasks'] ?? []);
|
||||
$completedTasks = count(array_filter($data));
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Status updated successfully.',
|
||||
'progress' => [
|
||||
'completed' => $completedTasks,
|
||||
'total' => $totalTasks
|
||||
],
|
||||
'lastActivityAt' => date('d/m/y')
|
||||
]);
|
||||
} else {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Failed to update status.']);
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Database error: ' . $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
@ -10,3 +10,151 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
// Handler for showing the edit person modal
|
||||
$('#editPersonModal').on('show.bs.modal', function (event) {
|
||||
var button = $(event.relatedTarget); // Button that triggered the modal
|
||||
var personId = button.data('person-id'); // Extract info from data-* attributes
|
||||
var modal = $(this);
|
||||
|
||||
// Clear previous data
|
||||
modal.find('form').trigger('reset');
|
||||
modal.find('#editPersonId').val('');
|
||||
modal.find('#editRoles').empty();
|
||||
// Clear file paths
|
||||
modal.find('#editCompanyLogoPath, #editPersonPhotoPath, #editGainsSheetPath, #editTopWantedPath, #editTopOwnedPath').text('');
|
||||
|
||||
if (personId) {
|
||||
// AJAX request to get person details
|
||||
$.ajax({
|
||||
url: '_get_person_details.php',
|
||||
type: 'GET',
|
||||
data: { id: personId },
|
||||
dataType: 'json',
|
||||
success: function(response) {
|
||||
if (response.error) {
|
||||
alert('Error fetching person details: ' + response.error);
|
||||
return;
|
||||
}
|
||||
|
||||
var person = response.person;
|
||||
var all_functions = response.all_functions;
|
||||
var person_functions = response.person_functions;
|
||||
|
||||
if (!person) {
|
||||
alert('Could not find person data.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Populate the form fields
|
||||
modal.find('#editPersonId').val(person.id);
|
||||
modal.find('#editFirstName').val(person.firstName);
|
||||
modal.find('#editLastName').val(person.lastName);
|
||||
modal.find('#editPhone').val(person.phone);
|
||||
modal.find('#editEmail').val(person.email);
|
||||
modal.find('#editRole').val(person.role);
|
||||
modal.find('#editBniGroup').val(person.bni_group_id);
|
||||
modal.find('#editCompanyName').val(person.companyName);
|
||||
modal.find('#editNip').val(person.nip);
|
||||
modal.find('#editIndustry').val(person.industry);
|
||||
modal.find('#editCompanySize').val(person.company_size_revenue);
|
||||
modal.find('#editBusinessDescription').val(person.business_description);
|
||||
|
||||
// Populate file paths
|
||||
if (person.company_logo_path) {
|
||||
modal.find('#editCompanyLogoPath').text('Current file: ' + person.company_logo_path.split('/').pop());
|
||||
}
|
||||
if (person.person_photo_path) {
|
||||
modal.find('#editPersonPhotoPath').text('Current file: ' + person.person_photo_path.split('/').pop());
|
||||
}
|
||||
if (person.gains_sheet_path) {
|
||||
modal.find('#editGainsSheetPath').text('Current file: ' + person.gains_sheet_path.split('/').pop());
|
||||
}
|
||||
if (person.top_wanted_contacts_path) {
|
||||
modal.find('#editTopWantedPath').text('Current file: ' + person.top_wanted_contacts_path.split('/').pop());
|
||||
}
|
||||
if (person.top_owned_contacts_path) {
|
||||
modal.find('#editTopOwnedPath').text('Current file: ' + person.top_owned_contacts_path.split('/').pop());
|
||||
}
|
||||
|
||||
// Populate functions/roles dropdown and select assigned ones
|
||||
var rolesSelect = modal.find('#editRoles');
|
||||
rolesSelect.empty(); // Clear existing options
|
||||
|
||||
if (all_functions && all_functions.length > 0) {
|
||||
const groupedFunctions = all_functions.reduce((acc, func) => {
|
||||
const groupName = func.group_name || 'General';
|
||||
if (!acc[groupName]) {
|
||||
acc[groupName] = [];
|
||||
}
|
||||
acc[groupName].push(func);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
for (const groupName in groupedFunctions) {
|
||||
const optgroup = $('<optgroup>').attr('label', groupName);
|
||||
groupedFunctions[groupName].forEach(function(func) {
|
||||
var option = $('<option></option>').val(func.id).text(func.name);
|
||||
if (person_functions && person_functions.includes(String(func.id))) {
|
||||
option.prop('selected', true);
|
||||
}
|
||||
optgroup.append(option);
|
||||
});
|
||||
rolesSelect.append(optgroup);
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger change to show/hide conditional fields
|
||||
modal.find('#editRole').trigger('change');
|
||||
|
||||
// Also set up the delete button
|
||||
$('#deleteUserBtn').data('person-id', person.id);
|
||||
$('#personNameToDelete').text(person.firstName + ' ' + person.lastName);
|
||||
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
alert('An error occurred while fetching person data. Please try again.');
|
||||
console.error("AJAX Error:", status, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Show/hide group selection based on role for both Edit and Create modals
|
||||
$(document).on('change', '#editRole, #createRole', function() {
|
||||
const role = $(this).val();
|
||||
const isMember = role === 'member';
|
||||
|
||||
// Find the correct context (modal) for the elements
|
||||
const modal = $(this).closest('.modal-content');
|
||||
|
||||
modal.find('.member-only-fields').toggle(isMember);
|
||||
modal.find('#edit-group-selection-div, #create-group-selection-div').toggle(isMember);
|
||||
});
|
||||
|
||||
// Handle Delete Person confirmation
|
||||
$('#confirmDeleteBtn').on('click', function() {
|
||||
var personId = $('#deleteUserBtn').data('person-id');
|
||||
if (personId) {
|
||||
// Use a form submission to perform the delete
|
||||
var form = $('<form></form>');
|
||||
form.attr("method", "post");
|
||||
form.attr("action", "_delete_person.php");
|
||||
|
||||
var field = $('<input></input>');
|
||||
field.attr("type", "hidden");
|
||||
field.attr("name", "id");
|
||||
field.attr("value", personId);
|
||||
form.append(field);
|
||||
|
||||
$(document.body).append(form);
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
|
||||
// Set initial state for create form
|
||||
$('#createPersonModal').on('show.bs.modal', function () {
|
||||
$('#createRole').trigger('change');
|
||||
});
|
||||
});
|
||||
@ -2,4 +2,4 @@
|
||||
# https://curl.se/docs/http-cookies.html
|
||||
# This file was generated by libcurl! Edit at your own risk.
|
||||
|
||||
localhost FALSE / FALSE 0 PHPSESSID u19ekrhqoemk4c5avca3umanfb
|
||||
127.0.0.1 FALSE / FALSE 0 PHPSESSID abf9a8g0sidv8idojcrr65jetp
|
||||
|
||||
11
db_setup.php
11
db_setup.php
@ -67,17 +67,17 @@ try {
|
||||
// 3. Process Instances table (updated FK)
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS `process_instances` (
|
||||
`id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
`personId` INT(11) UNSIGNED NOT NULL,
|
||||
`processDefinitionId` INT(11) UNSIGNED NOT NULL,
|
||||
`person_id` INT(11) UNSIGNED NOT NULL,
|
||||
`process_definition_id` INT(11) UNSIGNED NOT NULL,
|
||||
`current_status` VARCHAR(255) NOT NULL DEFAULT 'none',
|
||||
`current_node_id` VARCHAR(255),
|
||||
`current_reason` TEXT,
|
||||
`suggested_next_step` TEXT,
|
||||
`data_json` TEXT,
|
||||
`lastActivityAt` TIMESTAMP NULL,
|
||||
FOREIGN KEY (personId) REFERENCES people(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (processDefinitionId) REFERENCES process_definitions(id) ON DELETE CASCADE,
|
||||
UNIQUE KEY `person_process` (`personId`, `processDefinitionId`)
|
||||
FOREIGN KEY (person_id) REFERENCES people(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (process_definition_id) REFERENCES process_definitions(id) ON DELETE CASCADE,
|
||||
UNIQUE KEY `person_process` (`person_id`, `process_definition_id`)
|
||||
)");
|
||||
echo "Process instances table created or already exists.\n";
|
||||
|
||||
@ -122,6 +122,7 @@ try {
|
||||
echo "Migrated process_instances: contactId -> personId.\n";
|
||||
}
|
||||
|
||||
|
||||
// Drop old tables if they exist
|
||||
$pdo->exec("DROP TABLE IF EXISTS `users`, `contacts`;");
|
||||
echo "Dropped old 'users' and 'contacts' tables.\n";
|
||||
|
||||
626
index.php
626
index.php
@ -1,4 +1,5 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'WorkflowEngine.php';
|
||||
|
||||
$workflowEngine = new WorkflowEngine();
|
||||
@ -15,7 +16,8 @@ $stmt = $pdo->query("
|
||||
$people = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Fetch process definitions
|
||||
$stmt = $pdo->prepare("SELECT * FROM process_definitions WHERE name NOT IN (?, ?) ORDER BY name");
|
||||
$stmt = $pdo->prepare("SELECT * FROM process_definitions WHERE name NOT IN (?, ?)"
|
||||
. " ORDER BY name");
|
||||
$stmt->execute(['Obsluga goscia', 'Przygotowanie spotkania grupy']);
|
||||
$processes = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
@ -31,10 +33,14 @@ foreach ($all_instances as $instance) {
|
||||
|
||||
|
||||
$status_colors = [
|
||||
'none' => 'secondary',
|
||||
'negative' => 'danger',
|
||||
'in_progress' => 'warning',
|
||||
'positive' => 'success',
|
||||
'completed' => '#28a745',
|
||||
'positive' => '#28a745',
|
||||
'in_progress' => '#fd7e14',
|
||||
'negative' => '#dc3545',
|
||||
'error' => '#dc3545',
|
||||
'none' => '#808080',
|
||||
'not_started' => '#808080',
|
||||
'inactive' => '#808080',
|
||||
];
|
||||
|
||||
$pdo = db();
|
||||
@ -65,167 +71,216 @@ $bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
|
||||
<?php include '_sidebar.php'; ?>
|
||||
|
||||
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
|
||||
<?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; ?>
|
||||
<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; ?>
|
||||
<?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
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<?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
|
||||
// Fetch ID for the meeting process to make headers clickable
|
||||
$stmt_meeting_process = $pdo->prepare("SELECT id FROM process_definitions WHERE name = ?");
|
||||
$stmt_meeting_process->execute(['Przygotowanie spotkania grupy']);
|
||||
$meeting_process = $stmt_meeting_process->fetch(PDO::FETCH_ASSOC);
|
||||
$meeting_process_id = $meeting_process ? $meeting_process['id'] : 'null';
|
||||
?>
|
||||
<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['firstName'] . ' ' . $person['lastName']) ?></div>
|
||||
<div class="person-details">
|
||||
<span class="d-block"><?= htmlspecialchars($person['companyName']) ?></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['firstName'] . ' ' . $person['lastName']) ?>">
|
||||
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;
|
||||
$definition = isset($process['definition_json']) ? json_decode($process['definition_json'], true) : null;
|
||||
$isChecklist = ($definition && isset($definition['type']) && $definition['type'] === 'checklist');
|
||||
$lastActivity = $instance && $instance['lastActivityAt'] ? date('d/m/y', strtotime($instance['lastActivityAt'])) : '';
|
||||
|
||||
$color = $status_colors['inactive'];
|
||||
$title = 'Inactive';
|
||||
|
||||
if ($isChecklist) {
|
||||
$status = 'inactive';
|
||||
if ($instance) {
|
||||
$tasks = $definition['tasks'] ?? [];
|
||||
$instanceData = $instance['data_json'] ? json_decode($instance['data_json'], true) : [];
|
||||
$totalTasks = count($tasks);
|
||||
$completedTasks = 0;
|
||||
foreach ($tasks as $task) {
|
||||
if (!empty($instanceData[$task['code']])) {
|
||||
$completedTasks++;
|
||||
}
|
||||
}
|
||||
|
||||
$title = "$completedTasks/$totalTasks completed";
|
||||
if ($totalTasks > 0 && $completedTasks === $totalTasks) {
|
||||
$status = 'completed';
|
||||
} elseif ($completedTasks > 0) {
|
||||
$status = 'in_progress';
|
||||
} else {
|
||||
$status = 'inactive'; // Initialized but no progress
|
||||
}
|
||||
}
|
||||
$color = $status_colors[$status] ?? $status_colors['inactive'];
|
||||
?>
|
||||
<td class="align-middle checklist-cell text-center" style="cursor: pointer;" data-bs-toggle="modal" data-bs-target="#instanceModal" data-person-id="<?= $person['id'] ?>" data-process-id="<?= $process['id'] ?>" data-instance-id="<?= $instance['id'] ?? '' ?>">
|
||||
<span title="<?= $title ?>" style="height: 20px; width: 20px; background-color: <?= $color ?>; border-radius: 50%; display: inline-block;"></span>
|
||||
<small class="text-muted d-block last-activity-date mt-1"><?= $lastActivity ?></small>
|
||||
</td>
|
||||
<?php } else {
|
||||
$status = $instance ? $instance['current_status'] : 'inactive';
|
||||
$color = $status_colors[$status] ?? $status_colors['inactive'];
|
||||
$title = ucfirst($status);
|
||||
?>
|
||||
<td class="text-center align-middle" style="cursor: pointer;" data-bs-toggle="modal" data-bs-target="#instanceModal" data-person-id="<?= $person['id'] ?>" data-process-id="<?= $process['id'] ?>">
|
||||
<span title="<?= $title ?>" 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>
|
||||
|
||||
<?php
|
||||
// Define process groups
|
||||
// Show all processes from the DB directly.
|
||||
$inne_procesy_cols = $processes;
|
||||
$inne_procesy_cols[] = ['id' => 'temp_szkolenia', 'name' => 'Szkolenia dla nowego członka'];
|
||||
|
||||
// --- 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">
|
||||
<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 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><?= 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="person_ids[]" value="<?= $person['id'] ?>"></td>
|
||||
<td class="person-cell">
|
||||
<div class="person-main">
|
||||
<div class="person-name"><?= htmlspecialchars($person['firstName'] . ' ' . $person['lastName']) ?></div>
|
||||
<div class="person-details">
|
||||
<span class="d-block"><?= htmlspecialchars($person['companyName']) ?></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['firstName'] . ' ' . $person['lastName']) ?>">
|
||||
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];
|
||||
echo "<span class=\"badge rounded-circle bg-$color\" style=\"width: 20px; height: 20px; display: inline-block;\" 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;
|
||||
$status = $instance ? $instance['current_status'] : 'none';
|
||||
$color = $status_colors[$status];
|
||||
$lastActivity = $instance && $instance['lastActivityAt'] ? date('d/m/y', strtotime($instance['lastActivityAt'])) : '';
|
||||
?>
|
||||
<td class="text-center align-middle" data-bs-toggle="modal" data-bs-target="#instanceModal" data-person-id="<?= $person['id'] ?>" data-process-id="<?= $process['id'] ?>">
|
||||
<span class="badge rounded-circle bg-<?= $color ?>" style="width: 20px; height: 20px; display: inline-block;" title="<?= ucfirst($status) ?>"> </span>
|
||||
<small class="text-muted d-block"><?= $lastActivity ?></small>
|
||||
</td>
|
||||
<?php endforeach; ?>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="process-detail-view" style="display: none;">
|
||||
<!-- This will be populated dynamically -->
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
@ -523,40 +578,176 @@ $bni_groups = $stmt_bni_groups->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
<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;
|
||||
const personId = button.getAttribute('data-person-id');
|
||||
const processId = button.getAttribute('data-process-id');
|
||||
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(`_get_instance_details.php?person_id=${personId}&process_id=${processId}`)
|
||||
.then(response => response.text())
|
||||
.then(html => {
|
||||
modalBody.innerHTML = html;
|
||||
const newTitle = modalBody.querySelector('#instance-modal-title');
|
||||
if (newTitle) {
|
||||
modalTitle.innerHTML = newTitle.innerHTML;
|
||||
newTitle.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';
|
||||
});
|
||||
// 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;
|
||||
}
|
||||
});
|
||||
|
||||
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?personId=${personId}&processId=${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 formData = new FormData();
|
||||
formData.append('instanceId', instanceId);
|
||||
formData.append('transitionId', transitionId);
|
||||
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 submitRequestAndReloadModal(url, formData) {
|
||||
const modalBody = instanceModal.querySelector('.modal-body');
|
||||
showLoading(modalBody);
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
// Reload modal content after successful submission
|
||||
fetchAndRenderModalContent(currentPersonId, currentProcessId);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error submitting request:', error);
|
||||
modalBody.innerHTML = `<div class="alert alert-danger">Wystąpił błąd sieciowy.</div>`;
|
||||
});
|
||||
}
|
||||
|
||||
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 ---
|
||||
@ -578,6 +769,8 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
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';
|
||||
|
||||
@ -614,7 +807,6 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
const meetingDate = new Date(meeting.start_datetime);
|
||||
const formattedDate = meetingDate.toLocaleDateString('pl-PL', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
||||
|
||||
// In a real scenario, you'd get this info from the backend
|
||||
const isLastInBatch = false;
|
||||
const isLastInGroup = false;
|
||||
|
||||
@ -670,6 +862,8 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
function findLastElementForGroup(parent, selector, groupId, findFirst = false) {
|
||||
const elements = parent.querySelectorAll(`${selector}[data-group-id="${groupId}"]`);
|
||||
if (elements.length === 0) return null;
|
||||
@ -678,14 +872,74 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
function htmlspecialchars(str) {
|
||||
if (typeof str !== 'string') return '';
|
||||
return str.replace(/[&<>"']/g, match => ({
|
||||
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.personId);
|
||||
if (!person) return;
|
||||
|
||||
tableHtml += `<tr><td class="person-cell">
|
||||
<div class="person-main">
|
||||
<div class="person-name">${htmlspecialchars(person.firstName + ' ' + person.lastName)}</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>
|
||||
|
||||
@ -700,7 +954,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form action="_bulk_update_status.php" method="post">
|
||||
<input type="hidden" name="person_ids" id="bulkStatusPersonIds">
|
||||
<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>
|
||||
@ -735,7 +989,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form action="_bulk_add_event.php" method="post">
|
||||
<input type="hidden" name="person_ids" id="bulkEventPersonIds">
|
||||
<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>
|
||||
@ -765,7 +1019,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form action="_bulk_init_instances.php" method="post">
|
||||
<input type="hidden" name="person_ids" id="bulkInitPersonIds">
|
||||
<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): ?>
|
||||
|
||||
54
test_workflow.php
Normal file
54
test_workflow.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
// test_workflow.php
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 1);
|
||||
|
||||
require_once 'WorkflowEngine.php';
|
||||
require_once 'db/config.php'; // Include the database configuration
|
||||
|
||||
echo "Testing WorkflowEngine...<br>";
|
||||
|
||||
// Establish a direct database connection for setup
|
||||
$pdo = db();
|
||||
|
||||
// 1. Get the first person from the database
|
||||
$stmt = $pdo->query("SELECT id FROM people LIMIT 1");
|
||||
$person = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$person) {
|
||||
die("<b>Error:</b> No people found in the database. Please run `db_setup.php` or ensure the database is seeded correctly.<br>");
|
||||
}
|
||||
$personId = $person['id'];
|
||||
echo "Prerequisite check: Using person with ID $personId.<br>";
|
||||
|
||||
// 2. Get the first process definition from the database
|
||||
$stmt = $pdo->query("SELECT id FROM process_definitions LIMIT 1");
|
||||
$process = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$process) {
|
||||
die("<b>Error:</b> No process definitions found in the database. Please run `db_setup.php` or ensure the database is seeded correctly.<br>");
|
||||
}
|
||||
$processDefinitionId = $process['id'];
|
||||
echo "Prerequisite check: Using process definition with ID $processDefinitionId.<br>";
|
||||
|
||||
|
||||
$engine = new WorkflowEngine();
|
||||
$userId = $personId;
|
||||
|
||||
echo "Attempting to get or create instance with personId: $personId and processDefinitionId: $processDefinitionId<br>";
|
||||
|
||||
try {
|
||||
$instance = $engine->getOrCreateInstanceByDefId($personId, $processDefinitionId, $userId);
|
||||
|
||||
if ($instance) {
|
||||
echo "<b>Success!</b> Successfully got or created instance:<br>";
|
||||
echo "<pre>";
|
||||
print_r($instance);
|
||||
echo "</pre>";
|
||||
} else {
|
||||
echo "<b>Error:</b> Failed to get or create instance, but no exception was thrown.<br>";
|
||||
echo "This might happen if the process definition exists, but `startProcess` fails internally.<br>";
|
||||
echo "Check PHP error logs for more details.<br>";
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo "An exception occurred: " . $e->getMessage() . "<br>";
|
||||
echo "Stack trace:<br><pre>" . $e->getTraceAsString() . "</pre>";
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user