Wersja bazowa
This commit is contained in:
parent
110c203f6a
commit
9234472371
263
WorkflowEngine.php
Normal file
263
WorkflowEngine.php
Normal file
@ -0,0 +1,263 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
/**
|
||||
* Class WorkflowEngine
|
||||
*
|
||||
* Centralny serwis do zarządzania logiką procesów.
|
||||
* Interfejs użytkownika nie powinien bezpośrednio zmieniać statusów ani instancji; wszystkie operacje muszą przechodzić przez ten silnik.
|
||||
*/
|
||||
class WorkflowEngine {
|
||||
|
||||
private $pdo;
|
||||
|
||||
public function __construct() {
|
||||
$this->pdo = db();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pobiera wszystkie dane niezbędne dla głównej macierzy pulpitu procesów.
|
||||
*
|
||||
* @return array Tablica zawierająca 'people', 'definitions' i zmapowaną tablicę 'instances'.
|
||||
*/
|
||||
public function getDashboardMatrix(): array {
|
||||
// Get all people (potential assignees)
|
||||
$stmt_people = $this->pdo->prepare("SELECT id, firstName, lastName, companyName, role, email, phone FROM people ORDER BY lastName, firstName");
|
||||
$stmt_people->execute();
|
||||
$people = $stmt_people->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Fetch all process definitions
|
||||
$stmt_defs = $this->pdo->prepare("SELECT id, name FROM process_definitions ORDER BY name");
|
||||
$stmt_defs->execute();
|
||||
$process_definitions = $stmt_defs->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Fetch instances
|
||||
$stmt_instances = $this->pdo->prepare("SELECT * FROM process_instances");
|
||||
$stmt_instances->execute();
|
||||
$instances_data = $stmt_instances->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$instances = [];
|
||||
foreach ($instances_data as $instance) {
|
||||
$instances[$instance['personId']][$instance['processDefinitionId']] = $instance;
|
||||
}
|
||||
|
||||
return [
|
||||
'people' => $people,
|
||||
'definitions' => $process_definitions,
|
||||
'instances' => $instances,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Rozpoczyna nową instancję procesu dla danej osoby.
|
||||
*
|
||||
* @param string $processCode Unikalny kod definicji procesu.
|
||||
* @param int $personId ID osoby.
|
||||
* @param int $userId ID użytkownika inicjującego akcję.
|
||||
* @return int|null ID nowo utworzonej instancji lub null w przypadku niepowodzenia.
|
||||
*/
|
||||
public function startProcess(string $processCode, int $personId, int $userId): ?int {
|
||||
// 1. Znajdź aktywną definicję procesu po kodzie.
|
||||
// 2. Pobierz start_node_id z definicji.
|
||||
// 3. Utwórz nową instancję procesu ze statusem 'in_progress' i current_node_id.
|
||||
// 4. Utwórz zdarzenie systemowe dla rozpoczęcia procesu.
|
||||
// TODO: Implementacja logiki.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pobiera pojedynczą instancję procesu, tworząc ją, jeśli nie istnieje.
|
||||
*
|
||||
* @param int $personId
|
||||
* @param int $processDefinitionId
|
||||
* @param int $userId Użytkownik inicjujący utworzenie, jeśli to nastąpi.
|
||||
* @return array|null Tablica asocjacyjna z danymi instancji.
|
||||
*/
|
||||
public function getOrCreateInstanceByDefId(int $personId, int $processDefinitionId, int $userId): ?array {
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM process_instances WHERE personId = ? AND processDefinitionId = ?");
|
||||
$stmt->execute([$personId, $processDefinitionId]);
|
||||
$instance = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$instance) {
|
||||
// Fetch the process definition to get the initial status
|
||||
$stmt_def = $this->pdo->prepare("SELECT definition_json FROM process_definitions WHERE id = ?");
|
||||
$stmt_def->execute([$processDefinitionId]);
|
||||
$definition_raw = $stmt_def->fetchColumn();
|
||||
$definition = $definition_raw ? json_decode($definition_raw, true) : null;
|
||||
$initial_status = $definition['initial_status'] ?? 'none';
|
||||
|
||||
$stmt_insert = $this->pdo->prepare("INSERT INTO process_instances (personId, processDefinitionId, current_status) VALUES (?, ?, ?)");
|
||||
$stmt_insert->execute([$personId, $processDefinitionId, $initial_status]);
|
||||
$instanceId = $this->pdo->lastInsertId();
|
||||
|
||||
// Utwórz zdarzenie systemowe dla utworzenia instancji
|
||||
$this->addEvent($instanceId, 'system', 'Instancja utworzona.', null, [], $userId);
|
||||
|
||||
// Pobierz ponownie nowo utworzoną instancję
|
||||
$stmt->execute([$personId, $processDefinitionId]);
|
||||
$instance = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dodaje nowe zdarzenie do historii instancji.
|
||||
* To jest wewnętrzna metoda pomocnicza.
|
||||
*/
|
||||
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, event_type, message, node_id, payload_json, createdById) VALUES (?, ?, ?, ?, ?, ?)"
|
||||
);
|
||||
$stmt->execute([$instanceId, $eventType, $message, $nodeId, json_encode($payload), $userId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pobiera historię zdarzeń dla danej instancji.
|
||||
*
|
||||
* @param int $instanceId
|
||||
* @return array
|
||||
*/
|
||||
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->execute([$instanceId]);
|
||||
return $stmt_events->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pobiera listę dostępnych przejść z bieżącego węzła instancji.
|
||||
*
|
||||
* @param int $instanceId
|
||||
* @return array Lista dostępnych przejść.
|
||||
*/
|
||||
public function getAvailableTransitions(int $instanceId): array {
|
||||
$stmt = $this->pdo->prepare(
|
||||
'SELECT pi.current_status, pd.definition_json '
|
||||
. 'FROM process_instances pi '
|
||||
. 'JOIN process_definitions pd ON pi.processDefinitionId = pd.id '
|
||||
. 'WHERE pi.id = ?'
|
||||
);
|
||||
$stmt->execute([$instanceId]);
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$result || empty($result['definition_json'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$definition = json_decode($result['definition_json'], true);
|
||||
if (!$definition || !isset($definition['transitions'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$current_status = $result['current_status'];
|
||||
$allowed_transitions = $definition['transitions'][$current_status] ?? [];
|
||||
|
||||
$transitions = [];
|
||||
foreach ($allowed_transitions as $target_status) {
|
||||
$transitions[] = [
|
||||
'id' => 'transition_' . str_replace(' ', '_', strtolower($target_status)), // e.g., transition_in_progress
|
||||
'name' => 'Oznacz jako ' . $target_status, // e.g., Oznacz jako In Progress
|
||||
'target_status' => $target_status
|
||||
];
|
||||
}
|
||||
|
||||
return $transitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stosuje przejście do instancji procesu. To jest główna metoda do postępu w przepływie pracy.
|
||||
*
|
||||
* @param int $instanceId ID instancji procesu.
|
||||
* @param string $transitionId ID przejścia do zastosowania (z definition_json).
|
||||
* @param array $inputPayload Dane zebrane od użytkownika dla tego kroku.
|
||||
* @param int $userId ID użytkownika wykonującego akcję.
|
||||
* @return bool True w przypadku sukcesu, false w przypadku porażki.
|
||||
*/
|
||||
public function applyTransition(int $instanceId, string $transitionId, array $inputPayload, int $userId): bool {
|
||||
$this->pdo->beginTransaction();
|
||||
|
||||
try {
|
||||
// 1. Pobierz instancję i dostępne przejścia
|
||||
$stmt = $this->pdo->prepare("SELECT * FROM process_instances WHERE id = ?");
|
||||
$stmt->execute([$instanceId]);
|
||||
$instance = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$instance) {
|
||||
// Instancja nie znaleziona
|
||||
$this->pdo->rollBack();
|
||||
return false;
|
||||
}
|
||||
|
||||
$availableTransitions = $this->getAvailableTransitions($instanceId);
|
||||
|
||||
// 2. Sprawdź, czy przejście jest dozwolone
|
||||
$selectedTransition = null;
|
||||
if ($transitionId === 'note') { // Specjalny przypadek dodawania notatki
|
||||
$selectedTransition = ['id' => 'note', 'name' => 'Dodano notatkę', 'target_status' => $instance['current_status']];
|
||||
} else {
|
||||
foreach ($availableTransitions as $trans) {
|
||||
if ($trans['id'] === $transitionId) {
|
||||
$selectedTransition = $trans;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$selectedTransition) {
|
||||
// Nieprawidłowe lub niedozwolone przejście
|
||||
$this->pdo->rollBack();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. Utwórz zdarzenie
|
||||
$eventType = ($transitionId === 'note') ? 'note' : 'transition_applied';
|
||||
$message = $inputPayload['message'] ?? $selectedTransition['name'];
|
||||
$this->addEvent($instanceId, $eventType, $message, null, $inputPayload, $userId);
|
||||
|
||||
// 4. Zaktualizuj instancję
|
||||
$stmt_update = $this->pdo->prepare(
|
||||
"UPDATE process_instances SET current_status = ?, lastActivityAt = CURRENT_TIMESTAMP WHERE id = ?"
|
||||
);
|
||||
$stmt_update->execute([$selectedTransition['target_status'], $instanceId]);
|
||||
|
||||
$this->pdo->commit();
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->pdo->rollBack();
|
||||
error_log("Błąd w applyTransition: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Masowa operacja stosowania tego samego przejścia do wielu osób dla danego procesu.
|
||||
*
|
||||
* @param string $processCode
|
||||
* @param array $personIds
|
||||
* @param string $transitionId
|
||||
* @param array $inputPayload
|
||||
* @param int $userId
|
||||
* @return array Podsumowanie wyników (np. ['success' => count, 'failed' => count]).
|
||||
*/
|
||||
public function bulkApplyTransition(string $processCode, array $personIds, string $transitionId, array $inputPayload, int $userId): array {
|
||||
// 1. Upewnij się, że instancje istnieją dla wszystkich osób (użyj ensureInstances).
|
||||
// 2. Przejdź przez pętlę personIds i wywołaj applyTransition dla każdej z nich.
|
||||
// TODO: Implementacja logiki.
|
||||
return ['success' => 0, 'failed' => 0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Zapewnia, że instancje procesów istnieją dla danego zestawu osób i kodów procesów.
|
||||
* Jeśli instancja brakuje, zostanie utworzona.
|
||||
*
|
||||
* @param array $personIds
|
||||
* @param array $processCodes
|
||||
* @param int $userId
|
||||
* @return array Podsumowanie utworzonych instancji.
|
||||
*/
|
||||
public function ensureInstances(array $personIds, array $processCodes, int $userId): array {
|
||||
// TODO: Implementacja logiki do tworzenia brakujących instancji.
|
||||
return ['created' => 0];
|
||||
}
|
||||
}
|
||||
32
_add_bni_group.php
Normal file
32
_add_bni_group.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
if (isset($_POST['add'])) {
|
||||
$name = $_POST['name'];
|
||||
$city = $_POST['city'] ?? null;
|
||||
$active = isset($_POST['active']) ? 1 : 0;
|
||||
$display_order = $_POST['display_order'] ?? 0;
|
||||
|
||||
if (empty($name)) {
|
||||
$_SESSION['error_message'] = 'Name is required.';
|
||||
header('Location: bni_groups.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("INSERT INTO bni_groups (name, city, active, display_order) VALUES (:name, :city, :active, :display_order)");
|
||||
$stmt->bindParam(':name', $name);
|
||||
$stmt->bindParam(':city', $city);
|
||||
$stmt->bindParam(':active', $active, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':display_order', $display_order, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
$_SESSION['success_message'] = 'BNI Group added successfully!';
|
||||
} catch (PDOException $e) {
|
||||
$_SESSION['error_message'] = 'Error adding BNI group: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: bni_groups.php');
|
||||
exit;
|
||||
88
_add_calendar_event.php
Normal file
88
_add_calendar_event.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header("Location: login.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
require_once 'db/config.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$title = $_POST['title'] ?? '';
|
||||
$description = $_POST['description'] ?? '';
|
||||
$start_datetime = $_POST['start_datetime'] ?? '';
|
||||
$end_datetime = $_POST['end_datetime'] ?? '';
|
||||
$event_type_id = $_POST['event_type_id'] ?? null;
|
||||
$recurrence = $_POST['recurrence'] ?? '';
|
||||
$recurrence_end_date = $_POST['recurrence_end_date'] ?? '';
|
||||
|
||||
if (empty($recurrence)) {
|
||||
$recurrence = null;
|
||||
}
|
||||
if (empty($recurrence_end_date)) {
|
||||
$recurrence_end_date = null;
|
||||
}
|
||||
|
||||
if (empty($title) || empty($start_datetime) || empty($end_datetime) || empty($event_type_id)) {
|
||||
header("Location: calendar.php?error=empty_fields");
|
||||
exit();
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// Insert the main event
|
||||
$stmt = $pdo->prepare("INSERT INTO calendar_events (title, description, start_datetime, end_datetime, event_type_id, recurrence, recurrence_end_date) VALUES (?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$title, $description, $start_datetime, $end_datetime, $event_type_id, $recurrence, $recurrence_end_date]);
|
||||
$parent_event_id = $pdo->lastInsertId();
|
||||
|
||||
if ($recurrence && !empty($recurrence_end_date)) {
|
||||
$start_date = new DateTime($start_datetime);
|
||||
$end_date = new DateTime($end_datetime);
|
||||
$recurrence_end = new DateTime($recurrence_end_date);
|
||||
$interval_spec = '';
|
||||
|
||||
switch ($recurrence) {
|
||||
case 'daily':
|
||||
$interval_spec = 'P1D';
|
||||
break;
|
||||
case 'weekly':
|
||||
$interval_spec = 'P1W';
|
||||
break;
|
||||
case 'monthly':
|
||||
$interval_spec = 'P1M';
|
||||
break;
|
||||
}
|
||||
|
||||
if ($interval_spec) {
|
||||
$interval = new DateInterval($interval_spec);
|
||||
$period_start = clone $start_date;
|
||||
$period_start->add($interval);
|
||||
|
||||
$period = new DatePeriod($period_start, $interval, $recurrence_end);
|
||||
|
||||
$stmt_recur = $pdo->prepare("INSERT INTO calendar_events (title, description, start_datetime, end_datetime, event_type_id, parent_event_id) VALUES (?, ?, ?, ?, ?, ?)");
|
||||
|
||||
foreach ($period as $date) {
|
||||
$new_start_datetime = $date->format('Y-m-d H:i:s');
|
||||
$end_date_clone = clone $date;
|
||||
$new_end_datetime = $end_date_clone->add($start_date->diff($end_date))->format('Y-m-d H:i:s');
|
||||
$stmt_recur->execute([$title, $description, $new_start_datetime, $new_end_datetime, $event_type_id, $parent_event_id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
header("Location: calendar.php");
|
||||
exit();
|
||||
|
||||
} catch (Exception $e) {
|
||||
$pdo->rollBack();
|
||||
error_log($e->getMessage());
|
||||
header("Location: calendar.php?error=db_error");
|
||||
exit();
|
||||
}
|
||||
}
|
||||
18
_add_event_type.php
Normal file
18
_add_event_type.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
if (isset($_POST['add'])) {
|
||||
$name = $_POST['name'];
|
||||
$color = $_POST['color'];
|
||||
$display_order = $_POST['display_order'];
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("INSERT INTO event_types (name, color, display_order) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$name, $color, $display_order]);
|
||||
|
||||
session_start();
|
||||
$_SESSION['success_message'] = 'Event type added successfully.';
|
||||
header('Location: event_types.php');
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
21
_add_function.php
Normal file
21
_add_function.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION['user_id']) || $_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
header('Location: login.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
include 'db/config.php';
|
||||
|
||||
if (isset($_POST['name'])) {
|
||||
$name = trim($_POST['name']);
|
||||
$display_order = trim($_POST['display_order']);
|
||||
if (!empty($name)) {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("functions (name, display_order) VALUES (:name, :display_order)");
|
||||
$stmt->execute(['name' => $name, 'display_order' => $display_order]);
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: roles.php');
|
||||
exit();
|
||||
31
_add_process_event.php
Normal file
31
_add_process_event.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
http_response_code(401);
|
||||
die('Unauthorized');
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$instanceId = $_POST['instanceId'] ?? null;
|
||||
$eventType = $_POST['eventType'] ?? null;
|
||||
$description = $_POST['description'] ?? null;
|
||||
$userId = $_SESSION['user_id'];
|
||||
|
||||
if (!$instanceId || !$eventType) {
|
||||
http_response_code(400);
|
||||
die('Missing parameters');
|
||||
}
|
||||
|
||||
// Create the event
|
||||
$stmt = $pdo->prepare("INSERT INTO process_events (processInstanceId, eventType, description, createdBy) VALUES (?, ?, ?, ?)");
|
||||
$stmt->execute([$instanceId, $eventType, $description, $userId]);
|
||||
|
||||
// Update the last activity time for the instance
|
||||
$stmt_update = $pdo->prepare("UPDATE process_instances SET lastActivityAt = NOW() WHERE id = ?");
|
||||
$stmt_update->execute([$instanceId]);
|
||||
|
||||
// Redirect back to the dashboard
|
||||
header("Location: process_dashboard.php");
|
||||
exit;
|
||||
43
_apply_transition.php
Normal file
43
_apply_transition.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
header('Location: process_dashboard.php');
|
||||
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');
|
||||
exit;
|
||||
}
|
||||
|
||||
$instanceId = (int)$_POST['instanceId'];
|
||||
$transitionId = $_POST['transitionId'];
|
||||
$userId = $_SESSION['user_id'] ?? null;
|
||||
$payload = $_POST['payload'] ?? null;
|
||||
|
||||
if (!$userId) {
|
||||
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'Błąd: Sesja wygasła.'];
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$engine = new WorkflowEngine();
|
||||
$success = $engine->applyTransition($instanceId, $transitionId, $payload, $userId);
|
||||
|
||||
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.'];
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("Error applying transition: " . $e->getMessage());
|
||||
$_SESSION['flash_message'] = ['type' => 'danger', 'message' => 'Wystąpił krytyczny błąd: ' . $e->getMessage()];
|
||||
}
|
||||
|
||||
header('Location: process_dashboard.php');
|
||||
exit;
|
||||
53
_bulk_add_event.php
Normal file
53
_bulk_add_event.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
http_response_code(401);
|
||||
die('Unauthorized');
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$person_ids = json_decode($_POST['person_ids'] ?? '[]');
|
||||
$process_id = $_POST['process_id'] ?? null;
|
||||
$message = $_POST['description'] ?? null; // The form sends 'description'
|
||||
$userId = $_SESSION['user_id'];
|
||||
|
||||
if (empty($person_ids) || !$process_id || !$message) {
|
||||
http_response_code(400);
|
||||
die('Missing parameters');
|
||||
}
|
||||
|
||||
$placeholders = implode(',', array_fill(0, count($person_ids), '?'));
|
||||
|
||||
// Get all relevant instance IDs
|
||||
$stmt = $pdo->prepare("SELECT id FROM process_instances WHERE processDefinitionId = ? AND personId IN ($placeholders)");
|
||||
$params = array_merge([$process_id], $person_ids);
|
||||
$stmt->execute($params);
|
||||
$instance_ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
if (!empty($instance_ids)) {
|
||||
$instance_placeholders = implode(',', array_fill(0, count($instance_ids), '?'));
|
||||
|
||||
// Update last activity
|
||||
$stmt_update = $pdo->prepare("UPDATE process_instances SET lastActivityAt = NOW() WHERE id IN ($instance_placeholders)");
|
||||
$stmt_update->execute($instance_ids);
|
||||
|
||||
// Bulk insert events
|
||||
$event_sql = "INSERT INTO process_events (processInstanceId, event_type, message, createdById) VALUES ";
|
||||
$event_rows = [];
|
||||
$event_params = [];
|
||||
foreach($instance_ids as $instance_id) {
|
||||
$event_rows[] = "(?, 'note', ?, ?)";
|
||||
$event_params[] = $instance_id;
|
||||
$event_params[] = $message;
|
||||
$event_params[] = $userId;
|
||||
}
|
||||
$event_sql .= implode(', ', $event_rows);
|
||||
$stmt_event = $pdo->prepare($event_sql);
|
||||
$stmt_event->execute($event_params);
|
||||
}
|
||||
|
||||
$_SESSION['flash_message'] = "Bulk event addition completed.";
|
||||
header('Location: process_dashboard.php');
|
||||
exit;
|
||||
33
_bulk_init_instances.php
Normal file
33
_bulk_init_instances.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
http_response_code(401);
|
||||
die('Unauthorized');
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$person_ids = json_decode($_POST['person_ids'] ?? '[]');
|
||||
$process_id = $_POST['process_id'] ?? null;
|
||||
|
||||
if (empty($person_ids) || !$process_id) {
|
||||
http_response_code(400);
|
||||
die('Missing parameters');
|
||||
}
|
||||
|
||||
$sql = "INSERT IGNORE INTO process_instances (personId, processDefinitionId, current_status) VALUES ";
|
||||
$rows = [];
|
||||
$params = [];
|
||||
foreach($person_ids as $person_id) {
|
||||
$rows[] = "(?, ?, 'none')";
|
||||
$params[] = $person_id;
|
||||
$params[] = $process_id;
|
||||
}
|
||||
$sql .= implode(', ', $rows);
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
|
||||
$_SESSION['flash_message'] = "Bulk initialization completed.";
|
||||
header('Location: process_dashboard.php');
|
||||
exit;
|
||||
54
_bulk_update_status.php
Normal file
54
_bulk_update_status.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
http_response_code(401);
|
||||
die('Unauthorized');
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$person_ids = json_decode($_POST['person_ids'] ?? '[]');
|
||||
$process_id = $_POST['process_id'] ?? null;
|
||||
$status = $_POST['status'] ?? null;
|
||||
$userId = $_SESSION['user_id'];
|
||||
|
||||
if (empty($person_ids) || !$process_id || !$status) {
|
||||
http_response_code(400);
|
||||
die('Missing parameters');
|
||||
}
|
||||
|
||||
$placeholders = implode(',', array_fill(0, count($person_ids), '?'));
|
||||
|
||||
// Get all relevant instance IDs
|
||||
$stmt = $pdo->prepare("SELECT id FROM process_instances WHERE processDefinitionId = ? AND personId IN ($placeholders)");
|
||||
$params = array_merge([$process_id], $person_ids);
|
||||
$stmt->execute($params);
|
||||
$instance_ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
if (!empty($instance_ids)) {
|
||||
$instance_placeholders = implode(',', array_fill(0, count($instance_ids), '?'));
|
||||
|
||||
// Update statuses
|
||||
$stmt_update = $pdo->prepare("UPDATE process_instances SET current_status = ?, lastActivityAt = NOW() WHERE id IN ($instance_placeholders)");
|
||||
$stmt_update->execute(array_merge([$status], $instance_ids));
|
||||
|
||||
// Bulk insert events
|
||||
$event_sql = "INSERT INTO process_events (processInstanceId, event_type, message, createdById) VALUES ";
|
||||
$event_rows = [];
|
||||
$event_params = [];
|
||||
$message = "Status changed to $status";
|
||||
foreach($instance_ids as $instance_id) {
|
||||
$event_rows[] = "(?, 'status_change', ?, ?)";
|
||||
$event_params[] = $instance_id;
|
||||
$event_params[] = $message;
|
||||
$event_params[] = $userId;
|
||||
}
|
||||
$event_sql .= implode(', ', $event_rows);
|
||||
$stmt_event = $pdo->prepare($event_sql);
|
||||
$stmt_event->execute($event_params);
|
||||
}
|
||||
|
||||
$_SESSION['flash_message'] = "Bulk status update completed.";
|
||||
header('Location: process_dashboard.php');
|
||||
exit;
|
||||
53
_create_person.php
Normal file
53
_create_person.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
session_start();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$firstName = $_POST['firstName'];
|
||||
$lastName = $_POST['lastName'];
|
||||
$email = $_POST['email'];
|
||||
$password = $_POST['password'];
|
||||
$companyName = $_POST['companyName'] ?? null;
|
||||
$phone = $_POST['phone'] ?? null;
|
||||
$role = $_POST['role'] ?? 'członek'; // Default to 'członek'
|
||||
$functions = isset($_POST['functions']) ? $_POST['functions'] : [];
|
||||
|
||||
if (empty($firstName) || empty($lastName) || empty($email) || empty($password)) {
|
||||
$_SESSION['error_message'] = 'Imię, nazwisko, email i hasło są wymagane.';
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// Insert person details
|
||||
$sql = 'INSERT INTO people (firstName, lastName, email, password, companyName, phone, role) VALUES (?, ?, ?, ?, ?, ?, ?)';
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$firstName, $lastName, $email, password_hash($password, PASSWORD_DEFAULT), $companyName, $phone, $role]);
|
||||
$personId = $pdo->lastInsertId();
|
||||
|
||||
// Assign functions
|
||||
if (!empty($functions)) {
|
||||
$sql = "INSERT INTO user_functions (user_id, function_id) VALUES (?, ?)";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
foreach ($functions as $functionId) {
|
||||
$stmt->execute([$personId, $functionId]);
|
||||
}
|
||||
}
|
||||
|
||||
$_SESSION['success_message'] = 'Osoba dodana pomyślnie.';
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log('Create failed: ' . $e->getMessage());
|
||||
if ($e->errorInfo[1] == 1062) {
|
||||
$_SESSION['error_message'] = 'Błąd: Konto z tym adresem email już istnieje.';
|
||||
} else {
|
||||
$_SESSION['error_message'] = 'Błąd podczas dodawania osoby.';
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: index.php');
|
||||
exit();
|
||||
}
|
||||
?>
|
||||
20
_delete_bni_group.php
Normal file
20
_delete_bni_group.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
if (isset($_GET['id'])) {
|
||||
$id = $_GET['id'];
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("DELETE FROM bni_groups WHERE id = :id");
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
$_SESSION['success_message'] = 'BNI Group deleted successfully!';
|
||||
} catch (PDOException $e) {
|
||||
$_SESSION['error_message'] = 'Error deleting BNI group: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: bni_groups.php');
|
||||
exit;
|
||||
48
_delete_calendar_event.php
Normal file
48
_delete_calendar_event.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header("Location: login.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
require_once 'db/config.php';
|
||||
|
||||
if (isset($_GET['id'])) {
|
||||
$event_id = $_GET['id'];
|
||||
$pdo = db();
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// Check if the event is a parent event
|
||||
$stmt = $pdo->prepare("SELECT parent_event_id FROM calendar_events WHERE id = ?");
|
||||
$stmt->execute([$event_id]);
|
||||
$event = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($event) {
|
||||
if ($event['parent_event_id'] === null) {
|
||||
// It's a parent event, delete it and all its children
|
||||
$stmt_delete_children = $pdo->prepare("DELETE FROM calendar_events WHERE parent_event_id = ?");
|
||||
$stmt_delete_children->execute([$event_id]);
|
||||
}
|
||||
|
||||
// Delete the event itself
|
||||
$stmt_delete = $pdo->prepare("DELETE FROM calendar_events WHERE id = ?");
|
||||
$stmt_delete->execute([$event_id]);
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
header("Location: calendar.php");
|
||||
exit();
|
||||
|
||||
} catch (Exception $e) {
|
||||
$pdo->rollBack();
|
||||
error_log($e->getMessage());
|
||||
header("Location: calendar.php?error=db_error");
|
||||
exit();
|
||||
}
|
||||
} else {
|
||||
header("Location: calendar.php");
|
||||
exit();
|
||||
}
|
||||
16
_delete_event_type.php
Normal file
16
_delete_event_type.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
if (isset($_GET['id'])) {
|
||||
$id = $_GET['id'];
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("DELETE FROM event_types WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
|
||||
session_start();
|
||||
$_SESSION['success_message'] = 'Event type deleted successfully.';
|
||||
header('Location: event_types.php');
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
21
_delete_function.php
Normal file
21
_delete_function.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION['user_id']) || !isset($_GET['id'])) {
|
||||
header('Location: login.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
include 'db/config.php';
|
||||
|
||||
$id = $_GET['id'];
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("DELETE FROM functions WHERE id = :id");
|
||||
$stmt->execute(['id' => $id]);
|
||||
|
||||
// Optional: Also delete user_functions associated with this function
|
||||
$stmt = $pdo->prepare("DELETE FROM user_functions WHERE function_id = :function_id");
|
||||
$stmt->execute(['function_id' => $id]);
|
||||
|
||||
header('Location: functions.php');
|
||||
exit();
|
||||
16
_delete_person.php
Normal file
16
_delete_person.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
session_start();
|
||||
|
||||
if (isset($_GET['id'])) {
|
||||
$id = $_GET['id'];
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("DELETE FROM people WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
|
||||
$_SESSION['success_message'] = 'Osoba usunięta pomyślnie.';
|
||||
header('Location: persons.php');
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
28
_footer.php
Normal file
28
_footer.php
Normal file
@ -0,0 +1,28 @@
|
||||
<!-- Bootstrap Bundle with Popper -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- jQuery -->
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<!-- jQuery UI -->
|
||||
<script src="https://code.jquery.com/ui/1.13.2/jquery-ui.min.js"></script>
|
||||
<link rel="stylesheet" href="https://code.jquery.com/ui/1.13.2/themes/base/jquery-ui.css">
|
||||
|
||||
<!-- Custom JS -->
|
||||
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const sidebarLinks = document.querySelectorAll('#sidebar .nav-link');
|
||||
const currentPath = window.location.pathname.split('/').pop();
|
||||
|
||||
sidebarLinks.forEach(link => {
|
||||
const linkPath = link.getAttribute('href').split('/').pop();
|
||||
if (linkPath === currentPath) {
|
||||
link.classList.add('active');
|
||||
} else {
|
||||
link.classList.remove('active');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
26
_get_event_details.php
Normal file
26
_get_event_details.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header("HTTP/1.1 401 Unauthorized");
|
||||
exit();
|
||||
}
|
||||
|
||||
require_once 'db/config.php';
|
||||
|
||||
if (isset($_GET['id'])) {
|
||||
$event_id = $_GET['id'];
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT c.*, t.name as type_name FROM calendar_events c LEFT JOIN event_types t ON c.event_type_id = t.id WHERE c.id = ?");
|
||||
$stmt->execute([$event_id]);
|
||||
$event = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($event) {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($event);
|
||||
} else {
|
||||
header("HTTP/1.1 404 Not Found");
|
||||
}
|
||||
} else {
|
||||
header("HTTP/1.1 400 Bad Request");
|
||||
}
|
||||
88
_get_instance_details.php
Normal file
88
_get_instance_details.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
require_once 'WorkflowEngine.php';
|
||||
session_start();
|
||||
|
||||
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
|
||||
|
||||
if (!$personId || !$processDefinitionId) {
|
||||
http_response_code(400);
|
||||
die('Brakujące parametry');
|
||||
}
|
||||
|
||||
$userId = $_SESSION['user_id'];
|
||||
$engine = new WorkflowEngine();
|
||||
|
||||
// 1. Pobierz lub utwórz instancję
|
||||
$instance = $engine->getOrCreateInstanceByDefId($personId, $processDefinitionId, $userId);
|
||||
if (!$instance) {
|
||||
http_response_code(500);
|
||||
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();
|
||||
$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->execute([$processDefinitionId]);
|
||||
$process = $stmt_process->fetch();
|
||||
?>
|
||||
|
||||
<h4><?= htmlspecialchars($person['firstName'] . ' ' . $person['lastName']) ?> - <?= htmlspecialchars($process['name']) ?></h4>
|
||||
<p>Status: <span class="badge bg-secondary"><?= htmlspecialchars($instance['current_status']) ?></span></p>
|
||||
<hr>
|
||||
|
||||
<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'];
|
||||
?>
|
||||
<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>
|
||||
<?php endif; ?>
|
||||
32
_get_person_details.php
Normal file
32
_get_person_details.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
if (isset($_GET['id'])) {
|
||||
$person_id = $_GET['id'];
|
||||
$pdo = db();
|
||||
|
||||
// Fetch person details
|
||||
$stmt = $pdo->prepare("SELECT * FROM people WHERE id = ?");
|
||||
$stmt->execute([$person_id]);
|
||||
$person = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// Fetch all functions
|
||||
$stmt = $pdo->query("SELECT id, name FROM functions ORDER BY display_order");
|
||||
$all_functions = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Fetch person's functions
|
||||
$stmt = $pdo->prepare("SELECT function_id FROM user_functions WHERE user_id = ?");
|
||||
$stmt->execute([$person_id]);
|
||||
$person_functions = $stmt->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||
|
||||
$response = [
|
||||
'person' => $person,
|
||||
'all_functions' => $all_functions,
|
||||
'person_functions' => $person_functions
|
||||
];
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($response);
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
44
_header.php
Normal file
44
_header.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title><?php echo getenv('PROJECT_NAME') ?: 'My App'; ?> - Dashboard</title>
|
||||
<meta name="description" content="<?php echo getenv('PROJECT_DESCRIPTION') ?: 'A modern web application.'; ?>">
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Bootstrap Icons -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
|
||||
<!-- Google Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Custom CSS -->
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
|
||||
<!-- OG Meta Tags -->
|
||||
<meta property="og:title" content="<?php echo getenv('PROJECT_NAME') ?: 'My App'; ?>">
|
||||
<meta property="og:description" content="<?php echo getenv('PROJECT_DESCRIPTION') ?: 'A modern web application.'; ?>">
|
||||
<meta property="og:image" content="<?php echo getenv('PROJECT_IMAGE_URL') ?: 'https://via.placeholder.com/1200x630.png?text=Visit+My+App'; ?>">
|
||||
<meta property="og:url" content="<?php echo (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]"; ?>">
|
||||
<meta property="og:type" content="website">
|
||||
|
||||
<!-- Twitter Card -->
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="<?php echo getenv('PROJECT_NAME') ?: 'My App'; ?>">
|
||||
<meta name="twitter:description" content="<?php echo getenv('PROJECT_DESCRIPTION') ?: 'A modern web application.'; ?>">
|
||||
<meta name="twitter:image" content="<?php echo getenv('PROJECT_IMAGE_URL') ?: 'https://via.placeholder.com/1200x630.png?text=Visit+My+App'; ?>">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
37
_init_instances.php
Normal file
37
_init_instances.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
session_start();
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Get all active people
|
||||
$stmt_people = $pdo->prepare("SELECT id FROM people WHERE active = 1");
|
||||
$stmt_people->execute();
|
||||
$people = $stmt_people->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
// Get all active process definitions
|
||||
$stmt_processes = $pdo->prepare("SELECT id FROM process_definitions WHERE is_active = 1");
|
||||
$stmt_processes->execute();
|
||||
$processes = $stmt_processes->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
$insert_stmt = $pdo->prepare("INSERT IGNORE INTO process_instances (personId, processDefinitionId, current_status) VALUES (?, ?, 'none')");
|
||||
|
||||
$count = 0;
|
||||
foreach ($people as $personId) {
|
||||
foreach ($processes as $processId) {
|
||||
$insert_stmt->execute([$personId, $processId]);
|
||||
if ($insert_stmt->rowCount() > 0) {
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$_SESSION['flash_message'] = "Initialized $count new process instances.";
|
||||
|
||||
header("Location: process_dashboard.php"); // Redirect to the main dashboard
|
||||
exit;
|
||||
16
_navbar.php
Normal file
16
_navbar.php
Normal file
@ -0,0 +1,16 @@
|
||||
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
|
||||
<a class="navbar-brand col-md-3 col-lg-2 me-0 px-3" href="#"><?php echo getenv('PROJECT_NAME') ?: 'My App'; ?></a>
|
||||
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<ul class="navbar-nav flex-row px-3">
|
||||
<?php if (isset($_SESSION['user_name'])): ?>
|
||||
<li class="nav-item text-nowrap">
|
||||
<span class="nav-link">Witaj, <?= htmlspecialchars($_SESSION['user_name']) ?></span>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<li class="nav-item text-nowrap ms-2">
|
||||
<a class="nav-link" href="logout.php">Wyloguj</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
49
_save_process_definition.php
Normal file
49
_save_process_definition.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$processId = $_POST['process_id'] ?? null;
|
||||
$name = $_POST['name'];
|
||||
$definition_json = $_POST['definition_json'];
|
||||
|
||||
if (empty($name)) {
|
||||
die('Process name is required.');
|
||||
}
|
||||
|
||||
// Validate JSON
|
||||
if (!empty($definition_json)) {
|
||||
json_decode($definition_json);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
die('Invalid JSON format in definition.');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
if (empty($processId)) {
|
||||
// Create new process
|
||||
$sql = 'INSERT INTO process_definitions (name, definition_json) VALUES (?, ?)';
|
||||
$params = [$name, $definition_json];
|
||||
$message = 'Process created successfully.';
|
||||
} else {
|
||||
// Update existing process
|
||||
$sql = 'UPDATE process_definitions SET name = ?, definition_json = ? WHERE id = ?';
|
||||
$params = [$name, $definition_json, $processId];
|
||||
$message = 'Process updated successfully.';
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
|
||||
session_start();
|
||||
$_SESSION['success_message'] = $message;
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log('Save process definition failed: ' . $e->getMessage());
|
||||
die('Save process definition failed. Please check the logs.');
|
||||
}
|
||||
|
||||
header('Location: process_definitions.php');
|
||||
exit();
|
||||
}
|
||||
56
_sidebar.php
Normal file
56
_sidebar.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php $current_page = basename($_SERVER['PHP_SELF']); ?>
|
||||
<nav id="sidebar" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
|
||||
<div class="position-sticky pt-3">
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= ($current_page == 'index.php' || $current_page == '') ? 'active' : '' ?>" aria-current="page" href="/">
|
||||
<i class="bi bi-kanban"></i>
|
||||
<span class="nav-link-text">Pulpit procesów</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= ($current_page == 'calendar.php') ? 'active' : '' ?>" href="calendar.php">
|
||||
<i class="bi bi-calendar-event"></i>
|
||||
<span class="nav-link-text">Kalendarz</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="#settings-submenu" data-bs-toggle="collapse" aria-expanded="false">
|
||||
<i class="bi bi-gear"></i>
|
||||
<span class="nav-link-text">Ustawienia</span>
|
||||
</a>
|
||||
<ul class="nav flex-column collapse" id="settings-submenu" data-bs-parent="#sidebar">
|
||||
<li class="nav-item submenu-item">
|
||||
<a class="nav-link <?= ($current_page == 'event_types.php') ? 'active' : '' ?>" href="event_types.php">
|
||||
<i class="bi bi-tags"></i>
|
||||
<span class="nav-link-text">Typy zdarzeń</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item submenu-item">
|
||||
<a class="nav-link <?= ($current_page == 'bni_groups.php') ? 'active' : '' ?>" href="bni_groups.php">
|
||||
<i class="bi bi-people"></i>
|
||||
<span class="nav-link-text">Grupy BNI</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item submenu-item">
|
||||
<a class="nav-link <?= ($current_page == 'functions.php') ? 'active' : '' ?>" href="functions.php">
|
||||
<i class="bi bi-person-rolodex"></i>
|
||||
<span class="nav-link-text">Funkcje</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<?php if (isset($_SESSION['role']) && $_SESSION['role'] === 'admin'): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?= ($current_page == 'process_definitions.php') ? 'active' : '' ?>" href="process_definitions.php">
|
||||
<i class="bi bi-diagram-3"></i>
|
||||
<span class="nav-link-text">Definicje procesów</span>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
34
_update_bni_group.php
Normal file
34
_update_bni_group.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
if (isset($_POST['edit'])) {
|
||||
$id = $_POST['id'];
|
||||
$name = $_POST['name'];
|
||||
$city = $_POST['city'] ?? null;
|
||||
$active = isset($_POST['active']) ? 1 : 0;
|
||||
$display_order = $_POST['display_order'] ?? 0;
|
||||
|
||||
if (empty($name) || empty($id)) {
|
||||
$_SESSION['error_message'] = 'Name and ID are required.';
|
||||
header('Location: bni_groups.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("UPDATE bni_groups SET name = :name, city = :city, active = :active, display_order = :display_order WHERE id = :id");
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':name', $name);
|
||||
$stmt->bindParam(':city', $city);
|
||||
$stmt->bindParam(':active', $active, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':display_order', $display_order, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
$_SESSION['success_message'] = 'BNI Group updated successfully!';
|
||||
} catch (PDOException $e) {
|
||||
$_SESSION['error_message'] = 'Error updating BNI group: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: bni_groups.php');
|
||||
exit;
|
||||
67
_update_bni_group_order.php
Normal file
67
_update_bni_group_order.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
session_start();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (isset($_POST['order']) && is_array($_POST['order'])) {
|
||||
$ordered_ids = $_POST['order'];
|
||||
|
||||
$pdo = db();
|
||||
$pdo->beginTransaction();
|
||||
|
||||
try {
|
||||
foreach ($ordered_ids as $index => $id) {
|
||||
$sql = "UPDATE bni_groups SET display_order = ? WHERE id = ?";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$index + 1, $id]);
|
||||
}
|
||||
$pdo->commit();
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => true, 'message' => 'Order updated successfully.']);
|
||||
exit();
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$pdo->rollBack();
|
||||
header('Content-Type: application/json');
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => 'Error updating display order: ' . $e->getMessage()]);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for old form submission, though it's being deprecated
|
||||
if (isset($_POST['ids']) && isset($_POST['display_order'])) {
|
||||
$ids = $_POST['ids'];
|
||||
$display_orders = $_POST['display_order'];
|
||||
|
||||
if (count($ids) !== count($display_orders)) {
|
||||
$_SESSION['error_message'] = "Something went wrong. Please try again.";
|
||||
header("Location: bni_groups.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$pdo->beginTransaction();
|
||||
|
||||
try {
|
||||
for ($i = 0; $i < count($ids); $i++) {
|
||||
$sql = "UPDATE bni_groups SET display_order = ? WHERE id = ?";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$display_orders[$i], $ids[$i]]);
|
||||
}
|
||||
$pdo->commit();
|
||||
$_SESSION['success_message'] = "Display order updated successfully.";
|
||||
} catch (PDOException $e) {
|
||||
$pdo->rollBack();
|
||||
$_SESSION['error_message'] = "Error updating display order: " . $e->getMessage();
|
||||
}
|
||||
|
||||
header("Location: bni_groups.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
http_response_code(400);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid request.']);
|
||||
40
_update_calendar_event.php
Normal file
40
_update_calendar_event.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header("Location: login.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
require_once 'db/config.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$event_id = $_POST['event_id'] ?? null;
|
||||
$title = $_POST['title'] ?? '';
|
||||
$description = $_POST['description'] ?? '';
|
||||
$start_datetime = $_POST['start_datetime'] ?? '';
|
||||
$end_datetime = $_POST['end_datetime'] ?? '';
|
||||
$event_type_id = $_POST['event_type_id'] ?? null;
|
||||
|
||||
// For now, we don't support updating recurring events.
|
||||
// This will be implemented in the future.
|
||||
|
||||
if (empty($event_id) || empty($title) || empty($start_datetime) || empty($end_datetime) || empty($event_type_id)) {
|
||||
header("Location: calendar.php?error=empty_fields");
|
||||
exit();
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("UPDATE calendar_events SET title = ?, description = ?, start_datetime = ?, end_datetime = ?, event_type_id = ? WHERE id = ?");
|
||||
|
||||
try {
|
||||
$stmt->execute([$title, $description, $start_datetime, $end_datetime, $event_type_id, $event_id]);
|
||||
header("Location: calendar.php");
|
||||
exit();
|
||||
} catch (PDOException $e) {
|
||||
// Handle database error
|
||||
error_log($e->getMessage());
|
||||
header("Location: calendar.php?error=db_error");
|
||||
exit();
|
||||
}
|
||||
}
|
||||
19
_update_event_type.php
Normal file
19
_update_event_type.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
if (isset($_POST['edit'])) {
|
||||
$id = $_POST['id'];
|
||||
$name = $_POST['name'];
|
||||
$color = $_POST['color'];
|
||||
$display_order = $_POST['display_order'];
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("UPDATE event_types SET name = ?, color = ?, display_order = ? WHERE id = ?");
|
||||
$stmt->execute([$name, $color, $display_order, $id]);
|
||||
|
||||
session_start();
|
||||
$_SESSION['success_message'] = 'Event type updated successfully.';
|
||||
header('Location: event_types.php');
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
67
_update_event_type_order.php
Normal file
67
_update_event_type_order.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
session_start();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (isset($_POST['order']) && is_array($_POST['order'])) {
|
||||
$ordered_ids = $_POST['order'];
|
||||
|
||||
$pdo = db();
|
||||
$pdo->beginTransaction();
|
||||
|
||||
try {
|
||||
foreach ($ordered_ids as $index => $id) {
|
||||
$sql = "UPDATE event_types SET display_order = ? WHERE id = ?";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$index + 1, $id]);
|
||||
}
|
||||
$pdo->commit();
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => true, 'message' => 'Order updated successfully.']);
|
||||
exit();
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$pdo->rollBack();
|
||||
header('Content-Type: application/json');
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => 'Error updating display order: ' . $e->getMessage()]);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for old form submission, though it's being deprecated
|
||||
if (isset($_POST['ids']) && isset($_POST['display_order'])) {
|
||||
$ids = $_POST['ids'];
|
||||
$display_orders = $_POST['display_order'];
|
||||
|
||||
if (count($ids) !== count($display_orders)) {
|
||||
$_SESSION['error_message'] = "Something went wrong. Please try again.";
|
||||
header("Location: event_types.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$pdo->beginTransaction();
|
||||
|
||||
try {
|
||||
for ($i = 0; $i < count($ids); $i++) {
|
||||
$sql = "UPDATE event_types SET display_order = ? WHERE id = ?";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$display_orders[$i], $ids[$i]]);
|
||||
}
|
||||
$pdo->commit();
|
||||
$_SESSION['success_message'] = "Display order updated successfully.";
|
||||
} catch (PDOException $e) {
|
||||
$pdo->rollBack();
|
||||
$_SESSION['error_message'] = "Error updating display order: " . $e->getMessage();
|
||||
}
|
||||
|
||||
header("Location: event_types.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
http_response_code(400);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid request.']);
|
||||
23
_update_function.php
Normal file
23
_update_function.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
session_start();
|
||||
if (!isset($_SESSION['user_id']) || $_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
header('Location: login.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
include 'db/config.php';
|
||||
|
||||
if (isset($_POST['id'], $_POST['name'])) {
|
||||
$id = $_POST['id'];
|
||||
$name = trim($_POST['name']);
|
||||
$display_order = trim($_POST['display_order']);
|
||||
|
||||
if (!empty($name)) {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("UPDATE functions SET name = :name, display_order = :display_order WHERE id = :id");
|
||||
$stmt->execute(['name' => $name, 'display_order' => $display_order, 'id' => $id]);
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: roles.php');
|
||||
exit();
|
||||
67
_update_function_order.php
Normal file
67
_update_function_order.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
session_start();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (isset($_POST['order']) && is_array($_POST['order'])) {
|
||||
$ordered_ids = $_POST['order'];
|
||||
|
||||
$pdo = db();
|
||||
$pdo->beginTransaction();
|
||||
|
||||
try {
|
||||
foreach ($ordered_ids as $index => $id) {
|
||||
$sql = "UPDATE functions SET display_order = ? WHERE id = ?";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$index + 1, $id]);
|
||||
}
|
||||
$pdo->commit();
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => true, 'message' => 'Order updated successfully.']);
|
||||
exit();
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$pdo->rollBack();
|
||||
header('Content-Type: application/json');
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => 'Error updating display order: ' . $e->getMessage()]);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for old form submission, though it's being deprecated
|
||||
if (isset($_POST['ids']) && isset($_POST['display_order'])) {
|
||||
$ids = $_POST['ids'];
|
||||
$display_orders = $_POST['display_order'];
|
||||
|
||||
if (count($ids) !== count($display_orders)) {
|
||||
$_SESSION['error_message'] = "Something went wrong. Please try again.";
|
||||
header("Location: functions.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$pdo->beginTransaction();
|
||||
|
||||
try {
|
||||
for ($i = 0; $i < count($ids); $i++) {
|
||||
$sql = "UPDATE functions SET display_order = ? WHERE id = ?";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$display_orders[$i], $ids[$i]]);
|
||||
}
|
||||
$pdo->commit();
|
||||
$_SESSION['success_message'] = "Display order updated successfully.";
|
||||
} catch (PDOException $e) {
|
||||
$pdo->rollBack();
|
||||
$_SESSION['error_message'] = "Error updating display order: " . $e->getMessage();
|
||||
}
|
||||
|
||||
header("Location: functions.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
http_response_code(400);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid request.']);
|
||||
30
_update_instance_status.php
Normal file
30
_update_instance_status.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
http_response_code(401);
|
||||
die('Unauthorized');
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$instanceId = $_POST['instanceId'] ?? null;
|
||||
$status = $_POST['status'] ?? null;
|
||||
$userId = $_SESSION['user_id'];
|
||||
|
||||
if (!$instanceId || !$status) {
|
||||
http_response_code(400);
|
||||
die('Missing parameters');
|
||||
}
|
||||
|
||||
// Update instance status and last activity time
|
||||
$stmt = $pdo->prepare("UPDATE process_instances SET status = ?, lastActivityAt = NOW() WHERE id = ?");
|
||||
$stmt->execute([$status, $instanceId]);
|
||||
|
||||
// Create a status change event
|
||||
$stmt_event = $pdo->prepare("INSERT INTO process_events (processInstanceId, eventType, description, createdBy) VALUES (?, 'status_change', ?, ?)");
|
||||
$stmt_event->execute([$instanceId, "Status changed to $status", $userId]);
|
||||
|
||||
// Redirect back to the dashboard
|
||||
header("Location: process_dashboard.php");
|
||||
exit;
|
||||
52
_update_person.php
Normal file
52
_update_person.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
session_start();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$personId = $_POST['id'];
|
||||
$firstName = $_POST['firstName'];
|
||||
$lastName = $_POST['lastName'];
|
||||
$email = $_POST['email'];
|
||||
$companyName = $_POST['companyName'];
|
||||
$phone = $_POST['phone'];
|
||||
$role = $_POST['role'] ?? 'członek'; // Default to 'członek'
|
||||
$functions = isset($_POST['functions']) ? $_POST['functions'] : [];
|
||||
$password = $_POST['password'];
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// Update person details
|
||||
if (!empty($password)) {
|
||||
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
|
||||
$sql = "UPDATE people SET firstName = ?, lastName = ?, email = ?, companyName = ?, phone = ?, password = ?, role = ? WHERE id = ?";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$firstName, $lastName, $email, $companyName, $phone, $passwordHash, $role, $personId]);
|
||||
} else {
|
||||
$sql = "UPDATE people SET firstName = ?, lastName = ?, email = ?, companyName = ?, phone = ?, role = ? WHERE id = ?";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$firstName, $lastName, $email, $companyName, $phone, $role, $personId]);
|
||||
}
|
||||
|
||||
// Update functions
|
||||
$stmt = $pdo->prepare("DELETE FROM user_functions WHERE user_id = ?");
|
||||
$stmt->execute([$personId]);
|
||||
|
||||
if (!empty($functions)) {
|
||||
$sql = "INSERT INTO user_functions (user_id, function_id) VALUES (?, ?)";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
foreach ($functions as $functionId) {
|
||||
$stmt->execute([$personId, $functionId]);
|
||||
}
|
||||
}
|
||||
|
||||
$_SESSION['success_message'] = 'Osoba zaktualizowana pomyślnie.';
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log('Update failed: ' . $e->getMessage());
|
||||
$_SESSION['error_message'] = "Błąd podczas aktualizacji osoby.";
|
||||
}
|
||||
|
||||
header('Location: index.php');
|
||||
exit();
|
||||
}
|
||||
96
assets/css/custom.css
Normal file
96
assets/css/custom.css
Normal file
@ -0,0 +1,96 @@
|
||||
|
||||
body {
|
||||
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
padding: 48px 0 0;
|
||||
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
|
||||
width: 250px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.sidebar-collapsed {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.sidebar-collapsed .nav-link-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar-collapsed .nav-link i {
|
||||
font-size: 1.5rem;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
|
||||
.main-content {
|
||||
margin-left: 250px;
|
||||
transition: margin-left 0.3s;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.main-content-collapsed {
|
||||
margin-left: 80px;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.nav-link.active {
|
||||
color: #0d6efd;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
padding-top: .75rem;
|
||||
padding-bottom: .75rem;
|
||||
font-size: 1rem;
|
||||
background-color: rgba(0, 0, 0, .25);
|
||||
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25);
|
||||
}
|
||||
|
||||
.navbar .form-control {
|
||||
padding: .75rem 1rem;
|
||||
border-width: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* Calendar styles */
|
||||
.calendar {
|
||||
table-layout: fixed;
|
||||
}
|
||||
.calendar td {
|
||||
height: 120px;
|
||||
vertical-align: top;
|
||||
border: 1px solid #ddd;
|
||||
padding: 4px;
|
||||
}
|
||||
.calendar .day-number {
|
||||
font-size: 0.8rem;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
.calendar .not-month {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.events {
|
||||
margin-top: 4px;
|
||||
}
|
||||
.event {
|
||||
font-size: 0.75rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.submenu-item .nav-link {
|
||||
padding-left: 2.5rem;
|
||||
}
|
||||
12
assets/js/main.js
Normal file
12
assets/js/main.js
Normal file
@ -0,0 +1,12 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const mainContent = document.getElementById('main-content');
|
||||
const sidebarToggler = document.getElementById('sidebar-toggler');
|
||||
|
||||
if (sidebarToggler) {
|
||||
sidebarToggler.addEventListener('click', function () {
|
||||
sidebar.classList.toggle('sidebar-collapsed');
|
||||
mainContent.classList.toggle('main-content-collapsed');
|
||||
});
|
||||
}
|
||||
});
|
||||
201
bni_groups.php
Normal file
201
bni_groups.php
Normal file
@ -0,0 +1,201 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
include '_header.php';
|
||||
include '_navbar.php';
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->query("SELECT * FROM bni_groups ORDER BY display_order");
|
||||
$bni_groups = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
?>
|
||||
|
||||
<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">
|
||||
<h1 class="h2 pt-3 pb-2 mb-3 border-bottom">BNI Groups</h1>
|
||||
|
||||
<?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 class="d-flex justify-content-end mb-3">
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addModal">
|
||||
Add New BNI Group
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive mt-4">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 30px;"></th>
|
||||
<th>Name</th>
|
||||
<th>City</th>
|
||||
<th>Active</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="sortable-list">
|
||||
<?php foreach ($bni_groups as $group): ?>
|
||||
<tr data-id="<?= $group['id'] ?>">
|
||||
<td class="handle"><i class="bi bi-grip-vertical"></i></td>
|
||||
<td><?= htmlspecialchars($group['name']) ?></td>
|
||||
<td><?= htmlspecialchars($group['city']) ?></td>
|
||||
<td><?= $group['active'] ? 'Yes' : 'No' ?></td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-warning btn-sm" data-bs-toggle="modal" data-bs-target="#editModal"
|
||||
data-id="<?= $group['id'] ?>"
|
||||
data-name="<?= htmlspecialchars($group['name']) ?>"
|
||||
data-city="<?= htmlspecialchars($group['city']) ?>"
|
||||
data-active="<?= $group['active'] ?>">
|
||||
Edit
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#deleteModal" data-id="<?= $group['id'] ?>">
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Modal -->
|
||||
<div class="modal fade" id="addModal" tabindex="-1" aria-labelledby="addModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="addModalLabel">Add BNI Group</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="_add_bni_group.php" method="post">
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="addName" class="form-label">Name</label>
|
||||
<input type="text" class="form-control" id="addName" name="name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="addCity" class="form-label">City</label>
|
||||
<input type="text" class="form-control" id="addCity" name="city">
|
||||
</div>
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" value="1" id="addActive" name="active" checked>
|
||||
<label class="form-check-label" for="addActive">
|
||||
Active
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="submit" name="add" class="btn btn-primary">Add</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Modal -->
|
||||
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="editModalLabel">Edit BNI Group</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="_update_bni_group.php" method="post">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="editId" name="id">
|
||||
<div class="mb-3">
|
||||
<label for="editName" class="form-label">Name</label>
|
||||
<input type="text" class="form-control" id="editName" name="name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="editCity" class="form-label">City</label>
|
||||
<input type="text" class="form-control" id="editCity" name="city">
|
||||
</div>
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" value="1" id="editActive" name="active">
|
||||
<label class="form-check-label" for="editActive">
|
||||
Active
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="submit" name="edit" class="btn btn-primary">Save changes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Modal -->
|
||||
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="deleteModalLabel">Delete BNI Group</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Are you sure you want to delete this BNI group?
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<a href="#" id="deleteLink" class="btn btn-danger">Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<?php include '_footer.php'; ?>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var editModal = document.getElementById('editModal');
|
||||
editModal.addEventListener('show.bs.modal', function (event) {
|
||||
var button = event.relatedTarget;
|
||||
var id = button.getAttribute('data-id');
|
||||
var name = button.getAttribute('data-name');
|
||||
var city = button.getAttribute('data-city');
|
||||
var active = button.getAttribute('data-active');
|
||||
|
||||
var idInput = editModal.querySelector('#editId');
|
||||
var nameInput = editModal.querySelector('#editName');
|
||||
var cityInput = editModal.querySelector('#editCity');
|
||||
var activeInput = editModal.querySelector('#editActive');
|
||||
|
||||
idInput.value = id;
|
||||
nameInput.value = name;
|
||||
cityInput.value = city;
|
||||
activeInput.checked = (active == 1);
|
||||
});
|
||||
|
||||
var deleteModal = document.getElementById('deleteModal');
|
||||
deleteModal.addEventListener('show.bs.modal', function (event) {
|
||||
var button = event.relatedTarget;
|
||||
var id = button.getAttribute('data-id');
|
||||
var deleteLink = deleteModal.querySelector('#deleteLink');
|
||||
deleteLink.href = '_delete_bni_group.php?id=' + id;
|
||||
});
|
||||
|
||||
$(function() {
|
||||
$("#sortable-list").sortable({
|
||||
handle: ".handle",
|
||||
update: function(event, ui) {
|
||||
var order = $(this).sortable('toArray', {attribute: 'data-id'});
|
||||
$.post('_update_bni_group_order.php', { order: order });
|
||||
}
|
||||
}).disableSelection();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
303
calendar.php
Normal file
303
calendar.php
Normal file
@ -0,0 +1,303 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
include '_header.php';
|
||||
|
||||
// Get current month and year
|
||||
$month = isset($_GET['month']) ? (int)$_GET['month'] : date('m');
|
||||
$year = isset($_GET['year']) ? (int)$_GET['year'] : date('Y');
|
||||
|
||||
// Create a DateTime object for the first day of the month
|
||||
$firstDayOfMonth = new DateTime("$year-$month-01");
|
||||
$daysInMonth = $firstDayOfMonth->format('t');
|
||||
$dayOfWeek = $firstDayOfMonth->format('N'); // 1 (for Monday) through 7 (for Sunday)
|
||||
|
||||
// Get events for the current month
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT c.*, t.name as type_name, t.color as type_color FROM calendar_events c LEFT JOIN event_types t ON c.event_type_id = t.id WHERE MONTH(c.start_datetime) = ? AND YEAR(c.start_datetime) = ? ORDER BY c.start_datetime ASC");
|
||||
$stmt->execute([$month, $year]);
|
||||
$events = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$eventsByDay = [];
|
||||
foreach ($events as $event) {
|
||||
$day = (new DateTime($event['start_datetime']))->format('j');
|
||||
if (!isset($eventsByDay[$day])) {
|
||||
$eventsByDay[$day] = [];
|
||||
}
|
||||
$eventsByDay[$day][] = $event;
|
||||
}
|
||||
|
||||
// Get event types for the modal
|
||||
$stmt_types = $pdo->query("SELECT * FROM event_types ORDER BY display_order");
|
||||
$event_types = $stmt_types->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$prevMonth = $month == 1 ? 12 : $month - 1;
|
||||
$prevYear = $month == 1 ? $year - 1 : $year;
|
||||
$nextMonth = $month == 12 ? 1 : $month + 1;
|
||||
$nextYear = $month == 12 ? $year + 1 : $year;
|
||||
|
||||
?>
|
||||
|
||||
|
||||
<?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">
|
||||
|
||||
<h2 class="text-center"><?php echo $firstDayOfMonth->format('F Y'); ?></h2>
|
||||
<div class="d-flex justify-content-between mb-3">
|
||||
<a href="?month=<?php echo $prevMonth; ?>&year=<?php echo $prevYear; ?>" class="btn btn-primary">< Previous</a>
|
||||
<a href="?month=<?php echo $nextMonth; ?>&year=<?php echo $nextYear; ?>" class="btn btn-primary">Next ></a>
|
||||
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#addEventModal">
|
||||
Add Event
|
||||
</button>
|
||||
</div>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Monday</th>
|
||||
<th>Tuesday</th>
|
||||
<th>Wednesday</th>
|
||||
<th>Thursday</th>
|
||||
<th>Friday</th>
|
||||
<th>Saturday</th>
|
||||
<th>Sunday</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<?php
|
||||
// Print empty cells
|
||||
for ($i = 1; $i < $dayOfWeek; $i++) {
|
||||
echo "<td></td>";
|
||||
}
|
||||
|
||||
$currentDay = 1;
|
||||
while ($currentDay <= $daysInMonth) {
|
||||
if ($dayOfWeek > 7) {
|
||||
$dayOfWeek = 1;
|
||||
echo "</tr><tr>";
|
||||
}
|
||||
|
||||
echo "<td class=\"calendar-day\" data-date=\"$year-$month-$currentDay\">";
|
||||
echo "<strong>$currentDay</strong>";
|
||||
if (isset($eventsByDay[$currentDay])) {
|
||||
echo "<ul class=\"list-unstyled\">";
|
||||
foreach ($eventsByDay[$currentDay] as $event) {
|
||||
echo '<li class="badge" style="background-color: '.($event['type_color'] ?? '#007bff').'" data-event-id="'.$event['id'].'">' . htmlspecialchars($event['title']) . "</li>";
|
||||
}
|
||||
echo "</ul>";
|
||||
}
|
||||
echo "</td>";
|
||||
|
||||
$currentDay++;
|
||||
$dayOfWeek++;
|
||||
}
|
||||
|
||||
// Print remaining empty cells
|
||||
while ($dayOfWeek <= 7) {
|
||||
echo "<td></td>";
|
||||
$dayOfWeek++;
|
||||
}
|
||||
?>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Event Details Modal -->
|
||||
<div class="modal fade" id="eventDetailsModal" tabindex="-1" aria-labelledby="eventDetailsModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="eventDetailsModalLabel">Event Details</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<h5 id="event-title"></h5>
|
||||
<p id="event-description"></p>
|
||||
<p><strong>Starts:</strong> <span id="event-start"></span></p>
|
||||
<p><strong>Ends:</strong> <span id="event-end"></span></p>
|
||||
<p><strong>Type:</strong> <span id="event-type"></span></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary" id="edit-event-btn">Edit</button>
|
||||
<a href="#" id="delete-event-btn" class="btn btn-danger">Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="addEventModal" tabindex="-1" aria-labelledby="addEventModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="addEventModalLabel">Add Calendar Event</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form action="_add_calendar_event.php" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="title" class="form-label">Title</label>
|
||||
<input type="text" class="form-control" id="title" name="title" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="description" class="form-label">Description</label>
|
||||
<textarea class="form-control" id="description" name="description"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="start_datetime" class="form-label">Start Time</label>
|
||||
<input type="datetime-local" class="form-control" id="start_datetime" name="start_datetime" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="end_datetime" class="form-label">End Time</label>
|
||||
<input type="datetime-local" class="form-control" id="end_datetime" name="end_datetime" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="event_type_id" class="form-label">Type</label>
|
||||
<select class="form-select" id="event_type_id" name="event_type_id">
|
||||
<?php foreach ($event_types as $type): ?>
|
||||
<option value="<?= $type['id'] ?>"><?= htmlspecialchars($type['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="recurrence" class="form-label">Recurrence</label>
|
||||
<select class="form-select" id="recurrence" name="recurrence">
|
||||
<option value="">Does not repeat</option>
|
||||
<option value="daily">Daily</option>
|
||||
<option value="weekly">Weekly</option>
|
||||
<option value="monthly">Monthly</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3" id="recurrence_end_date_container" style="display: none;">
|
||||
<label for="recurrence_end_date" class="form-label">Recurrence End Date</label>
|
||||
<input type="date" class="form-control" id="recurrence_end_date" name="recurrence_end_date">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Save Event</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="editEventModal" tabindex="-1" aria-labelledby="editEventModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="editEventModalLabel">Edit Calendar Event</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form action="_update_calendar_event.php" method="post">
|
||||
<input type="hidden" id="edit_event_id" name="event_id">
|
||||
<div class="mb-3">
|
||||
<label for="edit_title" class="form-label">Title</label>
|
||||
<input type="text" class="form-control" id="edit_title" name="title" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="edit_description" class="form-label">Description</label>
|
||||
<textarea class="form-control" id="edit_description" name="description"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="edit_start_datetime" class="form-label">Start Time</label>
|
||||
<input type="datetime-local" class="form-control" id="edit_start_datetime" name="start_datetime" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="edit_end_datetime" class="form-label">End Time</label>
|
||||
<input type="datetime-local" class="form-control" id="edit_end_datetime" name="end_datetime" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="edit_event_type_id" class="form-label">Type</label>
|
||||
<select class="form-select" id="edit_event_type_id" name="event_type_id">
|
||||
<?php foreach ($event_types as $type): ?>
|
||||
<option value="<?= $type['id'] ?>"><?= htmlspecialchars($type['name']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const addEventModal = new bootstrap.Modal(document.getElementById('addEventModal'));
|
||||
const eventDetailsModal = new bootstrap.Modal(document.getElementById('eventDetailsModal'));
|
||||
const editEventModal = new bootstrap.Modal(document.getElementById('editEventModal'));
|
||||
|
||||
let currentEventData = null;
|
||||
|
||||
document.querySelectorAll('.calendar-day').forEach(day => {
|
||||
day.addEventListener('click', function(e) {
|
||||
if (e.target.classList.contains('badge')) {
|
||||
const eventId = e.target.dataset.eventId;
|
||||
fetch(`_get_event_details.php?id=${eventId}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
currentEventData = data;
|
||||
document.getElementById('event-title').textContent = data.title;
|
||||
document.getElementById('event-description').textContent = data.description;
|
||||
document.getElementById('event-start').textContent = new Date(data.start_datetime).toLocaleString();
|
||||
document.getElementById('event-end').textContent = new Date(data.end_datetime).toLocaleString();
|
||||
document.getElementById('event-type').textContent = data.type_name;
|
||||
document.getElementById('delete-event-btn').href = `_delete_calendar_event.php?id=${eventId}`;
|
||||
eventDetailsModal.show();
|
||||
});
|
||||
} else {
|
||||
const date = this.dataset.date;
|
||||
const now = new Date();
|
||||
const hours = String(now.getHours()).padStart(2, '0');
|
||||
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||
const currentTime = `${hours}:${minutes}`;
|
||||
|
||||
const startInput = document.getElementById('start_datetime');
|
||||
const month = String(new Date(date).getMonth() + 1).padStart(2, '0');
|
||||
const dayOfMonth = String(new Date(date).getDate()).padStart(2, '0');
|
||||
const year = new Date(date).getFullYear();
|
||||
startInput.value = `${year}-${month}-${dayOfMonth}T${currentTime}`;
|
||||
|
||||
addEventModal.show();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('edit-event-btn').addEventListener('click', function() {
|
||||
if (currentEventData) {
|
||||
if (currentEventData.parent_event_id !== null) {
|
||||
alert("Editing of recurring events is not supported yet. Please edit the parent event.");
|
||||
return;
|
||||
}
|
||||
document.getElementById('edit_event_id').value = currentEventData.id;
|
||||
document.getElementById('edit_title').value = currentEventData.title;
|
||||
document.getElementById('edit_description').value = currentEventData.description;
|
||||
document.getElementById('edit_start_datetime').value = currentEventData.start_datetime.slice(0, 16);
|
||||
document.getElementById('edit_end_datetime').value = currentEventData.end_datetime.slice(0, 16);
|
||||
document.getElementById('edit_event_type_id').value = currentEventData.event_type_id;
|
||||
eventDetailsModal.hide();
|
||||
editEventModal.show();
|
||||
}
|
||||
});
|
||||
|
||||
const recurrenceSelect = document.getElementById('recurrence');
|
||||
const recurrenceEndDateContainer = document.getElementById('recurrence_end_date_container');
|
||||
|
||||
recurrenceSelect.addEventListener('change', function() {
|
||||
if (this.value) {
|
||||
recurrenceEndDateContainer.style.display = 'block';
|
||||
} else {
|
||||
recurrenceEndDateContainer.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php
|
||||
include '_footer.php';
|
||||
?>
|
||||
8
db/migrations/001_add_process_definition_json.php
Normal file
8
db/migrations/001_add_process_definition_json.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
function migrate_001($pdo) {
|
||||
$sql = "ALTER TABLE process_definitions ADD COLUMN definition_json TEXT NULL AFTER name;";
|
||||
$pdo->exec($sql);
|
||||
echo "Migration 001 applied: Added definition_json to process_definitions.\n";
|
||||
}
|
||||
|
||||
17
db/migrations/002_create_calendar_events_table.php
Normal file
17
db/migrations/002_create_calendar_events_table.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
function migrate_002($pdo) {
|
||||
$sql = "CREATE TABLE IF NOT EXISTS calendar_events (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
start_datetime DATETIME NOT NULL,
|
||||
end_datetime DATETIME NOT NULL,
|
||||
type VARCHAR(50) NOT NULL, -- 'meeting', 'training'
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);";
|
||||
$pdo->exec($sql);
|
||||
echo "Migration 002 applied: Created calendar_events table.\n";
|
||||
}
|
||||
|
||||
|
||||
26
db/migrations/003_create_event_types_table.php
Normal file
26
db/migrations/003_create_event_types_table.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
$sql = "CREATE TABLE IF NOT EXISTS event_types (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
color VARCHAR(7) NOT NULL DEFAULT '#007bff'
|
||||
);";
|
||||
|
||||
$pdo->exec($sql);
|
||||
|
||||
// Add some default values
|
||||
$sql_insert = "INSERT INTO event_types (name, color) VALUES
|
||||
('Meeting', '#007bff'),
|
||||
('Training', '#ffc107'),
|
||||
('Other', '#28a745');";
|
||||
$pdo->exec($sql_insert);
|
||||
|
||||
|
||||
echo "Migration 003 completed successfully.\n";
|
||||
} catch (PDOException $e) {
|
||||
die("Migration 003 failed: " . $e->getMessage() . "\n");
|
||||
}
|
||||
34
db/migrations/004_add_event_type_id_to_calendar_events.php
Normal file
34
db/migrations/004_add_event_type_id_to_calendar_events.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// First, add the column without the foreign key constraint
|
||||
$sql_add_column = "ALTER TABLE calendar_events ADD COLUMN event_type_id INT NULL;";
|
||||
$pdo->exec($sql_add_column);
|
||||
|
||||
// Set a default value for existing rows to avoid foreign key constraint errors
|
||||
// Get the ID of the 'Other' event type
|
||||
$stmt = $pdo->query("SELECT id FROM event_types WHERE name = 'Other' LIMIT 1");
|
||||
$other_type = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$default_type_id = $other_type ? $other_type['id'] : 1; // Fallback to 1 if 'Other' not found
|
||||
|
||||
$sql_update_existing = "UPDATE calendar_events SET event_type_id = ? WHERE event_type_id IS NULL;";
|
||||
$stmt_update = $pdo->prepare($sql_update_existing);
|
||||
$stmt_update->execute([$default_type_id]);
|
||||
|
||||
// Now, add the foreign key constraint
|
||||
$sql_add_fk = "ALTER TABLE calendar_events ADD CONSTRAINT fk_event_type FOREIGN KEY (event_type_id) REFERENCES event_types(id) ON DELETE SET NULL;";
|
||||
$pdo->exec($sql_add_fk);
|
||||
|
||||
echo "Migration 004 completed successfully.\n";
|
||||
} catch (PDOException $e) {
|
||||
// Check if the column already exists, which might happen on re-runs
|
||||
if (strpos($e->getMessage(), 'Duplicate column name') !== false) {
|
||||
echo "Migration 004 seems to be already applied (Column exists). Skipping.\n";
|
||||
} else {
|
||||
die("Migration 004 failed: " . $e->getMessage() . "\n");
|
||||
}
|
||||
}
|
||||
|
||||
18
db/migrations/005_create_bni_groups_table.php
Normal file
18
db/migrations/005_create_bni_groups_table.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
require_once(__DIR__ . '/../config.php');
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$sql = "CREATE TABLE IF NOT EXISTS `bni_groups` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`name` VARCHAR(255) NOT NULL,
|
||||
`city` VARCHAR(255),
|
||||
`active` BOOLEAN NOT NULL DEFAULT 1,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);";
|
||||
$pdo->exec($sql);
|
||||
echo "Table 'bni_groups' created successfully." . PHP_EOL;
|
||||
} catch (PDOException $e) {
|
||||
echo "Error creating table 'bni_groups': " . $e->getMessage() . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
20
db/migrations/006_add_recurrence_to_calendar_events.php
Normal file
20
db/migrations/006_add_recurrence_to_calendar_events.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
$sql = <<<SQL
|
||||
ALTER TABLE `calendar_events`
|
||||
ADD COLUMN `recurrence` VARCHAR(20) DEFAULT NULL,
|
||||
ADD COLUMN `recurrence_end_date` DATE DEFAULT NULL,
|
||||
ADD COLUMN `parent_event_id` INT DEFAULT NULL;
|
||||
SQL;
|
||||
|
||||
$pdo->exec($sql);
|
||||
|
||||
echo "Migration 006 executed successfully: Added recurrence columns to calendar_events table.\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
die("Error executing migration 006: " . $e->getMessage() . "\n");
|
||||
}
|
||||
17
db/migrations/007_remove_type_from_calendar_events.php
Normal file
17
db/migrations/007_remove_type_from_calendar_events.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$sql = "ALTER TABLE calendar_events DROP COLUMN type;";
|
||||
$pdo->exec($sql);
|
||||
echo "Migration 007 completed successfully: Dropped 'type' column from calendar_events.\n";
|
||||
} catch (PDOException $e) {
|
||||
// Check if the column has already been dropped
|
||||
if (strpos($e->getMessage(), 'column not found') !== false || strpos($e->getMessage(), 'Unknown column') !== false) {
|
||||
echo "Migration 007 seems to be already applied (Column not found). Skipping.\n";
|
||||
} else {
|
||||
die("Migration 007 failed: " . $e->getMessage() . "\n");
|
||||
}
|
||||
}
|
||||
|
||||
16
db/migrations/008_create_roles_table.php
Normal file
16
db/migrations/008_create_roles_table.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
require_once(__DIR__ . '/../config.php');
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$sql = "CREATE TABLE IF NOT EXISTS `roles` (
|
||||
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
|
||||
$pdo->exec($sql);
|
||||
echo "Table 'roles' created successfully." . PHP_EOL;
|
||||
} catch (PDOException $e) {
|
||||
echo "Error creating table 'roles': " . $e->getMessage() . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
18
db/migrations/009_create_user_roles_table.php
Normal file
18
db/migrations/009_create_user_roles_table.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
require_once(__DIR__ . '/../config.php');
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$sql = "CREATE TABLE IF NOT EXISTS `user_roles` (
|
||||
`user_id` INT(11) UNSIGNED NOT NULL,
|
||||
`role_id` INT(11) UNSIGNED NOT NULL,
|
||||
PRIMARY KEY (`user_id`, `role_id`),
|
||||
FOREIGN KEY (`user_id`) REFERENCES `people`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`role_id`) REFERENCES `roles`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
|
||||
$pdo->exec($sql);
|
||||
echo "Table 'user_roles' created successfully." . PHP_EOL;
|
||||
} catch (PDOException $e) {
|
||||
echo "Error creating table 'user_roles': " . $e->getMessage() . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
12
db/migrations/010_add_display_order_to_event_types.php
Normal file
12
db/migrations/010_add_display_order_to_event_types.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$sql = "ALTER TABLE event_types ADD COLUMN display_order INT NOT NULL DEFAULT 0";
|
||||
$pdo->exec($sql);
|
||||
echo "Migration 010_add_display_order_to_event_types executed successfully." . PHP_EOL;
|
||||
} catch (PDOException $e) {
|
||||
echo "Error executing migration 010_add_display_order_to_event_types: " . $e->getMessage() . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
12
db/migrations/011_add_display_order_to_bni_groups.php
Normal file
12
db/migrations/011_add_display_order_to_bni_groups.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$sql = "ALTER TABLE bni_groups ADD COLUMN display_order INT NOT NULL DEFAULT 0";
|
||||
$pdo->exec($sql);
|
||||
echo "Migration 011_add_display_order_to_bni_groups executed successfully." . PHP_EOL;
|
||||
} catch (PDOException $e) {
|
||||
echo "Error executing migration 011_add_display_order_to_bni_groups: " . $e->getMessage() . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
12
db/migrations/012_add_display_order_to_roles.php
Normal file
12
db/migrations/012_add_display_order_to_roles.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$sql = "ALTER TABLE roles ADD COLUMN display_order INT NOT NULL DEFAULT 0";
|
||||
$pdo->exec($sql);
|
||||
echo "Migration 012_add_display_order_to_roles executed successfully." . PHP_EOL;
|
||||
} catch (PDOException $e) {
|
||||
echo "Error executing migration 012_add_display_order_to_roles: " . $e->getMessage() . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
21
db/migrations/013_rename_roles_to_functions.php
Normal file
21
db/migrations/013_rename_roles_to_functions.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
require_once(__DIR__ . '/../config.php');
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
$sql = "RENAME TABLE `roles` TO `functions`;";
|
||||
$pdo->exec($sql);
|
||||
echo "Table 'roles' renamed to 'functions' successfully." . PHP_EOL;
|
||||
|
||||
$sql = "RENAME TABLE `user_roles` TO `user_functions`;";
|
||||
$pdo->exec($sql);
|
||||
echo "Table 'user_roles' renamed to 'user_functions' successfully." . PHP_EOL;
|
||||
|
||||
$sql = "ALTER TABLE `user_functions` CHANGE `role_id` `function_id` INT(11) UNSIGNED NOT NULL;";
|
||||
$pdo->exec($sql);
|
||||
echo "Column 'role_id' renamed to 'function_id' in 'user_functions' table successfully." . PHP_EOL;
|
||||
|
||||
} catch (PDOException $e) {
|
||||
echo "Error renaming tables or columns: " . $e->getMessage() . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
21
db/migrations/014_add_role_to_people_table.php
Normal file
21
db/migrations/014_add_role_to_people_table.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../../db/config.php';
|
||||
|
||||
function migrate_014_add_role_to_people_table() {
|
||||
$pdo = db();
|
||||
|
||||
// Check if the column already exists
|
||||
try {
|
||||
$result = $pdo->query("SELECT `role` FROM `people` LIMIT 1");
|
||||
} catch (PDOException $e) {
|
||||
// Column does not exist, so add it
|
||||
$pdo->exec("ALTER TABLE `people` ADD COLUMN `role` VARCHAR(50) NOT NULL DEFAULT 'członek' AFTER `email`");
|
||||
echo "Migration 014: Added 'role' column to 'people' table.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
echo "Migration 014: 'role' column already exists in 'people' table. No changes made.\n";
|
||||
}
|
||||
|
||||
migrate_014_add_role_to_people_table();
|
||||
|
||||
133
db_setup.php
Normal file
133
db_setup.php
Normal file
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
echo "Starting database setup...\n";
|
||||
|
||||
// 1. People table (unified table for users and contacts)
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS `people` (
|
||||
`id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
`firstName` VARCHAR(255) NOT NULL,
|
||||
`lastName` VARCHAR(255) NOT NULL,
|
||||
`email` VARCHAR(255) NOT NULL UNIQUE,
|
||||
`password` VARCHAR(255) NULL,
|
||||
`companyName` VARCHAR(255) DEFAULT NULL,
|
||||
`phone` VARCHAR(50) DEFAULT NULL,
|
||||
`role` ENUM('admin', 'team_member', 'member', 'guest') NOT NULL DEFAULT 'guest',
|
||||
`is_user` BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
`active` BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
`createdAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)");
|
||||
echo "People table created or already exists.\n";
|
||||
|
||||
// Seed default admin user
|
||||
$stmt = $pdo->prepare("SELECT id FROM people WHERE email = ?");
|
||||
$stmt->execute(['admin@example.com']);
|
||||
if ($stmt->fetchColumn() === false) {
|
||||
$password = password_hash('password', PASSWORD_DEFAULT);
|
||||
$insert_stmt = $pdo->prepare(
|
||||
"INSERT INTO people (email, password, role, firstName, lastName, is_user, active) VALUES (?, ?, 'admin', 'Admin', 'User', TRUE, TRUE)"
|
||||
);
|
||||
$insert_stmt->execute(['admin@example.com', $password]);
|
||||
echo "Default admin user created. Email: admin@example.com, Password: password\n";
|
||||
}
|
||||
|
||||
// 2. Process Definitions table
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS `process_definitions` (
|
||||
`id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
`code` VARCHAR(255) NOT NULL UNIQUE,
|
||||
`name` VARCHAR(255) NOT NULL,
|
||||
`description` TEXT,
|
||||
`version` INT NOT NULL DEFAULT 1,
|
||||
`is_active` BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
`start_node_id` VARCHAR(255),
|
||||
`definition_json` TEXT,
|
||||
`createdAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)");
|
||||
echo "Process definitions table created or already exists.\n";
|
||||
|
||||
// Seed process_definitions
|
||||
$processes = [
|
||||
['code' => 'mentoring', 'name' => 'Mentoring nowego czlonka', 'description' => 'Proces wdrozenia nowego czlonka do organizacji.'],
|
||||
['code' => 'meeting_preparation', 'name' => 'Przygotowanie spotkania grupy', 'description' => 'Proces przygotowania do spotkania grupy, w tym agenda, materialy, etc.'],
|
||||
['code' => 'guest_handling', 'name' => 'Obsluga goscia', 'description' => 'Proces obslugi gosci odwiedzajacych organizacje.']
|
||||
];
|
||||
$stmt = $pdo->prepare("SELECT id FROM process_definitions WHERE code = ?");
|
||||
$insert_stmt = $pdo->prepare("INSERT INTO process_definitions (code, name, description) VALUES (?, ?, ?)");
|
||||
foreach ($processes as $process) {
|
||||
$stmt->execute([$process['code']]);
|
||||
if ($stmt->fetchColumn() === false) {
|
||||
$insert_stmt->execute([$process['code'], $process['name'], $process['description']]);
|
||||
echo "Seeded process: " . $process['name'] . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
`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`)
|
||||
)");
|
||||
echo "Process instances table created or already exists.\n";
|
||||
|
||||
// 4. Process Events table (updated FK)
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS `process_events` (
|
||||
`id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
`processInstanceId` INT(11) UNSIGNED NOT NULL,
|
||||
`event_type` VARCHAR(50) NOT NULL,
|
||||
`message` TEXT,
|
||||
`node_id` VARCHAR(255),
|
||||
`payload_json` TEXT,
|
||||
`createdBy` INT(11) UNSIGNED NOT NULL,
|
||||
`createdAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (processInstanceId) REFERENCES process_instances(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (createdBy) REFERENCES people(id) ON DELETE CASCADE
|
||||
)");
|
||||
echo "Process events table created or already exists.\n";
|
||||
|
||||
// MIGRATIONS
|
||||
echo "Starting migrations...\n";
|
||||
|
||||
// Migration: Rename `processId` to `processDefinitionId` in `process_instances`
|
||||
$stmt = $pdo->query("SHOW COLUMNS FROM `process_instances` LIKE 'processId'");
|
||||
if ($stmt->fetch()) {
|
||||
$checkFk = $pdo->query("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE REFERENCED_TABLE_SCHEMA = SCHEMA() AND TABLE_NAME = 'process_instances' AND COLUMN_NAME = 'processId';")->fetch();
|
||||
if ($checkFk) {
|
||||
$pdo->exec("ALTER TABLE `process_instances` DROP FOREIGN KEY `{$checkFk['CONSTRAINT_NAME']}`;");
|
||||
}
|
||||
$pdo->exec("ALTER TABLE `process_instances` CHANGE `processId` `processDefinitionId` INT(11) UNSIGNED NOT NULL;");
|
||||
$pdo->exec("ALTER TABLE `process_instances` ADD FOREIGN KEY (`processDefinitionId`) REFERENCES `process_definitions`(`id`) ON DELETE CASCADE;");
|
||||
echo "Migrated process_instances: processId -> processDefinitionId.\n";
|
||||
}
|
||||
|
||||
// Migration: Rename `contactId` to `personId` and update foreign key in `process_instances`
|
||||
$stmt = $pdo->query("SHOW COLUMNS FROM `process_instances` LIKE 'contactId'");
|
||||
if ($stmt->fetch()) {
|
||||
$checkFk = $pdo->query("SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE REFERENCED_TABLE_SCHEMA = SCHEMA() AND TABLE_NAME = 'process_instances' AND COLUMN_NAME = 'contactId';")->fetch();
|
||||
if ($checkFk) {
|
||||
$pdo->exec("ALTER TABLE `process_instances` DROP FOREIGN KEY `{$checkFk['CONSTRAINT_NAME']}`;");
|
||||
}
|
||||
$pdo->exec("ALTER TABLE `process_instances` CHANGE `contactId` `personId` INT(11) UNSIGNED NOT NULL;");
|
||||
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";
|
||||
|
||||
echo "\nDatabase setup/update completed successfully.\n";
|
||||
|
||||
} catch (PDOException $e) {
|
||||
die("Database setup failed: " . $e->getMessage());
|
||||
}
|
||||
185
event_types.php
Normal file
185
event_types.php
Normal file
@ -0,0 +1,185 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
include '_header.php';
|
||||
include '_navbar.php';
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->query("SELECT * FROM event_types ORDER BY display_order");
|
||||
$event_types = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
?>
|
||||
|
||||
<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">
|
||||
<h1 class="h2 pt-3 pb-2 mb-3 border-bottom">Event Types</h1>
|
||||
|
||||
<?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 class="d-flex justify-content-end mb-3">
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addModal">
|
||||
Add New Event Type
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive mt-4">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 30px;"></th>
|
||||
<th>Name</th>
|
||||
<th>Color</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="sortable-list">
|
||||
<?php foreach ($event_types as $type): ?>
|
||||
<tr data-id="<?= $type['id'] ?>">
|
||||
<td class="handle"><i class="bi bi-grip-vertical"></i></td>
|
||||
<td><?= htmlspecialchars($type['name']) ?></td>
|
||||
<td><span class="badge" style="background-color: <?= htmlspecialchars($type['color']) ?>"><?= htmlspecialchars($type['color']) ?></span></td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-warning btn-sm" data-bs-toggle="modal" data-bs-target="#editModal" data-id="<?= $type['id'] ?>" data-name="<?= htmlspecialchars($type['name']) ?>" data-color="<?= htmlspecialchars($type['color']) ?>" data-display-order="<?= htmlspecialchars($type['display_order']) ?>">
|
||||
Edit
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#deleteModal" data-id="<?= $type['id'] ?>">
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Modal -->
|
||||
<div class="modal fade" id="addModal" tabindex="-1" aria-labelledby="addModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="addModalLabel">Add Event Type</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="_add_event_type.php" method="post">
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="addName" class="form-label">Name</label>
|
||||
<input type="text" class="form-control" id="addName" name="name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="addColor" class="form-label">Color</label>
|
||||
<input type="color" class="form-control" id="addColor" name="color" value="#007bff" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="submit" name="add" class="btn btn-primary">Add</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Modal -->
|
||||
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="editModalLabel">Edit Event Type</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="_update_event_type.php" method="post">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="editId" name="id">
|
||||
<input type="hidden" id="editDisplayOrder" name="display_order">
|
||||
<div class="mb-3">
|
||||
<label for="editName" class="form-label">Name</label>
|
||||
<input type="text" class="form-control" id="editName" name="name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="editColor" class="form-label">Color</label>
|
||||
<input type="color" class="form-control" id="editColor" name="color" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="submit" name="edit" class="btn btn-primary">Save changes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Modal -->
|
||||
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="deleteModalLabel">Delete Event Type</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Are you sure you want to delete this event type?
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<a href="#" id="deleteLink" class="btn btn-danger">Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<?php include '_footer.php'; ?>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var editModal = document.getElementById('editModal');
|
||||
editModal.addEventListener('show.bs.modal', function (event) {
|
||||
var button = event.relatedTarget;
|
||||
var id = button.getAttribute('data-id');
|
||||
var name = button.getAttribute('data-name');
|
||||
var color = button.getAttribute('data-color');
|
||||
var display_order = button.getAttribute('data-display-order');
|
||||
|
||||
var modalTitle = editModal.querySelector('.modal-title');
|
||||
var idInput = editModal.querySelector('#editId');
|
||||
var nameInput = editModal.querySelector('#editName');
|
||||
var colorInput = editModal.querySelector('#editColor');
|
||||
var displayOrderInput = editModal.querySelector('#editDisplayOrder');
|
||||
|
||||
idInput.value = id;
|
||||
nameInput.value = name;
|
||||
colorInput.value = color;
|
||||
displayOrderInput.value = display_order;
|
||||
});
|
||||
|
||||
var deleteModal = document.getElementById('deleteModal');
|
||||
deleteModal.addEventListener('show.bs.modal', function (event) {
|
||||
var button = event.relatedTarget;
|
||||
var id = button.getAttribute('data-id');
|
||||
var deleteLink = deleteModal.querySelector('#deleteLink');
|
||||
deleteLink.href = '_delete_event_type.php?id=' + id;
|
||||
});
|
||||
|
||||
$(function() {
|
||||
$("#sortable-list").sortable({
|
||||
handle: ".handle",
|
||||
update: function(event, ui) {
|
||||
var order = $(this).sortable('toArray', {attribute: 'data-id'});
|
||||
$.post('_update_event_type_order.php', { order: order });
|
||||
}
|
||||
}).disableSelection();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
168
functions.php
Normal file
168
functions.php
Normal file
@ -0,0 +1,168 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
include '_header.php';
|
||||
include '_navbar.php';
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->query("SELECT * FROM functions ORDER BY display_order");
|
||||
$functions = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
?>
|
||||
|
||||
<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">
|
||||
<h1 class="h2 pt-3 pb-2 mb-3 border-bottom">Functions</h1>
|
||||
|
||||
<?php if (isset($_SESSION['success_message'])): ?>
|
||||
<div class="alert alert-success alert-dismissible fade show mt-3" function="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 class="d-flex justify-content-end mb-3">
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addModal">
|
||||
Add New Function
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive mt-4">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 30px;"></th>
|
||||
<th>Name</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="sortable-list">
|
||||
<?php foreach ($functions as $function): ?>
|
||||
<tr data-id="<?= $function['id'] ?>">
|
||||
<td class="handle"><i class="bi bi-grip-vertical"></i></td>
|
||||
<td><?= htmlspecialchars($function['name']) ?></td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-warning btn-sm" data-bs-toggle="modal" data-bs-target="#editModal" data-id="<?= $function['id'] ?>" data-name="<?= htmlspecialchars($function['name']) ?>">
|
||||
Edit
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#deleteModal" data-id="<?= $function['id'] ?>">
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Modal -->
|
||||
<div class="modal fade" id="addModal" tabindex="-1" aria-labelledby="addModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="addModalLabel">Add Function</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="_add_function.php" method="post">
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="addName" class="form-label">Name</label>
|
||||
<input type="text" class="form-control" id="addName" name="name" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="submit" name="add" class="btn btn-primary">Add</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Modal -->
|
||||
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="editModalLabel">Edit Function</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="_update_function.php" method="post">
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="editId" name="id">
|
||||
<div class="mb-3">
|
||||
<label for="editName" class="form-label">Name</label>
|
||||
<input type="text" class="form-control" id="editName" name="name" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="submit" name="edit" class="btn btn-primary">Save changes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Modal -->
|
||||
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="deleteModalLabel">Delete Function</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Are you sure you want to delete this function?
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<a href="#" id="deleteLink" class="btn btn-danger">Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<?php include '_footer.php'; ?>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var editModal = document.getElementById('editModal');
|
||||
editModal.addEventListener('show.bs.modal', function (event) {
|
||||
var button = event.relatedTarget;
|
||||
var id = button.getAttribute('data-id');
|
||||
var name = button.getAttribute('data-name');
|
||||
|
||||
var modalTitle = editModal.querySelector('.modal-title');
|
||||
var idInput = editModal.querySelector('#editId');
|
||||
var nameInput = editModal.querySelector('#editName');
|
||||
|
||||
idInput.value = id;
|
||||
nameInput.value = name;
|
||||
});
|
||||
|
||||
var deleteModal = document.getElementById('deleteModal');
|
||||
deleteModal.addEventListener('show.bs.modal', function (event) {
|
||||
var button = event.relatedTarget;
|
||||
var id = button.getAttribute('data-id');
|
||||
var deleteLink = deleteModal.querySelector('#deleteLink');
|
||||
deleteLink.href = '_delete_function.php?id=' + id;
|
||||
});
|
||||
|
||||
$(function() {
|
||||
$("#sortable-list").sortable({
|
||||
handle: ".handle",
|
||||
update: function(event, ui) {
|
||||
var order = $(this).sortable('toArray', {attribute: 'data-id'});
|
||||
$.post('_update_function_order.php', { order: order });
|
||||
}
|
||||
}).disableSelection();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
716
index.php
716
index.php
@ -1,150 +1,574 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
@ini_set('display_errors', '1');
|
||||
@error_reporting(E_ALL);
|
||||
@date_default_timezone_set('UTC');
|
||||
require_once 'WorkflowEngine.php';
|
||||
|
||||
$workflowEngine = new WorkflowEngine();
|
||||
$dashboardData = $workflowEngine->getDashboardMatrix();
|
||||
|
||||
$people = $dashboardData['people'];
|
||||
$processes = $dashboardData['definitions'];
|
||||
$instances = $dashboardData['instances'];
|
||||
|
||||
$status_colors = [
|
||||
'none' => 'secondary',
|
||||
'negative' => 'danger',
|
||||
'in_progress' => 'warning',
|
||||
'positive' => 'success',
|
||||
];
|
||||
|
||||
$pdo = db();
|
||||
$stmt_functions = $pdo->query("SELECT * FROM functions ORDER BY display_order");
|
||||
$all_functions = $stmt_functions->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$functions_by_id = [];
|
||||
foreach ($all_functions as $function) {
|
||||
$functions_by_id[$function['id']] = $function['name'];
|
||||
}
|
||||
|
||||
$stmt_person_functions = $pdo->query("SELECT user_id, function_id FROM user_functions");
|
||||
$person_functions_map = [];
|
||||
while ($row = $stmt_person_functions->fetch(PDO::FETCH_ASSOC)) {
|
||||
$person_functions_map[$row['user_id']][] = $row['function_id'];
|
||||
}
|
||||
|
||||
$phpVersion = PHP_VERSION;
|
||||
$now = date('Y-m-d H:i:s');
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>New Style</title>
|
||||
<?php
|
||||
// Read project preview data from environment
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
?>
|
||||
<?php if ($projectDescription): ?>
|
||||
<!-- Meta description -->
|
||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
||||
<!-- Open Graph meta tags -->
|
||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<!-- Twitter meta tags -->
|
||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<?php endif; ?>
|
||||
<?php if ($projectImageUrl): ?>
|
||||
<!-- Open Graph image -->
|
||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<!-- Twitter image -->
|
||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<?php endif; ?>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-color-start: #6a11cb;
|
||||
--bg-color-end: #2575fc;
|
||||
--text-color: #ffffff;
|
||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
body::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
||||
animation: bg-pan 20s linear infinite;
|
||||
z-index: -1;
|
||||
}
|
||||
@keyframes bg-pan {
|
||||
0% { background-position: 0% 0%; }
|
||||
100% { background-position: 100% 100%; }
|
||||
}
|
||||
main {
|
||||
padding: 2rem;
|
||||
}
|
||||
.card {
|
||||
background: var(--card-bg-color);
|
||||
border: 1px solid var(--card-border-color);
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.loader {
|
||||
margin: 1.25rem auto 1.25rem;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.hint {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px; height: 1px;
|
||||
padding: 0; margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap; border: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 1rem;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
p {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
code {
|
||||
background: rgba(0,0,0,0.2);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
footer {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="card">
|
||||
<h1>Analyzing your requirements and generating your website…</h1>
|
||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||
<span class="sr-only">Loading…</span>
|
||||
</div>
|
||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
||||
<?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">
|
||||
<?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']); ?>
|
||||
<script>
|
||||
setTimeout(function() {
|
||||
let alert = document.querySelector('.alert-success');
|
||||
if (alert) {
|
||||
new bootstrap.Alert(alert).close();
|
||||
}
|
||||
}, 5000);
|
||||
</script>
|
||||
<?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']); ?>
|
||||
<script>
|
||||
setTimeout(function() {
|
||||
let alert = document.querySelector('.alert-danger');
|
||||
if (alert) {
|
||||
new bootstrap.Alert(alert).close();
|
||||
}
|
||||
}, 5000);
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2">Process 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>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-sm">
|
||||
<thead>
|
||||
<tr class="text-center">
|
||||
<th class="bg-light"><input type="checkbox" id="selectAll"></th>
|
||||
<th class="bg-light">Person</th>
|
||||
<?php foreach ($processes as $process): ?>
|
||||
<th style="writing-mode: vertical-lr;" class="bg-light"><?= htmlspecialchars($process['name']) ?></th>
|
||||
<?php endforeach; ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($people as $person): ?>
|
||||
<tr>
|
||||
<td><input type="checkbox" class="person-checkbox" name="person_ids[]" value="<?= $person['id'] ?>"></td>
|
||||
<td class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<?= htmlspecialchars($person['firstName'] . ' ' . $person['lastName']) ?>
|
||||
<br>
|
||||
<small class="text-muted"><?= htmlspecialchars($person['companyName']) ?></small>
|
||||
<br>
|
||||
<small class="text-info fw-bold"><?= htmlspecialchars(ucfirst($person['role'])) ?></small>
|
||||
<br>
|
||||
<small class="text-muted">
|
||||
<?php
|
||||
$person_function_names = [];
|
||||
if (isset($person_functions_map[$person['id']])) {
|
||||
foreach ($person_functions_map[$person['id']] as $function_id) {
|
||||
if (isset($functions_by_id[$function_id])) {
|
||||
$person_function_names[] = htmlspecialchars($functions_by_id[$function_id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
echo implode(', ', $person_function_names);
|
||||
?>
|
||||
</small>
|
||||
</div>
|
||||
<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 foreach ($processes as $process):
|
||||
$instance = isset($instances[$person['id']][$process['id']]) ? $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" data-bs-toggle="modal" data-bs-target="#instanceModal" data-person-id="<?= $person['id'] ?>" data-process-id="<?= $process['id'] ?>" style="cursor: pointer;">
|
||||
<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>
|
||||
</main>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
</div>
|
||||
|
||||
<!-- Edit Person Modal -->
|
||||
<div class="modal fade" id="editPersonModal" tabindex="-1" aria-labelledby="editPersonModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl">
|
||||
<div class="modal-content">
|
||||
<form id="editPersonForm" action="_update_person.php" method="post">
|
||||
<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">
|
||||
<input type="hidden" name="id" id="editPersonId">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="editFirstName" class="form-label">Imię</label>
|
||||
<input type="text" class="form-control" id="editFirstName" name="firstName" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="editLastName" class="form-label">Nazwisko</label>
|
||||
<input type="text" class="form-control" id="editLastName" name="lastName" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="editCompanyName" class="form-label">Nazwa firmy</label>
|
||||
<input type="text" class="form-control" id="editCompanyName" name="companyName">
|
||||
</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>
|
||||
<div class="col-md-6">
|
||||
<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 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">
|
||||
<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>
|
||||
</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-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">
|
||||
<form id="createPersonForm" action="_create_person.php" method="post">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<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="firstName" 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="lastName" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="createCompanyName" class="form-label">Company Name</label>
|
||||
<input type="text" class="form-control" id="createCompanyName" name="companyName">
|
||||
</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>
|
||||
<div class="col-md-6">
|
||||
<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 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">
|
||||
<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>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">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 () {
|
||||
var instanceModal = document.getElementById('instanceModal');
|
||||
instanceModal.addEventListener('show.bs.modal', function (event) {
|
||||
var button = event.relatedTarget;
|
||||
var personId = button.dataset.personId;
|
||||
var processId = button.dataset.processId;
|
||||
var modalBody = instanceModal.querySelector('.modal-body');
|
||||
|
||||
// Load content via AJAX
|
||||
fetch(`_get_instance_details.php?personId=${personId}&processId=${processId}`)
|
||||
.then(response => response.text())
|
||||
.then(html => {
|
||||
modalBody.innerHTML = html;
|
||||
});
|
||||
});
|
||||
|
||||
// Bulk actions
|
||||
const selectAll = document.getElementById('selectAll');
|
||||
const checkboxes = document.querySelectorAll('.person-checkbox');
|
||||
const bulkActionsGroup = document.getElementById('bulk-actions-group');
|
||||
|
||||
function toggleBulkActions() {
|
||||
const anyChecked = Array.from(checkboxes).some(c => c.checked);
|
||||
bulkActionsGroup.style.display = anyChecked ? 'block' : 'none';
|
||||
}
|
||||
|
||||
selectAll.addEventListener('change', function () {
|
||||
checkboxes.forEach(c => c.checked = selectAll.checked);
|
||||
toggleBulkActions();
|
||||
});
|
||||
|
||||
checkboxes.forEach(c => c.addEventListener('change', toggleBulkActions));
|
||||
|
||||
// Pass selected people to modals
|
||||
function setupBulkModal(modalId, hiddenInputId) {
|
||||
const modal = document.getElementById(modalId);
|
||||
modal.addEventListener('show.bs.modal', function() {
|
||||
const selectedIds = Array.from(checkboxes).filter(c => c.checked).map(c => c.value);
|
||||
document.getElementById(hiddenInputId).value = JSON.stringify(selectedIds);
|
||||
});
|
||||
}
|
||||
|
||||
setupBulkModal('bulkStatusModal', 'bulkStatusPersonIds');
|
||||
setupBulkModal('bulkEventModal', 'bulkEventPersonIds');
|
||||
setupBulkModal('bulkInitModal', 'bulkInitPersonIds');
|
||||
|
||||
// Populate edit person modal
|
||||
const editPersonModal = document.getElementById('editPersonModal');
|
||||
if(editPersonModal) {
|
||||
editPersonModal.addEventListener('show.bs.modal', function (event) {
|
||||
const button = event.relatedTarget;
|
||||
const personId = button.getAttribute('data-person-id');
|
||||
var personName = button.getAttribute('data-person-name');
|
||||
|
||||
var deleteBtn = document.getElementById('deleteUserBtn');
|
||||
deleteBtn.dataset.personId = personId;
|
||||
deleteBtn.dataset.personName = personName;
|
||||
|
||||
fetch('_get_person_details.php?id=' + personId)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.getElementById('editPersonId').value = data.person.id;
|
||||
document.getElementById('editFirstName').value = data.person.firstName;
|
||||
document.getElementById('editLastName').value = data.person.lastName;
|
||||
document.getElementById('editEmail').value = data.person.email;
|
||||
document.getElementById('editCompanyName').value = data.person.companyName;
|
||||
document.getElementById('editPhone').value = data.person.phone;
|
||||
document.getElementById('editRole').value = data.person.role;
|
||||
|
||||
const functionsSelect = document.getElementById('editRoles');
|
||||
functionsSelect.innerHTML = ''; // Clear existing options
|
||||
|
||||
data.all_functions.forEach(func => {
|
||||
const option = document.createElement('option');
|
||||
option.value = func.id;
|
||||
option.textContent = func.name;
|
||||
if (data.person_functions.includes(func.id)) {
|
||||
option.selected = true;
|
||||
}
|
||||
functionsSelect.appendChild(option);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// When the delete button in the edit modal is clicked, pass the data to the delete confirmation modal
|
||||
document.getElementById('deleteUserBtn').addEventListener('click', function() {
|
||||
var personId = this.dataset.personId;
|
||||
var personName = this.dataset.personName;
|
||||
|
||||
var deletePersonModal = document.getElementById('deletePersonModal');
|
||||
deletePersonModal.querySelector('#personNameToDelete').textContent = personName;
|
||||
deletePersonModal.dataset.personId = personId;
|
||||
});
|
||||
|
||||
// Populate delete person modal
|
||||
var deletePersonModal = document.getElementById('deletePersonModal');
|
||||
deletePersonModal.addEventListener('show.bs.modal', function (event) {
|
||||
var button = event.relatedTarget;
|
||||
if (button.id !== 'deleteUserBtn') {
|
||||
var personId = button.dataset.personId;
|
||||
var personName = button.dataset.personName;
|
||||
document.getElementById('personNameToDelete').textContent = personName;
|
||||
deletePersonModal.dataset.personId = personId;
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('confirmDeleteBtn').addEventListener('click', function() {
|
||||
let personIdToDelete = deletePersonModal.dataset.personId;
|
||||
if (personIdToDelete) {
|
||||
const formData = new FormData();
|
||||
formData.append('person_id', personIdToDelete);
|
||||
|
||||
fetch('_delete_person.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const modal = bootstrap.Modal.getInstance(deletePersonModal);
|
||||
modal.hide();
|
||||
|
||||
if (data.success) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
let errorAlert = document.querySelector('.alert-danger');
|
||||
let errorContainer = errorAlert.parentElement;
|
||||
if (!errorAlert) {
|
||||
errorContainer = document.querySelector('main'); // fallback
|
||||
const alertHTML = `
|
||||
<div class="alert alert-danger alert-dismissible fade show mt-3" role="alert">
|
||||
${data.error}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>`;
|
||||
errorContainer.insertAdjacentHTML('afterbegin', alertHTML);
|
||||
} else {
|
||||
errorAlert.innerHTML = `${data.error} <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>`;
|
||||
errorAlert.classList.remove('d-none');
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
const modal = bootstrap.Modal.getInstance(deletePersonModal);
|
||||
modal.hide();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
</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="person_ids" 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="person_ids" 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="person_ids" 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>
|
||||
80
login.php
Normal file
80
login.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'db/config.php';
|
||||
|
||||
$error = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$email = $_POST['email'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
if (empty($email) || empty($password)) {
|
||||
$error = 'Proszę podać email i hasło.';
|
||||
} else {
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT * FROM people WHERE email = ? AND is_user = TRUE AND active = TRUE");
|
||||
$stmt->execute([$email]);
|
||||
$person = $stmt->fetch();
|
||||
|
||||
if ($person && password_verify($password, $person['password'])) {
|
||||
$_SESSION['user_id'] = $person['id'];
|
||||
$_SESSION['user_email'] = $person['email'];
|
||||
$_SESSION['user_role'] = $person['role'];
|
||||
$_SESSION['user_name'] = $person['firstName'] . ' ' . $person['lastName'];
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
} else {
|
||||
$error = 'Nieprawidłowe dane logowania. Spróbuj ponownie.';
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$error = 'Błąd bazy danych: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="pl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Logowanie</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.login-card {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card login-card">
|
||||
<div class="card-body">
|
||||
<h3 class="card-title text-center mb-4">Logowanie do systemu</h3>
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
|
||||
<?php endif; ?>
|
||||
<form method="POST" action="login.php">
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Adres email</label>
|
||||
<input type="email" class="form-control" id="email" name="email" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Hasło</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-primary">Zaloguj się</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
6
logout.php
Normal file
6
logout.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
session_start();
|
||||
session_unset();
|
||||
session_destroy();
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
62
migrate.php
Normal file
62
migrate.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
function run_migrations() {
|
||||
$pdo = db();
|
||||
|
||||
try {
|
||||
// Create migrations table if it doesn't exist
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS migrations (id INT AUTO_INCREMENT PRIMARY KEY, migration VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)");
|
||||
|
||||
// Get all run migrations
|
||||
$stmt = $pdo->query("SELECT migration FROM migrations");
|
||||
$run_migrations = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
// Get all migration files
|
||||
$migration_files = glob('db/migrations/*.php');
|
||||
|
||||
foreach ($migration_files as $file) {
|
||||
$migration_name = basename($file, '.php');
|
||||
|
||||
if (!in_array($migration_name, $run_migrations)) {
|
||||
echo "Running migration: $migration_name...\n";
|
||||
require_once $file;
|
||||
|
||||
try {
|
||||
// Migration functions are named like migrate_001, migrate_002 etc.
|
||||
$function_name = 'migrate_' . preg_replace('/[^0-9]/', '', $migration_name);
|
||||
if(function_exists($function_name)){
|
||||
$function_name($pdo);
|
||||
} else {
|
||||
// Fallback for older naming convention
|
||||
$function_name = str_replace('.php', '', basename($file));
|
||||
if(function_exists($function_name)) {
|
||||
$function_name($pdo);
|
||||
}
|
||||
}
|
||||
|
||||
// Record migration
|
||||
$stmt = $pdo->prepare("INSERT INTO migrations (migration) VALUES (?)");
|
||||
$stmt->execute([$migration_name]);
|
||||
echo "Migration $migration_name has been applied.\n";
|
||||
} catch (PDOException $e) {
|
||||
echo "Error running migration $migration_name: " . $e->getMessage() . "\n";
|
||||
}
|
||||
} else {
|
||||
echo "Migration $migration_name already applied.\n";
|
||||
}
|
||||
}
|
||||
|
||||
echo "All migrations have been run.\n";
|
||||
|
||||
} catch (PDOException $e) {
|
||||
die("Migration failed: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// If the script is run directly from the command line
|
||||
if (php_sapi_name() === 'cli') {
|
||||
run_migrations();
|
||||
}
|
||||
|
||||
|
||||
158
process_definitions.php
Normal file
158
process_definitions.php
Normal file
@ -0,0 +1,158 @@
|
||||
<?php
|
||||
require_once 'WorkflowEngine.php';
|
||||
|
||||
$workflowEngine = new WorkflowEngine();
|
||||
// TODO: Create a method in WorkflowEngine to get all process definitions
|
||||
$pdo = db();
|
||||
$stmt = $pdo->query("SELECT * FROM process_definitions ORDER BY name");
|
||||
$processes = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
?>
|
||||
<?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 class="d-flex justify-content-between align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2">Process Definitions</h1>
|
||||
<div class="btn-toolbar mb-2 mb-md-0">
|
||||
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#createProcessModal">
|
||||
Create Process
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-sm">
|
||||
<thead>
|
||||
<tr class="text-center">
|
||||
<th class="bg-light">Name</th>
|
||||
<th class="bg-light">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($processes as $process): ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($process['name']) ?></td>
|
||||
<td class="text-center">
|
||||
<button class="btn btn-sm btn-secondary edit-process-btn"
|
||||
data-process-id="<?= $process['id'] ?>"
|
||||
data-process-name="<?= htmlspecialchars($process['name']) ?>"
|
||||
data-process-definition='<?= htmlspecialchars($process['definition_json'] ?? '') ?>'>
|
||||
Edit
|
||||
</button>
|
||||
<!-- TODO: Add delete functionality -->
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create/Edit Process Modal -->
|
||||
<div class="modal fade" id="createProcessModal" tabindex="-1" aria-labelledby="createProcessModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="createProcessModalLabel">Create Process</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="createProcessForm" action="_save_process_definition.php" method="post">
|
||||
<input type="hidden" name="process_id" id="processId">
|
||||
<div class="mb-3">
|
||||
<label for="processName" class="form-label">Process Name</label>
|
||||
<input type="text" class="form-control" id="processName" name="name" required>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h5>Statuses</h5>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="initialStatus" class="form-label">Initial Status</label>
|
||||
<select class="form-select" id="initialStatus"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="statuses-list" class="mb-3"></div>
|
||||
<div class="input-group mb-3">
|
||||
<input type="text" id="newStatusInput" class="form-control" placeholder="New status name">
|
||||
<button class="btn btn-outline-secondary" type="button" id="addStatusBtn">Add Status</button>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h5>Transitions</h5>
|
||||
<div id="transitions-list" class="mb-3"></div>
|
||||
<div class="row">
|
||||
<div class="col-5">
|
||||
<select id="fromStatusSelect" class="form-select"></select>
|
||||
</div>
|
||||
<div class="col-2 text-center">=></div>
|
||||
<div class="col-5">
|
||||
<select id="toStatusSelect" class="form-select"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-grid gap-2 mt-3">
|
||||
<button class="btn btn-outline-secondary" type="button" id="addTransitionBtn">Add Transition</button>
|
||||
</div>
|
||||
|
||||
<textarea name="definition_json" id="definitionJson" class="d-none"></textarea>
|
||||
|
||||
<hr class="mt-4">
|
||||
|
||||
<button type="submit" class="btn btn-primary">Save Process</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php include '_footer.php'; ?>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const createProcessModal = document.getElementById('createProcessModal');
|
||||
const modalTitle = createProcessModal.querySelector('.modal-title');
|
||||
const form = createProcessModal.querySelector('#createProcessForm');
|
||||
const processIdInput = createProcessModal.querySelector('#processId');
|
||||
const processNameInput = createProcessModal.querySelector('#processName');
|
||||
const definitionJsonTextarea = createProcessModal.querySelector('#definitionJson');
|
||||
|
||||
// Handle create button click
|
||||
const createButton = document.querySelector('button[data-bs-target="#createProcessModal"]');
|
||||
createButton.addEventListener('click', function() {
|
||||
modalTitle.textContent = 'Create Process';
|
||||
form.action = '_save_process_definition.php';
|
||||
processIdInput.value = '';
|
||||
processNameInput.value = '';
|
||||
definitionJsonTextarea.value = '';
|
||||
});
|
||||
|
||||
// Handle edit button click
|
||||
document.querySelectorAll('.edit-process-btn').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const processId = this.dataset.processId;
|
||||
const processName = this.dataset.processName;
|
||||
const processDefinition = this.dataset.processDefinition;
|
||||
|
||||
modalTitle.textContent = 'Edit Process';
|
||||
form.action = '_save_process_definition.php';
|
||||
processIdInput.value = processId;
|
||||
processNameInput.value = processName;
|
||||
definitionJsonTextarea.value = processDefinition;
|
||||
|
||||
const modal = new bootstrap.Modal(createProcessModal);
|
||||
modal.show();
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
Loading…
x
Reference in New Issue
Block a user