Compare commits

..

1 Commits

Author SHA1 Message Date
Flatlogic Bot
f44439d5ee KENANGA APPS 01 2026-06-18 09:37:21 +00:00
6 changed files with 379 additions and 577 deletions

File diff suppressed because one or more lines are too long

View File

@ -1,39 +1,15 @@
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const chatForm = document.getElementById('chat-form'); document.querySelectorAll('.toast').forEach((toastEl) => {
const chatInput = document.getElementById('chat-input'); if (window.bootstrap) {
const chatMessages = document.getElementById('chat-messages'); const toast = new bootstrap.Toast(toastEl, { delay: 4200 });
toast.show();
}
});
const appendMessage = (text, sender) => { const moneyInputs = document.querySelectorAll('.money-input');
const msgDiv = document.createElement('div'); moneyInputs.forEach((input) => {
msgDiv.classList.add('message', sender); input.addEventListener('input', () => {
msgDiv.textContent = text; if (Number(input.value) < 0) input.value = '0';
chatMessages.appendChild(msgDiv);
chatMessages.scrollTop = chatMessages.scrollHeight;
};
chatForm.addEventListener('submit', async (e) => {
e.preventDefault();
const message = chatInput.value.trim();
if (!message) return;
appendMessage(message, 'visitor');
chatInput.value = '';
try {
const response = await fetch('api/chat.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message })
});
const data = await response.json();
// Artificial delay for realism
setTimeout(() => {
appendMessage(data.reply, 'bot');
}, 500);
} catch (error) {
console.error('Error:', error);
appendMessage("Sorry, something went wrong. Please try again.", 'bot');
}
}); });
});
}); });

460
index.php
View File

