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

Create New Work Order

+ Back to Dashboard +
+ +
+
+ +
+ +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+ +
+
+
+
+ + + + + \ 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 + + + + + + + + +
+
+

Edit Inventory Item

+ Back to Inventory +
+ +
+
+ +
+ +
+ +
+ + +
+
+ + +
+
+ + +
+ +
+
+
+
+ + + + 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 #<?php echo htmlspecialchars($work_order['id']); ?> - 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'); - -
-
-
-
- Active Jobs - View All -
-
-
    - -
  • No active jobs.
  • - - -
  • -
    -
    WO-:
    - Customer: | Technician: -
    - -
  • - - -
-
-
+ +
+
+
Work Orders
-
-
-
- Pending Dispatch - View All +
+
+
+
-
-
    - -
  • No jobs pending dispatch.
  • +
    + +
    +
    + +
    + + +
    + + + + + + + + + + + + + + + + + - -
  • -
    -
    WO-:
    - Customer: -
    - -
  • + + + + + + + + + + - - + +
    IDCustomerJob TypeStatusTechnicianCreated At
    No work orders found.
    # + View +
-
+ 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

+
+ + +
+ + +
+
+ Add New Item +
+
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ +
+
+ Inventory Items +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Item NameQuantityPrice
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 + + + + + + + + + + + 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

+
+ + +
+

Work Order #

+

Status:

+
+ +
+
+
+
+

Customer:

+

Job Type:

+
+
+

Assigned Technician:

+

Last Updated:

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

Reports

+
+ + +
+
+
+
+
+
+ +
+
+

Hours

+ Avg. Completion Time +
+
+
+
+
+
+ +
+ +
+
+
+
Work Orders by Status
+
+
+
+ + + + + + + + + + + + + + $count): ?> + + + + + + + +
StatusCount
No data available.
+
+
+
+
+ + +
+
+
+
Work Orders by Technician
+
+
+
+ + + + + + + + + + + + + + $count): ?> + + + + + + + +
TechnicianAssigned Orders
No technicians assigned to orders.
+
+
+
+
+
+
+ + + + + + + \ 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 #<?php echo htmlspecialchars($work_order['id']); ?> - 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 #

+
+ +
+ +
+ + Edit + Back to Dashboard +
+
+ +
+
+
+
+ +
Work order dispatched successfully!
+ + +
+
+

Customer:

+

Job Type:

+

Address:

+
+
+

Status:

+

Technician:

+

Created At:

+ +

Dispatched At:

+ + +

Completed At:

+ +
+
+ + + +
+
+
+
+ +
+
+
+
+ Parts Used +
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Part NameQuantity UsedUnit PriceTotal 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.

+
+ + +
+
+
+
+
+
+ + + + + + +