Compare commits

...

1 Commits

Author SHA1 Message Date
Flatlogic Bot
6908213cbc init 2026-02-15 21:17:22 +00:00
10 changed files with 673 additions and 128 deletions

26
add_log.php Normal file
View File

@ -0,0 +1,26 @@
<?php
session_start();
require_once __DIR__ . '/cls/class.pef.php';
if (!isset($_SESSION['user'])) {
header('Location: login.php');
exit;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$pef = new Pef();
$projectId = (int)($_POST['project_id'] ?? 0);
$version = $_POST['version'] ?? '';
$logEntry = $_POST['log_entry'] ?? '';
$author = $_SESSION['user']['full_name'] ?? 'System';
if ($projectId && $version && $logEntry) {
$pef->addLog($projectId, $version, $logEntry, $author);
}
header("Location: index.php?view=projects&project_id=$projectId");
exit;
}
header('Location: index.php');
exit;

137
cls/class.pef.php Normal file
View File

@ -0,0 +1,137 @@
<?php
require_once __DIR__ . '/../db/config.php';
class Pef {
private $db;
public function __construct() {
$this->db = db();
}
/**
* Get all projects, optionally filtered by industry or status.
*/
public function getProjects(?string $industry = null, ?string $status = null): array {
$sql = "SELECT * FROM projects WHERE 1=1";
$params = [];
if ($industry) {
$sql .= " AND industry = :industry";
$params['industry'] = $industry;
}
if ($status) {
$sql .= " AND status = :status";
$params['status'] = $status;
}
$sql .= " ORDER BY updated_at DESC";
$stmt = $this->db->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll();
}
/**
* User authentication.
*/
public function login(string $username, string $password): ?array {
$stmt = $this->db->prepare("SELECT * FROM users WHERE username = :username");
$stmt->execute(['username' => $username]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
unset($user['password']);
return $user;
}
return null;
}
/**
* Update project status or details.
*/
public function updateProject(int $id, array $data): bool {
$fields = [];
foreach ($data as $key => $value) {
$fields[] = "$key = :$key";
}
$sql = "UPDATE projects SET " . implode(', ', $fields) . " WHERE id = :id";
$data['id'] = $id;
$stmt = $this->db->prepare($sql);
return $stmt->execute($data);
}
/**
* Delete project.
*/
public function deleteProject(int $id): bool {
$stmt = $this->db->prepare("DELETE FROM projects WHERE id = :id");
return $stmt->execute(['id' => $id]);
}
/**
* Dashboard stats.
*/
public function getStats(): array {
$stats = [];
$stats['total_projects'] = $this->db->query("SELECT COUNT(*) FROM projects")->fetchColumn();
$stats['active_projects'] = $this->db->query("SELECT COUNT(*) FROM projects WHERE status = 'active'")->fetchColumn();
$stats['latest_logs'] = $this->db->query("SELECT pl.*, p.name as project_name FROM project_logs pl JOIN projects p ON pl.project_id = p.id ORDER BY pl.created_at DESC LIMIT 5")->fetchAll();
return $stats;
}
/**
* Get a single project by ID.
*/
public function getProject(int $id): ?array {
$stmt = $this->db->prepare("SELECT * FROM projects WHERE id = :id");
$stmt->execute(['id' => $id]);
return $stmt->fetch() ?: null;
}
/**
* Get all logs for a specific project.
*/
public function getProjectLogs(int $projectId): array {
$stmt = $this->db->prepare("SELECT * FROM project_logs WHERE project_id = :project_id ORDER BY created_at DESC");
$stmt->execute(['project_id' => $projectId]);
return $stmt->fetchAll();
}
/**
* Create a new project.
*/
public function createProject(string $name, string $description, string $industry, string $version = '1.0.0'): int {
$stmt = $this->db->prepare("INSERT INTO projects (name, description, industry, current_version) VALUES (:name, :description, :industry, :version)");
$stmt->execute([
'name' => $name,
'description' => $description,
'industry' => $industry,
'version' => $version
]);
return (int)$this->db->lastInsertId();
}
/**
* Add a log entry for a project and update its current version.
*/
public function addLog(int $projectId, string $version, string $logEntry, string $author = 'System'): bool {
try {
$this->db->beginTransaction();
$stmt = $this->db->prepare("INSERT INTO project_logs (project_id, version, log_entry, author) VALUES (:project_id, :version, :log_entry, :author)");
$stmt->execute([
'project_id' => $projectId,
'version' => $version,
'log_entry' => $logEntry,
'author' => $author
]);
$stmt = $this->db->prepare("UPDATE projects SET current_version = :version WHERE id = :id");
$stmt->execute(['version' => $version, 'id' => $projectId]);
$this->db->commit();
return true;
} catch (Exception $e) {
$this->db->rollBack();
return false;
}
}
}

