diff --git a/assets/css/custom.css b/assets/css/custom.css
index 9d9d9d0..cd2a8a5 100644
--- a/assets/css/custom.css
+++ b/assets/css/custom.css
@@ -1,8 +1,8 @@
/* General Body Styles */
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
- background-color: #1C1C1E; /* Dark Mode Background */
- color: #F5F5F7; /* Dark Mode Text */
+ background-color: #F8F9FA; /* Light Mode Background */
+ color: #212529; /* Dark Text */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@@ -14,8 +14,8 @@ body {
left: 0;
bottom: 0;
width: 260px;
- background-color: #2C2C2E; /* Dark Mode Surface */
- border-right: 1px solid #3A3A3C;
+ background-color: #FFFFFF; /* White Background */
+ border-right: 1px solid #DEE2E6;
padding: 1.5rem;
display: flex;
flex-direction: column;
@@ -24,13 +24,13 @@ body {
.sidebar .logo {
font-size: 1.5rem;
font-weight: 700;
- color: #FFFFFF;
+ color: #000000; /* Black Text */
margin-bottom: 2rem;
text-align: center;
}
.sidebar .nav-link {
- color: #E5E5EA;
+ color: #495057; /* Gray Text */
font-size: 1rem;
font-weight: 500;
padding: 0.75rem 1rem;
@@ -61,6 +61,7 @@ body {
.header h1 {
font-size: 2rem;
font-weight: 700;
+ color: #000000; /* Black Text */
}
/* Buttons */
@@ -80,15 +81,15 @@ body {
/* Cards */
.card {
- background-color: #2C2C2E; /* Dark Mode Surface */
- border: 1px solid #3A3A3C;
+ background-color: #FFFFFF; /* White Background */
+ border: 1px solid #DEE2E6;
border-radius: 12px;
margin-bottom: 1.5rem;
}
.card-header {
background-color: transparent;
- border-bottom: 1px solid #3A3A3C;
+ border-bottom: 1px solid #DEE2E6;
font-weight: 600;
padding: 1rem 1.5rem;
}
@@ -101,12 +102,12 @@ body {
.empty-state {
text-align: center;
padding: 4rem 2rem;
- color: #8E8E93;
+ color: #6C757D; /* Gray Text */
}
.empty-state h5 {
font-weight: 600;
- color: #E5E5EA;
+ color: #212529; /* Dark Text */
}
/* KPI Cards */
@@ -127,13 +128,13 @@ body {
.kpi-value {
font-size: 2rem;
font-weight: 700;
- color: #FFFFFF;
+ color: #000000; /* Black Text */
margin-bottom: 0;
}
.kpi-title {
font-size: 0.9rem;
- color: #8E8E93;
+ color: #6C757D; /* Gray Text */
font-weight: 500;
}
@@ -156,7 +157,7 @@ body {
/* List Group Customization */
.list-group-item {
background-color: transparent;
- border-color: #3A3A3C;
+ border-color: #DEE2E6;
padding: 1rem 0;
}
.list-group-flush > .list-group-item:last-child {
@@ -164,4 +165,9 @@ body {
}
.list-group-item:first-child {
padding-top: 0;
-}
\ No newline at end of file
+}
+
+/* Table Customization */
+.table {
+ color: #000000; /* Black text for tables */
+}
diff --git a/assets/js/main.js b/assets/js/main.js
index 6130445..07a5102 100644
--- a/assets/js/main.js
+++ b/assets/js/main.js
@@ -1 +1,17 @@
// Custom JavaScript will go here
+
+document.addEventListener('DOMContentLoaded', function() {
+ const copyButton = document.getElementById('copy-button');
+ if (copyButton) {
+ copyButton.addEventListener('click', function() {
+ const portalLinkInput = document.getElementById('portal-link');
+ portalLinkInput.select();
+ document.execCommand('copy');
+ // Optional: Provide feedback to the user
+ copyButton.innerHTML = '';
+ setTimeout(function() {
+ copyButton.innerHTML = '';
+ }, 2000);
+ });
+ }
+});
\ No newline at end of file
diff --git a/assets/pasted-20251109-033103-90e1e692.png b/assets/pasted-20251109-033103-90e1e692.png
new file mode 100644
index 0000000..0dc7c7c
Binary files /dev/null and b/assets/pasted-20251109-033103-90e1e692.png differ
diff --git a/assets/pasted-20251109-033333-cd8ba0ae.png b/assets/pasted-20251109-033333-cd8ba0ae.png
new file mode 100644
index 0000000..9d6c0cb
Binary files /dev/null and b/assets/pasted-20251109-033333-cd8ba0ae.png differ
diff --git a/create_work_order.php b/create_work_order.php
new file mode 100644
index 0000000..3f1bf4c
--- /dev/null
+++ b/create_work_order.php
@@ -0,0 +1,150 @@
+Dear {$customer_name},
Your work order for {$job_type} has been created.
Thank you!
";
+ MailService::sendMail($customer_email, $subject, $body);
+ }
+ header("Location: index.php");
+ exit;
+ } else {
+ $message = "Failed to create work order.";
+ }
+}
+?>
+
+
+
+
+
+ Create Work Order - RedSolutions CRM
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/db/002_add_dispatched_at_column.sql b/db/002_add_dispatched_at_column.sql
new file mode 100644
index 0000000..0b06cd5
--- /dev/null
+++ b/db/002_add_dispatched_at_column.sql
@@ -0,0 +1 @@
+ALTER TABLE work_orders ADD COLUMN dispatched_at TIMESTAMP NULL;
diff --git a/db/003_add_uuid_to_work_orders.sql b/db/003_add_uuid_to_work_orders.sql
new file mode 100644
index 0000000..eac867a
--- /dev/null
+++ b/db/003_add_uuid_to_work_orders.sql
@@ -0,0 +1,2 @@
+ALTER TABLE work_orders ADD COLUMN uuid CHAR(36) NOT NULL;
+UPDATE work_orders SET uuid = UUID() WHERE uuid IS NULL OR uuid = '';
diff --git a/db/004_create_users_table.sql b/db/004_create_users_table.sql
new file mode 100644
index 0000000..d222231
--- /dev/null
+++ b/db/004_create_users_table.sql
@@ -0,0 +1,7 @@
+CREATE TABLE IF NOT EXISTS `users` (
+ `id` INT AUTO_INCREMENT PRIMARY KEY,
+ `username` VARCHAR(255) NOT NULL UNIQUE,
+ `password` VARCHAR(255) NOT NULL,
+ `role` VARCHAR(50) NOT NULL DEFAULT 'technician', -- Can be 'technician' or 'admin'
+ `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB;
diff --git a/db/005_add_email_to_users.sql b/db/005_add_email_to_users.sql
new file mode 100644
index 0000000..f6b62fd
--- /dev/null
+++ b/db/005_add_email_to_users.sql
@@ -0,0 +1 @@
+ALTER TABLE `users` ADD COLUMN `email` VARCHAR(255) NOT NULL AFTER `username`;
\ No newline at end of file
diff --git a/db/006_add_customer_email_to_work_orders.sql b/db/006_add_customer_email_to_work_orders.sql
new file mode 100644
index 0000000..178d94f
--- /dev/null
+++ b/db/006_add_customer_email_to_work_orders.sql
@@ -0,0 +1 @@
+ALTER TABLE `work_orders` ADD COLUMN `customer_email` VARCHAR(255) NULL AFTER `customer_name`;
\ No newline at end of file
diff --git a/db/007_create_inventory_table.sql b/db/007_create_inventory_table.sql
new file mode 100644
index 0000000..922049e
--- /dev/null
+++ b/db/007_create_inventory_table.sql
@@ -0,0 +1,7 @@
+CREATE TABLE IF NOT EXISTS `inventory` (
+ `id` INT AUTO_INCREMENT PRIMARY KEY,
+ `item_name` VARCHAR(255) NOT NULL,
+ `quantity` INT NOT NULL DEFAULT 0,
+ `price` DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
+ `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+) ENGINE=InnoDB;
\ No newline at end of file
diff --git a/db/008_create_work_order_parts_table.sql b/db/008_create_work_order_parts_table.sql
new file mode 100644
index 0000000..b22a4c1
--- /dev/null
+++ b/db/008_create_work_order_parts_table.sql
@@ -0,0 +1,9 @@
+CREATE TABLE IF NOT EXISTS `work_order_parts` (
+ `id` INT AUTO_INCREMENT PRIMARY KEY,
+ `work_order_id` INT NOT NULL,
+ `inventory_id` INT NOT NULL,
+ `quantity_used` INT NOT NULL DEFAULT 1,
+ `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (`work_order_id`) REFERENCES `work_orders`(`id`) ON DELETE CASCADE,
+ FOREIGN KEY (`inventory_id`) REFERENCES `inventory`(`id`) ON DELETE CASCADE
+) ENGINE=InnoDB;
\ No newline at end of file
diff --git a/db/009_add_technician_id_to_work_orders.sql b/db/009_add_technician_id_to_work_orders.sql
new file mode 100644
index 0000000..9615910
--- /dev/null
+++ b/db/009_add_technician_id_to_work_orders.sql
@@ -0,0 +1,2 @@
+ALTER TABLE work_orders ADD COLUMN assigned_technician_id INT NULL;
+ALTER TABLE work_orders ADD CONSTRAINT fk_technician FOREIGN KEY (assigned_technician_id) REFERENCES users(id) ON DELETE SET NULL;
diff --git a/db/010_add_completed_at_to_work_orders.sql b/db/010_add_completed_at_to_work_orders.sql
new file mode 100644
index 0000000..c941009
--- /dev/null
+++ b/db/010_add_completed_at_to_work_orders.sql
@@ -0,0 +1 @@
+ALTER TABLE work_orders ADD COLUMN completed_at TIMESTAMP NULL;
diff --git a/db/migrate.php b/db/migrate.php
index 453a713..67a98de 100644
--- a/db/migrate.php
+++ b/db/migrate.php
@@ -3,10 +3,36 @@ require_once __DIR__ . '/config.php';
try {
$pdo = db();
- $sql = file_get_contents(__DIR__ . '/001_create_work_orders.sql');
- $pdo->exec($sql);
- echo "Database migration and seeding completed successfully.\n";
+
+ // Create migrations table if it doesn't exist
+ $pdo->exec("CREATE TABLE IF NOT EXISTS migrations (migration VARCHAR(255) NOT NULL, PRIMARY KEY (migration))");
+
+ // Get all migration files
+ $migration_files = glob(__DIR__ . '/*.sql');
+
+ // Get executed migrations
+ $executed_migrations = $pdo->query("SELECT migration FROM migrations")->fetchAll(PDO::FETCH_COLUMN);
+
+ foreach ($migration_files as $file) {
+ $migration_name = basename($file);
+ if (!in_array($migration_name, $executed_migrations)) {
+ $sql = file_get_contents($file);
+ $pdo->exec($sql);
+ $stmt = $pdo->prepare("INSERT INTO migrations (migration) VALUES (?)");
+ $stmt->execute([$migration_name]);
+ echo "Executed migration: $migration_name\n";
+ } else {
+ echo "Skipping already executed migration: $migration_name\n";
+ }
+ }
+
+ echo "Database migrations completed successfully.\n";
+
+ // Seed the database with default data
+ echo "Seeding database...\n";
+ require_once __DIR__ . '/seed.php';
+ echo "Database seeding completed.\n";
+
} catch (PDOException $e) {
die("Database migration failed: " . $e->getMessage() . "\n");
-}
-
+}
\ No newline at end of file
diff --git a/db/seed.php b/db/seed.php
new file mode 100644
index 0000000..66db60f
--- /dev/null
+++ b/db/seed.php
@@ -0,0 +1,25 @@
+query("SELECT COUNT(*) FROM users WHERE username = 'admin'");
+ if ($stmt->fetchColumn() == 0) {
+ $username = 'admin';
+ $password = 'password'; // Default password, change this!
+ $email = 'admin@example.com';
+ $role = 'admin';
+
+ if (create_user($username, $email, $password, $role)) {
+ echo "Default admin user created successfully. Username: admin, Password: password\n";
+ } else {
+ echo "Failed to create default admin user.\n";
+ }
+ } else {
+ echo "Admin user already exists. No action taken.\n";
+ }
+}
+
+seed_default_user();
+
diff --git a/edit_inventory_item.php b/edit_inventory_item.php
new file mode 100644
index 0000000..8b417a3
--- /dev/null
+++ b/edit_inventory_item.php
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+ Edit Inventory Item - Flatlogic
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/edit_work_order.php b/edit_work_order.php
new file mode 100644
index 0000000..3039605
--- /dev/null
+++ b/edit_work_order.php
@@ -0,0 +1,170 @@
+Dear {$customer_name},Your work order for {$job_type} has been completed.
Thank you for your business!
";
+ MailService::sendMail($customer_email, $subject, $body);
+ }
+
+ if ($technician_id != $original_work_order['assigned_technician_id']) {
+ $technician_user = get_user_by_id($technician_id);
+ if ($technician_user && !empty($technician_user['email'])) {
+ $subject = "You have been assigned a new work order";
+ $body = "Hello {$technician_user['username']},
You have been assigned to a new work order #{$id}.
Click here to view the details.
";
+ MailService::sendMail($technician_user['email'], $subject, $body);
+ }
+ }
+
+ header("Location: work_order_details.php?id=" . $id . "&success=1");
+ exit;
+ } else {
+ $error_message = "Failed to update work order.";
+ }
+}
+
+if (!$work_order) {
+ header("Location: index.php?error=notfound");
+ exit;
+}
+
+?>
+
+
+
+
+
+ Edit Work Order # - Flatlogic
+
+
+
+
+
+
+
+
+
+
+
Edit Work Order #
+
+
+ Back to Details
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/includes/functions.php b/includes/functions.php
index 064adcc..d9f9299 100644
--- a/includes/functions.php
+++ b/includes/functions.php
@@ -3,17 +3,24 @@
function get_work_orders_by_status($status) {
$pdo = db();
- $stmt = $pdo->prepare("SELECT * FROM work_orders WHERE status = ? ORDER BY created_at DESC");
+ $stmt = $pdo->prepare("SELECT wo.*, u.username as assigned_technician FROM work_orders wo LEFT JOIN users u ON wo.assigned_technician_id = u.id WHERE status = ? ORDER BY created_at DESC");
$stmt->execute([$status]);
return $stmt->fetchAll();
}
function get_all_work_orders() {
$pdo = db();
- $stmt = $pdo->query("SELECT * FROM work_orders ORDER BY created_at DESC");
+ $stmt = $pdo->query("SELECT wo.*, u.username as assigned_technician FROM work_orders wo LEFT JOIN users u ON wo.assigned_technician_id = u.id ORDER BY created_at DESC");
return $stmt->fetchAll();
}
+function get_work_order_by_id($id) {
+ $pdo = db();
+ $stmt = $pdo->prepare("SELECT wo.*, u.username as assigned_technician_name FROM work_orders wo LEFT JOIN users u ON wo.assigned_technician_id = u.id WHERE wo.id = ?");
+ $stmt->execute([$id]);
+ return $stmt->fetch();
+}
+
function get_kpis($work_orders) {
$total_jobs = count($work_orders);
$completed_jobs = 0;
@@ -30,3 +37,235 @@ function get_kpis($work_orders) {
'revenue' => '12,345' // Static for now
];
}
+
+function create_work_order($customer_name, $customer_email, $job_type, $status, $address, $technician_id) {
+ $pdo = db();
+ $sql = "INSERT INTO work_orders (customer_name, customer_email, job_type, status, address, assigned_technician_id, uuid) VALUES (?, ?, ?, ?, ?, ?, UUID())";
+ $stmt = $pdo->prepare($sql);
+ return $stmt->execute([$customer_name, $customer_email, $job_type, $status, $address, $technician_id]);
+}
+
+function update_work_order($id, $customer_name, $customer_email, $job_type, $status, $address, $technician_id) {
+ $pdo = db();
+ $completed_at_sql = ($status === 'Completed') ? ", completed_at = CURRENT_TIMESTAMP" : "";
+ $sql = "UPDATE work_orders SET customer_name = ?, customer_email = ?, job_type = ?, status = ?, address = ?, assigned_technician_id = ? {$completed_at_sql} WHERE id = ?";
+ $stmt = $pdo->prepare($sql);
+ return $stmt->execute([$customer_name, $customer_email, $job_type, $status, $address, $technician_id, $id]);
+}
+
+function dispatch_work_order($id) {
+ $pdo = db();
+ $sql = "UPDATE work_orders SET status = 'In Progress', dispatched_at = CURRENT_TIMESTAMP WHERE id = ?";
+ $stmt = $pdo->prepare($sql);
+ return $stmt->execute([$id]);
+}
+
+function get_work_order_by_uuid($uuid) {
+ $pdo = db();
+ $stmt = $pdo->prepare("SELECT wo.*, u.username as assigned_technician_name FROM work_orders wo LEFT JOIN users u ON wo.assigned_technician_id = u.id WHERE wo.uuid = ?");
+ $stmt->execute([$uuid]);
+ return $stmt->fetch();
+}
+
+function search_work_orders($search_term = '', $status = '') {
+ $pdo = db();
+ $sql = "SELECT wo.*, u.username as assigned_technician FROM work_orders wo LEFT JOIN users u ON wo.assigned_technician_id = u.id";
+ $params = [];
+ $where_clauses = [];
+
+ if (!empty($search_term)) {
+ $where_clauses[] = "(wo.customer_name LIKE ? OR wo.job_type LIKE ? OR wo.address LIKE ? OR u.username LIKE ?)";
+ $search_param = "%{$search_term}%";
+ array_push($params, $search_param, $search_param, $search_param, $search_param);
+ }
+
+ if (!empty($status)) {
+ $where_clauses[] = "wo.status = ?";
+ $params[] = $status;
+ }
+
+ if (!empty($where_clauses)) {
+ $sql .= " WHERE " . implode(' AND ', $where_clauses);
+ }
+
+ $sql .= " ORDER BY wo.created_at DESC";
+
+ $stmt = $pdo->prepare($sql);
+ $stmt->execute($params);
+ return $stmt->fetchAll();
+}
+
+// --- User & Auth Functions ---
+
+function create_user($username, $email, $password, $role) {
+ $pdo = db();
+ $hash = password_hash($password, PASSWORD_DEFAULT);
+ $sql = "INSERT INTO users (username, email, password, role) VALUES (?, ?, ?, ?)";
+ $stmt = $pdo->prepare($sql);
+ return $stmt->execute([$username, $email, $hash, $role]);
+}
+
+function get_user_by_username($username) {
+ $pdo = db();
+ $stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");
+ $stmt->execute([$username]);
+ return $stmt->fetch();
+}
+
+function get_user_by_id($id) {
+ $pdo = db();
+ $stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
+ $stmt->execute([$id]);
+ return $stmt->fetch();
+}
+
+function get_users_by_role($role) {
+ $pdo = db();
+ $stmt = $pdo->prepare("SELECT id, username FROM users WHERE role = ? ORDER BY username ASC");
+ $stmt->execute([$role]);
+ return $stmt->fetchAll();
+}
+
+function start_secure_session() {
+ $session_name = 'sec_session_id'; // Set a custom session name
+ $secure = true; // Set to true if using https.
+ $httponly = true; // This stops javascript being able to access the session id.
+
+ ini_set('session.use_only_cookies', 1); // Forces sessions to only use cookies.
+ $cookieParams = session_get_cookie_params();
+ session_set_cookie_params($cookieParams["lifetime"], $cookieParams["path"], $cookieParams["domain"], $secure, $httponly);
+
+ session_name($session_name);
+ session_start();
+ session_regenerate_id();
+}
+
+function is_logged_in() {
+ return isset($_SESSION['user_id']);
+}
+
+function require_login() {
+ if (!is_logged_in()) {
+ header('Location: /login.php');
+ exit();
+ }
+}
+
+function get_user_role() {
+ return isset($_SESSION['role']) ? $_SESSION['role'] : null;
+}
+
+// --- Reporting Functions ---
+
+function get_work_order_counts_by_status() {
+ $pdo = db();
+ $sql = "SELECT status, COUNT(*) as count FROM work_orders GROUP BY status";
+ $stmt = $pdo->query($sql);
+ return $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
+}
+
+function get_work_order_counts_by_technician() {
+ $pdo = db();
+ $sql = "SELECT u.username, COUNT(wo.id) as count FROM work_orders wo JOIN users u ON wo.assigned_technician_id = u.id GROUP BY u.username";
+ $stmt = $pdo->query($sql);
+ return $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
+}
+
+function get_average_completion_time() {
+ $pdo = db();
+ $sql = "SELECT AVG(TIMESTAMPDIFF(HOUR, created_at, completed_at)) as avg_hours FROM work_orders WHERE completed_at IS NOT NULL";
+ $stmt = $pdo->query($sql);
+ $result = $stmt->fetch();
+ return $result ? round($result['avg_hours'], 1) : 0;
+}
+
+// --- Inventory Functions ---
+
+function get_inventory_items() {
+ $pdo = db();
+ $stmt = $pdo->query("SELECT * FROM inventory ORDER BY item_name ASC");
+ return $stmt->fetchAll();
+}
+
+function get_inventory_item_by_id($id) {
+ $pdo = db();
+ $stmt = $pdo->prepare("SELECT * FROM inventory WHERE id = ?");
+ $stmt->execute([$id]);
+ return $stmt->fetch();
+}
+
+function add_inventory_item($item_name, $quantity, $price) {
+ $pdo = db();
+ $sql = "INSERT INTO inventory (item_name, quantity, price) VALUES (?, ?, ?)";
+ $stmt = $pdo->prepare($sql);
+ return $stmt->execute([$item_name, $quantity, $price]);
+}
+
+function update_inventory_item($id, $item_name, $quantity, $price) {
+ $pdo = db();
+ $sql = "UPDATE inventory SET item_name = ?, quantity = ?, price = ? WHERE id = ?";
+ $stmt = $pdo->prepare($sql);
+ return $stmt->execute([$item_name, $quantity, $price, $id]);
+}
+
+function delete_inventory_item($id) {
+ $pdo = db();
+ $sql = "DELETE FROM inventory WHERE id = ?";
+ $stmt = $pdo->prepare($sql);
+ return $stmt->execute([$id]);
+}
+
+function get_work_order_parts($work_order_id) {
+ $pdo = db();
+ $sql = "SELECT i.item_name, wp.quantity_used, i.price, wp.id as part_id FROM work_order_parts wp JOIN inventory i ON wp.inventory_id = i.id WHERE wp.work_order_id = ?";
+ $stmt = $pdo->prepare($sql);
+ $stmt->execute([$work_order_id]);
+ return $stmt->fetchAll();
+}
+
+function add_part_to_work_order($work_order_id, $inventory_id, $quantity_used) {
+ $pdo = db();
+ $pdo->beginTransaction();
+ try {
+ $sql = "INSERT INTO work_order_parts (work_order_id, inventory_id, quantity_used) VALUES (?, ?, ?)";
+ $stmt = $pdo->prepare($sql);
+ $stmt->execute([$work_order_id, $inventory_id, $quantity_used]);
+
+ $sql = "UPDATE inventory SET quantity = quantity - ? WHERE id = ?";
+ $stmt = $pdo->prepare($sql);
+ $stmt->execute([$quantity_used, $inventory_id]);
+
+ $pdo->commit();
+ return true;
+ } catch (Exception $e) {
+ $pdo->rollBack();
+ return false;
+ }
+}
+
+function remove_part_from_work_order($part_id) {
+ $pdo = db();
+ $pdo->beginTransaction();
+ try {
+ $sql = "SELECT inventory_id, quantity_used FROM work_order_parts WHERE id = ?";
+ $stmt = $pdo->prepare($sql);
+ $stmt->execute([$part_id]);
+ $part = $stmt->fetch();
+
+ if ($part) {
+ $sql = "UPDATE inventory SET quantity = quantity + ? WHERE id = ?";
+ $stmt = $pdo->prepare($sql);
+ $stmt->execute([$part['quantity_used'], $part['inventory_id']]);
+
+ $sql = "DELETE FROM work_order_parts WHERE id = ?";
+ $stmt = $pdo->prepare($sql);
+ $stmt->execute([$part_id]);
+ }
+
+ $pdo->commit();
+ return true;
+ } catch (Exception $e) {
+ $pdo->rollBack();
+ return false;
+ }
+}
diff --git a/index.php b/index.php
index 2ead38d..810e3d0 100644
--- a/index.php
+++ b/index.php
@@ -23,45 +23,62 @@
require_once 'db/config.php';
require_once 'includes/functions.php';
-$all_work_orders = get_all_work_orders();
-$kpis = get_kpis($all_work_orders);
+start_secure_session();
+require_login();
+
+$search_term = $_GET['search'] ?? '';
+$status = $_GET['status'] ?? '';
+
+$work_orders = search_work_orders($search_term, $status);
+$kpis = get_kpis($work_orders);
+
+$all_statuses = ['Pending Dispatch', 'In Progress', 'Completed', 'On Hold', 'Cancelled'];
-$active_jobs = get_work_orders_by_status('In Progress');
-$pending_dispatch = get_work_orders_by_status('Pending Dispatch');
?>
-
+
-
@@ -89,7 +106,7 @@ $pending_dispatch = get_work_orders_by_status('Pending Dispatch');
-
+
Pending Dispatch
@@ -119,8 +136,8 @@ $pending_dispatch = get_work_orders_by_status('Pending Dispatch');
-
- Total Jobs
+
+ Revenue
@@ -128,60 +145,68 @@ $pending_dispatch = get_work_orders_by_status('Pending Dispatch');
-
-
-
-
+
+
+
-
-
-
+
diff --git a/inventory.php b/inventory.php
new file mode 100644
index 0000000..7a29766
--- /dev/null
+++ b/inventory.php
@@ -0,0 +1,159 @@
+
+
+
+
+
+
+
Inventory - Flatlogic
+
+
+
+
+
+
+
+
+
+
+
Inventory
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Item Name |
+ Quantity |
+ Price |
+ |
+
+
+
+
+
+ | No inventory items found. |
+
+
+
+
+ |
+ |
+ $ |
+
+ Edit
+ Delete
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/login.php b/login.php
new file mode 100644
index 0000000..843eb36
--- /dev/null
+++ b/login.php
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
Login - RedSolutions CRM
+
+
+
+
+
+
+
+
+
+
+
RedSolutions CRM
+
Please sign in to continue
+
+
+
+
+
+
+
+
+
+
© Flatlogic
+
+
+
+
+
+
diff --git a/logout.php b/logout.php
new file mode 100644
index 0000000..82e7e57
--- /dev/null
+++ b/logout.php
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
Work Order Status - Flatlogic
+
+
+
+
+
+
+
+
+
+
+
+
Work Order #
+
Status:
+
+
+
+
+
+
+
Customer:
+
Job Type:
+
+
+
Assigned Technician:
+
Last Updated:
+
+
+
+
+
+
+
Invalid Work Order
+
The work order you are looking for could not be found. Please check the link and try again.
+
+
+
+
+ Powered by Flatlogic
+
+
+
+
+
+
diff --git a/reports.php b/reports.php
new file mode 100644
index 0000000..422669d
--- /dev/null
+++ b/reports.php
@@ -0,0 +1,176 @@
+
+
+
+
+
+
Reports - RedSolutions CRM
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Hours
+ Avg. Completion Time
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Status |
+ Count |
+
+
+
+
+
+ | No data available. |
+
+
+ $count): ?>
+
+ |
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Technician |
+ Assigned Orders |
+
+
+
+
+
+ | No technicians assigned to orders. |
+
+
+ $count): ?>
+
+ |
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/work_order_details.php b/work_order_details.php
new file mode 100644
index 0000000..de9c642
--- /dev/null
+++ b/work_order_details.php
@@ -0,0 +1,233 @@
+
+
+
+
+
+
Work Order # - Flatlogic
+
+
+
+
+
+
+
+
+
+
+Dear {$work_order['customer_name']},
Your work order for {$work_order['job_type']} has been dispatched.
A technician will be in contact with you shortly.
Thank you!
";
+ MailService::sendMail($work_order['customer_email'], $subject, $body);
+ }
+ header("Location: work_order_details.php?id=" . $work_order_id . "&dispatch_success=1");
+ exit;
+}
+
+if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_part'])) {
+ $inventory_id = $_POST['inventory_id'];
+ $quantity_used = $_POST['quantity_used'];
+ add_part_to_work_order($work_order_id, $inventory_id, $quantity_used);
+ header("Location: work_order_details.php?id=" . $work_order_id);
+ exit;
+}
+
+if (isset($_GET['remove_part'])) {
+ $part_id = $_GET['remove_part'];
+ remove_part_from_work_order($part_id);
+ header("Location: work_order_details.php?id=" . $work_order_id);
+ exit;
+}
+
+if ($work_order_id > 0) {
+ $work_order = get_work_order_by_id($work_order_id);
+ $work_order_parts = get_work_order_parts($work_order_id);
+ $inventory_items = get_inventory_items();
+}
+?>
+
+
+
+
+
+
+
+
+
+
+
+
+
Work order dispatched successfully!
+
+
+
+
+
Customer:
+
Job Type:
+
Address:
+
+
+
Status:
+
Technician:
+
Created At:
+
+
Dispatched At:
+
+
+
Completed At:
+
+
+
+
+
+ Error: Work Order not found or invalid ID provided.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Part Name |
+ Quantity Used |
+ Unit Price |
+ Total Price |
+ |
+
+
+
+
+
+ | No parts used yet. |
+
+
+
+
+ |
+ |
+ $ |
+ $ |
+
+ Remove
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Customer Portal
+
Share this link with the customer to allow them to view the status of their work order.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+