@ -1,150 +1,332 @@
<?php <?php
declare(strict_types=1); require_once __DIR__ . '/db/config.php';
@ini_set('display_errors', '1'); @date_default_timezone_set('Asia/Jakarta');
@error_reporting(E_ALL);
@date_default_timezone_set('UTC');
$phpVersion = PHP_VERSION; $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Sistem manajemen percetakan Kenanga Kreasindo untuk order, SPK, produksi, invoice, dan tracking customer.';
$now = date('Y-m-d H:i:s'); $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
$projectName = $_SERVER['PROJECT_NAME'] ?? 'Kenanga Kreasindo Printing';
function e(string $value): string { return htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); }
function rupiah(float $value): string { return 'Rp ' . number_format($value, 0, ',', '.'); }
function nextNumber(PDO $pdo, string $prefix, string $column): string {
$stmt = $pdo->prepare("SELECT COUNT(*) AS total FROM print_orders WHERE $column LIKE :prefix");
$stmt->execute([':prefix' => $prefix . '%']);
$count = (int)($stmt->fetch()['total'] ?? 0) + 1;
return $prefix . str_pad((string)$count, 5, '0', STR_PAD_LEFT);
}
function statusBadge(string $status): string {
return match ($status) {
'Order diterima' => 'secondary',
'SPK dibuat' => 'info',
'Produksi berjalan' => 'primary',
'QC / Finishing' => 'warning',
'Barang selesai' => 'success',
'Invoice / Tagihan' => 'dark',
default => 'secondary',
};
}
function ensureSchema(PDO $pdo): void {
$pdo->exec("CREATE TABLE IF NOT EXISTS print_orders (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
order_no VARCHAR(40) NOT NULL UNIQUE,
spk_no VARCHAR(40) NOT NULL UNIQUE,
invoice_no VARCHAR(40) NOT NULL UNIQUE,
customer_name VARCHAR(140) NOT NULL,
customer_phone VARCHAR(40) NOT NULL,
customer_email VARCHAR(160) NULL,
project_name VARCHAR(180) NOT NULL,
category VARCHAR(100) NOT NULL,
quantity INT UNSIGNED NOT NULL DEFAULT 1,
size_info VARCHAR(120) NULL,
material VARCHAR(160) NULL,
finishing VARCHAR(180) NULL,
deadline DATE NULL,
operator_name VARCHAR(120) NULL,
cs_name VARCHAR(120) NULL,
status VARCHAR(40) NOT NULL DEFAULT 'Order diterima',
progress_note TEXT NULL,
unit_price DECIMAL(14,2) NOT NULL DEFAULT 0,
discount DECIMAL(14,2) NOT NULL DEFAULT 0,
paid_amount DECIMAL(14,2) NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_phone (customer_phone),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
}
$pdo = db();
ensureSchema($pdo);
$errors = [];
$notice = '';
$createdId = 0;
$statuses = ['Order diterima', 'SPK dibuat', 'Produksi berjalan', 'QC / Finishing', 'Barang selesai', 'Invoice / Tagihan'];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
if ($action === 'create_order') {
$customerName = trim((string)($_POST['customer_name'] ?? ''));
$customerPhone = preg_replace('/[^0-9+]/', '', (string)($_POST['customer_phone'] ?? ''));
$customerEmail = trim((string)($_POST['customer_email'] ?? ''));
$project = trim((string)($_POST['project_name'] ?? ''));
$category = trim((string)($_POST['category'] ?? ''));
$qty = max(1, (int)($_POST['quantity'] ?? 1));
$deadline = trim((string)($_POST['deadline'] ?? '')) ?: null;
$unitPrice = max(0, (float)($_POST['unit_price'] ?? 0));
$discount = max(0, (float)($_POST['discount'] ?? 0));
$paid = max(0, (float)($_POST['paid_amount'] ?? 0));
foreach ([
'Nama customer' => $customerName,
'Nomor telepon' => $customerPhone,
'Nama pekerjaan' => $project,
'Kategori' => $category,
] as $label => $value) {
if ($value === '') { $errors[] = $label . ' wajib diisi.'; }
}
if ($customerEmail !== '' && !filter_var($customerEmail, FILTER_VALIDATE_EMAIL)) { $errors[] = 'Format email customer tidak valid.'; }
if (!$errors) {
$year = date('y');
$month = date('m');
$orderNo = nextNumber($pdo, 'ORD-' . date('Y') . '-', 'order_no');
$invoiceNo = nextNumber($pdo, 'VI-' . $year . '-', 'invoice_no');
$spkPrefix = str_pad((string)(((int)$pdo->query('SELECT COUNT(*) FROM print_orders')->fetchColumn()) + 1), 2, '0', STR_PAD_LEFT) . '-SPK-' . $month . '-' . date('Y');
$spkNo = $spkPrefix;
$stmt = $pdo->prepare('INSERT INTO print_orders (order_no, spk_no, invoice_no, customer_name, customer_phone, customer_email, project_name, category, quantity, size_info, material, finishing, deadline, operator_name, cs_name, status, progress_note, unit_price, discount, paid_amount) VALUES (:order_no, :spk_no, :invoice_no, :customer_name, :customer_phone, :customer_email, :project_name, :category, :quantity, :size_info, :material, :finishing, :deadline, :operator_name, :cs_name, :status, :progress_note, :unit_price, :discount, :paid_amount)');
$stmt->execute([
':order_no' => $orderNo,
':spk_no' => $spkNo,
':invoice_no' => $invoiceNo,
':customer_name' => $customerName,
':customer_phone' => $customerPhone,
':customer_email' => $customerEmail ?: null,
':project_name' => $project,
':category' => $category,
':quantity' => $qty,
':size_info' => trim((string)($_POST['size_info'] ?? '')) ?: null,
':material' => trim((string)($_POST['material'] ?? '')) ?: null,
':finishing' => trim((string)($_POST['finishing'] ?? '')) ?: null,
':deadline' => $deadline,
':operator_name' => trim((string)($_POST['operator_name'] ?? '')) ?: null,
':cs_name' => trim((string)($_POST['cs_name'] ?? '')) ?: null,
':status' => 'SPK dibuat',
':progress_note' => trim((string)($_POST['progress_note'] ?? 'Order diterima dan SPK siap dicetak.')),
':unit_price' => $unitPrice,
':discount' => $discount,
':paid_amount' => $paid,
]);
$createdId = (int)$pdo->lastInsertId();
$notice = 'Order berhasil dibuat. SPK dan Invoice sudah otomatis tersedia.';
}
} elseif ($action === 'update_status') {
$id = (int)($_POST['id'] ?? 0);
$status = (string)($_POST['status'] ?? '');
$note = trim((string)($_POST['progress_note'] ?? ''));
if ($id > 0 && in_array($status, $statuses, true)) {
$stmt = $pdo->prepare('UPDATE print_orders SET status = :status, progress_note = :note WHERE id = :id');
$stmt->execute([':status' => $status, ':note' => $note, ':id' => $id]);
$notice = 'Status produksi berhasil diperbarui.';
}
}
}
$trackResult = null;
if (isset($_GET['track_order'], $_GET['track_phone'])) {
$trackOrder = trim((string)$_GET['track_order']);
$trackPhone = preg_replace('/[^0-9+]/', '', (string)$_GET['track_phone']);
$stmt = $pdo->prepare('SELECT * FROM print_orders WHERE order_no = :order_no AND customer_phone = :phone LIMIT 1');
$stmt->execute([':order_no' => $trackOrder, ':phone' => $trackPhone]);
$trackResult = $stmt->fetch() ?: false;
}
$orders = $pdo->query('SELECT * FROM print_orders ORDER BY created_at DESC LIMIT 25')->fetchAll();
$totalOrders = (int)$pdo->query('SELECT COUNT(*) FROM print_orders')->fetchColumn();
$activeOrders = (int)$pdo->query("SELECT COUNT(*) FROM print_orders WHERE status NOT IN ('Barang selesai','Invoice / Tagihan')")->fetchColumn();
$finishedOrders = (int)$pdo->query("SELECT COUNT(*) FROM print_orders WHERE status = 'Barang selesai'")->fetchColumn();
$finance = $pdo->query('SELECT COALESCE(SUM(quantity * unit_price - discount),0) AS billed, COALESCE(SUM(paid_amount),0) AS paid FROM print_orders')->fetch();
$receivable = max(0, (float)$finance['billed'] - (float)$finance['paid']);
?> ?>
<!doctype html> <!doctype html>
<html lang="en"> <html lang="id">
<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><?= e($projectName) ?> — Order, SPK, Produksi & Invoice</title>
<?php <meta name="description" content="<?= e($projectDescription) ?>">
// Read project preview data from environment <meta property="og:title" content="<?= e($projectName) ?>">
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? ''; <meta property="og:description" content="<?= e($projectDescription) ?>">
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ''; <meta property="twitter:title" content="<?= e($projectName) ?>">
?> <meta property="twitter:description" content="<?= e($projectDescription) ?>">
<?php if ($projectDescription): ?> <?php if ($projectImageUrl): ?>
<!-- Meta description --> <meta property="og:image" content="<?= e($projectImageUrl) ?>">
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' /> <meta property="twitter:image" content="<?= e($projectImageUrl) ?>">
<!-- Open Graph meta tags --> <?php endif; ?>
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" /> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Twitter meta tags --> <link rel="stylesheet" href="assets/css/custom.css?v=<?= time() ?>">
<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.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-color-start: #6a11cb;
--bg-color-end: #2575fc;
--text-color: #ffffff;
--card-bg-color: rgba(255, 255, 255, 0.01);
--card-border-color: rgba(255, 255, 255, 0.1);
}
body {
margin: 0;
font-family: 'Inter', sans-serif;
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
overflow: hidden;
position: relative;
}
body::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
animation: bg-pan 20s linear infinite;
z-index: -1;
}
@keyframes bg-pan {
0% { background-position: 0% 0%; }
100% { background-position: 100% 100%; }
}
main {
padding: 2rem;
}
.card {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: 16px;
padding: 2rem;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
}
.loader {
margin: 1.25rem auto 1.25rem;
width: 48px;
height: 48px;
border: 3px solid rgba(255, 255, 255, 0.25);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.hint {
opacity: 0.9;
}
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap; border: 0;
}
h1 {
font-size: 3rem;
font-weight: 700;
margin: 0 0 1rem;
letter-spacing: -1px;
}
p {
margin: 0.5rem 0;
font-size: 1.1rem;
}
code {
background: rgba(0,0,0,0.2);
padding: 2px 6px;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
footer {
position: absolute;
bottom: 1rem;
font-size: 0.8rem;
opacity: 0.7;
}
</style>
</head> </head>
<body> <body>
<main> <nav class="navbar navbar-expand-lg kk-navbar sticky-top">
<div class="card"> <div class="container-fluid px-3 px-lg-4">
<h1>Analyzing your requirements and generating your website…</h1> <a class="navbar-brand d-flex align-items-center gap-2" href="#top" aria-label="Kenanga Kreasindo Home">
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes"> <span class="brand-mark">K</span><span>Kenanga Kreasindo</span>
<span class="sr-only">Loading…</span> </a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navMenu" aria-controls="navMenu" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button>
<div class="collapse navbar-collapse" id="navMenu">
<div class="navbar-nav ms-auto gap-lg-2">
<a class="nav-link" href="#order">Order</a>
<a class="nav-link" href="#produksi">Produksi</a>
<a class="nav-link" href="#tracking">Tracking Customer</a>
<a class="nav-link" href="#laporan">Keuangan</a>
</div> </div>
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
<p class="hint">This page will update automatically as the plan is implemented.</p>
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
</div> </div>
</main> </div>
<footer> </nav>
Page updated: <?= htmlspecialchars($now) ?> (UTC)
</footer> <main id="top" class="container-fluid px-3 px-lg-4 py-4">
<?php if ($notice || $errors): ?>
<div class="toast-container position-fixed top-0 end-0 p-3">
<div class="toast show" role="status" aria-live="polite" aria-atomic="true">
<div class="toast-header"><strong class="me-auto"><?= $errors ? 'Validasi belum lengkap' : 'Berhasil' ?></strong><button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button></div>
<div class="toast-body"><?= $errors ? e(implode(' ', $errors)) : e($notice) ?></div>
</div>
</div>
<?php endif; ?>
<section class="hero panel mb-4" aria-labelledby="heroTitle">
<div class="row g-4 align-items-center">
<div class="col-lg-7">
<p class="eyebrow mb-2">Sistem Percetakan</p>
<h1 id="heroTitle">Order masuk, SPK, produksi, invoice, dan tracking customer dalam satu alur.</h1>
<p class="hero-copy mb-4">MVP awal untuk Percetakan Kenanga Kreasindo: admin membuat order, sistem menerbitkan SPK dan invoice, produksi memperbarui status, customer mengecek progres memakai nomor order + telepon.</p>
<div class="d-flex flex-wrap gap-2">
<a href="#order" class="btn btn-dark btn-lg">Buat Order & SPK</a>
<a href="#tracking" class="btn btn-outline-dark btn-lg">Cek Status Customer</a>
</div>
</div>
<div class="col-lg-5">
<div class="company-card">
<div class="small text-muted">Percetakan</div>
<h2 class="h4 mb-3">Kenanga Kreasindo</h2>
<div class="meta-row"><span>Alamat</span><strong>Perum BCL Jln Kenanga Raya</strong></div>
<div class="meta-row"><span>Email</span><strong>kenangakreasindo@gmail.com</strong></div>
<div class="meta-row"><span>Runtime</span><strong>PHP <?= e(PHP_VERSION) ?></strong></div>
</div>
</div>
</div>
</section>
<section id="laporan" class="row g-3 mb-4" aria-label="Ringkasan laporan">
<div class="col-6 col-lg-3"><div class="stat-card"><span>Total Order</span><strong><?= $totalOrders ?></strong></div></div>
<div class="col-6 col-lg-3"><div class="stat-card"><span>Produksi Aktif</span><strong><?= $activeOrders ?></strong></div></div>
<div class="col-6 col-lg-3"><div class="stat-card"><span>Barang Selesai</span><strong><?= $finishedOrders ?></strong></div></div>
<div class="col-6 col-lg-3"><div class="stat-card"><span>Piutang</span><strong><?= e(rupiah($receivable)) ?></strong></div></div>
</section>
<div class="row g-4">
<section id="order" class="col-xl-5" aria-labelledby="orderTitle">
<div class="panel h-100">
<div class="section-heading">
<p class="eyebrow">Penerimaan Order</p>
<h2 id="orderTitle">Buat order + SPK</h2>
</div>
<form method="post" class="kk-form" novalidate>
<input type="hidden" name="action" value="create_order">
<div class="row g-3">
<div class="col-md-6"><label class="form-label">Nama Customer *</label><input class="form-control" name="customer_name" required></div>
<div class="col-md-6"><label class="form-label">Telepon *</label><input class="form-control" name="customer_phone" required placeholder="08..."></div>
<div class="col-12"><label class="form-label">Email</label><input class="form-control" name="customer_email" type="email" placeholder="opsional"></div>
<div class="col-md-8"><label class="form-label">Nama Pekerjaan *</label><input class="form-control" name="project_name" required placeholder="Contoh: Banner Grand Opening"></div>
<div class="col-md-4"><label class="form-label">Kategori *</label><select class="form-select" name="category" required><option value="Digital Printing">Digital Printing</option><option value="Offset">Offset</option><option value="Merchandise">Merchandise</option><option value="Apparel">Apparel</option><option value="Lainnya">Lainnya</option></select></div>
<div class="col-md-4"><label class="form-label">Qty *</label><input class="form-control" name="quantity" type="number" min="1" value="1" required></div>
<div class="col-md-4"><label class="form-label">Ukuran</label><input class="form-control" name="size_info" placeholder="A3 / 3x1 m"></div>
<div class="col-md-4"><label class="form-label">Deadline</label><input class="form-control" name="deadline" type="date"></div>
<div class="col-md-6"><label class="form-label">Bahan</label><input class="form-control" name="material" placeholder="Flexi 280 / Art carton"></div>
<div class="col-md-6"><label class="form-label">Finishing</label><input class="form-control" name="finishing" placeholder="Laminasi, potong, mata ayam"></div>
<div class="col-md-6"><label class="form-label">CS</label><input class="form-control" name="cs_name" placeholder="Nama CS"></div>
<div class="col-md-6"><label class="form-label">Operator</label><input class="form-control" name="operator_name" placeholder="Nama operator"></div>
<div class="col-md-4"><label class="form-label">Harga / Unit</label><input class="form-control money-input" name="unit_price" type="number" min="0" value="0"></div>
<div class="col-md-4"><label class="form-label">Diskon</label><input class="form-control money-input" name="discount" type="number" min="0" value="0"></div>
<div class="col-md-4"><label class="form-label">Terbayar</label><input class="form-control money-input" name="paid_amount" type="number" min="0" value="0"></div>
<div class="col-12"><label class="form-label">Detail Pesanan / Catatan SPK</label><textarea class="form-control" name="progress_note" rows="3" placeholder="Instruksi file, warna, referensi, catatan operator..."></textarea></div>
</div>
<div class="d-flex justify-content-between align-items-center mt-4">
<small class="text-muted">* wajib diisi. Nomor order, SPK, dan invoice otomatis.</small>
<button class="btn btn-dark" type="submit">Simpan Order</button>
</div>
</form>
</div>
</section>
<section id="produksi" class="col-xl-7" aria-labelledby="productionTitle">
<div class="panel h-100">
<div class="section-heading d-flex justify-content-between align-items-start gap-3">
<div><p class="eyebrow">Produksi & Monitoring</p><h2 id="productionTitle">Daftar order terbaru</h2></div>
<?php if ($createdId): ?><a class="btn btn-outline-dark btn-sm" href="order_detail.php?id=<?= $createdId ?>">Lihat order baru</a><?php endif; ?>
</div>
<?php if (!$orders): ?>
<div class="empty-state"><strong>Belum ada order.</strong><span>Buat order pertama untuk menghasilkan SPK, tracking, dan invoice.</span></div>
<?php else: ?>
<div class="table-responsive">
<table class="table align-middle kk-table">
<thead><tr><th>Order</th><th>Customer</th><th>Pekerjaan</th><th>Status</th><th>Tagihan</th><th>Aksi</th></tr></thead>
<tbody>
<?php foreach ($orders as $order): $amount = ((float)$order['quantity'] * (float)$order['unit_price']) - (float)$order['discount']; ?>
<tr>
<td><strong><?= e($order['order_no']) ?></strong><div class="small text-muted"><?= e(date('d M Y', strtotime($order['created_at']))) ?></div></td>
<td><?= e($order['customer_name']) ?><div class="small text-muted"><?= e($order['customer_phone']) ?></div></td>
<td><?= e($order['project_name']) ?><div class="small text-muted"><?= e($order['category']) ?> · <?= (int)$order['quantity'] ?> pcs</div></td>
<td><span class="badge text-bg-<?= e(statusBadge($order['status'])) ?>"><?= e($order['status']) ?></span></td>
<td><?= e(rupiah(max(0, $amount))) ?><div class="small text-muted">Bayar <?= e(rupiah((float)$order['paid_amount'])) ?></div></td>
<td><div class="btn-group btn-group-sm"><a class="btn btn-outline-dark" href="order_detail.php?id=<?= (int)$order['id'] ?>">Detail</a><a class="btn btn-outline-dark" href="spk.php?id=<?= (int)$order['id'] ?>">SPK</a><a class="btn btn-outline-dark" href="invoice.php?id=<?= (int)$order['id'] ?>">Invoice</a></div></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</section>
</div>
<section id="tracking" class="panel mt-4" aria-labelledby="trackingTitle">
<div class="row g-4 align-items-start">
<div class="col-lg-5">
<div class="section-heading"><p class="eyebrow">Customer Portal</p><h2 id="trackingTitle">Cek progres order</h2></div>
<p class="text-muted">Customer tidak perlu login. Masukkan nomor order dan telepon yang tercatat di order.</p>
<form method="get" class="row g-2">
<div class="col-md-6"><label class="form-label">Nomor Order</label><input class="form-control" name="track_order" value="<?= e((string)($_GET['track_order'] ?? '')) ?>" placeholder="ORD-2026-00001" required></div>
<div class="col-md-6"><label class="form-label">Telepon</label><input class="form-control" name="track_phone" value="<?= e((string)($_GET['track_phone'] ?? '')) ?>" placeholder="08..." required></div>
<div class="col-12"><button class="btn btn-dark" type="submit">Cek Status</button></div>
</form>
</div>
<div class="col-lg-7">
<?php if ($trackResult === null): ?>
<div class="empty-state"><strong>Hasil tracking muncul di sini.</strong><span>Gunakan nomor order dari admin/Kasir.</span></div>
<?php elseif ($trackResult === false): ?>
<div class="alert alert-warning mb-0">Order tidak ditemukan. Pastikan nomor order dan telepon sudah benar.</div>
<?php else: ?>
<div class="tracking-card">
<div class="d-flex justify-content-between gap-3 flex-wrap"><div><span class="small text-muted">Order</span><h3><?= e($trackResult['order_no']) ?></h3></div><span class="badge text-bg-<?= e(statusBadge($trackResult['status'])) ?> align-self-start"><?= e($trackResult['status']) ?></span></div>
<p class="mb-2"><strong><?= e($trackResult['project_name']) ?></strong> — <?= e($trackResult['category']) ?>, <?= (int)$trackResult['quantity'] ?> pcs</p>
<p class="text-muted mb-3"><?= nl2br(e((string)$trackResult['progress_note'])) ?></p>
<ol class="progress-steps">
<?php $current = array_search($trackResult['status'], $statuses, true); foreach ($statuses as $i => $s): ?>
<li class="<?= $i <= (int)$current ? 'done' : '' ?>"><span><?= e($s) ?></span></li>
<?php endforeach; ?>
</ol>
</div>
<?php endif; ?>
</div>
</div>
</section>
</main>
<footer class="container-fluid px-3 px-lg-4 pb-4">
<div class="footer-line">© <?= date('Y') ?> Kenanga Kreasindo · Perum BCL Jln Kenanga Raya · kenangakreasindo@gmail.com</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?= time() ?>"></script>
</body> </body>
</html> </html>

15
invoice.php Normal file
View File

@ -0,0 +1,15 @@
<?php
require_once __DIR__ . '/db/config.php';
@date_default_timezone_set('Asia/Jakarta');
function e(string $value): string { return htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); }
function rupiah(float $value): string { return 'Rp ' . number_format($value, 0, ',', '.'); }
$pdo = db();
$stmt = $pdo->prepare('SELECT * FROM print_orders WHERE id = :id LIMIT 1');
$stmt->execute([':id' => max(0, (int)($_GET['id'] ?? 0))]);
$order = $stmt->fetch();
if (!$order) { http_response_code(404); echo 'Invoice tidak ditemukan.'; exit; }
$subtotal = (float)$order['quantity'] * (float)$order['unit_price'];
$total = max(0, $subtotal - (float)$order['discount']);
$remaining = max(0, $total - (float)$order['paid_amount']);
?>
<!doctype html><html lang="id"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Invoice <?= e($order['invoice_no']) ?> — Kenanga Kreasindo</title><meta name="description" content="Invoice Kenanga Kreasindo."><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"><link rel="stylesheet" href="assets/css/custom.css?v=<?= time() ?>"></head><body class="print-body"><main class="print-sheet"><div class="print-actions"><a class="btn btn-outline-dark btn-sm" href="order_detail.php?id=<?= (int)$order['id'] ?>">← Detail</a><button class="btn btn-dark btn-sm" onclick="window.print()">Print / Save PDF</button></div><section class="doc-card"><header class="doc-header"><div><h1>Kenanga Kreasindo</h1><p>Perum BCL Jln Kenanga Raya · kenangakreasindo@gmail.com</p></div><div class="doc-number"><span>Invoice No</span><strong><?= e($order['invoice_no']) ?></strong><small><?= e(date('d M Y', strtotime($order['created_at']))) ?></small></div></header><div class="row g-3 mb-4"><div class="col-6"><div class="doc-box"><span>Company / Customer</span><strong><?= e($order['customer_name']) ?></strong><small><?= e($order['customer_phone']) ?></small></div></div><div class="col-6"><div class="doc-box"><span>Project</span><strong><?= e($order['project_name']) ?></strong><small>Order: <?= e($order['order_no']) ?></small></div></div></div><table class="table invoice-table"><thead><tr><th>Item</th><th>Category</th><th class="text-end">Qty</th><th class="text-end">Disc</th><th class="text-end">Price/Unit</th><th class="text-end">Amount</th></tr></thead><tbody><tr><td><?= e($order['project_name']) ?></td><td><?= e($order['category']) ?></td><td class="text-end"><?= (int)$order['quantity'] ?></td><td class="text-end"><?= e(rupiah((float)$order['discount'])) ?></td><td class="text-end"><?= e(rupiah((float)$order['unit_price'])) ?></td><td class="text-end"><?= e(rupiah($total)) ?></td></tr></tbody></table><div class="row justify-content-end"><div class="col-md-5"><div class="total-box"><div><span>Subtotal</span><strong><?= e(rupiah($subtotal)) ?></strong></div><div><span>Disc</span><strong><?= e(rupiah((float)$order['discount'])) ?></strong></div><div><span>Total</span><strong><?= e(rupiah($total)) ?></strong></div><div><span>Paid</span><strong><?= e(rupiah((float)$order['paid_amount'])) ?></strong></div><div class="remaining"><span>Remaining payment</span><strong><?= e(rupiah($remaining)) ?></strong></div></div></div></div><div class="payment-box"><strong>Informasi Pembayaran</strong><p>Transfer ke rekening resmi Kenanga Kreasindo. Nomor rekening dapat diisi pada iterasi berikutnya sesuai rekening aktif.</p></div><div class="signature-row"><div>Received</div><div>Kenanga Kreasindo</div><div>Customer</div></div></section></main></body></html>

