Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2e9c01463 | ||
|
|
953eb569ce | ||
|
|
3776a03a8f | ||
|
|
6785f05d29 | ||
|
|
c6e4994eb6 | ||
|
|
c1f814ac1a | ||
|
|
dddb7eeaf5 | ||
|
|
6d001f8a7f | ||
|
|
e1227cfbcd | ||
|
|
44f71c32c4 | ||
|
|
4df1537bcb |
49
add_process.php
Normal file
49
add_process.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
// Basic validation
|
||||||
|
if (empty(trim($_POST['name'])) || empty(trim($_POST['description']))) {
|
||||||
|
// Redirect back with an error message if needed, but for now we just exit.
|
||||||
|
header('Location: index.php?error=emptyfields');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = trim($_POST['name']);
|
||||||
|
$description = trim($_POST['description']);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = db();
|
||||||
|
$sql = "INSERT INTO processes (name, description) VALUES (?, ?)";
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute([$name, $description]);
|
||||||
|
|
||||||
|
$process_id = $pdo->lastInsertId();
|
||||||
|
|
||||||
|
// Insert process steps if available
|
||||||
|
if (isset($_POST['steps']) && is_array($_POST['steps'])) {
|
||||||
|
$step_order = 0;
|
||||||
|
$stmt_steps = $pdo->prepare("INSERT INTO process_steps (process_id, title, description, step_order) VALUES (?, ?, ?, ?)");
|
||||||
|
foreach ($_POST['steps'] as $step) {
|
||||||
|
if (isset($step['title']) && isset($step['description'])) {
|
||||||
|
$stmt_steps->execute([$process_id, $step['title'], $step['description'], $step_order]);
|
||||||
|
$step_order++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
// In a real app, log this error. For now, we'll just die.
|
||||||
|
error_log("DB Error: " . $e->getMessage());
|
||||||
|
header('Location: index.php?error=dberror');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect back to the main page after successful insertion
|
||||||
|
header('Location: index.php?success=processadded');
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
// If not a POST request, redirect to the main page
|
||||||
|
header('Location: index.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
?>
|
||||||
33
api/generate_process_suggestion.php
Normal file
33
api/generate_process_suggestion.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../ai/LocalAIApi.php';
|
||||||
|
|
||||||
|
$keyword = $_GET['keyword'] ?? 'new process';
|
||||||
|
|
||||||
|
$prompt = "Generate a creative and concise name, a short description, and a list of detailed steps for a business process related to '" . htmlspecialchars($keyword) . "'. Each step should have a 'title' and a 'description'. Respond in JSON format with 'name', 'description', and 'steps' keys. Example: {"name": "Employee Onboarding Process", "description": "A systematic process to integrate new hires into the company.", "steps": [{"title": "Send welcome email", "description": "Automatically send a welcome email with company resources."}, {"title": "Setup IT access", "description": "Provision access to internal systems and tools."}]}";
|
||||||
|
|
||||||
|
$params = [
|
||||||
|
'input' => [
|
||||||
|
['role' => 'system', 'content' => 'You are a helpful assistant that generates process names and descriptions.'],
|
||||||
|
['role' => 'user', 'content' => $prompt],
|
||||||
|
],
|
||||||
|
'model' => 'gpt-5-mini', // Using the default model from ai/config.php
|
||||||
|
];
|
||||||
|
|
||||||
|
$response = LocalAIApi::createResponse($params);
|
||||||
|
|
||||||
|
if (!empty($response['success'])) {
|
||||||
|
$decoded = LocalAIApi::decodeJsonFromResponse($response);
|
||||||
|
if ($decoded && isset($decoded['name']) && isset($decoded['description']) && isset($decoded['steps'])) {
|
||||||
|
echo json_encode(['success' => true, 'data' => $decoded]);
|
||||||
|
} else {
|
||||||
|
// Fallback if AI didn't return valid JSON or missing keys
|
||||||
|
$text = LocalAIApi::extractText($response);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'AI response not in expected JSON format: ' . substr($text, 0, 200)]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$error_message = $response['error'] ?? 'Unknown AI error';
|
||||||
|
echo json_encode(['success' => false, 'error' => $error_message]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
62
assets/css/custom.css
Normal file
62
assets/css/custom.css
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
ProcessFlow Optimizer Custom Stylesheet
|
||||||
|
*/
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--bs-primary: #4A90E2;
|
||||||
|
--bs-primary-rgb: 74, 144, 226;
|
||||||
|
--bs-secondary: #50E3C2;
|
||||||
|
--bs-secondary-rgb: 80, 227, 194;
|
||||||
|
--bs-body-bg: #F8F9FA;
|
||||||
|
--bs-body-font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
--bs-border-radius: 0.5rem;
|
||||||
|
--bs-card-border-width: 0;
|
||||||
|
--bs-card-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: #212529;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: var(--bs-primary);
|
||||||
|
border-color: var(--bs-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
background-color: var(--bs-primary);
|
||||||
|
border-color: var(--bs-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(90deg, #4A90E2 0%, #63A4F7 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 2.5rem 0;
|
||||||
|
margin-bottom: -1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
font-weight: 700;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.header p {
|
||||||
|
color: rgba(255,255,255,0.8) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--bs-border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-group-item {
|
||||||
|
border-bottom: 1px solid #eee !important;
|
||||||
|
}
|
||||||
|
.list-group-item:last-child {
|
||||||
|
border-bottom: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus {
|
||||||
|
border-color: #80bdff;
|
||||||
|
box-shadow: 0 0 0 0.25rem rgba(var(--bs-primary-rgb), 0.25);
|
||||||
|
}
|
||||||
18
db/migrate.php
Normal file
18
db/migrate.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'config.php';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = db();
|
||||||
|
$sql = "
|
||||||
|
CREATE TABLE IF NOT EXISTS processes (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);";
|
||||||
|
$pdo->exec($sql);
|
||||||
|
echo "Database migration successful. 'processes' table is ready.";
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
die("Database migration failed: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
?>
|
||||||
9
db/migrations/20230102_create_process_steps_table.sql
Normal file
9
db/migrations/20230102_create_process_steps_table.sql
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS process_steps (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
process_id INT NOT NULL,
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
step_order INT NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (process_id) REFERENCES processes(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
38
delete_process.php
Normal file
38
delete_process.php
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['id'])) {
|
||||||
|
$id = filter_var($_POST['id'], FILTER_SANITIZE_NUMBER_INT);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = db();
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
// Delete associated steps first
|
||||||
|
$stmtSteps = $pdo->prepare("DELETE FROM process_steps WHERE process_id = :id");
|
||||||
|
$stmtSteps->bindParam(':id', $id, PDO::PARAM_INT);
|
||||||
|
$stmtSteps->execute();
|
||||||
|
|
||||||
|
// Then delete the process
|
||||||
|
$stmtProcess = $pdo->prepare("DELETE FROM processes WHERE id = :id");
|
||||||
|
$stmtProcess->bindParam(':id', $id, PDO::PARAM_INT);
|
||||||
|
if ($stmtProcess->execute()) {
|
||||||
|
$pdo->commit();
|
||||||
|
header('Location: index.php?success=processdeleted');
|
||||||
|
exit();
|
||||||
|
} else {
|
||||||
|
$pdo->rollBack();
|
||||||
|
header('Location: index.php?error=deletionfailed');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
error_log("DB Error: " . $e->getMessage());
|
||||||
|
header('Location: index.php?error=dberror');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
header('Location: index.php?error=invalidrequest');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
?>
|
||||||
173
edit_process.php
Normal file
173
edit_process.php
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
$process = null;
|
||||||
|
$error = '';
|
||||||
|
|
||||||
|
if (isset($_GET['id'])) {
|
||||||
|
$id = $_GET['id'];
|
||||||
|
try {
|
||||||
|
$pdo = db();
|
||||||
|
$stmt = $pdo->prepare("SELECT id, name, description FROM processes WHERE id = :id");
|
||||||
|
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||||
|
$stmt->execute();
|
||||||
|
$process = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($process) {
|
||||||
|
$stmt_steps = $pdo->prepare("SELECT id, title, description, step_order FROM process_steps WHERE process_id = :process_id ORDER BY step_order ASC");
|
||||||
|
$stmt_steps->bindParam(':process_id', $id, PDO::PARAM_INT);
|
||||||
|
$stmt_steps->execute();
|
||||||
|
$process['steps'] = $stmt_steps->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} else {
|
||||||
|
$error = "Process not found.";
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
error_log("DB Error: " . $e->getMessage());
|
||||||
|
$error = "Could not retrieve process details.";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$error = "No process ID provided.";
|
||||||
|
}
|
||||||
|
|
||||||
|
$project_name = htmlspecialchars($_SERVER['PROJECT_NAME'] ?? 'ProcessFlow Optimizer');
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Edit Process - <?php echo $project_name; ?></title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||||
|
<script src="https://unpkg.com/feather-icons"></script>
|
||||||
|
</head>
|
||||||
|
<body class="bg-light">
|
||||||
|
|
||||||
|
<header class="header">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="h3 mb-0"><?php echo $project_name; ?></h1>
|
||||||
|
<p class="text-muted mb-0">A business process analyzer and automated optimizer</p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="container py-5">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<?php if ($error): ?>
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
<?php echo $error; ?>
|
||||||
|
</div>
|
||||||
|
<a href="index.php" class="btn btn-primary">Go Back</a>
|
||||||
|
<?php elseif ($process): ?>
|
||||||
|
<h2 class="h4 card-title fw-bold">Edit Process: <?php echo htmlspecialchars($process['name']); ?></h2>
|
||||||
|
<p class="card-subtitle mb-4 text-muted">Modify the details of your process.</p>
|
||||||
|
<form action="update_process.php" method="POST">
|
||||||
|
<input type="hidden" name="id" value="<?php echo $process['id']; ?>">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="name" class="form-label">Process Name</label>
|
||||||
|
<input type="text" class="form-control" id="name" name="name" value="<?php echo htmlspecialchars($process['name']); ?>" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="description" class="form-label">Description</label>
|
||||||
|
<textarea class="form-control" id="description" name="description" rows="4" required><?php echo htmlspecialchars($process['description']); ?></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="my-4">
|
||||||
|
<h5 class="fw-bold mb-3">Process Steps</h5>
|
||||||
|
<div id="processStepsContainer">
|
||||||
|
<?php if (!empty($process['steps'])): ?>
|
||||||
|
<?php foreach ($process['steps'] as $index => $step): ?>
|
||||||
|
<div class="mb-3 p-3 border rounded bg-light position-relative process-step-item">
|
||||||
|
<input type="hidden" name="steps[<?php echo $index; ?>][id]" value="<?php echo htmlspecialchars($step['id']); ?>">
|
||||||
|
<div class="mb-2">
|
||||||
|
<label for="stepTitle_<?php echo $index; ?>" class="form-label fw-bold">Step <?php echo $index + 1; ?> Title</label>
|
||||||
|
<input type="text" class="form-control" id="stepTitle_<?php echo $index; ?>" name="steps[<?php echo $index; ?>][title]" value="<?php echo htmlspecialchars($step['title']); ?>" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="stepDescription_<?php echo $index; ?>" class="form-label">Step <?php echo $index + 1; ?> Description</label>
|
||||||
|
<textarea class="form-control" id="stepDescription_<?php echo $index; ?>" name="steps[<?php echo $index; ?>][description]" rows="2" required><?php echo htmlspecialchars($step['description']); ?></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-sm btn-danger position-absolute top-0 end-0 mt-2 me-2 remove-step-btn" title="Remove Step">
|
||||||
|
<i data-feather="x" style="width: 16px; height: 16px;"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<button type="button" id="addStepBtn" class="btn btn-outline-secondary btn-sm mb-4">
|
||||||
|
<i data-feather="plus" style="width: 16px; height: 16px;"></i> Add New Step
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary">Update Process</button>
|
||||||
|
<a href="index.php" class="btn btn-link">Cancel</a>
|
||||||
|
</form>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="text-center py-4 text-muted">
|
||||||
|
<p>© <?php echo date("Y"); ?> <?php echo $project_name; ?>. All rights reserved.</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
feather.replace()
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const processStepsContainer = document.getElementById('processStepsContainer');
|
||||||
|
const addStepBtn = document.getElementById('addStepBtn');
|
||||||
|
|
||||||
|
let stepIndex = processStepsContainer.children.length; // Start index after existing steps
|
||||||
|
|
||||||
|
function updateStepNumbers() {
|
||||||
|
Array.from(processStepsContainer.children).forEach((stepItem, index) => {
|
||||||
|
stepItem.querySelector('label[for^="stepTitle_"]').textContent = `Step ${index + 1} Title`;
|
||||||
|
stepItem.querySelector('label[for^="stepDescription_"]').textContent = `Step ${index + 1} Description`;
|
||||||
|
|
||||||
|
// Update name attributes for proper form submission
|
||||||
|
stepItem.querySelector('input[name$="[title]"]').name = `steps[${index}][title]`;
|
||||||
|
stepItem.querySelector('textarea[name$="[description]"]').name = `steps[${index}][description]`;
|
||||||
|
const hiddenIdInput = stepItem.querySelector('input[name$="[id]"]');
|
||||||
|
if (hiddenIdInput) {
|
||||||
|
hiddenIdInput.name = `steps[${index}][id]`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addStepBtn.addEventListener('click', function() {
|
||||||
|
const newStepHtml = `
|
||||||
|
<div class="mb-3 p-3 border rounded bg-light position-relative process-step-item">
|
||||||
|
<div class="mb-2">
|
||||||
|
<label for="stepTitle_${stepIndex}" class="form-label fw-bold">Step ${stepIndex + 1} Title</label>
|
||||||
|
<input type="text" class="form-control" id="stepTitle_${stepIndex}" name="steps[${stepIndex}][title]" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="stepDescription_${stepIndex}" class="form-label">Step ${stepIndex + 1} Description</label>
|
||||||
|
<textarea class="form-control" id="stepDescription_${stepIndex}" name="steps[${stepIndex}][description]" rows="2" required></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-sm btn-danger position-absolute top-0 end-0 mt-2 me-2 remove-step-btn" title="Remove Step">
|
||||||
|
<i data-feather="x" style="width: 16px; height: 16px;"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
processStepsContainer.insertAdjacentHTML('beforeend', newStepHtml);
|
||||||
|
feather.replace(); // Re-render feather icons for new button
|
||||||
|
stepIndex++;
|
||||||
|
updateStepNumbers();
|
||||||
|
});
|
||||||
|
|
||||||
|
processStepsContainer.addEventListener('click', function(event) {
|
||||||
|
if (event.target.closest('.remove-step-btn')) {
|
||||||
|
event.target.closest('.process-step-item').remove();
|
||||||
|
updateStepNumbers();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
539
index.php
539
index.php
@ -1,150 +1,407 @@
|
|||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
require_once 'db/config.php';
|
||||||
@ini_set('display_errors', '1');
|
|
||||||
@error_reporting(E_ALL);
|
|
||||||
@date_default_timezone_set('UTC');
|
|
||||||
|
|
||||||
$phpVersion = PHP_VERSION;
|
// Fetch all processes from the database
|
||||||
$now = date('Y-m-d H:i:s');
|
// Pagination settings
|
||||||
|
$items_per_page = 5;
|
||||||
|
$current_page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
|
||||||
|
$offset = ($current_page - 1) * $items_per_page;
|
||||||
|
|
||||||
|
$processes = [];
|
||||||
|
$search_query = $_GET['search'] ?? '';
|
||||||
|
$total_processes = 0;
|
||||||
|
|
||||||
|
// Sorting settings
|
||||||
|
$allowed_sort_columns = ['name', 'created_at'];
|
||||||
|
$sort_by = $_GET['sort_by'] ?? 'created_at';
|
||||||
|
$sort_order = $_GET['sort_order'] ?? 'DESC';
|
||||||
|
|
||||||
|
// Validate sort_by column
|
||||||
|
if (!in_array($sort_by, $allowed_sort_columns)) {
|
||||||
|
$sort_by = 'created_at'; // Default to created_at if invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate sort_order
|
||||||
|
$sort_order = strtoupper($sort_order);
|
||||||
|
if (!in_array($sort_order, ['ASC', 'DESC'])) {
|
||||||
|
$sort_order = 'DESC'; // Default to DESC if invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = db();
|
||||||
|
|
||||||
|
// First, get the total number of processes (with search filter if applied)
|
||||||
|
$count_sql = "SELECT COUNT(*) FROM processes";
|
||||||
|
if (!empty($search_query)) {
|
||||||
|
$count_sql .= " WHERE name LIKE :search_query OR description LIKE :search_query";
|
||||||
|
}
|
||||||
|
$count_stmt = $pdo->prepare($count_sql);
|
||||||
|
if (!empty($search_query)) {
|
||||||
|
$count_stmt->bindValue(':search_query', '%' . $search_query . '%', PDO::PARAM_STR);
|
||||||
|
}
|
||||||
|
$count_stmt->execute();
|
||||||
|
$total_processes = $count_stmt->fetchColumn();
|
||||||
|
|
||||||
|
// Then, fetch the processes for the current page (with search filter and pagination)
|
||||||
|
$sql = "SELECT id, name, description, created_at FROM processes";
|
||||||
|
if (!empty($search_query)) {
|
||||||
|
$sql .= " WHERE name LIKE :search_query OR description LIKE :search_query";
|
||||||
|
}
|
||||||
|
$sql .= " ORDER BY ".$sort_by." ".$sort_order." LIMIT :limit OFFSET :offset";
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
|
||||||
|
if (!empty($search_query)) {
|
||||||
|
$stmt->bindValue(':search_query', '%' . $search_query . '%', PDO::PARAM_STR);
|
||||||
|
}
|
||||||
|
$stmt->bindValue(':limit', $items_per_page, PDO::PARAM_INT);
|
||||||
|
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
||||||
|
$stmt->execute();
|
||||||
|
$processes = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
// Silently log error, or display a friendly message.
|
||||||
|
error_log("DB Error: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
$total_pages = ceil($total_processes / $items_per_page);
|
||||||
|
|
||||||
|
|
||||||
|
$project_name = htmlspecialchars($_SERVER['PROJECT_NAME'] ?? 'ProcessFlow Optimizer');
|
||||||
|
$project_description = htmlspecialchars($_SERVER['PROJECT_DESCRIPTION'] ?? 'Analyze and optimize your business processes.');
|
||||||
|
$project_image_url = htmlspecialchars($_SERVER['PROJECT_IMAGE_URL'] ?? '');
|
||||||
?>
|
?>
|
||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>New Style</title>
|
<title><?php echo $project_name; ?></title>
|
||||||
<?php
|
<meta name="description" content="<?php echo $project_description; ?>">
|
||||||
// Read project preview data from environment
|
<!-- Open Graph / Twitter Meta Tags -->
|
||||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
<meta property="og:title" content="<?php echo $project_name; ?>">
|
||||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
<meta property="og:description" content="<?php echo $project_description; ?>">
|
||||||
?>
|
<meta property="og:image" content="<?php echo $project_image_url; ?>">
|
||||||
<?php if ($projectDescription): ?>
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
<!-- Meta description -->
|
<meta name="twitter:title" content="<?php echo $project_name; ?>">
|
||||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
<meta name="twitter:description" content="<?php echo $project_description; ?>">
|
||||||
<!-- Open Graph meta tags -->
|
<meta name="twitter:image" content="<?php echo $project_image_url; ?>">
|
||||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
|
||||||
<!-- Twitter meta tags -->
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||||
<?php endif; ?>
|
<script src="https://unpkg.com/feather-icons"></script>
|
||||||
<?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>
|
</head>
|
||||||
<body>
|
<body class="bg-light">
|
||||||
<main>
|
<div id="message-area" class="container mt-4">
|
||||||
<div class="card">
|
<?php
|
||||||
<h1>Analyzing your requirements and generating your website…</h1>
|
if (isset($_GET['success'])) {
|
||||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
$message = '';
|
||||||
<span class="sr-only">Loading…</span>
|
switch ($_GET['success']) {
|
||||||
</div>
|
case 'processadded':
|
||||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
$message = 'Process added successfully!';
|
||||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
break;
|
||||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
case 'processupdated':
|
||||||
|
$message = 'Process updated successfully!';
|
||||||
|
break;
|
||||||
|
case 'processdeleted':
|
||||||
|
$message = 'Process deleted successfully!';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$message = 'Action successful!';
|
||||||
|
}
|
||||||
|
echo '<div class="alert alert-success alert-dismissible fade show" role="alert">' . $message . '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_GET['error'])) {
|
||||||
|
$message = '';
|
||||||
|
switch ($_GET['error']) {
|
||||||
|
case 'emptyfields':
|
||||||
|
$message = 'Please fill in all fields.';
|
||||||
|
break;
|
||||||
|
case 'dberror':
|
||||||
|
$message = 'A database error occurred. Please try again.';
|
||||||
|
break;
|
||||||
|
case 'deletionfailed':
|
||||||
|
$message = 'Failed to delete process. Please try again.';
|
||||||
|
break;
|
||||||
|
case 'invalidrequest':
|
||||||
|
$message = 'Invalid request.';
|
||||||
|
break;
|
||||||
|
case 'processnotfound':
|
||||||
|
$message = 'Process not found.';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$message = 'An error occurred. Please try again.';
|
||||||
|
}
|
||||||
|
echo '<div class="alert alert-danger alert-dismissible fade show" role="alert">' . $message . '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
|
||||||
<footer>
|
<header class="header">
|
||||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
<div class="container">
|
||||||
</footer>
|
<h1 class="h3 mb-0"><?php echo $project_name; ?></h1>
|
||||||
|
<p class="text-muted mb-0">A business process analyzer and automated optimizer</p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="container py-5">
|
||||||
|
<div class="row g-5">
|
||||||
|
<!-- Left Column: Form -->
|
||||||
|
<div class="col-lg-5">
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<h2 class="h4 card-title fw-bold">Map a New Process</h2>
|
||||||
|
<p class="card-subtitle mb-4 text-muted">Define a process to begin analysis.</p>
|
||||||
|
<form action="add_process.php" method="POST" id="addProcessForm">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="name" class="form-label">Process Name</label>
|
||||||
|
<input type="text" class="form-control" id="name" name="name" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="description" class="form-label">Description</label>
|
||||||
|
<textarea class="form-control" id="description" name="description" rows="4" required></textarea>
|
||||||
|
</div>
|
||||||
|
<div id="aiGeneratedSteps" class="mb-3 border p-3 rounded" style="display:none;">
|
||||||
|
<h6>AI Suggested Steps:</h6>
|
||||||
|
<div id="stepsContainer"></div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 text-end">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary" id="aiSuggestBtn">
|
||||||
|
<span id="aiSuggestSpinner" class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
|
||||||
|
Suggest with AI
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary w-100">Add Process</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right Column: Process List -->
|
||||||
|
<div class="col-lg-7">
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<h2 class="h4 card-title fw-bold">Existing Processes</h2>
|
||||||
|
<form action="index.php" method="GET" class="mb-4">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control" placeholder="Search by name or description" name="search" value="<?php echo htmlspecialchars($_GET['search'] ?? ''); ?>">
|
||||||
|
<button class="btn btn-outline-secondary" type="submit" id="button-search">Search</button>
|
||||||
|
<?php if (isset($_GET['search'])): ?>
|
||||||
|
<a href="index.php" class="btn btn-outline-secondary" type="button">Clear</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<?php if (empty($processes)): ?>
|
||||||
|
<div class="text-center py-5">
|
||||||
|
<i data-feather="search" class="text-muted mb-3" style="width: 48px; height: 48px;"></i>
|
||||||
|
<p class="text-muted">No processes defined yet. <br> Use the form to add your first process.</p>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="list-group list-group-flush">
|
||||||
|
<div class="list-group-item px-0 d-flex justify-content-between align-items-center bg-light fw-bold">
|
||||||
|
<div class="col-4">
|
||||||
|
<a href="?<?php echo http_build_query(array_merge($_GET, ['sort_by' => 'name', 'sort_order' => ($sort_by == 'name' && $sort_order == 'ASC') ? 'DESC' : 'ASC'])); ?>" class="text-decoration-none text-dark">
|
||||||
|
Process Name
|
||||||
|
<?php if ($sort_by == 'name'): ?>
|
||||||
|
<i data-feather="<?php echo ($sort_order == 'ASC') ? 'arrow-up' : 'arrow-down'; ?>" style="width: 14px; height: 14px;"></i>
|
||||||
|
<?php endif; ?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-5">Description</div>
|
||||||
|
<div class="col-3 text-end">
|
||||||
|
<a href="?<?php echo http_build_query(array_merge($_GET, ['sort_by' => 'created_at', 'sort_order' => ($sort_by == 'created_at' && $sort_order == 'ASC') ? 'DESC' : 'ASC'])); ?>" class="text-decoration-none text-dark">
|
||||||
|
Created At
|
||||||
|
<?php if ($sort_by == 'created_at'): ?>
|
||||||
|
<i data-feather="<?php echo ($sort_order == 'ASC') ? 'arrow-up' : 'arrow-down'; ?>" style="width: 14px; height: 14px;"></i>
|
||||||
|
<?php endif; ?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php foreach ($processes as $process): ?>
|
||||||
|
<div class="list-group-item px-0">
|
||||||
|
<div class="d-flex w-100 justify-content-between align-items-start">
|
||||||
|
<div>
|
||||||
|
<h5 class="mb-1 fw-bold"><?php echo htmlspecialchars($process['name']); ?></h5>
|
||||||
|
<small class="text-muted"><?php echo date("M d, Y", strtotime($process['created_at'])); ?></small>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<a href="view_process.php?id=<?php echo $process['id']; ?>" class="btn btn-sm btn-outline-info me-2" title="View Process">
|
||||||
|
<i data-feather="eye" style="width: 16px; height: 16px;"></i>
|
||||||
|
</a>
|
||||||
|
<form action="edit_process.php" method="GET" class="me-2">
|
||||||
|
<input type="hidden" name="id" value="<?php echo $process['id']; ?>">
|
||||||
|
<button type="submit" class="btn btn-sm btn-outline-primary" title="Edit Process">
|
||||||
|
<i data-feather="edit" style="width: 16px; height: 16px;"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<form id="deleteForm-<?php echo $process['id']; ?>" action="delete_process.php" method="POST" style="display:none;">
|
||||||
|
<input type="hidden" name="id" value="<?php echo $process['id']; ?>">
|
||||||
|
</form>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-danger" title="Delete Process" data-bs-toggle="modal" data-bs-target="#confirmDeleteModal" data-bs-id="<?php echo $process['id']; ?>">
|
||||||
|
<i data-feather="trash-2" style="width: 16px; height: 16px;"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="mb-1 text-muted"><?php echo htmlspecialchars($process['description']); ?></p>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php if ($total_pages > 1): ?>
|
||||||
|
<nav aria-label="Page navigation">
|
||||||
|
<ul class="pagination justify-content-center mt-4">
|
||||||
|
<li class="page-item <?php echo ($current_page <= 1) ? 'disabled' : ''; ?>">
|
||||||
|
<a class="page-link" href="?<?php echo http_build_query(array_merge($_GET, ['page' => $current_page - 1])); ?>" tabindex="-1" aria-disabled="true">Previous</a>
|
||||||
|
</li>
|
||||||
|
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
|
||||||
|
<li class="page-item <?php echo ($current_page == $i) ? 'active' : ''; ?>">
|
||||||
|
<a class="page-link" href="?<?php echo http_build_query(array_merge($_GET, ['page' => $i])); ?>"><?php echo $i; ?></a>
|
||||||
|
</li>
|
||||||
|
<?php endfor; ?>
|
||||||
|
<li class="page-item <?php echo ($current_page >= $total_pages) ? 'disabled' : ''; ?>">
|
||||||
|
<a class="page-link" href="?<?php echo http_build_query(array_merge($_GET, ['page' => $current_page + 1])); ?>">Next</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="text-center py-4 text-muted">
|
||||||
|
<p>© <?php echo date("Y"); ?> <?php echo $project_name; ?>. All rights reserved.</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<div class="modal fade" id="confirmDeleteModal" tabindex="-1" aria-labelledby="confirmDeleteModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="confirmDeleteModalLabel">Confirm Deletion</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 process? This action cannot be undone.
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="button" class="btn btn-danger" id="confirmDeleteBtn">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
function htmlspecialchars(str) {
|
||||||
|
var map = {
|
||||||
|
'&': '&',
|
||||||
|
'<': '<',
|
||||||
|
'>': '>',
|
||||||
|
'"': '"',
|
||||||
|
''': '''
|
||||||
|
};
|
||||||
|
return str.replace(/[&<>'"']/g, function(m) { return map[m]; });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client-side validation for the Add Process form
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
var form = document.getElementById('addProcessForm');
|
||||||
|
if (form) {
|
||||||
|
form.addEventListener('submit', function(event) {
|
||||||
|
if (!form.checkValidity()) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
form.classList.add('was-validated');
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
feather.replace()
|
||||||
|
|
||||||
|
// AI Suggestion Feature
|
||||||
|
const aiSuggestBtn = document.getElementById('aiSuggestBtn');
|
||||||
|
const aiSuggestSpinner = document.getElementById('aiSuggestSpinner');
|
||||||
|
const processNameInput = document.getElementById('name');
|
||||||
|
const processDescriptionInput = document.getElementById('description');
|
||||||
|
const messageArea = document.getElementById('message-area');
|
||||||
|
|
||||||
|
if (aiSuggestBtn) {
|
||||||
|
aiSuggestBtn.addEventListener('click', async () => {
|
||||||
|
aiSuggestBtn.disabled = true;
|
||||||
|
aiSuggestSpinner.classList.remove('d-none');
|
||||||
|
messageArea.innerHTML = ''; // Clear previous messages
|
||||||
|
|
||||||
|
const keyword = processNameInput.value.trim();
|
||||||
|
const queryParam = keyword ? `?keyword=${encodeURIComponent(keyword)}` : '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`api/generate_process_suggestion.php${queryParam}`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
processNameInput.value = data.data.name;
|
||||||
|
processDescriptionInput.value = data.data.description;
|
||||||
|
|
||||||
|
const aiGeneratedStepsDiv = document.getElementById('aiGeneratedSteps');
|
||||||
|
const stepsContainer = document.getElementById('stepsContainer');
|
||||||
|
stepsContainer.innerHTML = ''; // Clear previous steps
|
||||||
|
|
||||||
|
if (data.data.steps && data.data.steps.length > 0) {
|
||||||
|
aiGeneratedStepsDiv.style.display = 'block';
|
||||||
|
data.data.steps.forEach((step, index) => {
|
||||||
|
const stepHtml = `
|
||||||
|
<div class="mb-3 p-2 border rounded bg-light">
|
||||||
|
<div class="mb-2">
|
||||||
|
<label for="stepTitle_${index}" class="form-label fw-bold">Step ${index + 1} Title</label>
|
||||||
|
<input type="text" class="form-control" id="stepTitle_${index}" name="steps[${index}][title]" value="${htmlspecialchars(step.title)}" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="stepDescription_${index}" class="form-label">Step ${index + 1} Description</label>
|
||||||
|
<textarea class="form-control" id="stepDescription_${index}" name="steps[${index}][description]" rows="2" required>${htmlspecialchars(step.description)}</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
stepsContainer.insertAdjacentHTML('beforeend', stepHtml);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
aiGeneratedStepsDiv.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
messageArea.innerHTML = '<div class="alert alert-success alert-dismissible fade show" role="alert">AI suggestion generated successfully!<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
|
||||||
|
} else {
|
||||||
|
messageArea.innerHTML = `<div class="alert alert-danger alert-dismissible fade show" role="alert">Failed to get AI suggestion: ${data.error}<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching AI suggestion:', error);
|
||||||
|
messageArea.innerHTML = '<div class="alert alert-danger alert-dismissible fade show" role="alert">An error occurred while fetching AI suggestion. Please try again.<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
|
||||||
|
} finally {
|
||||||
|
aiSuggestBtn.disabled = false;
|
||||||
|
aiSuggestSpinner.classList.add('d-none');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
// JavaScript for handling the delete confirmation modal
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
var confirmDeleteModal = document.getElementById('confirmDeleteModal');
|
||||||
|
var confirmDeleteBtn = document.getElementById('confirmDeleteBtn');
|
||||||
|
|
||||||
|
confirmDeleteModal.addEventListener('show.bs.modal', function (event) {
|
||||||
|
// Button that triggered the modal
|
||||||
|
var button = event.relatedTarget;
|
||||||
|
// Extract info from data-bs-id attributes
|
||||||
|
var processId = button.getAttribute('data-bs-id');
|
||||||
|
|
||||||
|
// Update the modal's delete button to point to the correct delete_process.php with the processId
|
||||||
|
confirmDeleteBtn.onclick = function() {
|
||||||
|
document.getElementById('deleteForm-' + processId).submit();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
83
update_process.php
Normal file
83
update_process.php
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$id = $_POST['id'] ?? null;
|
||||||
|
$name = $_POST['name'] ?? null;
|
||||||
|
$description = $_POST['description'] ?? null;
|
||||||
|
|
||||||
|
if ($id && $name && $description) {
|
||||||
|
try {
|
||||||
|
$pdo = db();
|
||||||
|
$stmt = $pdo->prepare("UPDATE processes SET name = :name, description = :description WHERE id = :id");
|
||||||
|
$stmt->bindParam(':name', $name, PDO::PARAM_STR);
|
||||||
|
$stmt->bindParam(':description', $description, PDO::PARAM_STR);
|
||||||
|
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
// Handle process steps
|
||||||
|
$submitted_steps = $_POST['steps'] ?? [];
|
||||||
|
$existing_step_ids = [];
|
||||||
|
|
||||||
|
// Fetch existing steps to identify deletions
|
||||||
|
$stmt_fetch_existing = $pdo->prepare("SELECT id FROM process_steps WHERE process_id = :process_id");
|
||||||
|
$stmt_fetch_existing->bindParam(':process_id', $id, PDO::PARAM_INT);
|
||||||
|
$stmt_fetch_existing->execute();
|
||||||
|
$db_existing_step_ids = $stmt_fetch_existing->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
|
$steps_to_keep_ids = [];
|
||||||
|
|
||||||
|
foreach ($submitted_steps as $order => $step) {
|
||||||
|
$step_id = $step['id'] ?? null;
|
||||||
|
$step_title = $step['title'] ?? '';
|
||||||
|
$step_description = $step['description'] ?? '';
|
||||||
|
|
||||||
|
if (!empty($step_title) && !empty($step_description)) {
|
||||||
|
if ($step_id) {
|
||||||
|
// Update existing step
|
||||||
|
$stmt_update_step = $pdo->prepare("UPDATE process_steps SET title = :title, description = :description, step_order = :step_order WHERE id = :id AND process_id = :process_id");
|
||||||
|
$stmt_update_step->bindParam(':title', $step_title, PDO::PARAM_STR);
|
||||||
|
$stmt_update_step->bindParam(':description', $step_description, PDO::PARAM_STR);
|
||||||
|
$stmt_update_step->bindParam(':step_order', $order, PDO::PARAM_INT);
|
||||||
|
$stmt_update_step->bindParam(':id', $step_id, PDO::PARAM_INT);
|
||||||
|
$stmt_update_step->bindParam(':process_id', $id, PDO::PARAM_INT);
|
||||||
|
$stmt_update_step->execute();
|
||||||
|
$steps_to_keep_ids[] = $step_id;
|
||||||
|
} else {
|
||||||
|
// Insert new step
|
||||||
|
$stmt_insert_step = $pdo->prepare("INSERT INTO process_steps (process_id, title, description, step_order) VALUES (:process_id, :title, :description, :step_order)");
|
||||||
|
$stmt_insert_step->bindParam(':process_id', $id, PDO::PARAM_INT);
|
||||||
|
$stmt_insert_step->bindParam(':title', $step_title, PDO::PARAM_STR);
|
||||||
|
$stmt_insert_step->bindParam(':description', $step_description, PDO::PARAM_STR);
|
||||||
|
$stmt_insert_step->bindParam(':step_order', $order, PDO::PARAM_INT);
|
||||||
|
$stmt_insert_step->execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete steps that were removed from the form
|
||||||
|
$steps_to_delete = array_diff($db_existing_step_ids, $steps_to_keep_ids);
|
||||||
|
if (!empty($steps_to_delete)) {
|
||||||
|
$placeholders = implode(',', array_fill(0, count($steps_to_delete), '?'));
|
||||||
|
$stmt_delete_steps = $pdo->prepare("DELETE FROM process_steps WHERE process_id = ? AND id IN ($placeholders)");
|
||||||
|
$stmt_delete_steps->execute(array_merge([$id], $steps_to_delete));
|
||||||
|
}
|
||||||
|
header('Location: index.php?success=processupdated');
|
||||||
|
exit();
|
||||||
|
} else {
|
||||||
|
header('Location: index.php?error=updatefailed');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
error_log("DB Error: " . $e->getMessage());
|
||||||
|
header('Location: index.php?error=dberror');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
header('Location: index.php?error=emptyfields');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
header('Location: index.php?error=invalidrequest');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
181
view_process.php
Normal file
181
view_process.php
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
$process = null;
|
||||||
|
$error_message = '';
|
||||||
|
|
||||||
|
if (isset($_GET['id']) && is_numeric($_GET['id'])) {
|
||||||
|
$id = (int)$_GET['id'];
|
||||||
|
try {
|
||||||
|
$pdo = db();
|
||||||
|
$stmt = $pdo->prepare("SELECT id, name, description, created_at FROM processes WHERE id = :id");
|
||||||
|
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||||
|
$stmt->execute();
|
||||||
|
$process = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($process) {
|
||||||
|
$stmt_steps = $pdo->prepare("SELECT title, description FROM process_steps WHERE process_id = :process_id ORDER BY step_order ASC");
|
||||||
|
$stmt_steps->bindParam(':process_id', $id, PDO::PARAM_INT);
|
||||||
|
$stmt_steps->execute();
|
||||||
|
$process['steps'] = $stmt_steps->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} else {
|
||||||
|
$error_message = 'Process not found.';
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
error_log("DB Error: " . $e->getMessage());
|
||||||
|
$error_message = 'A database error occurred. Please try again later.';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$error_message = 'Invalid process ID.';
|
||||||
|
}
|
||||||
|
|
||||||
|
$page_title = $process ? htmlspecialchars($process['name']) . ' Details' : 'Process Details';
|
||||||
|
$page_description = $process ? 'Details for process: ' . htmlspecialchars($process['name']) . '. ' . htmlspecialchars($process['description']) : 'View details of a specific process.';
|
||||||
|
|
||||||
|
$project_name = htmlspecialchars($_SERVER['PROJECT_NAME'] ?? 'ProcessFlow Optimizer');
|
||||||
|
$project_description = htmlspecialchars($_SERVER['PROJECT_DESCRIPTION'] ?? 'Analyze and optimize your business processes.');
|
||||||
|
$project_image_url = htmlspecialchars($_SERVER['PROJECT_IMAGE_URL'] ?? '');
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title><?php echo $page_title; ?></title>
|
||||||
|
<meta name="description" content="<?php echo $page_description; ?>">
|
||||||
|
<!-- Open Graph / Twitter Meta Tags -->
|
||||||
|
<meta property="og:title" content="<?php echo $page_title; ?>">
|
||||||
|
<meta property="og:description" content="<?php echo $page_description; ?>">
|
||||||
|
<meta property="og:image" content="<?php echo $project_image_url; ?>">
|
||||||
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
|
<meta name="twitter:title" content="<?php echo $page_title; ?>">
|
||||||
|
<meta name="twitter:description" content="<?php echo $page_description; ?>">
|
||||||
|
<meta name="twitter:image" content="<?php echo $project_image_url; ?>">
|
||||||
|
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||||
|
<script src="https://unpkg.com/feather-icons"></script>
|
||||||
|
</head>
|
||||||
|
<body class="bg-light">
|
||||||
|
<header class="header">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="h3 mb-0"><?php echo $project_name; ?></h1>
|
||||||
|
<p class="text-muted mb-0">A business process analyzer and automated optimizer</p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="container py-5">
|
||||||
|
<div class="card shadow-sm mb-4">
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<h2 class="h4 card-title fw-bold mb-0"><?php echo $page_title; ?></h2>
|
||||||
|
<a href="index.php" class="btn btn-outline-secondary btn-sm">
|
||||||
|
<i data-feather="arrow-left" style="width: 16px; height: 16px;"></i> Back to List
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($error_message): ?>
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
<?php echo htmlspecialchars($error_message); ?>
|
||||||
|
</div>
|
||||||
|
<?php elseif ($process): ?>
|
||||||
|
<dl class="row">
|
||||||
|
<dt class="col-sm-3">ID:</dt>
|
||||||
|
<dd class="col-sm-9"><?php echo htmlspecialchars($process['id']); ?></dd>
|
||||||
|
|
||||||
|
<dt class="col-sm-3">Name:</dt>
|
||||||
|
<dd class="col-sm-9"><?php echo htmlspecialchars($process['name']); ?></dd>
|
||||||
|
|
||||||
|
<dt class="col-sm-3">Description:</dt>
|
||||||
|
<dd class="col-sm-9"><?php echo nl2br(htmlspecialchars($process['description'])); ?></dd>
|
||||||
|
|
||||||
|
<dt class="col-sm-3">Created At:</dt>
|
||||||
|
<dd class="col-sm-9"><?php echo date("M d, Y H:i:s", strtotime($process['created_at'])); ?></dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<?php if (!empty($process['steps'])): ?>
|
||||||
|
<h5 class="mt-4">Process Steps:</h5>
|
||||||
|
<ul class="list-group mb-4">
|
||||||
|
<?php foreach ($process['steps'] as $index => $step): ?>
|
||||||
|
<li class="list-group-item">
|
||||||
|
<h6 class="mb-1">Step <?php echo $index + 1; ?>: <?php echo htmlspecialchars($step['title']); ?></h6>
|
||||||
|
<p class="mb-0 text-muted"><?php echo nl2br(htmlspecialchars($step['description'])); ?></p>
|
||||||
|
</li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="mt-4 text-end">
|
||||||
|
<a href="edit_process.php?id=<?php echo $process['id']; ?>" class="btn btn-primary me-2">
|
||||||
|
<i data-feather="edit" style="width: 16px; height: 16px;"></i> Edit Process
|
||||||
|
</a>
|
||||||
|
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#confirmDeleteModal" data-bs-id="<?php echo $process['id']; ?>">
|
||||||
|
<i data-feather="trash-2" style="width: 16px; height: 16px;"></i> Delete Process
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="text-center py-4 text-muted">
|
||||||
|
<p>© <?php echo date("Y"); ?> <?php echo $project_name; ?>. All rights reserved.</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<!-- Delete Confirmation Modal (identical to index.php for consistency) -->
|
||||||
|
<div class="modal fade" id="confirmDeleteModal" tabindex="-1" aria-labelledby="confirmDeleteModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="confirmDeleteModalLabel">Confirm Deletion</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 process? This action cannot be undone.
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="button" class="btn btn-danger" id="confirmDeleteBtn">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
feather.replace()
|
||||||
|
|
||||||
|
// JavaScript for handling the delete confirmation modal
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
var confirmDeleteModal = document.getElementById('confirmDeleteModal');
|
||||||
|
var confirmDeleteBtn = document.getElementById('confirmDeleteBtn');
|
||||||
|
|
||||||
|
confirmDeleteModal.addEventListener('show.bs.modal', function (event) {
|
||||||
|
// Button that triggered the modal
|
||||||
|
var button = event.relatedTarget;
|
||||||
|
// Extract info from data-bs-id attributes
|
||||||
|
var processId = button.getAttribute('data-bs-id');
|
||||||
|
|
||||||
|
// Update the modal's delete button to point to the correct delete_process.php with the processId
|
||||||
|
confirmDeleteBtn.onclick = function() {
|
||||||
|
// Create a form dynamically to submit POST request
|
||||||
|
var form = document.createElement('form');
|
||||||
|
form.setAttribute('method', 'POST');
|
||||||
|
form.setAttribute('action', 'delete_process.php');
|
||||||
|
|
||||||
|
var hiddenField = document.createElement('input');
|
||||||
|
hiddenField.setAttribute('type', 'hidden');
|
||||||
|
hiddenField.setAttribute('name', 'id');
|
||||||
|
hiddenField.setAttribute('value', processId);
|
||||||
|
|
||||||
|
form.appendChild(hiddenField);
|
||||||
|
document.body.appendChild(form);
|
||||||
|
form.submit();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user