Wersja bazowa

This commit is contained in:
Flatlogic Bot 2026-01-10 07:48:27 +00:00
parent 110c203f6a
commit 9234472371
62 changed files with 3829 additions and 146 deletions

263
WorkflowEngine.php Normal file
View 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 , 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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;

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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>

View 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
View 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
View 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;

View 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.']);

View 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
View 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;
}
?>

View 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
View 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();

View 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.']);

View 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
View 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
View 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
View 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
View 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
View 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">&lt; Previous</a>
<a href="?month=<?php echo $nextMonth; ?>&year=<?php echo $nextYear; ?>" class="btn btn-primary">Next &gt;</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';
?>

View 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";
}

View 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";
}

View 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");
}

View 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");
}
}

View 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);
}

View 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");
}

View 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");
}
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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
View 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
View 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
View 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>

712
index.php
View File

@ -1,150 +1,574 @@
<?php <?php
declare(strict_types=1); require_once 'WorkflowEngine.php';
@ini_set('display_errors', '1');
@error_reporting(E_ALL); $workflowEngine = new WorkflowEngine();
@date_default_timezone_set('UTC'); $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> <?php include '_header.php'; ?>
<html lang="en"> <?php include '_navbar.php'; ?>
<head>
<meta charset="utf-8" /> <div class="container-fluid">
<meta name="viewport" content="width=device-width, initial-scale=1" /> <div class="row">
<title>New Style</title> <?php include '_sidebar.php'; ?>
<?php
// Read project preview data from environment <main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? ''; <?php if (isset($_SESSION['success_message'])): ?>
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ''; <div class="alert alert-success alert-dismissible fade show mt-3" role="alert">
?> <?= $_SESSION['success_message']; ?>
<?php if ($projectDescription): ?> <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
<!-- 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> </div>
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p> <?php unset($_SESSION['success_message']); ?>
<p class="hint">This page will update automatically as the plan is implemented.</p> <script>
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p> 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) ?>">&nbsp;</span>
<small class="text-muted d-block"><?= $lastActivity ?></small>
</td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div> </div>
</main> </main>
<footer> </div>
Page updated: <?= htmlspecialchars($now) ?> (UTC) </div>
</footer>
</body> <!-- Edit Person Modal -->
</html> <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
View 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
View File

@ -0,0 +1,6 @@
<?php
session_start();
session_unset();
session_destroy();
header('Location: login.php');
exit;

62
migrate.php Normal file
View 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
View 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">=&gt;</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>

2
test.php Normal file
View File

@ -0,0 +1,2 @@
<?php
phpinfo();

16
test3.php Normal file
View File

@ -0,0 +1,16 @@
<?php
ini_set('display_errors', 1);
error_reporting(E_ALL);
session_start();
echo "session_start() called.<br>";
if (!isset($_SESSION['user_id'])) {
echo "user_id not set.<br>";
// header('Location: login.php');
// exit;
}
echo "end of test";
?>