20
order_detail.php Normal file
View File

@ -0,0 +1,20 @@
<?php
require_once __DIR__ . '/db/config.php';
@date_default_timezone_set('Asia/Jakarta');
function e(string $value): string { return htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); }
function rupiah(float $value): string { return 'Rp ' . number_format($value, 0, ',', '.'); }
function badge(string $status): string { return match ($status) { 'SPK dibuat' => 'info', 'Produksi berjalan' => 'primary', 'QC / Finishing' => 'warning', 'Barang selesai' => 'success', 'Invoice / Tagihan' => 'dark', default => 'secondary' }; }
$pdo = db();
$id = max(0, (int)($_GET['id'] ?? 0));
$stmt = $pdo->prepare('SELECT * FROM print_orders WHERE id = :id LIMIT 1');
$stmt->execute([':id' => $id]);
$order = $stmt->fetch();
if (!$order) { http_response_code(404); echo 'Order tidak ditemukan.'; exit; }
$statuses = ['Order diterima', 'SPK dibuat', 'Produksi berjalan', 'QC / Finishing', 'Barang selesai', 'Invoice / Tagihan'];
$amount = ((float)$order['quantity'] * (float)$order['unit_price']) - (float)$order['discount'];
$remaining = max(0, $amount - (float)$order['paid_amount']);
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Detail order percetakan Kenanga Kreasindo.';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
?>
<!doctype html><html lang="id"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Detail <?= e($order['order_no']) ?> — Kenanga Kreasindo</title><meta name="description" content="<?= e($projectDescription) ?>"><?php if ($projectImageUrl): ?><meta property="og:image" content="<?= e($projectImageUrl) ?>"><meta property="twitter:image" content="<?= e($projectImageUrl) ?>"><?php endif; ?><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"><link rel="stylesheet" href="assets/css/custom.css?v=<?= time() ?>"></head>
<body><main class="container py-4"><a class="btn btn-outline-dark btn-sm mb-3" href="index.php#produksi"> Kembali</a><div class="panel"><div class="d-flex justify-content-between flex-wrap gap-3"><div><p class="eyebrow">Detail Order</p><h1><?= e($order['order_no']) ?></h1><p class="text-muted mb-0"><?= e($order['customer_name']) ?> · <?= e($order['customer_phone']) ?></p></div><span class="badge text-bg-<?= e(badge($order['status'])) ?> align-self-start"><?= e($order['status']) ?></span></div><hr><div class="row g-4"><div class="col-lg-7"><h2 class="h5">Pekerjaan</h2><dl class="detail-grid"><dt>Project</dt><dd><?= e($order['project_name']) ?></dd><dt>Kategori</dt><dd><?= e($order['category']) ?></dd><dt>Qty</dt><dd><?= (int)$order['quantity'] ?></dd><dt>Ukuran</dt><dd><?= e((string)($order['size_info'] ?: '-')) ?></dd><dt>Bahan</dt><dd><?= e((string)($order['material'] ?: '-')) ?></dd><dt>Finishing</dt><dd><?= e((string)($order['finishing'] ?: '-')) ?></dd><dt>Deadline</dt><dd><?= e($order['deadline'] ? date('d M Y', strtotime($order['deadline'])) : '-') ?></dd></dl></div><div class="col-lg-5"><h2 class="h5">Keuangan</h2><dl class="detail-grid"><dt>Invoice</dt><dd><?= e($order['invoice_no']) ?></dd><dt>Total</dt><dd><?= e(rupiah(max(0, $amount))) ?></dd><dt>Terbayar</dt><dd><?= e(rupiah((float)$order['paid_amount'])) ?></dd><dt>Sisa Tagihan</dt><dd><strong><?= e(rupiah($remaining)) ?></strong></dd></dl></div></div><hr><form method="post" action="index.php#produksi" class="row g-3"><input type="hidden" name="action" value="update_status"><input type="hidden" name="id" value="<?= (int)$order['id'] ?>"><div class="col-md-4"><label class="form-label">Update Status</label><select class="form-select" name="status"><?php foreach ($statuses as $status): ?><option value="<?= e($status) ?>" <?= $status === $order['status'] ? 'selected' : '' ?>><?= e($status) ?></option><?php endforeach; ?></select></div><div class="col-md-8"><label class="form-label">Catatan Progress</label><textarea class="form-control" rows="3" name="progress_note"><?= e((string)$order['progress_note']) ?></textarea></div><div class="col-12 d-flex gap-2 flex-wrap"><button class="btn btn-dark" type="submit">Simpan Status</button><a class="btn btn-outline-dark" href="spk.php?id=<?= (int)$order['id'] ?>">Cetak SPK</a><a class="btn btn-outline-dark" href="invoice.php?id=<?= (int)$order['id'] ?>">Cetak Invoice</a></div></form></div></main><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script></body></html>