View File

@ -0,0 +1,20 @@
-- Initial schema for Construction and Education CMS
CREATE TABLE IF NOT EXISTS projects (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
industry ENUM('Construction', 'Education', 'Other') DEFAULT 'Construction',
current_version VARCHAR(50) DEFAULT '1.0.0',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS project_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
project_id INT NOT NULL,
version VARCHAR(50) NOT NULL,
log_entry TEXT NOT NULL,
author VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
);

View File

@ -0,0 +1,30 @@
-- Add users table
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
role ENUM('admin', 'editor', 'viewer') DEFAULT 'viewer',
full_name VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Safely add status column
SET @dbname = DATABASE();
SET @tablename = "projects";
SET @columnname = "status";
SET @preparedStatement = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = @dbname
AND TABLE_NAME = @tablename
AND COLUMN_NAME = @columnname) > 0,
"SELECT 1",
"ALTER TABLE projects ADD COLUMN status ENUM('active', 'completed', 'on_hold', 'archived') DEFAULT 'active' AFTER industry"
));
PREPARE stmt FROM @preparedStatement;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- Create default admin user (password: admin123)
INSERT INTO users (username, password, role, full_name)
VALUES ('admin', '$2y$10$Tsw26nakbueEO40q9tx8Pe4g1yQuWfQFhKXjyVYvbNakTgBi/7NkG', 'admin', 'System Administrator')
ON DUPLICATE KEY UPDATE password = VALUES(password);

447
index.php
View File

