diff --git a/assets/css/custom.css b/assets/css/custom.css
index 65a1626..2170f59 100644
--- a/assets/css/custom.css
+++ b/assets/css/custom.css
@@ -1,346 +1,77 @@
:root {
- --color-bg: #ffffff;
- --color-text: #1a1a1a;
- --color-primary: #2563EB; /* Vibrant Blue */
- --color-secondary: #000000;
- --color-accent: #A3E635; /* Lime Green */
- --color-surface: #f8f9fa;
- --font-heading: 'Space Grotesk', sans-serif;
- --font-body: 'Inter', sans-serif;
- --border-width: 2px;
- --shadow-hard: 5px 5px 0px #000;
- --shadow-hover: 8px 8px 0px #000;
- --radius-pill: 50rem;
- --radius-card: 1rem;
+ --primary: #111827;
+ --secondary: #6B7280;
+ --accent: #3B82F6;
+ --bg: #F9FAFB;
+ --surface: #FFFFFF;
+ --border: #E5E7EB;
}
body {
- font-family: var(--font-body);
- background-color: var(--color-bg);
- color: var(--color-text);
- overflow-x: hidden;
+ font-family: 'Inter', system-ui, -apple-system, sans-serif;
+ background-color: var(--bg);
+ color: var(--primary);
+ font-size: 0.875rem;
}
-h1, h2, h3, h4, h5, h6, .navbar-brand {
- font-family: var(--font-heading);
- letter-spacing: -0.03em;
-}
-
-/* Utilities */
-.text-primary { color: var(--color-primary) !important; }
-.bg-black { background-color: #000 !important; }
-.text-white { color: #fff !important; }
-.shadow-hard { box-shadow: var(--shadow-hard); }
-.border-2-black { border: var(--border-width) solid #000; }
-.py-section { padding-top: 5rem; padding-bottom: 5rem; }
-
-/* Navbar */
.navbar {
- background: rgba(255, 255, 255, 0.9);
- backdrop-filter: blur(10px);
- border-bottom: var(--border-width) solid transparent;
- transition: all 0.3s;
- padding-top: 1rem;
- padding-bottom: 1rem;
+ background-color: var(--surface);
+ border-bottom: 1px solid var(--border);
}
-.navbar.scrolled {
- border-bottom-color: #000;
- padding-top: 0.5rem;
- padding-bottom: 0.5rem;
+.card {
+ border: 1px solid var(--border);
+ border-radius: 4px;
+ box-shadow: none;
}
-.brand-text {
- font-size: 1.5rem;
- font-weight: 800;
-}
-
-.nav-link {
- font-weight: 500;
- color: var(--color-text);
- margin-left: 1rem;
- position: relative;
-}
-
-.nav-link:hover, .nav-link.active {
- color: var(--color-primary);
-}
-
-/* Buttons */
.btn {
- font-weight: 700;
- font-family: var(--font-heading);
- padding: 0.8rem 2rem;
- border-radius: var(--radius-pill);
- border: var(--border-width) solid #000;
- transition: all 0.2s cubic-bezier(0.25, 1, 0.5, 1);
- box-shadow: var(--shadow-hard);
-}
-
-.btn:hover {
- transform: translate(-2px, -2px);
- box-shadow: var(--shadow-hover);
-}
-
-.btn:active {
- transform: translate(2px, 2px);
- box-shadow: 0 0 0 #000;
+ border-radius: 4px;
+ font-weight: 500;
+ padding: 0.5rem 1rem;
}
.btn-primary {
- background-color: var(--color-primary);
- border-color: #000;
- color: #fff;
+ background-color: var(--primary);
+ border-color: var(--primary);
}
.btn-primary:hover {
- background-color: #1d4ed8;
- border-color: #000;
- color: #fff;
+ background-color: #1F2937;
+ border-color: #1F2937;
}
-.btn-outline-dark {
- background-color: #fff;
- color: #000;
+.table {
+ border: 1px solid var(--border);
+ background: var(--surface);
}
-.btn-cta {
- background-color: var(--color-accent);
- color: #000;
-}
-
-.btn-cta:hover {
- background-color: #8cc629;
- color: #000;
-}
-
-/* Hero Section */
-.hero-section {
- min-height: 100vh;
- padding-top: 80px;
-}
-
-.background-blob {
- position: absolute;
- border-radius: 50%;
- filter: blur(80px);
- opacity: 0.6;
- z-index: 1;
-}
-
-.blob-1 {
- top: -10%;
- right: -10%;
- width: 600px;
- height: 600px;
- background: radial-gradient(circle, var(--color-accent), transparent);
-}
-
-.blob-2 {
- bottom: 10%;
- left: -10%;
- width: 500px;
- height: 500px;
- background: radial-gradient(circle, var(--color-primary), transparent);
-}
-
-.highlight-text {
- background: linear-gradient(120deg, transparent 0%, transparent 40%, var(--color-accent) 40%, var(--color-accent) 100%);
- background-repeat: no-repeat;
- background-size: 100% 40%;
- background-position: 0 88%;
- padding: 0 5px;
-}
-
-.dot { color: var(--color-primary); }
-
-.badge-pill {
- display: inline-block;
- padding: 0.5rem 1rem;
- border: 2px solid #000;
- border-radius: 50px;
- font-weight: 700;
- background: #fff;
- box-shadow: 4px 4px 0 #000;
- font-family: var(--font-heading);
- font-size: 0.9rem;
-}
-
-/* Marquee */
-.marquee-container {
- overflow: hidden;
- white-space: nowrap;
- border-top: 2px solid #000;
- border-bottom: 2px solid #000;
-}
-
-.rotate-divider {
- transform: rotate(-2deg) scale(1.05);
- z-index: 10;
- position: relative;
- margin-top: -50px;
- margin-bottom: 30px;
-}
-
-.marquee-content {
- display: inline-block;
- animation: marquee 20s linear infinite;
- font-family: var(--font-heading);
- font-weight: 700;
- font-size: 1.5rem;
- letter-spacing: 2px;
-}
-
-@keyframes marquee {
- 0% { transform: translateX(0); }
- 100% { transform: translateX(-50%); }
-}
-
-/* Portfolio Cards */
-.project-card {
- border: 2px solid #000;
- border-radius: var(--radius-card);
- overflow: hidden;
- background: #fff;
- transition: transform 0.3s ease;
- box-shadow: var(--shadow-hard);
- height: 100%;
- display: flex;
- flex-direction: column;
-}
-
-.project-card:hover {
- transform: translateY(-10px);
- box-shadow: 8px 8px 0 #000;
-}
-
-.card-img-holder {
- height: 250px;
- display: flex;
- align-items: center;
- justify-content: center;
- border-bottom: 2px solid #000;
- position: relative;
- font-size: 4rem;
-}
-
-.placeholder-art {
- transition: transform 0.3s ease;
-}
-
-.project-card:hover .placeholder-art {
- transform: scale(1.2) rotate(10deg);
-}
-
-.bg-soft-blue { background-color: #e0f2fe; }
-.bg-soft-green { background-color: #dcfce7; }
-.bg-soft-purple { background-color: #f3e8ff; }
-.bg-soft-yellow { background-color: #fef9c3; }
-
-.category-tag {
- position: absolute;
- top: 15px;
- right: 15px;
- background: #000;
- color: #fff;
- padding: 5px 12px;
- border-radius: 20px;
+.table th {
+ background: #F3F4F6;
+ font-weight: 600;
+ text-transform: uppercase;
font-size: 0.75rem;
- font-weight: 700;
+ letter-spacing: 0.025em;
+ border-bottom: 1px solid var(--border);
}
-.card-body { padding: 1.5rem; }
-
-.link-arrow {
- text-decoration: none;
- color: #000;
- font-weight: 700;
- display: inline-flex;
- align-items: center;
- margin-top: auto;
+.activity-log {
+ max-height: 400px;
+ overflow-y: auto;
}
-.link-arrow i { transition: transform 0.2s; margin-left: 5px; }
-.link-arrow:hover i { transform: translateX(5px); }
-
-/* About */
-.about-image-stack {
- position: relative;
- height: 400px;
- width: 100%;
+.log-entry {
+ padding: 0.75rem;
+ border-bottom: 1px solid var(--border);
+ font-size: 0.8rem;
}
-.stack-card {
- position: absolute;
- width: 80%;
- height: 100%;
- border-radius: var(--radius-card);
- border: 2px solid #000;
- box-shadow: var(--shadow-hard);
- left: 10%;
- transform: rotate(-3deg);
- background-size: cover;
+.log-entry:last-child {
+ border-bottom: none;
}
-/* Forms */
-.form-control {
- border: 2px solid #000;
- border-radius: 0.5rem;
- padding: 1rem;
+.badge {
+ border-radius: 9999px;
font-weight: 500;
- background: #f8f9fa;
-}
-
-.form-control:focus {
- box-shadow: 4px 4px 0 var(--color-primary);
- border-color: #000;
- background: #fff;
-}
-
-/* Animations */
-.animate-up {
- opacity: 0;
- transform: translateY(30px);
- animation: fadeUp 0.8s ease forwards;
-}
-
-.delay-100 { animation-delay: 0.1s; }
-.delay-200 { animation-delay: 0.2s; }
-
-@keyframes fadeUp {
- to {
- opacity: 1;
- transform: translateY(0);
- }
-}
-
-/* Social */
-.social-links a {
- transition: transform 0.2s;
- display: inline-block;
-}
-.social-links a:hover {
- transform: scale(1.2) rotate(10deg);
- color: var(--color-accent) !important;
-}
-
-/* Responsive */
-@media (max-width: 991px) {
- .rotate-divider {
- transform: rotate(0);
- margin-top: 0;
- margin-bottom: 2rem;
- }
-
- .hero-section {
- padding-top: 120px;
- text-align: center;
- min-height: auto;
- padding-bottom: 100px;
- }
-
- .display-1 { font-size: 3.5rem; }
-
- .blob-1 { width: 300px; height: 300px; right: -20%; }
- .blob-2 { width: 300px; height: 300px; left: -20%; }
-}
+ padding: 0.25rem 0.625rem;
+}
\ No newline at end of file
diff --git a/db/migrations/20260123_init.sql b/db/migrations/20260123_init.sql
new file mode 100644
index 0000000..a389e47
--- /dev/null
+++ b/db/migrations/20260123_init.sql
@@ -0,0 +1,77 @@
+-- Initial Schema for Repairs Multi-tenant App
+CREATE TABLE IF NOT EXISTS companies (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(255) NOT NULL,
+ uprn_required BOOLEAN DEFAULT FALSE,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS users (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ company_id INT NOT NULL,
+ name VARCHAR(255) NOT NULL,
+ email VARCHAR(255) UNIQUE NOT NULL,
+ role ENUM('admin', 'standard') DEFAULT 'standard',
+ FOREIGN KEY (company_id) REFERENCES companies(id)
+);
+
+CREATE TABLE IF NOT EXISTS clients (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ company_id INT NOT NULL,
+ name VARCHAR(255) NOT NULL,
+ is_active BOOLEAN DEFAULT TRUE,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (company_id) REFERENCES companies(id)
+);
+
+CREATE TABLE IF NOT EXISTS job_statuses (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ company_id INT NOT NULL,
+ name VARCHAR(255) NOT NULL,
+ is_default BOOLEAN DEFAULT FALSE,
+ FOREIGN KEY (company_id) REFERENCES companies(id)
+);
+
+CREATE TABLE IF NOT EXISTS jobs (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ company_id INT NOT NULL,
+ job_ref VARCHAR(100) NOT NULL,
+ uprn VARCHAR(100),
+ address_1 VARCHAR(255),
+ address_2 VARCHAR(255),
+ address_3 VARCHAR(255),
+ postcode VARCHAR(20),
+ description TEXT,
+ status_id INT,
+ client_id INT,
+ works_approved BOOLEAN DEFAULT FALSE,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ UNIQUE(company_id, job_ref),
+ FOREIGN KEY (company_id) REFERENCES companies(id),
+ FOREIGN KEY (status_id) REFERENCES job_statuses(id),
+ FOREIGN KEY (client_id) REFERENCES clients(id)
+);
+
+CREATE TABLE IF NOT EXISTS activity_logs (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ job_id INT NOT NULL,
+ company_id INT NOT NULL,
+ user_id INT NOT NULL,
+ user_name VARCHAR(255),
+ event_type VARCHAR(100),
+ field_name VARCHAR(100),
+ old_value TEXT,
+ new_value TEXT,
+ metadata JSON,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (job_id) REFERENCES jobs(id),
+ FOREIGN KEY (company_id) REFERENCES companies(id)
+);
+
+-- Seed Initial Demo Company
+INSERT INTO companies (name) VALUES ('Repairs Pro Ltd');
+SET @company_id = LAST_INSERT_ID();
+
+INSERT INTO users (company_id, name, email, role) VALUES (@company_id, 'Admin User', 'admin@repairspro.com', 'admin');
+INSERT INTO job_statuses (company_id, name, is_default) VALUES (@company_id, 'To Be Surveyed', 1), (@company_id, 'Booking Required', 0), (@company_id, 'Completed', 0);
+INSERT INTO clients (company_id, name) VALUES (@company_id, 'Main Housing Assoc');
diff --git a/includes/helpers.php b/includes/helpers.php
new file mode 100644
index 0000000..23fc292
--- /dev/null
+++ b/includes/helpers.php
@@ -0,0 +1,34 @@
+query("SELECT * FROM users LIMIT 1")->fetch();
+}
+
+function logActivity($job_id, $event_type, $field_name = null, $old_value = null, $new_value = null) {
+ $user = getCurrentUser();
+ $stmt = db()->prepare("INSERT INTO activity_logs (job_id, company_id, user_id, user_name, event_type, field_name, old_value, new_value) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
+ $stmt->execute([
+ $job_id,
+ $user['company_id'],
+ $user['id'],
+ $user['name'],
+ $event_type,
+ $field_name,
+ $old_value,
+ $new_value
+ ]);
+}
+
+function getJobStatuses($company_id) {
+ $stmt = db()->prepare("SELECT * FROM job_statuses WHERE company_id = ?");
+ $stmt->execute([$company_id]);
+ return $stmt->fetchAll();
+}
+
+function getClients($company_id) {
+ $stmt = db()->prepare("SELECT * FROM clients WHERE company_id = ? AND is_active = 1");
+ $stmt->execute([$company_id]);
+ return $stmt->fetchAll();
+}
diff --git a/index.php b/index.php
index 7205f3d..923196e 100644
--- a/index.php
+++ b/index.php
@@ -1,150 +1,167 @@
prepare("INSERT INTO jobs (company_id, job_ref, address_1, description, status_id, client_id) VALUES (?, ?, ?, ?, ?, ?)");
+ $stmt->execute([$company_id, $job_ref, $address_1, $description, $status_id, $client_id]);
+ $job_id = db()->lastInsertId();
+
+ logActivity($job_id, 'job_created', null, null, "Job Ref: $job_ref");
+
+ header("Location: job_detail.php?id=" . $job_id);
+ exit;
+ } catch (Exception $e) {
+ $error = "Error creating job: " . $e->getMessage();
+ }
+}
+
+$jobs = db()->prepare("SELECT j.*, s.name as status_name, c.name as client_name
+ FROM jobs j
+ LEFT JOIN job_statuses s ON j.status_id = s.id
+ LEFT JOIN clients c ON j.client_id = c.id
+ WHERE j.company_id = ?
+ ORDER BY j.created_at DESC");
+$jobs->execute([$company_id]);
+$jobList = $jobs->fetchAll();
+
+$statuses = getJobStatuses($company_id);
+$clients = getClients($company_id);
?>
-
+
-
-
- New Style
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ Dashboard - Repairs Pro
+
+
+
-
-
-
Analyzing your requirements and generating your website…
-
- Loading…
-
-
= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.
-
This page will update automatically as the plan is implemented.
-
Runtime: PHP = htmlspecialchars($phpVersion) ?> — UTC = htmlspecialchars($now) ?>
+
+
+
+
+
All Jobs
+
+
+
+
+
+
+
+
+
+
+
+ | Job Ref |
+ Client |
+ Address |
+ Status |
+ Approved |
+ Action |
+
+
+
+
+
+ |
+ |
+ |
+
+
+
+
+ |
+
+
+ Yes
+
+ No
+
+ |
+
+ View
+ |
+
+
+
+ | No jobs found. Start by creating one. |
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
+