11
spk.php Normal file
View File

@ -0,0 +1,11 @@
<?php
require_once __DIR__ . '/db/config.php';
@date_default_timezone_set('Asia/Jakarta');
function e(string $value): string { return htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); }
$pdo = db();
$stmt = $pdo->prepare('SELECT * FROM print_orders WHERE id = :id LIMIT 1');
$stmt->execute([':id' => max(0, (int)($_GET['id'] ?? 0))]);
$order = $stmt->fetch();
if (!$order) { http_response_code(404); echo 'SPK tidak ditemukan.'; exit; }
?>
<!doctype html><html lang="id"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>SPK <?= e($order['spk_no']) ?> — Kenanga Kreasindo</title><meta name="description" content="Surat Perintah Kerja Kenanga Kreasindo."><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"><link rel="stylesheet" href="assets/css/custom.css?v=<?= time() ?>"></head><body class="print-body"><main class="print-sheet"><div class="print-actions"><a class="btn btn-outline-dark btn-sm" href="order_detail.php?id=<?= (int)$order['id'] ?>">← Detail</a><button class="btn btn-dark btn-sm" onclick="window.print()">Print / Save PDF</button></div><section class="doc-card"><header class="doc-header"><div><h1>Kenanga Kreasindo</h1><p>Perum BCL Jln Kenanga Raya · kenangakreasindo@gmail.com</p></div><div class="doc-number"><span>No SPK</span><strong><?= e($order['spk_no']) ?></strong></div></header><div class="doc-title">Surat Perintah Kerja</div><div class="row g-3 mb-3"><div class="col-6"><div class="doc-box"><span>PIC / Customer</span><strong><?= e($order['customer_name']) ?></strong><small><?= e($order['customer_phone']) ?></small></div></div><div class="col-6"><div class="doc-box"><span>Deadline / Pengambilan</span><strong><?= e($order['deadline'] ? date('d M Y', strtotime($order['deadline'])) : '-') ?></strong><small>CS: <?= e((string)($order['cs_name'] ?: '-')) ?></small></div></div></div><table class="table doc-table"><tbody><tr><th>Pekerjaan</th><td><?= e($order['project_name']) ?></td></tr><tr><th>Kategori / Qty</th><td><?= e($order['category']) ?> · <?= (int)$order['quantity'] ?> pcs</td></tr><tr><th>Bahan</th><td><?= e((string)($order['material'] ?: '-')) ?></td></tr><tr><th>Ukuran</th><td><?= e((string)($order['size_info'] ?: '-')) ?></td></tr><tr><th>Finishing</th><td><?= e((string)($order['finishing'] ?: '-')) ?></td></tr><tr><th>Detail Pesanan</th><td><?= nl2br(e((string)$order['progress_note'])) ?></td></tr><tr><th>Operator</th><td><?= e((string)($order['operator_name'] ?: '-')) ?></td></tr></tbody></table><h2 class="h6 mt-4">QC Checklist</h2><div class="qc-grid"><div><strong>Printing</strong><label><input type="checkbox"> Bahan sesuai</label><label><input type="checkbox"> Ukuran sesuai</label><label><input type="checkbox"> Hasil cetak sesuai</label></div><div><strong>Finishing</strong><label><input type="checkbox"> Potong / finishing sesuai</label><label><input type="checkbox"> Jumlah sesuai</label><label><input type="checkbox"> Packing selesai</label></div></div><div class="signature-row"><div>Operator</div><div>Checker QC</div><div>Customer</div></div></section></main></body></html>