@ -1,150 +1,341 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
@ini_set('display_errors', '1'); session_start();
@error_reporting(E_ALL); require_once __DIR__ . '/cls/class.pef.php';
@date_default_timezone_set('UTC');
$phpVersion = PHP_VERSION; if (!isset($_SESSION['user'])) {
$now = date('Y-m-d H:i:s'); header('Location: login.php');
exit;
}
$user = $_SESSION['user'];
$pef = new Pef();
$stats = $pef->getStats();
$projects = $pef->getProjects();
$selectedProjectId = isset($_GET['project_id']) ? (int)$_GET['project_id'] : null;
$selectedProject = $selectedProjectId ? $pef->getProject($selectedProjectId) : null;
$logs = $selectedProjectId ? $pef->getProjectLogs($selectedProjectId) : [];
$view = $_GET['view'] ?? 'dashboard';
?> ?>
<!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" />
<title>New Style</title> <title>CMS Dashboard - Construction & Education</title>
<?php
// Read project preview data from environment
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?>
<?php if ($projectDescription): ?>
<!-- Meta description -->
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
<!-- Open Graph meta tags -->
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<!-- Twitter meta tags -->
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
<?php endif; ?>
<?php if ($projectImageUrl): ?>
<!-- Open Graph image -->
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<!-- Twitter image -->
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
<?php endif; ?>
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <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"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style> <style>
:root { :root {
--bg-color-start: #6a11cb; --sidebar-bg: #1e293b;
--bg-color-end: #2575fc; --sidebar-hover: #334155;
--text-color: #ffffff; --primary: #3b82f6;
--card-bg-color: rgba(255, 255, 255, 0.01); --bg: #f8fafc;
--card-border-color: rgba(255, 255, 255, 0.1); --card: #ffffff;
} --text: #1e293b;
body { --text-muted: #64748b;
margin: 0; --border: #e2e8f0;
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;
} }
body { margin: 0; font-family: 'Inter', sans-serif; background: var(--bg); color: var(--text); display: flex; min-height: 100vh; }
/* Sidebar */
nav { width: 260px; background: var(--sidebar-bg); color: white; display: flex; flex-direction: column; }
.nav-header { padding: 2rem 1.5rem; font-weight: 700; font-size: 1.25rem; color: var(--primary); }
.nav-links { flex: 1; padding: 0 1rem; }
.nav-link { display: flex; align-items: center; padding: 0.75rem 1rem; color: #cbd5e1; text-decoration: none; border-radius: 8px; margin-bottom: 0.5rem; transition: all 0.2s; }
.nav-link:hover, .nav-link.active { background: var(--sidebar-hover); color: white; }
.nav-footer { padding: 1.5rem; border-top: 1px solid #334155; }
.user-info { font-size: 0.875rem; color: #94a3b8; margin-bottom: 1rem; }
.logout-btn { color: #f87171; text-decoration: none; font-size: 0.875rem; font-weight: 600; }
/* Main Content */
main { flex: 1; display: flex; flex-direction: column; }
.top-bar { height: 64px; background: white; border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; padding: 0 2rem; }
.content { padding: 2rem; overflow-y: auto; }
/* Cards & Stats */
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1.5rem; margin-bottom: 2rem; }
.stat-card { background: white; padding: 1.5rem; border-radius: 12px; border: 1px solid var(--border); }
.stat-label { font-size: 0.875rem; color: var(--text-muted); font-weight: 500; }
.stat-value { font-size: 1.5rem; font-weight: 700; margin-top: 0.5rem; }
.card { background: white; border-radius: 12px; border: 1px solid var(--border); overflow: hidden; margin-bottom: 1.5rem; }
.card-header { padding: 1.25rem 1.5rem; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; }
.card-title { margin: 0; font-size: 1rem; font-weight: 600; }
.card-body { padding: 1.5rem; }
/* Table */
table { width: 100%; border-collapse: collapse; }
th { text-align: left; padding: 1rem; font-size: 0.875rem; color: var(--text-muted); border-bottom: 1px solid var(--border); }
td { padding: 1rem; border-bottom: 1px solid var(--border); font-size: 0.9375rem; }
tr:last-child td { border-bottom: none; }
/* Badges */
.badge { padding: 0.25rem 0.625rem; border-radius: 9999px; font-size: 0.75rem; font-weight: 600; }
.badge-active { background: #dcfce7; color: #166534; }
.badge-completed { background: #e0f2fe; color: #0369a1; }
.badge-on_hold { background: #fef3c7; color: #92400e; }
.btn { padding: 0.5rem 1rem; border-radius: 6px; font-weight: 600; font-size: 0.875rem; cursor: pointer; border: none; text-decoration: none; display: inline-block; }
.btn-primary { background: var(--primary); color: white; }
.btn-outline { border: 1px solid var(--border); background: white; color: var(--text); }
/* Log Items */
.log-entry { padding: 1rem; border-left: 3px solid var(--primary); background: #f8fafc; margin-bottom: 1rem; border-radius: 0 8px 8px 0; }
.log-meta { display: flex; justify-content: space-between; font-size: 0.8rem; color: var(--text-muted); margin-bottom: 0.5rem; }
</style> </style>
</head> </head>
<body> <body>
<main> <nav>
<div class="card"> <div class="nav-header">PEF CMS</div>
<h1>Analyzing your requirements and generating your website…</h1> <div class="nav-links">
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes"> <a href="?view=dashboard" class="nav-link <?= $view === 'dashboard' ? 'active' : '' ?>">Overview</a>
<span class="sr-only">Loading…</span> <a href="?view=projects" class="nav-link <?= $view === 'projects' ? 'active' : '' ?>">Projects</a>
</div> </div>
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p> <div class="nav-footer">
<p class="hint">This page will update automatically as the plan is implemented.</p> <div class="user-info">
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p> Logged in as: <strong><?= htmlspecialchars($user['full_name']) ?></strong><br>
<small><?= ucfirst($user['role']) ?></small>
</div>
<a href="logout.php" class="logout-btn">Sign Out</a>
</div>
</nav>
<main>
<div class="top-bar">
<h2 style="font-size: 1.125rem; margin: 0;"><?= ucfirst($view) ?></h2>
<div style="font-size: 0.875rem; color: var(--text-muted);">
<?= date('l, F j, Y') ?>
</div>
</div>
<div class="content">
<?php if ($view === 'dashboard'): ?>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-label">Total Projects</div>
<div class="stat-value"><?= $stats['total_projects'] ?></div>
</div>
<div class="stat-card">
<div class="stat-label">Active Projects</div>
<div class="stat-value"><?= $stats['active_projects'] ?></div>
</div>
</div>
<div class="card">
<div class="card-header"><h3 class="card-title">Recent Activity</h3></div>
<div class="card-body">
<?php if (empty($stats['latest_logs'])): ?>
<p style="text-align:center; color:var(--text-muted); padding: 2rem;">No recent activity. Start by creating a project or adding a log.</p>
<?php else: ?>
<?php foreach ($stats['latest_logs'] as $log): ?>
<div class="log-entry">
<div class="log-meta">
<strong><?= htmlspecialchars($log['project_name']) ?> (v<?= htmlspecialchars($log['version']) ?>)</strong>
<span><?= $log['created_at'] ?></span>
</div>
<div style="font-size: 0.9rem;"><?= htmlspecialchars($log['log_entry']) ?></div>
<div style="font-size: 0.75rem; color: var(--text-muted); margin-top: 0.25rem;">By: <?= htmlspecialchars($log['author']) ?></div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
<?php elseif ($view === 'projects'): ?>
<?php if ($selectedProjectId): ?>
<!-- Project Detail View -->
<div style="margin-bottom: 1rem;">
<a href="?view=projects" class="btn btn-outline"> Back to Projects</a>
</div>
<div class="card">
<div class="card-header">
<h3 class="card-title"><?= htmlspecialchars($selectedProject['name']) ?></h3>
<div style="display:flex; gap:0.5rem; align-items:center;">
<span class="badge badge-<?= $selectedProject['status'] ?>"><?= ucfirst($selectedProject['status']) ?></span>
<?php if ($user['role'] !== 'viewer'): ?>
<button onclick="openEditModal()" class="btn btn-outline" style="padding: 0.25rem 0.5rem; font-size: 0.75rem;">Edit</button>
<?php endif; ?>
</div>
</div>
<div class="card-body">
<p><?= nl2br(htmlspecialchars($selectedProject['description'])) ?></p>
<div style="font-size: 0.875rem; color: var(--text-muted);">
Industry: <?= $selectedProject['industry'] ?> | Current Version: v<?= $selectedProject['current_version'] ?>
</div>
</div>
</div>
<?php if ($user['role'] !== 'viewer'): ?>
<div class="card">
<div class="card-header"><h3 class="card-title">Add New Log & Version</h3></div>
<div class="card-body">
<form action="add_log.php" method="POST" style="display: grid; gap: 1rem;">
<input type="hidden" name="project_id" value="<?= $selectedProjectId ?>">
<div>
<label style="display:block; font-size:0.8rem; font-weight:600; margin-bottom:0.25rem;">Version</label>
<input type="text" name="version" value="<?= htmlspecialchars($selectedProject['current_version']) ?>" style="width:100%; padding:0.5rem; border:1px solid var(--border); border-radius:4px;" required>
</div>
<div>
<label style="display:block; font-size:0.8rem; font-weight:600; margin-bottom:0.25rem;">What changed?</label>
<textarea name="log_entry" rows="3" style="width:100%; padding:0.5rem; border:1px solid var(--border); border-radius:4px;" required placeholder="Detailed log entry..."></textarea>
</div>
<button type="submit" class="btn btn-primary" style="width:fit-content;">Post Update</button>
</form>
</div>
</div>
<?php endif; ?>
<div class="card">
<div class="card-header"><h3 class="card-title">Version History</h3></div>
<div class="card-body">
<?php if (empty($logs)): ?>
<p style="text-align:center; color:var(--text-muted);">No logs available.</p>
<?php else: ?>
<?php foreach ($logs as $log): ?>
<div class="log-entry">
<div class="log-meta">
<strong>Version <?= htmlspecialchars($log['version']) ?></strong>
<span><?= $log['created_at'] ?></span>
</div>
<div><?= nl2br(htmlspecialchars($log['log_entry'])) ?></div>
<div style="font-size:0.75rem; color:var(--text-muted); margin-top:0.5rem;">Author: <?= htmlspecialchars($log['author']) ?></div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
<?php else: ?>
<!-- Projects List -->
<div class="card">
<div class="card-header">
<h3 class="card-title">Managed Projects</h3>
<?php if ($user['role'] !== 'viewer'): ?>
<button onclick="document.getElementById('addProjectModal').style.display='block'" class="btn btn-primary">+ New Project</button>
<?php endif; ?>
</div>
<div class="card-body" style="padding:0;">
<table>
<thead>
<tr>
<th>Project Name</th>
<th>Industry</th>
<th>Status</th>
<th>Version</th>
<th>Last Updated</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($projects as $p): ?>
<tr>
<td><strong><?= htmlspecialchars($p['name']) ?></strong></td>
<td><?= $p['industry'] ?></td>
<td><span class="badge badge-<?= $p['status'] ?>"><?= ucfirst($p['status']) ?></span></td>
<td>v<?= htmlspecialchars($p['current_version']) ?></td>
<td><?= date('Y-m-d', strtotime($p['updated_at'])) ?></td>
<td>
<a href="?view=projects&project_id=<?= $p['id'] ?>" class="btn btn-outline" style="padding: 0.25rem 0.75rem;">View</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<?php endif; ?>
<?php endif; ?>
</div> </div>
</main> </main>
<footer>
Page updated: <?= htmlspecialchars($now) ?> (UTC) <!-- Modals -->
</footer> <div id="addProjectModal" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.5); z-index:100; align-items:center; justify-content:center;">
<div style="background:white; padding:2rem; border-radius:12px; width:450px; position:relative; margin: 10% auto;">
<h3 style="margin-top:0;">Create New Project</h3>
<form action="manage_project.php?action=create" method="POST" style="display:grid; gap:1rem;">
<div>
<label style="display:block; font-size:0.8rem; font-weight:600; margin-bottom:0.25rem;">Name</label>
<input type="text" name="name" required style="width:100%; padding:0.5rem; border:1px solid var(--border); border-radius:4px;">
</div>
<div>
<label style="display:block; font-size:0.8rem; font-weight:600; margin-bottom:0.25rem;">Industry</label>
<select name="industry" style="width:100%; padding:0.5rem; border:1px solid var(--border); border-radius:4px;">
<option>Construction</option>
<option>Education</option>
<option>Other</option>
</select>
</div>
<div>
<label style="display:block; font-size:0.8rem; font-weight:600; margin-bottom:0.25rem;">Description</label>
<textarea name="description" rows="3" style="width:100%; padding:0.5rem; border:1px solid var(--border); border-radius:4px;"></textarea>
</div>
<div>
<label style="display:block; font-size:0.8rem; font-weight:600; margin-bottom:0.25rem;">Initial Version</label>
<input type="text" name="version" value="1.0.0" style="width:100%; padding:0.5rem; border:1px solid var(--border); border-radius:4px;">
</div>
<div style="display:flex; gap:0.5rem; justify-content:flex-end; margin-top:1rem;">
<button type="button" onclick="this.closest('#addProjectModal').style.display='none'" class="btn btn-outline">Cancel</button>
<button type="submit" class="btn btn-primary">Create Project</button>
</div>
</form>
</div>
</div>
<!-- Edit Project Modal -->
<?php if ($selectedProject): ?>
<div id="editProjectModal" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.5); z-index:100; align-items:center; justify-content:center;">
<div style="background:white; padding:2rem; border-radius:12px; width:450px; position:relative; margin: 10% auto;">
<h3 style="margin-top:0;">Edit Project</h3>
<form action="manage_project.php?action=update" method="POST" style="display:grid; gap:1rem;">
<input type="hidden" name="id" value="<?= $selectedProject['id'] ?>">
<div>
<label style="display:block; font-size:0.8rem; font-weight:600; margin-bottom:0.25rem;">Name</label>
<input type="text" name="name" value="<?= htmlspecialchars($selectedProject['name']) ?>" required style="width:100%; padding:0.5rem; border:1px solid var(--border); border-radius:4px;">
</div>
<div>
<label style="display:block; font-size:0.8rem; font-weight:600; margin-bottom:0.25rem;">Status</label>
<select name="status" style="width:100%; padding:0.5rem; border:1px solid var(--border); border-radius:4px;">
<option value="active" <?= $selectedProject['status'] === 'active' ? 'selected' : '' ?>>Active</option>
<option value="completed" <?= $selectedProject['status'] === 'completed' ? 'selected' : '' ?>>Completed</option>
<option value="on_hold" <?= $selectedProject['status'] === 'on_hold' ? 'selected' : '' ?>>On Hold</option>
<option value="archived" <?= $selectedProject['status'] === 'archived' ? 'selected' : '' ?>>Archived</option>
</select>
</div>
<div>
<label style="display:block; font-size:0.8rem; font-weight:600; margin-bottom:0.25rem;">Industry</label>
<select name="industry" style="width:100%; padding:0.5rem; border:1px solid var(--border); border-radius:4px;">
<option <?= $selectedProject['industry'] === 'Construction' ? 'selected' : '' ?>>Construction</option>
<option <?= $selectedProject['industry'] === 'Education' ? 'selected' : '' ?>>Education</option>
<option <?= $selectedProject['industry'] === 'Other' ? 'selected' : '' ?>>Other</option>
</select>
</div>
<div>
<label style="display:block; font-size:0.8rem; font-weight:600; margin-bottom:0.25rem;">Description</label>
<textarea name="description" rows="3" style="width:100%; padding:0.5rem; border:1px solid var(--border); border-radius:4px;"><?= htmlspecialchars($selectedProject['description']) ?></textarea>
</div>
<div style="display:flex; gap:0.5rem; justify-content:space-between; margin-top:1rem;">
<button type="button" onclick="if(confirm('Delete project?')) window.location.href='manage_project.php?action=delete&id=<?= $selectedProject['id'] ?>'" class="btn btn-outline" style="color:red; border-color:red;">Delete</button>
<div style="display:flex; gap:0.5rem;">
<button type="button" onclick="closeEditModal()" class="btn btn-outline">Cancel</button>
<button type="submit" class="btn btn-primary">Save Changes</button>
</div>
</div>
</form>
</div>
</div>
<?php endif; ?>
<script>
function openEditModal() {
document.getElementById('editProjectModal').style.display = 'block';
}
function closeEditModal() {
document.getElementById('editProjectModal').style.display = 'none';
}
</script>
</body> </body>
</html> </html>

63
login.php Normal file
View File

@ -0,0 +1,63 @@
<?php
session_start();
require_once __DIR__ . '/cls/class.pef.php';
if (isset($_SESSION['user'])) {
header('Location: index.php');
exit;
}
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$pef = new Pef();
$user = $pef->login($_POST['username'] ?? '', $_POST['password'] ?? '');
if ($user) {
$_SESSION['user'] = $user;
header('Location: index.php');
exit;
} else {
$error = 'Invalid username or password.';
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - Construction & Education CMS</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
<style>
body { font-family: 'Inter', sans-serif; background: #f8fafc; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; }
.login-card { background: white; padding: 2.5rem; border-radius: 12px; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); width: 100%; max-width: 400px; }
h1 { margin-top: 0; font-size: 1.5rem; color: #1e293b; text-align: center; }
.form-group { margin-bottom: 1rem; }
label { display: block; font-size: 0.875rem; font-weight: 500; color: #64748b; margin-bottom: 0.5rem; }
input { width: 100%; padding: 0.75rem; border: 1px solid #e2e8f0; border-radius: 6px; box-sizing: border-box; }
button { width: 100%; padding: 0.75rem; background: #3b82f6; color: white; border: none; border-radius: 6px; font-weight: 600; cursor: pointer; margin-top: 1rem; }
button:hover { background: #2563eb; }
.error { color: #ef4444; font-size: 0.875rem; text-align: center; margin-bottom: 1rem; }
.footer-note { text-align: center; margin-top: 1.5rem; font-size: 0.75rem; color: #94a3b8; }
</style>
</head>
<body>
<div class="login-card">
<h1>CMS Login</h1>
<?php if ($error): ?>
<div class="error"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<form method="POST">
<div class="form-group">
<label>Username</label>
<input type="text" name="username" required autofocus>
</div>
<div class="form-group">
<label>Password</label>
<input type="password" name="password" required>
</div>
<button type="submit">Sign In</button>
</form>
<div class="footer-note">Default: admin / admin123</div>
</div>
</body>
</html>

5
logout.php Normal file
View File

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

32
manage_project.php Normal file
View File

@ -0,0 +1,32 @@
<?php
session_start();
require_once __DIR__ . '/cls/class.pef.php';
if (!isset($_SESSION['user']) || $_SESSION['user']['role'] === 'viewer') {
die('Unauthorized');
}
$pef = new Pef();
$action = $_GET['action'] ?? '';
if ($action === 'create') {
$pef->createProject(
$_POST['name'],
$_POST['description'],
$_POST['industry'],
$_POST['version'] ?: '1.0.0'
);
} elseif ($action === 'update') {
$pef->updateProject((int)$_POST['id'], [
'name' => $_POST['name'],
'description' => $_POST['description'],
'industry' => $_POST['industry'],
'status' => $_POST['status']
]);
} elseif ($action === 'delete') {
$id = (int)($_POST['id'] ?? $_GET['id'] ?? 0);
$pef->deleteProject($id);
}
header('Location: index.php');
exit;

23
migrate.php Normal file
View File

@ -0,0 +1,23 @@
<?php
require_once __DIR__ . '/db/config.php';
function runMigrations() {
$pdo = db();
$migrationsDir = __DIR__ . '/db/migrations';
$files = glob($migrationsDir . '/*.sql');
sort($files);
foreach ($files as $file) {
$sql = file_get_contents($file);
try {
$pdo->exec($sql);
echo "Successfully ran migration: " . basename($file) . "\n";
} catch (PDOException $e) {
echo "Error running migration " . basename($file) . ": " . $e->getMessage() . "\n";
}
}
}
if (php_sapi_name() === 'cli' || isset($_GET['run'])) {
runMigrations();
}

18
seed.php Normal file
View File

@ -0,0 +1,18 @@
<?php
require_once __DIR__ . '/cls/class.pef.php';
$pef = new Pef();
// Check if we already have projects
if (count($pef->getProjects()) === 0) {
$p1 = $pef->createProject('Bridge Construction A1', 'Main bridge construction for the highway project.', 'Construction', '1.0.0');
$pef->addLog($p1, '1.0.0', 'Project initiated.', 'Admin');
$pef->addLog($p1, '1.1.0', 'Foundation completed.', 'Site Manager');
$p2 = $pef->createProject('Online Learning Platform', 'LMS for local schools.', 'Education', '0.9.0');
$pef->addLog($p2, '0.9.0', 'Beta version launched for testing.', 'Dev Team');
echo "Sample data seeded.\n";
} else {
echo "Database already contains data.\n";
}