Add files via upload
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?php
|
||||||
|
// Redirect to the views/index.php
|
||||||
|
header('Location: views/index.php');
|
||||||
|
exit();
|
||||||
|
?>
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
$servername = "localhost"; // your database server
|
||||||
|
$username = "root"; // your database username
|
||||||
|
$password = ""; // your database password
|
||||||
|
$dbname = "ayula_store"; // your database name
|
||||||
|
|
||||||
|
// Create connection
|
||||||
|
$conn = mysqli_connect($servername, $username, $password, $dbname);
|
||||||
|
|
||||||
|
// Check connection
|
||||||
|
if (!$conn) {
|
||||||
|
die("Koneksi gagal: " . mysqli_connect_error());
|
||||||
|
} else {
|
||||||
|
echo "";
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 412 B |
|
|
@ -0,0 +1,385 @@
|
||||||
|
<?php
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
// Ambil informasi user yang sedang login
|
||||||
|
$userRole = $_SESSION['role']; // 'user' atau 'admin'
|
||||||
|
$username = $_SESSION['username']; // Menambahkan username dari session
|
||||||
|
|
||||||
|
// Jika username adalah root, tampilkan nama yang lebih presentable
|
||||||
|
$displayName = ($username === 'root') ? 'Admin' : $username;
|
||||||
|
|
||||||
|
// Cek apakah session 'user_id' ada, yang berarti pengguna sudah login
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
// Jika session tidak ada, arahkan pengguna ke halaman login
|
||||||
|
header("Location: /ayula-store/index.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Database connection
|
||||||
|
include('../../routes/db_conn.php');
|
||||||
|
|
||||||
|
|
||||||
|
// Function to generate new product code
|
||||||
|
function generateProductCode($conn) {
|
||||||
|
// Query the database for the highest product code
|
||||||
|
$query = "SELECT kode_barang FROM barang WHERE kode_barang LIKE 'BRG%' ORDER BY kode_barang DESC LIMIT 1";
|
||||||
|
$result = $conn->query($query);
|
||||||
|
|
||||||
|
if ($result->num_rows > 0) {
|
||||||
|
// Extract existing code
|
||||||
|
$row = $result->fetch_assoc();
|
||||||
|
$lastCode = $row['kode_barang'];
|
||||||
|
|
||||||
|
// Extract the numeric part and increment
|
||||||
|
$numericPart = intval(substr($lastCode, 3)); // Extract numbers after 'BRG'
|
||||||
|
$nextNumeric = $numericPart + 1;
|
||||||
|
|
||||||
|
// Format with leading zeros (e.g., BRG001, BRG002, etc.)
|
||||||
|
$newCode = 'BRG' . str_pad($nextNumeric, 3, '0', STR_PAD_LEFT);
|
||||||
|
} else {
|
||||||
|
// If no existing codes, start with BRG001
|
||||||
|
$newCode = 'BRG001';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $newCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||||
|
// Generate new product code
|
||||||
|
$kode_barang = generateProductCode($conn);
|
||||||
|
|
||||||
|
// Get data from form
|
||||||
|
$nama_barang = isset($_POST['nama_barang']) ? $_POST['nama_barang'] : '';
|
||||||
|
$id_jenis = isset($_POST['id_jenis']) ? $_POST['id_jenis'] : '';
|
||||||
|
$stok = isset($_POST['stok']) ? $_POST['stok'] : 0;
|
||||||
|
$harga = isset($_POST['harga']) ? $_POST['harga'] : '';
|
||||||
|
|
||||||
|
// Check if image file was uploaded
|
||||||
|
if (isset($_FILES['image']) && $_FILES['image']['error'] == 0) {
|
||||||
|
$image = $_FILES['image'];
|
||||||
|
|
||||||
|
// Validate image
|
||||||
|
$image_name = basename($image['name']);
|
||||||
|
$target_dir = "../uploads/img-barang/"; // Target folder to store images
|
||||||
|
$target_file = $target_dir . $image_name;
|
||||||
|
$image_file_type = strtolower(pathinfo($target_file, PATHINFO_EXTENSION));
|
||||||
|
|
||||||
|
// Check if uploaded file is an image
|
||||||
|
if (in_array($image_file_type, ['jpg', 'jpeg', 'png', 'gif'])) {
|
||||||
|
// Move file to target folder
|
||||||
|
if (!move_uploaded_file($image['tmp_name'], $target_file)) {
|
||||||
|
echo "Error: Failed to upload image!";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "Error: Only images (JPG, JPEG, PNG, GIF) are allowed!";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$image_name = ''; // If no image was uploaded, set image name to empty
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate id_jenis
|
||||||
|
$query_jenis = "SELECT id_jenis FROM jenis_barang WHERE id_jenis = ?";
|
||||||
|
$stmt_jenis = $conn->prepare($query_jenis);
|
||||||
|
$stmt_jenis->bind_param("i", $id_jenis);
|
||||||
|
$stmt_jenis->execute();
|
||||||
|
$stmt_jenis->store_result();
|
||||||
|
|
||||||
|
if ($stmt_jenis->num_rows == 0 && $id_jenis != '') {
|
||||||
|
echo "Error: Category not found!";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate stock and price must be numbers
|
||||||
|
if (!preg_match('/^\d+$/', $stok) || !preg_match('/^\d+$/', $harga)) {
|
||||||
|
die("<script>alert('Stock and price must contain only numbers!'); window.history.back();</script>");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to integers for safety
|
||||||
|
$stok = intval($stok);
|
||||||
|
$harga = intval($harga);
|
||||||
|
|
||||||
|
// Insert data into database (now including kode_barang)
|
||||||
|
$sql = "INSERT INTO barang (kode_barang, nama_barang, id_jenis, stok, harga, image)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)";
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
$stmt->bind_param("ssisis", $kode_barang, $nama_barang, $id_jenis, $stok, $harga, $image_name);
|
||||||
|
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
// Redirect with success parameter
|
||||||
|
header("Location: productlist.php?success_add=1");
|
||||||
|
exit();
|
||||||
|
}}
|
||||||
|
|
||||||
|
// Fetch categories for the dropdown
|
||||||
|
$query_categories = "SELECT id_jenis, nama_jenis FROM jenis_barang ORDER BY nama_jenis";
|
||||||
|
$result_categories = $conn->query($query_categories);
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0">
|
||||||
|
<meta name="description" content="POS - Bootstrap Admin Template">
|
||||||
|
<meta name="keywords"
|
||||||
|
content="admin, estimates, bootstrap, business, corporate, creative, invoice, html5, responsive, Projects">
|
||||||
|
<meta name="author" content="Dreamguys - Bootstrap Admin Template">
|
||||||
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
<title>Dreams Pos admin template</title>
|
||||||
|
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="/ayula-store/bootstrap/assets/img/favicon.jpg">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/bootstrap.min.css">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/animate.css">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/plugins/select2/css/select2.min.css">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/dataTables.bootstrap4.min.css">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/plugins/fontawesome/css/fontawesome.min.css">
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/plugins/fontawesome/css/all.min.css">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/style.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="global-loader">
|
||||||
|
<div class="whirly-loader"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="main-wrapper">
|
||||||
|
<div class="header">
|
||||||
|
<div class="header-left active">
|
||||||
|
<a href="/ayula-store/views/dashboard/" class="logo">
|
||||||
|
<img src="../../src/img/logoayula.png" alt="" />
|
||||||
|
</a>
|
||||||
|
<a href="/ayula-store/views/dashboard/" class="logo-small">
|
||||||
|
<img src="../../src/img/smallest-ayula.png" alt="" />
|
||||||
|
</a>
|
||||||
|
<a id="toggle_btn" href="javascript:void(0);"> </a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a id="mobile_btn" class="mobile_btn" href="#sidebar">
|
||||||
|
<span class="bar-icon">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<ul class="nav user-menu">
|
||||||
|
<li class="nav-item dropdown has-arrow main-drop">
|
||||||
|
<a href="javascript:void(0);" class="dropdown-toggle nav-link userset" data-bs-toggle="dropdown">
|
||||||
|
<span class="user-img"><img src="../../src/img/userprofile.png" alt="">
|
||||||
|
<span class="status online"></span></span>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu menu-drop-user">
|
||||||
|
<div class="profilename">
|
||||||
|
<div class="profileset">
|
||||||
|
<span class="user-img"><img src="../../src/img/userprofile.png" alt="">
|
||||||
|
<span class="status online"></span></span>
|
||||||
|
<div class="profilesets">
|
||||||
|
<h6><?php echo $userRole == 'admin' ? 'Admin' : 'Karyawan'; ?></h6>
|
||||||
|
<h5><?php echo htmlspecialchars($displayName); ?></h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item" href="/ayula-store/views/report-issue/">
|
||||||
|
<img src="../../src/img/warning.png" class="me-2" alt="img" /> Laporkan Masalah
|
||||||
|
</a>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item logout pb-0" href="../../views/logout.php"><img
|
||||||
|
src="../../bootstrap/assets/img/icons/log-out.svg" class="me-2" alt="img" />Keluar</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="dropdown mobile-user-menu">
|
||||||
|
<a href="javascript:void(0);" class="nav-link dropdown-toggle" data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"><i class="fa fa-ellipsis-v"></i></a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
|
<a class="dropdown-item" href="/ayula-store/views/report-issue/">
|
||||||
|
<i class="fa fa-cog me-2"></i> Laporkan Masalah
|
||||||
|
</a>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item logout pb-0" href="../../views/logout.php"><img
|
||||||
|
src="../../bootstrap/assets/img/icons/log-out.svg" class="me-2" alt="img" />Keluar</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sidebar" id="sidebar">
|
||||||
|
<div class="sidebar-inner slimscroll">
|
||||||
|
<div id="sidebar-menu" class="sidebar-menu">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="/ayula-store/views/reporttt/report.php"><img src="../../bootstrap/assets/img/icons/dashboard.svg" alt="img" /><span>
|
||||||
|
Dashboard</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../bootstrap/assets/img/icons/product.svg" alt="img" /><span>
|
||||||
|
Barang</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/ayula-store/views/barang/productlist.php" >Daftar Barang</a></li>
|
||||||
|
<li><a href="/ayula-store/views/barang/addproduct.php" class="active">Tambah Barang</a></li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li >
|
||||||
|
<a href="/ayula-store/views/barang/topsis_restock_view.php"><img src="../../bootstrap/assets/img/icons/sales1.svg" alt="img" /><span>
|
||||||
|
Analisa Barang</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../bootstrap/assets/img/icons/users1.svg" alt="img" /><span>
|
||||||
|
Pengguna</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
|
||||||
|
<li><a href="/ayula-store/views/users/add-user.php">Pengguna Baru</a></li>
|
||||||
|
|
||||||
|
<li><a href="/ayula-store/views/users/">Daftar Pengguna</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="content">
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="page-title">
|
||||||
|
<h4>Tambah Barang</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form action="addproduct.php" method="POST" enctype="multipart/form-data">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-3 col-sm-6 col-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Nama Barang</label>
|
||||||
|
<input type="text" name="nama_barang" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-3 col-sm-6 col-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Kategori</label>
|
||||||
|
<select class="select" name="id_jenis" required>
|
||||||
|
<option value="">Pilih Kategori</option>
|
||||||
|
<?php
|
||||||
|
// Loop through all categories and create option tags
|
||||||
|
if ($result_categories && $result_categories->num_rows > 0) {
|
||||||
|
while($category = $result_categories->fetch_assoc()) {
|
||||||
|
echo '<option value="' . $category['id_jenis'] . '">' . $category['nama_jenis'] . '</option>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-3 col-sm-6 col-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Harga</label>
|
||||||
|
<input type="text" name="harga" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-3 col-sm-6 col-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Stok</label>
|
||||||
|
<input type="text" name="stok" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label> Gambar Barang</label>
|
||||||
|
<div class="image-upload">
|
||||||
|
<input type="file" name="image" id="image" accept="image/*">
|
||||||
|
<div class="image-uploads">
|
||||||
|
<img src="/ayula-store/bootstrap/assets/img/icons/upload.svg" alt="img">
|
||||||
|
<h4>Pilih Gambar</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<button type="submit" class="btn btn-submit">Tambah</button>
|
||||||
|
<a href="productlist.php" class="btn btn-cancel">Batal</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/feather.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/jquery.slimscroll.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/jquery.dataTables.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/dataTables.bootstrap4.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/plugins/select2/js/select2.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/plugins/sweetalert/sweetalert2.all.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/plugins/sweetalert/sweetalerts.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/script.js"></script>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
function onlyNumbers(event) {
|
||||||
|
let charCode = event.which ? event.which : event.keyCode;
|
||||||
|
if (charCode < 48 || charCode > 57) {
|
||||||
|
event.preventDefault();
|
||||||
|
Swal.fire({
|
||||||
|
icon: "error",
|
||||||
|
title: "Input Tidak Valid!",
|
||||||
|
text: "Hanya angka yang diperbolehkan.",
|
||||||
|
showConfirmButton: false,
|
||||||
|
timer: 2000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let stokInput = document.querySelector("input[name='stok']");
|
||||||
|
let hargaInput = document.querySelector("input[name='harga']");
|
||||||
|
|
||||||
|
stokInput.addEventListener("keypress", onlyNumbers);
|
||||||
|
hargaInput.addEventListener("keypress", onlyNumbers);
|
||||||
|
|
||||||
|
function validateOnBlur(input) {
|
||||||
|
input.addEventListener("blur", function () {
|
||||||
|
if (!/^\d+$/.test(input.value)) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: "warning",
|
||||||
|
title: "Input Tidak Valid!",
|
||||||
|
text: "Harap masukkan angka saja.",
|
||||||
|
showConfirmButton: false,
|
||||||
|
timer: 2000
|
||||||
|
});
|
||||||
|
input.value = "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
validateOnBlur(stokInput);
|
||||||
|
validateOnBlur(hargaInput);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
// This script creates the necessary directory structure for storing uploaded receipt images
|
||||||
|
|
||||||
|
// Define the directory path
|
||||||
|
$upload_dir = '../uploads/nota/';
|
||||||
|
|
||||||
|
// Check if directory exists
|
||||||
|
if (!file_exists($upload_dir)) {
|
||||||
|
// Create directory with full permissions (for development)
|
||||||
|
if (mkdir($upload_dir, 0777, true)) {
|
||||||
|
echo "Directory created successfully: " . $upload_dir;
|
||||||
|
|
||||||
|
// Set proper permissions (more secure for production)
|
||||||
|
chmod($upload_dir, 0755);
|
||||||
|
echo "<br>Permissions set to 0755";
|
||||||
|
|
||||||
|
// Create .htaccess file to protect direct access to images (optional)
|
||||||
|
$htaccess_content = "# Deny direct access to files
|
||||||
|
<FilesMatch \"\\.(jpg|jpeg|png|gif)$\">
|
||||||
|
Order Allow,Deny
|
||||||
|
Deny from all
|
||||||
|
</FilesMatch>
|
||||||
|
|
||||||
|
# Allow access through PHP scripts
|
||||||
|
<FilesMatch \"get_image\\.php$\">
|
||||||
|
Order Allow,Deny
|
||||||
|
Allow from all
|
||||||
|
</FilesMatch>";
|
||||||
|
|
||||||
|
file_put_contents($upload_dir . '/.htaccess', $htaccess_content);
|
||||||
|
echo "<br>.htaccess file created for security";
|
||||||
|
|
||||||
|
} else {
|
||||||
|
echo "Failed to create directory: " . $upload_dir;
|
||||||
|
echo "<br>Please create this directory manually and ensure it has write permissions.";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "Directory already exists: " . $upload_dir;
|
||||||
|
|
||||||
|
// Check if directory is writable
|
||||||
|
if (is_writable($upload_dir)) {
|
||||||
|
echo "<br>Directory is writable.";
|
||||||
|
} else {
|
||||||
|
echo "<br>Warning: Directory is not writable. Please set proper permissions.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "<br><br><a href='/ayula-store/views/barang/productlist.php'>Return to Product List</a>";
|
||||||
|
?>
|
||||||
|
|
@ -0,0 +1,404 @@
|
||||||
|
<?php
|
||||||
|
// Include database connection
|
||||||
|
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
// Ambil informasi user yang sedang login
|
||||||
|
$userRole = $_SESSION['role']; // 'user' atau 'admin'
|
||||||
|
$username = $_SESSION['username']; // Menambahkan username dari session
|
||||||
|
|
||||||
|
// Jika username adalah root, tampilkan nama yang lebih presentable
|
||||||
|
$displayName = ($username === 'root') ? 'Admin' : $username;
|
||||||
|
|
||||||
|
// Cek apakah session 'user_id' ada, yang berarti pengguna sudah login
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
// Jika session tidak ada, arahkan pengguna ke halaman login
|
||||||
|
header("Location: /ayula-store/index.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
function dbConnect() {
|
||||||
|
$servername = "localhost";
|
||||||
|
$username = "root";
|
||||||
|
$password = "";
|
||||||
|
$dbname = "ayula_store";
|
||||||
|
|
||||||
|
// Create connection
|
||||||
|
$conn = new mysqli($servername, $username, $password, $dbname);
|
||||||
|
|
||||||
|
// Check connection
|
||||||
|
if ($conn->connect_error) {
|
||||||
|
die("Connection failed: " . $conn->connect_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get product by ID
|
||||||
|
function getProductById($id) {
|
||||||
|
$conn = dbConnect();
|
||||||
|
|
||||||
|
$sql = "SELECT * FROM barang WHERE id_barang = ?";
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
$stmt->bind_param("i", $id);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
$product = $result->fetch_assoc();
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conn->close();
|
||||||
|
|
||||||
|
return $product;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all product categories
|
||||||
|
function getProductCategories() {
|
||||||
|
$conn = dbConnect();
|
||||||
|
|
||||||
|
$sql = "SELECT * FROM jenis_barang ORDER BY nama_jenis";
|
||||||
|
$result = $conn->query($sql);
|
||||||
|
$categories = [];
|
||||||
|
|
||||||
|
if ($result->num_rows > 0) {
|
||||||
|
while($row = $result->fetch_assoc()) {
|
||||||
|
$categories[] = $row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn->close();
|
||||||
|
return $categories;
|
||||||
|
}
|
||||||
|
//fungsi update product
|
||||||
|
function updateProduct($id, $data) {
|
||||||
|
$conn = dbConnect();
|
||||||
|
|
||||||
|
$sql = "UPDATE barang SET
|
||||||
|
nama_barang = ?,
|
||||||
|
harga = ?,
|
||||||
|
stok = ?,
|
||||||
|
id_jenis = ?,
|
||||||
|
image = ?
|
||||||
|
WHERE id_barang = ?";
|
||||||
|
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
// Change from "ssdiis" to "ssdisi" - notice the 'i' to 's' change for image parameter
|
||||||
|
$stmt->bind_param("ssdisi",
|
||||||
|
$data['nama_barang'],
|
||||||
|
$data['harga'],
|
||||||
|
$data['stok'],
|
||||||
|
$data['id_jenis'],
|
||||||
|
$data['image'],
|
||||||
|
$id
|
||||||
|
);
|
||||||
|
|
||||||
|
$result = $stmt->execute();
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conn->close();
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if form is submitted
|
||||||
|
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||||
|
$id = $_POST['id_barang'];
|
||||||
|
|
||||||
|
// Handle image upload if a new file is selected
|
||||||
|
$image = $_POST['current_image']; // Default to current image
|
||||||
|
|
||||||
|
if(isset($_FILES['product_image']) && $_FILES['product_image']['size'] > 0) {
|
||||||
|
$target_dir = "../uploads/img-barang/";
|
||||||
|
$file_extension = strtolower(pathinfo($_FILES["product_image"]["name"], PATHINFO_EXTENSION));
|
||||||
|
$new_filename = "product_" . time() . "." . $file_extension;
|
||||||
|
$target_file = $target_dir . $new_filename;
|
||||||
|
|
||||||
|
// Check if image file is an actual image
|
||||||
|
$check = getimagesize($_FILES["product_image"]["tmp_name"]);
|
||||||
|
if($check !== false) {
|
||||||
|
// Upload file
|
||||||
|
if (move_uploaded_file($_FILES["product_image"]["tmp_name"], $target_file)) {
|
||||||
|
$image = $new_filename;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare data for update
|
||||||
|
$data = [
|
||||||
|
'nama_barang' => $_POST['nama_barang'],
|
||||||
|
'harga' => $_POST['harga'],
|
||||||
|
'stok' => $_POST['stok'],
|
||||||
|
'id_jenis' => $_POST['id_jenis'],
|
||||||
|
'image' => $image
|
||||||
|
];
|
||||||
|
|
||||||
|
// Update product
|
||||||
|
if(updateProduct($id, $data)) {
|
||||||
|
// Redirect to product list with success message
|
||||||
|
// After successfully editing a product
|
||||||
|
header("Location: productlist.php?success_edit=1");
|
||||||
|
exit();
|
||||||
|
} else {
|
||||||
|
$error = "Failed to update product";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get product ID from URL
|
||||||
|
$product_id = isset($_GET['id']) ? $_GET['id'] : 0;
|
||||||
|
$product = getProductById($product_id);
|
||||||
|
|
||||||
|
// Get all categories
|
||||||
|
$categories = getProductCategories();
|
||||||
|
|
||||||
|
// If product doesn't exist, redirect to product list
|
||||||
|
if (!$product) {
|
||||||
|
header("Location: productlist.php?error=Product not found");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0">
|
||||||
|
<meta name="description" content="POS - Bootstrap Admin Template">
|
||||||
|
<meta name="keywords"
|
||||||
|
content="admin, estimates, bootstrap, business, corporate, creative, invoice, html5, responsive, Projects">
|
||||||
|
<meta name="author" content="Dreamguys - Bootstrap Admin Template">
|
||||||
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
<title>Ayula Store - Edit Product</title>
|
||||||
|
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="/ayula-store/bootstrap/assets/img/favicon.jpg">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/bootstrap.min.css">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/animate.css">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/plugins/select2/css/select2.min.css">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/dataTables.bootstrap4.min.css">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/plugins/fontawesome/css/fontawesome.min.css">
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/plugins/fontawesome/css/all.min.css">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/style.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="global-loader">
|
||||||
|
<div class="whirly-loader"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="main-wrapper">
|
||||||
|
<div class="header">
|
||||||
|
<div class="header-left active">
|
||||||
|
<a href="/ayula-store/views/dashboard/" class="logo">
|
||||||
|
<img src="../../src/img/logoayula.png" alt="" />
|
||||||
|
</a>
|
||||||
|
<a href="/ayula-store/views/dashboard/" class="logo-small">
|
||||||
|
<img src="../../src/img/smallest-ayula.png" alt="" />
|
||||||
|
</a>
|
||||||
|
<a id="toggle_btn" href="javascript:void(0);"> </a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a id="mobile_btn" class="mobile_btn" href="#sidebar">
|
||||||
|
<span class="bar-icon">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<ul class="nav user-menu">
|
||||||
|
<li class="nav-item dropdown has-arrow main-drop">
|
||||||
|
<a href="javascript:void(0);" class="dropdown-toggle nav-link userset" data-bs-toggle="dropdown">
|
||||||
|
<span class="user-img"><img src="../../src/img/userprofile.png" alt="">
|
||||||
|
<span class="status online"></span></span>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu menu-drop-user">
|
||||||
|
<div class="profilename">
|
||||||
|
<div class="profileset">
|
||||||
|
<span class="user-img"><img src="../../src/img/userprofile.png" alt="">
|
||||||
|
<span class="status online"></span></span>
|
||||||
|
<div class="profilesets">
|
||||||
|
<h6><?php echo $userRole == 'admin' ? 'Admin' : 'Karyawan'; ?></h6>
|
||||||
|
<h5><?php echo htmlspecialchars($displayName); ?></h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item" href="/ayula-store/views/report-issue/">
|
||||||
|
<img src="../../src/img/warning.png" class="me-2" alt="img" /> Laporkan Masalah
|
||||||
|
</a>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item logout pb-0" href="../../views/logout.php"><img
|
||||||
|
src="../../bootstrap/assets/img/icons/log-out.svg" class="me-2" alt="img" />Keluar</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="dropdown mobile-user-menu">
|
||||||
|
<a href="javascript:void(0);" class="nav-link dropdown-toggle" data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"><i class="fa fa-ellipsis-v"></i></a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
|
<a class="dropdown-item" href="/ayula-store/views/report-issue/">
|
||||||
|
<i class="fa fa-cog me-2"></i> Laporkan Masalah
|
||||||
|
</a>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item logout pb-0" href="../../views/logout.php"><img
|
||||||
|
src="../../bootstrap/assets/img/icons/log-out.svg" class="me-2" alt="img" />Keluar</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sidebar" id="sidebar">
|
||||||
|
<div class="sidebar-inner slimscroll">
|
||||||
|
<div id="sidebar-menu" class="sidebar-menu">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="/ayula-store/views/reporttt/report.php"><img src="../../bootstrap/assets/img/icons/dashboard.svg" alt="img" /><span>
|
||||||
|
Dashboard</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="submenu" >
|
||||||
|
<a href="javascript:void(0);"><img src="../../bootstrap/assets/img/icons/product.svg" alt="img" /><span>
|
||||||
|
Barang</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/ayula-store/views/barang/productlist.php" class="active">Daftar Barang</a></li>
|
||||||
|
<li><a href="/ayula-store/views/barang/addproduct.php">Tambah Barang</a></li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li >
|
||||||
|
<a href="/ayula-store/views/barang/topsis_restock_view.php"><img src="../../bootstrap/assets/img/icons/sales1.svg" alt="img" /><span>
|
||||||
|
Analisa Barang</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../bootstrap/assets/img/icons/users1.svg" alt="img" /><span>
|
||||||
|
Pengguna</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<?php if ($userRole == 'admin') { ?>
|
||||||
|
<li><a href="/ayula-store/views/users/add-user.php">Pengguna Baru</a></li>
|
||||||
|
<?php } ?>
|
||||||
|
<li><a href="/ayula-store/views/users/">Daftar Pengguna</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="content">
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="page-title">
|
||||||
|
<h4>Edit Barang</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<form action="editproduct.php" method="POST" enctype="multipart/form-data">
|
||||||
|
<!-- Hidden field for product ID -->
|
||||||
|
<input type="hidden" name="id_barang" value="<?php echo $product['id_barang']; ?>">
|
||||||
|
<input type="hidden" name="current_image" value="<?php echo $product['image']; ?>">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-4 col-sm-6 col-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Nama Barang</label>
|
||||||
|
<input type="text" name="nama_barang" value="<?php echo $product['nama_barang']; ?>" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-4 col-sm-6 col-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Kategori</label>
|
||||||
|
<select class="select" name="id_jenis" required>
|
||||||
|
<option value="">Pilih Kategori</option>
|
||||||
|
<?php foreach($categories as $category): ?>
|
||||||
|
<option value="<?php echo $category['id_jenis']; ?>" <?php if($category['id_jenis'] == $product['id_jenis']) echo 'selected'; ?>>
|
||||||
|
<?php echo $category['nama_jenis']; ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-3 col-sm-6 col-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Stok</label>
|
||||||
|
<input type="text" name="stok" value="<?php echo $product['stok']; ?>" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-3 col-sm-6 col-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Harga</label>
|
||||||
|
<input type="text" step="0.01" name="harga" value="<?php echo $product['harga']; ?>" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Gambar barang</label>
|
||||||
|
<div class="image-upload">
|
||||||
|
<input type="file" name="product_image" accept="image/*">
|
||||||
|
<div class="image-uploads">
|
||||||
|
<img src="/ayula-store/bootstrap/assets/img/icons/upload.svg" alt="img">
|
||||||
|
<h4>Pilih Gambar</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if($product['image']): ?>
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="product-list">
|
||||||
|
<ul class="row">
|
||||||
|
<li>
|
||||||
|
<div class="productviews">
|
||||||
|
<div class="productviewsimg">
|
||||||
|
<img src="../uploads/img-barang/<?php echo $product['image']; ?>" alt="img">
|
||||||
|
</div>
|
||||||
|
<div class="productviewscontent">
|
||||||
|
<div class="productviewsname">
|
||||||
|
<h2><?php echo $product['image']; ?></h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<button type="submit" class="btn btn-submit me-2">Edit</button>
|
||||||
|
<a href="productlist.php" class="btn btn-cancel">Batal</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/feather.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/jquery.slimscroll.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/jquery.dataTables.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/dataTables.bootstrap4.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/plugins/select2/js/select2.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/plugins/sweetalert/sweetalert2.all.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/plugins/sweetalert/sweetalerts.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/script.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
<?php
|
||||||
|
// Koneksi ke database
|
||||||
|
$servername = "localhost";
|
||||||
|
$username = "root"; // Sesuaikan dengan username database kamu
|
||||||
|
$password = ""; // Sesuaikan dengan password database kamu
|
||||||
|
$database = "ayula_store"; // Sesuaikan dengan nama database kamu
|
||||||
|
|
||||||
|
$conn = new mysqli($servername, $username, $password, $database);
|
||||||
|
|
||||||
|
// Periksa koneksi
|
||||||
|
if ($conn->connect_error) {
|
||||||
|
die("Koneksi gagal: " . $conn->connect_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ambil data untuk laporan
|
||||||
|
$sql = "SELECT r.id_report, r.tanggal, b.nama_barang, jb.nama_jenis, r.jumlah, r.harga
|
||||||
|
FROM report r
|
||||||
|
JOIN barang b ON r.id_barang = b.id_barang
|
||||||
|
JOIN jenis_barang jb ON b.id_jenis = jb.id_jenis
|
||||||
|
ORDER BY r.tanggal DESC";
|
||||||
|
|
||||||
|
$result = $conn->query($sql);
|
||||||
|
|
||||||
|
// Set header for Excel file
|
||||||
|
header('Content-Type: application/vnd.ms-excel');
|
||||||
|
header('Content-Disposition: attachment; filename="laporan_barang_' . date('Y-m-d') . '.xls"');
|
||||||
|
header('Pragma: no-cache');
|
||||||
|
header('Expires: 0');
|
||||||
|
|
||||||
|
// Output tabel HTML yang akan dibaca sebagai Excel
|
||||||
|
echo '<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
|
<title>Laporan Barang</title>
|
||||||
|
<style>
|
||||||
|
table {border-collapse: collapse;}
|
||||||
|
th, td {border: 1px solid #000; padding: 5px;}
|
||||||
|
th {background-color: #f0f0f0; font-weight: bold;}
|
||||||
|
.text-right {text-align: right;}
|
||||||
|
.text-center {text-align: center;}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h2>Laporan Barang - Ayula Store</h2>
|
||||||
|
<p>Tanggal Export: ' . date('d/m/Y H:i:s') . '</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>No</th>
|
||||||
|
<th>ID Laporan</th>
|
||||||
|
<th>Tanggal</th>
|
||||||
|
<th>Nama Barang</th>
|
||||||
|
<th>Jenis</th>
|
||||||
|
<th>Jumlah</th>
|
||||||
|
<th>Harga (Rp)</th>
|
||||||
|
<th>Total (Rp)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>';
|
||||||
|
|
||||||
|
$no = 1;
|
||||||
|
$grand_total = 0;
|
||||||
|
|
||||||
|
if ($result->num_rows > 0) {
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$total = $row['jumlah'] * $row['harga'];
|
||||||
|
$grand_total += $total;
|
||||||
|
|
||||||
|
echo '<tr>
|
||||||
|
<td class="text-center">' . $no++ . '</td>
|
||||||
|
<td>' . $row['id_report'] . '</td>
|
||||||
|
<td>' . date('d/m/Y H:i', strtotime($row['tanggal'])) . '</td>
|
||||||
|
<td>' . $row['nama_barang'] . '</td>
|
||||||
|
<td>' . $row['nama_jenis'] . '</td>
|
||||||
|
<td class="text-center">' . $row['jumlah'] . '</td>
|
||||||
|
<td class="text-right">' . number_format($row['harga'], 0, ',', '.') . '</td>
|
||||||
|
<td class="text-right">' . number_format($total, 0, ',', '.') . '</td>
|
||||||
|
</tr>';
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '<tr>
|
||||||
|
<td colspan="7" class="text-right"><strong>Grand Total</strong></td>
|
||||||
|
<td class="text-right"><strong>' . number_format($grand_total, 0, ',', '.') . '</strong></td>
|
||||||
|
</tr>';
|
||||||
|
} else {
|
||||||
|
echo '<tr><td colspan="8" class="text-center">Tidak ada data laporan</td></tr>';
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '</tbody>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>';
|
||||||
|
|
||||||
|
// Close database connection
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
|
After Width: | Height: | Size: 286 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 248 KiB |
|
|
@ -0,0 +1,248 @@
|
||||||
|
<?php
|
||||||
|
// Koneksi ke database
|
||||||
|
$servername = "localhost";
|
||||||
|
$username = "root"; // Sesuaikan dengan username database kamu
|
||||||
|
$password = ""; // Sesuaikan dengan password database kamu
|
||||||
|
$database = "ayula_store"; // Sesuaikan dengan nama database kamu
|
||||||
|
|
||||||
|
$conn = new mysqli($servername, $username, $password, $database);
|
||||||
|
|
||||||
|
// Periksa koneksi
|
||||||
|
if ($conn->connect_error) {
|
||||||
|
die("Koneksi gagal: " . $conn->connect_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cek ID laporan
|
||||||
|
if (!isset($_GET['id']) || empty($_GET['id'])) {
|
||||||
|
die("ID Laporan tidak valid");
|
||||||
|
}
|
||||||
|
|
||||||
|
$id_report = $_GET['id'];
|
||||||
|
|
||||||
|
// Query untuk mengambil data laporan
|
||||||
|
$sql = "SELECT r.id_report, r.tanggal, b.id_barang, b.nama_barang, jb.nama_jenis,
|
||||||
|
r.jumlah, r.harga, r.image
|
||||||
|
FROM report r
|
||||||
|
JOIN barang b ON r.id_barang = b.id_barang
|
||||||
|
JOIN jenis_barang jb ON b.id_jenis = jb.id_jenis
|
||||||
|
WHERE r.id_report = ?";
|
||||||
|
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
$stmt->bind_param("s", $id_report);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
if ($result->num_rows == 0) {
|
||||||
|
die("Laporan tidak ditemukan");
|
||||||
|
}
|
||||||
|
|
||||||
|
$report = $result->fetch_assoc();
|
||||||
|
$total = $report['jumlah'] * $report['harga'];
|
||||||
|
$image_url = !empty($report['image']) ? '../uploads/nota/' . $report['image'] : '/ayula-store/bootstrap/assets/img/no-image.png';
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0" />
|
||||||
|
<title>Cetak Laporan - <?php echo $report['id_report']; ?></title>
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/bootstrap.min.css" />
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-subtitle {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #555;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.company-details {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-meta {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-meta table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-meta td {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-table th, .invoice-table td {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-table th {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receipt-image {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 300px;
|
||||||
|
margin: 20px 0;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receipt-container {
|
||||||
|
text-align: center;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signature-section {
|
||||||
|
margin-top: 50px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signature-box {
|
||||||
|
width: 45%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signature-line {
|
||||||
|
border-top: 1px solid #000;
|
||||||
|
margin-top: 70px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-print {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@page {
|
||||||
|
margin: 15mm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="no-print" style="text-align: right; margin-bottom: 15px;">
|
||||||
|
<button class="btn btn-primary" onclick="window.print()">Cetak Laporan</button>
|
||||||
|
<button class="btn btn-secondary" onclick="window.close()">Tutup</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="invoice-header">
|
||||||
|
<div class="invoice-title">LAPORAN BARANG</div>
|
||||||
|
<div class="invoice-subtitle">AYULA STORE</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="company-details">
|
||||||
|
<strong>Ayula Store</strong><br>
|
||||||
|
Jl. Contoh No. 123, Kota<br>
|
||||||
|
Telp: (021) 123-4567<br>
|
||||||
|
Email: info@ayulastore.com
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="invoice-meta">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td width="150"><strong>ID Laporan</strong></td>
|
||||||
|
<td>: <?php echo $report['id_report']; ?></td>
|
||||||
|
<td width="150"><strong>Tanggal</strong></td>
|
||||||
|
<td>: <?php echo date('d/m/Y H:i', strtotime($report['tanggal'])); ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>ID Barang</strong></td>
|
||||||
|
<td>: <?php echo $report['id_barang']; ?></td>
|
||||||
|
<td><strong>Jenis Barang</strong></td>
|
||||||
|
<td>: <?php echo $report['nama_jenis']; ?></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="invoice-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="5%">No</th>
|
||||||
|
<th width="45%">Nama Barang</th>
|
||||||
|
<th width="15%">Jumlah</th>
|
||||||
|
<th width="15%">Harga</th>
|
||||||
|
<th width="20%">Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center">1</td>
|
||||||
|
<td><?php echo $report['nama_barang']; ?></td>
|
||||||
|
<td class="text-center"><?php echo $report['jumlah']; ?></td>
|
||||||
|
<td class="text-right">Rp <?php echo number_format($report['harga'], 0, ',', '.'); ?></td>
|
||||||
|
<td class="text-right">Rp <?php echo number_format($total, 0, ',', '.'); ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="text-right"><strong>Total</strong></td>
|
||||||
|
<td class="text-right"><strong>Rp <?php echo number_format($total, 0, ',', '.'); ?></strong></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="receipt-container">
|
||||||
|
<h4>Gambar Nota</h4>
|
||||||
|
<img src="<?php echo $image_url; ?>" alt="Nota" class="receipt-image">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="signature-section">
|
||||||
|
<div class="signature-box">
|
||||||
|
<div class="signature-line"></div>
|
||||||
|
<strong>Penanggung Jawab</strong>
|
||||||
|
</div>
|
||||||
|
<div class="signature-box">
|
||||||
|
<div class="signature-line"></div>
|
||||||
|
<strong>Admin</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
// Auto print when page loads
|
||||||
|
window.onload = function() {
|
||||||
|
// Add a small delay to ensure everything is loaded
|
||||||
|
setTimeout(function() {
|
||||||
|
//window.print();
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,349 @@
|
||||||
|
<?php
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
// Ambil informasi user yang sedang login
|
||||||
|
$userRole = $_SESSION['role']; // 'user' atau 'admin'
|
||||||
|
$username = $_SESSION['username']; // Menambahkan username dari session
|
||||||
|
|
||||||
|
// Jika username adalah root, tampilkan nama yang lebih presentable
|
||||||
|
$displayName = ($username === 'root') ? 'Admin' : $username;
|
||||||
|
|
||||||
|
// Cek apakah session 'user_id' ada, yang berarti pengguna sudah login
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
// Jika session tidak ada, arahkan pengguna ke halaman login
|
||||||
|
header("Location: /ayula-store/index.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Database connection
|
||||||
|
include('../../routes/db_conn.php');
|
||||||
|
|
||||||
|
$product_id = $_GET['id']; // Fetching product ID from URL parameter
|
||||||
|
|
||||||
|
// SQL query to fetch product details
|
||||||
|
$sql = "SELECT * FROM barang WHERE id_barang = ?";
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
$stmt->bind_param('i', $product_id); // Bind product_id as integer
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
if ($result->num_rows > 0) {
|
||||||
|
$product = $result->fetch_assoc();
|
||||||
|
} else {
|
||||||
|
echo "Product not found.";
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0">
|
||||||
|
<meta name="description" content="POS - Bootstrap Admin Template">
|
||||||
|
<meta name="keywords"
|
||||||
|
content="admin, estimates, bootstrap, business, corporate, creative, invoice, html5, responsive, Projects">
|
||||||
|
<meta name="author" content="Dreamguys - Bootstrap Admin Template">
|
||||||
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
<title>Dreams Pos admin template</title>
|
||||||
|
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="/ayula-store/bootstrap/assets/img/favicon.jpg">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/bootstrap.min.css">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/animate.css">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/plugins/select2/css/select2.min.css">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/plugins/owlcarousel/owl.carousel.min.css">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/dataTables.bootstrap4.min.css">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/plugins/fontawesome/css/fontawesome.min.css">
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/plugins/fontawesome/css/all.min.css">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/style.css">
|
||||||
|
|
||||||
|
<!-- Add JsBarcode library -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.5/dist/JsBarcode.all.min.js"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.barcode-container {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.barcode-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.barcode-print-btn {
|
||||||
|
margin-top: 10px;
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #ff9f43;
|
||||||
|
color: white;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.barcode-print-btn:hover {
|
||||||
|
background-color: #ff8a1e;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.barcode-print-btn i {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
@media print {
|
||||||
|
body * {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
.barcode-print-section, .barcode-print-section * {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
.barcode-print-section {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.barcode-print-btn {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="global-loader">
|
||||||
|
<div class="whirly-loader"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="main-wrapper">
|
||||||
|
<div class="header">
|
||||||
|
<div class="header-left active">
|
||||||
|
<a href="/ayula-store/views/dashboard/" class="logo">
|
||||||
|
<img src="../../src/img/logoayula.png" alt="" />
|
||||||
|
</a>
|
||||||
|
<a href="/ayula-store/views/dashboard/" class="logo-small">
|
||||||
|
<img src="../../src/img/smallest-ayula.png" alt="" />
|
||||||
|
</a>
|
||||||
|
<a id="toggle_btn" href="javascript:void(0);"> </a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a id="mobile_btn" class="mobile_btn" href="#sidebar">
|
||||||
|
<span class="bar-icon">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<ul class="nav user-menu">
|
||||||
|
<li class="nav-item dropdown has-arrow main-drop">
|
||||||
|
<a href="javascript:void(0);" class="dropdown-toggle nav-link userset" data-bs-toggle="dropdown">
|
||||||
|
<span class="user-img"><img src="../../src/img/userprofile.png" alt="">
|
||||||
|
<span class="status online"></span></span>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu menu-drop-user">
|
||||||
|
<div class="profilename">
|
||||||
|
<div class="profileset">
|
||||||
|
<span class="user-img"><img src="../../src/img/userprofile.png" alt="">
|
||||||
|
<span class="status online"></span></span>
|
||||||
|
<div class="profilesets">
|
||||||
|
<h6><?php echo $userRole == 'admin' ? 'Admin' : 'Karyawan'; ?></h6>
|
||||||
|
<h5><?php echo htmlspecialchars($displayName); ?></h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item" href="/ayula-store/views/report-issue/">
|
||||||
|
<img src="../../src/img/warning.png" class="me-2" alt="img" /> Laporkan Masalah
|
||||||
|
</a>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item logout pb-0" href="../../views/logout.php"><img
|
||||||
|
src="../../bootstrap/assets/img/icons/log-out.svg" class="me-2" alt="img" />Keluar</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="dropdown mobile-user-menu">
|
||||||
|
<a href="javascript:void(0);" class="nav-link dropdown-toggle" data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"><i class="fa fa-ellipsis-v"></i></a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
|
<a class="dropdown-item" href="/ayula-store/views/report-issue/">
|
||||||
|
<i class="fa fa-cog me-2"></i> Laporkan Masalah
|
||||||
|
</a>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item logout pb-0" href="../../views/logout.php"><img
|
||||||
|
src="../../bootstrap/assets/img/icons/log-out.svg" class="me-2" alt="img" />Keluar</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sidebar" id="sidebar">
|
||||||
|
<div class="sidebar-inner slimscroll">
|
||||||
|
<div id="sidebar-menu" class="sidebar-menu">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="/ayula-store/views/reporttt/report.php"><img src="../../bootstrap/assets/img/icons/dashboard.svg" alt="img" /><span>
|
||||||
|
Dashboard</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../bootstrap/assets/img/icons/product.svg" alt="img" /><span>
|
||||||
|
Barang</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/ayula-store/views/barang/productlist.php" class="active">Daftar Barang</a></li>
|
||||||
|
<li><a href="/ayula-store/views/barang/addproduct.php">Tambah Barang</a></li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li >
|
||||||
|
<a href="/ayula-store/views/barang/topsis_restock_view.php"><img src="../../bootstrap/assets/img/icons/sales1.svg" alt="img" /><span>
|
||||||
|
Analisa Barang</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- Other menu items -->
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../bootstrap/assets/img/icons/users1.svg" alt="img" /><span>
|
||||||
|
Pengguna</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<?php if ($userRole == 'admin') { ?>
|
||||||
|
<li><a href="/ayula-store/views/users/add-user.php">Pengguna Baru</a></li>
|
||||||
|
<?php } ?>
|
||||||
|
<li><a href="/ayula-store/views/users/">Daftar Pengguna</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="content">
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="page-title">
|
||||||
|
<h4>Detail barang</h4>
|
||||||
|
<h6></h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-8 col-sm-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<!-- Barcode Section -->
|
||||||
|
<div class="barcode-container barcode-print-section">
|
||||||
|
<svg id="barcode"></svg>
|
||||||
|
<span class="barcode-label"><?php echo isset($product['kode_barang']) ? $product['kode_barang'] : 'No Product Code'; ?></span>
|
||||||
|
<a href="javascript:void(0);" class="barcode-print-btn" onclick="printBarcode()">
|
||||||
|
<i class="fa fa-print"></i> Print Barcode
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="productdetails">
|
||||||
|
<ul class="product-bar">
|
||||||
|
<li>
|
||||||
|
<h4>barang</h4>
|
||||||
|
<h6><?php echo isset($product['nama_barang']) ? $product['nama_barang'] : ''; ?></h6>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h4>kode barang</h4>
|
||||||
|
<h6><?php echo isset($product['kode_barang']) ? $product['kode_barang'] : ''; ?></h6>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h4>Stok</h4>
|
||||||
|
<h6><?php echo isset($product['stok']) ? $product['stok'] : '0'; ?></h6>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h4>Harga</h4>
|
||||||
|
<h6>Rp <?php echo isset($product['harga']) ? number_format($product['harga'], 0, ',', '.') : '0'; ?></h6>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-4 col-sm-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="slider-product-details">
|
||||||
|
<div class="owl-carousel owl-theme product-slide">
|
||||||
|
<div class="slider-product">
|
||||||
|
<?php if (isset($product['image']) && !empty($product['image'])): ?>
|
||||||
|
<img src="../uploads/img-barang/<?php echo $product['image']; ?>" alt="Product Image">
|
||||||
|
<h4><?php echo $product['image']; ?></h4>
|
||||||
|
<?php else: ?>
|
||||||
|
<img src="/ayula-store/bootstrap/assets/img/product/noimage.png" alt="No Image">
|
||||||
|
<h4>No Image Available</h4>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/jquery-3.6.0.min.js"></script>
|
||||||
|
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/feather.min.js"></script>
|
||||||
|
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/jquery.slimscroll.min.js"></script>
|
||||||
|
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
|
<script src="/ayula-store/bootstrap/assets/plugins/owlcarousel/owl.carousel.min.js"></script>
|
||||||
|
|
||||||
|
<script src="/ayula-store/bootstrap/assets/plugins/select2/js/select2.min.js"></script>
|
||||||
|
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/script.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Generate barcode
|
||||||
|
$(document).ready(function() {
|
||||||
|
<?php if (isset($product['kode_barang']) && !empty($product['kode_barang'])): ?>
|
||||||
|
JsBarcode("#barcode", "<?php echo $product['kode_barang']; ?>", {
|
||||||
|
format: "CODE128",
|
||||||
|
width: 2,
|
||||||
|
height: 70,
|
||||||
|
displayValue: false,
|
||||||
|
margin: 10,
|
||||||
|
background: "#ffffff",
|
||||||
|
lineColor: "#000000"
|
||||||
|
});
|
||||||
|
<?php else: ?>
|
||||||
|
JsBarcode("#barcode", "BRG000", {
|
||||||
|
format: "CODE128",
|
||||||
|
width: 2,
|
||||||
|
height: 70,
|
||||||
|
displayValue: false,
|
||||||
|
margin: 10,
|
||||||
|
background: "#ffffff",
|
||||||
|
lineColor: "#000000"
|
||||||
|
});
|
||||||
|
<?php endif; ?>
|
||||||
|
});
|
||||||
|
|
||||||
|
// Function to print barcode
|
||||||
|
function printBarcode() {
|
||||||
|
window.print();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
<?php
|
||||||
|
// Koneksi ke database
|
||||||
|
$servername = "localhost";
|
||||||
|
$username = "root";
|
||||||
|
$password = "";
|
||||||
|
$database = "ayula_store";
|
||||||
|
|
||||||
|
$conn = new mysqli($servername, $username, $password, $database);
|
||||||
|
|
||||||
|
// Periksa koneksi
|
||||||
|
if ($conn->connect_error) {
|
||||||
|
die(json_encode(['success' => false, 'message' => 'Koneksi database gagal: ' . $conn->connect_error]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cek action yang diminta
|
||||||
|
if (isset($_POST['action']) && $_POST['action'] == 'create_report') {
|
||||||
|
createReport($conn);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Aksi tidak valid']);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createReport($conn) {
|
||||||
|
// Handle upload gambar nota
|
||||||
|
$upload_dir = '../uploads/nota/';
|
||||||
|
|
||||||
|
// Buat direktori jika belum ada
|
||||||
|
if (!file_exists($upload_dir)) {
|
||||||
|
mkdir($upload_dir, 0777, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$image_name = '';
|
||||||
|
|
||||||
|
if (isset($_FILES['receipt_image']) && $_FILES['receipt_image']['error'] == 0) {
|
||||||
|
// Generate nama file unik
|
||||||
|
$file_extension = pathinfo($_FILES['receipt_image']['name'], PATHINFO_EXTENSION);
|
||||||
|
$image_name = 'nota_' . time() . '_' . mt_rand(1000, 9999) . '.' . $file_extension;
|
||||||
|
$target_file = $upload_dir . $image_name;
|
||||||
|
|
||||||
|
// Validasi tipe file
|
||||||
|
$allowed_types = ['jpg', 'jpeg', 'png', 'gif'];
|
||||||
|
if (!in_array(strtolower($file_extension), $allowed_types)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Format file tidak didukung. Gunakan JPG, JPEG, PNG, atau GIF']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validasi ukuran file (max 5MB)
|
||||||
|
if ($_FILES['receipt_image']['size'] > 5 * 1024 * 1024) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Ukuran file terlalu besar (maksimum 5MB)']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload file
|
||||||
|
if (!move_uploaded_file($_FILES['receipt_image']['tmp_name'], $target_file)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Gagal mengunggah gambar']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Gambar nota harus diunggah']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mulai transaksi
|
||||||
|
$conn->begin_transaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Loop through the items to insert
|
||||||
|
if (isset($_POST['id_barang'])) {
|
||||||
|
foreach ($_POST['id_barang'] as $key => $id_barang) {
|
||||||
|
$jumlah = $_POST['jumlah'][$key] ?? '';
|
||||||
|
$harga = $_POST['harga'][$key] ?? '';
|
||||||
|
|
||||||
|
// Validasi data
|
||||||
|
if (empty($id_barang) || empty($jumlah) || empty($harga)) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Semua field harus diisi']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buat ID report otomatis (format: RPT-YYYYMMDDxxx)
|
||||||
|
$date = date('Ymd');
|
||||||
|
$query = "SELECT MAX(SUBSTRING(id_report, 12)) as last_id FROM report WHERE id_report LIKE 'RPT-$date%'";
|
||||||
|
$result = $conn->query($query);
|
||||||
|
$row = $result->fetch_assoc();
|
||||||
|
$last_id = $row['last_id'] ?? 0;
|
||||||
|
$new_id = 'RPT-' . $date . str_pad(intval($last_id) + 1, 3, '0', STR_PAD_LEFT);
|
||||||
|
|
||||||
|
// Simpan data ke tabel report
|
||||||
|
$query = "INSERT INTO report (id_report, id_barang, tanggal, jumlah, harga, image)
|
||||||
|
VALUES (?, ?, NOW(), ?, ?, ?)";
|
||||||
|
$stmt = $conn->prepare($query);
|
||||||
|
$stmt->bind_param('sssss', $new_id, $id_barang, $jumlah, $harga, $image_name);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
if ($stmt->affected_rows <= 0) {
|
||||||
|
throw new Exception("Gagal menyimpan data report");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Exception("ID Barang tidak ditemukan");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit transaksi
|
||||||
|
$conn->commit();
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Data berhasil ditambahkan ke laporan',
|
||||||
|
'image_url' => '/ayula-store/uploads/nota/' . $image_name
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// Rollback transaksi jika terjadi kesalahan
|
||||||
|
$conn->rollback();
|
||||||
|
|
||||||
|
// Hapus file gambar jika upload sudah terjadi
|
||||||
|
if (!empty($image_name) && file_exists($upload_dir . $image_name)) {
|
||||||
|
unlink($upload_dir . $image_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Error: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
<?php
|
||||||
|
// Koneksi ke database
|
||||||
|
$servername = "localhost";
|
||||||
|
$username = "root"; // Sesuaikan dengan username database kamu
|
||||||
|
$password = ""; // Sesuaikan dengan password database kamu
|
||||||
|
$database = "ayula_store"; // Sesuaikan dengan nama database kamu
|
||||||
|
|
||||||
|
$conn = new mysqli($servername, $username, $password, $database);
|
||||||
|
|
||||||
|
// Periksa koneksi
|
||||||
|
if ($conn->connect_error) {
|
||||||
|
die("Koneksi gagal: " . $conn->connect_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ambil data untuk laporan
|
||||||
|
$sql = "SELECT r.id_report, r.tanggal, b.nama_barang, jb.nama_jenis, r.jumlah, r.harga
|
||||||
|
FROM report r
|
||||||
|
JOIN barang b ON r.id_barang = b.id_barang
|
||||||
|
JOIN jenis_barang jb ON b.id_jenis = jb.id_jenis
|
||||||
|
ORDER BY r.tanggal DESC";
|
||||||
|
|
||||||
|
$result = $conn->query($sql);
|
||||||
|
|
||||||
|
// Set header for Excel file
|
||||||
|
header('Content-Type: application/vnd.ms-excel');
|
||||||
|
header('Content-Disposition: attachment; filename="laporan_barang_' . date('Y-m-d') . '.xls"');
|
||||||
|
header('Pragma: no-cache');
|
||||||
|
header('Expires: 0');
|
||||||
|
|
||||||
|
// Output tabel HTML yang akan dibaca sebagai Excel
|
||||||
|
echo '<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
|
<title>Laporan Barang</title>
|
||||||
|
<style>
|
||||||
|
table {border-collapse: collapse;}
|
||||||
|
th, td {border: 1px solid #000; padding: 5px;}
|
||||||
|
th {background-color: #f0f0f0; font-weight: bold;}
|
||||||
|
.text-right {text-align: right;}
|
||||||
|
.text-center {text-align: center;}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h2>Laporan Barang - Ayula Store</h2>
|
||||||
|
<p>Tanggal Export: ' . date('d/m/Y H:i:s') . '</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>No</th>
|
||||||
|
<th>ID Laporan</th>
|
||||||
|
<th>Tanggal</th>
|
||||||
|
<th>Nama Barang</th>
|
||||||
|
<th>Jenis</th>
|
||||||
|
<th>Jumlah</th>
|
||||||
|
<th>Harga (Rp)</th>
|
||||||
|
<th>Total (Rp)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>';
|
||||||
|
|
||||||
|
$no = 1;
|
||||||
|
$grand_total = 0;
|
||||||
|
|
||||||
|
if ($result->num_rows > 0) {
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$total = $row['jumlah'] * $row['harga'];
|
||||||
|
$grand_total += $total;
|
||||||
|
|
||||||
|
echo '<tr>
|
||||||
|
<td class="text-center">' . $no++ . '</td>
|
||||||
|
<td>' . $row['id_report'] . '</td>
|
||||||
|
<td>' . date('d/m/Y H:i', strtotime($row['tanggal'])) . '</td>
|
||||||
|
<td>' . $row['nama_barang'] . '</td>
|
||||||
|
<td>' . $row['nama_jenis'] . '</td>
|
||||||
|
<td class="text-center">' . $row['jumlah'] . '</td>
|
||||||
|
<td class="text-right">' . number_format($row['harga'], 0, ',', '.') . '</td>
|
||||||
|
<td class="text-right">' . number_format($total, 0, ',', '.') . '</td>
|
||||||
|
</tr>';
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '<tr>
|
||||||
|
<td colspan="7" class="text-right"><strong>Grand Total</strong></td>
|
||||||
|
<td class="text-right"><strong>' . number_format($grand_total, 0, ',', '.') . '</strong></td>
|
||||||
|
</tr>';
|
||||||
|
} else {
|
||||||
|
echo '<tr><td colspan="8" class="text-center">Tidak ada data laporan</td></tr>';
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '</tbody>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>';
|
||||||
|
|
||||||
|
// Close database connection
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?php
|
||||||
|
require_once('topsis_restok.php');
|
||||||
|
tampilkanSaranRestok();
|
||||||
|
?>
|
||||||
|
|
@ -0,0 +1,174 @@
|
||||||
|
<?php
|
||||||
|
function restockWithTOPSIS($conn, $criteria_weights = null) {
|
||||||
|
// Bobot default (sesuai manual)
|
||||||
|
if ($criteria_weights === null) {
|
||||||
|
$criteria_weights = [
|
||||||
|
'stock_level' => 0.4,
|
||||||
|
'price_value' => 0.3,
|
||||||
|
'turnover_rate' => 0.3,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ambil data produk dan penjualan 30 hari terakhir sesuai struktur DB
|
||||||
|
$query = "
|
||||||
|
SELECT
|
||||||
|
bk.id_barangK AS id_barang,
|
||||||
|
bk.nama_barang,
|
||||||
|
bk.stok,
|
||||||
|
bk.harga,
|
||||||
|
COALESCE(SUM(dt.jumlah), 0) AS total_penjualan
|
||||||
|
FROM barang_kasir bk
|
||||||
|
LEFT JOIN detail_transaksi dt ON bk.id_barangK = dt.id_barangK
|
||||||
|
LEFT JOIN transaksi t ON dt.id_transaksi = t.id_transaksi
|
||||||
|
AND t.tanggal BETWEEN DATE_SUB(NOW(), INTERVAL 30 DAY) AND NOW()
|
||||||
|
GROUP BY bk.id_barangK, bk.nama_barang, bk.stok, bk.harga
|
||||||
|
";
|
||||||
|
|
||||||
|
$result = mysqli_query($conn, $query);
|
||||||
|
if (!$result) {
|
||||||
|
return ["error" => "Query failed: " . mysqli_error($conn)];
|
||||||
|
}
|
||||||
|
|
||||||
|
$products = [];
|
||||||
|
while ($row = mysqli_fetch_assoc($result)) {
|
||||||
|
// Pastikan harga sudah dalam format numeric (decimal)
|
||||||
|
$harga = (float)$row['harga'];
|
||||||
|
|
||||||
|
// Hitung turnover rate (penjualan/stok), hindari pembagian 0
|
||||||
|
$turnover_rate = ($row['stok'] > 0) ? ($row['total_penjualan'] / $row['stok']) : 0;
|
||||||
|
|
||||||
|
$products[] = [
|
||||||
|
'id_barang' => $row['id_barang'],
|
||||||
|
'nama_barang' => $row['nama_barang'],
|
||||||
|
'stok' => (int)$row['stok'],
|
||||||
|
'harga' => $harga,
|
||||||
|
'turnover_rate' => $turnover_rate,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($products)) {
|
||||||
|
return ["message" => "No products found"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buat decision matrix dengan reciprocal stok (1/stok)
|
||||||
|
$decision_matrix = [];
|
||||||
|
foreach ($products as $product) {
|
||||||
|
$reciprocal_stock = ($product['stok'] > 0) ? (1 / $product['stok']) : 0;
|
||||||
|
$decision_matrix[] = [
|
||||||
|
'id_barang' => $product['id_barang'],
|
||||||
|
'nama_barang' => $product['nama_barang'],
|
||||||
|
'stock_level' => $reciprocal_stock,
|
||||||
|
'price_value' => $product['harga'],
|
||||||
|
'turnover_rate' => $product['turnover_rate'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalisasi
|
||||||
|
$normalized_matrix = normalizeMatrix($decision_matrix);
|
||||||
|
|
||||||
|
// Terapkan bobot
|
||||||
|
$weighted_matrix = applyWeights($normalized_matrix, $criteria_weights);
|
||||||
|
|
||||||
|
// Tentukan solusi ideal positif dan negatif
|
||||||
|
$ideal_solution = [];
|
||||||
|
$negative_ideal_solution = [];
|
||||||
|
foreach (['stock_level', 'price_value', 'turnover_rate'] as $criteria) {
|
||||||
|
$values = array_column($weighted_matrix, $criteria);
|
||||||
|
$ideal_solution[$criteria] = max($values);
|
||||||
|
$negative_ideal_solution[$criteria] = min($values);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hitung jarak ke solusi ideal positif dan negatif
|
||||||
|
$separation_ideal = [];
|
||||||
|
$separation_negative = [];
|
||||||
|
foreach ($weighted_matrix as $key => $product) {
|
||||||
|
$separation_ideal[$key] = sqrt(
|
||||||
|
pow($product['stock_level'] - $ideal_solution['stock_level'], 2) +
|
||||||
|
pow($product['price_value'] - $ideal_solution['price_value'], 2) +
|
||||||
|
pow($product['turnover_rate'] - $ideal_solution['turnover_rate'], 2)
|
||||||
|
);
|
||||||
|
$separation_negative[$key] = sqrt(
|
||||||
|
pow($product['stock_level'] - $negative_ideal_solution['stock_level'], 2) +
|
||||||
|
pow($product['price_value'] - $negative_ideal_solution['price_value'], 2) +
|
||||||
|
pow($product['turnover_rate'] - $negative_ideal_solution['turnover_rate'], 2)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hitung skor TOPSIS (relative closeness)
|
||||||
|
$relative_closeness = [];
|
||||||
|
foreach ($weighted_matrix as $key => $product) {
|
||||||
|
$denominator = $separation_negative[$key] + $separation_ideal[$key];
|
||||||
|
$relative_closeness[$key] = ($denominator > 0) ? ($separation_negative[$key] / $denominator) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Siapkan hasil
|
||||||
|
$topsis_scores = [];
|
||||||
|
foreach ($relative_closeness as $key => $score) {
|
||||||
|
$topsis_scores[] = [
|
||||||
|
'id_barang' => $decision_matrix[$key]['id_barang'],
|
||||||
|
'nama_barang' => $decision_matrix[$key]['nama_barang'],
|
||||||
|
'stok' => $products[$key]['stok'],
|
||||||
|
'harga' => $products[$key]['harga'],
|
||||||
|
'topsis_score' => round($score, 4),
|
||||||
|
'rank' => 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Urutkan berdasarkan skor TOPSIS descending
|
||||||
|
usort($topsis_scores, function($a, $b) {
|
||||||
|
return $b['topsis_score'] <=> $a['topsis_score'];
|
||||||
|
});
|
||||||
|
|
||||||
|
// Beri peringkat
|
||||||
|
foreach ($topsis_scores as $key => $product) {
|
||||||
|
$topsis_scores[$key]['rank'] = $key + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $topsis_scores;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeMatrix($matrix) {
|
||||||
|
$normalized = [];
|
||||||
|
$sum_squares = [
|
||||||
|
'stock_level' => 0,
|
||||||
|
'price_value' => 0,
|
||||||
|
'turnover_rate' => 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($matrix as $product) {
|
||||||
|
$sum_squares['stock_level'] += pow($product['stock_level'], 2);
|
||||||
|
$sum_squares['price_value'] += pow($product['price_value'], 2);
|
||||||
|
$sum_squares['turnover_rate'] += pow($product['turnover_rate'], 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($sum_squares as $key => $value) {
|
||||||
|
$sum_squares[$key] = sqrt($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($matrix as $key => $product) {
|
||||||
|
$normalized[$key] = [
|
||||||
|
'id_barang' => $product['id_barang'],
|
||||||
|
'nama_barang' => $product['nama_barang'],
|
||||||
|
'stock_level' => ($sum_squares['stock_level'] > 0) ? ($product['stock_level'] / $sum_squares['stock_level']) : 0,
|
||||||
|
'price_value' => ($sum_squares['price_value'] > 0) ? ($product['price_value'] / $sum_squares['price_value']) : 0,
|
||||||
|
'turnover_rate' => ($sum_squares['turnover_rate'] > 0) ? ($product['turnover_rate'] / $sum_squares['turnover_rate']) : 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyWeights($matrix, $weights) {
|
||||||
|
$weighted = [];
|
||||||
|
foreach ($matrix as $key => $product) {
|
||||||
|
$weighted[$key] = [
|
||||||
|
'id_barang' => $product['id_barang'],
|
||||||
|
'nama_barang' => $product['nama_barang'],
|
||||||
|
'stock_level' => $product['stock_level'] * $weights['stock_level'],
|
||||||
|
'price_value' => $product['price_value'] * $weights['price_value'],
|
||||||
|
'turnover_rate' => $product['turnover_rate'] * $weights['turnover_rate'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $weighted;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
<?php
|
||||||
|
// File: topsis_implementation.php
|
||||||
|
require_once 'topsis_functions.php';
|
||||||
|
|
||||||
|
function processRestock($conn, $productId, $amount, $note = '') {
|
||||||
|
$productId = (int)$productId;
|
||||||
|
$amount = (int)$amount;
|
||||||
|
|
||||||
|
if ($productId <= 0 || $amount <= 0) {
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Invalid product ID or amount'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ambil stok sekarang dari barang_kasir
|
||||||
|
$query = "SELECT stok FROM barang_kasir WHERE id_barangK = ?";
|
||||||
|
$stmt = mysqli_prepare($conn, $query);
|
||||||
|
mysqli_stmt_bind_param($stmt, "i", $productId);
|
||||||
|
mysqli_stmt_execute($stmt);
|
||||||
|
$result = mysqli_stmt_get_result($stmt);
|
||||||
|
|
||||||
|
if (!$row = mysqli_fetch_assoc($result)) {
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Product not found'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentStock = (int)$row['stok'];
|
||||||
|
$newStock = $currentStock + $amount;
|
||||||
|
|
||||||
|
// Update stok barang_kasir
|
||||||
|
$updateQuery = "UPDATE barang_kasir SET stok = ? WHERE id_barangK = ?";
|
||||||
|
$updateStmt = mysqli_prepare($conn, $updateQuery);
|
||||||
|
mysqli_stmt_bind_param($updateStmt, "ii", $newStock, $productId);
|
||||||
|
$success = mysqli_stmt_execute($updateStmt);
|
||||||
|
|
||||||
|
if (!$success) {
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Failed to update stock: ' . mysqli_error($conn)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log aktivitas restok
|
||||||
|
$logQuery = "INSERT INTO log_restok (id_barang, jumlah, tanggal, catatan)
|
||||||
|
VALUES (?, ?, NOW(), ?)";
|
||||||
|
$logStmt = mysqli_prepare($conn, $logQuery);
|
||||||
|
mysqli_stmt_bind_param($logStmt, "iis", $productId, $amount, $note);
|
||||||
|
mysqli_stmt_execute($logStmt);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Stock updated successfully',
|
||||||
|
'new_stock' => $newStock
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle AJAX request
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||||
|
$host = "localhost";
|
||||||
|
$username = "root"; // sesuaikan username DB Anda
|
||||||
|
$password = ""; // sesuaikan password DB Anda
|
||||||
|
$database = "ayula_store";
|
||||||
|
|
||||||
|
$conn = mysqli_connect($host, $username, $password, $database);
|
||||||
|
if (!$conn) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Database connection failed: ' . mysqli_connect_error()
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$action = $_POST['action'];
|
||||||
|
|
||||||
|
switch ($action) {
|
||||||
|
case 'restock':
|
||||||
|
if (isset($_POST['productId']) && isset($_POST['amount'])) {
|
||||||
|
$result = processRestock(
|
||||||
|
$conn,
|
||||||
|
$_POST['productId'],
|
||||||
|
$_POST['amount'],
|
||||||
|
$_POST['note'] ?? ''
|
||||||
|
);
|
||||||
|
echo json_encode($result);
|
||||||
|
} else {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Missing required parameters'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Unknown action'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_close($conn);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRestockLogTable($conn) {
|
||||||
|
$query = "CREATE TABLE IF NOT EXISTS log_restok (
|
||||||
|
id INT(11) PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
id_barang INT(11) NOT NULL,
|
||||||
|
jumlah INT(10) NOT NULL,
|
||||||
|
tanggal DATETIME NOT NULL,
|
||||||
|
catatan TEXT,
|
||||||
|
FOREIGN KEY (id_barang) REFERENCES barang_kasir(id_barangK)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
|
||||||
|
|
||||||
|
return mysqli_query($conn, $query);
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportToCSV($data, $filename = 'topsis_results.csv') {
|
||||||
|
header('Content-Type: text/csv');
|
||||||
|
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
||||||
|
|
||||||
|
$output = fopen('php://output', 'w');
|
||||||
|
|
||||||
|
fputcsv($output, ['Rank', 'ID Barang', 'Nama Barang', 'Stok', 'Harga', 'TOPSIS Score']);
|
||||||
|
|
||||||
|
foreach ($data as $row) {
|
||||||
|
fputcsv($output, [
|
||||||
|
$row['rank'],
|
||||||
|
$row['id_barang'],
|
||||||
|
$row['nama_barang'],
|
||||||
|
$row['stok'],
|
||||||
|
$row['harga'],
|
||||||
|
$row['topsis_score']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose($output);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
@ -0,0 +1,627 @@
|
||||||
|
<?php
|
||||||
|
// File: topsis_restock_view.php
|
||||||
|
|
||||||
|
// Include the TOPSIS function
|
||||||
|
require_once 'topsis_functions.php';
|
||||||
|
|
||||||
|
// Database connection
|
||||||
|
$host = "localhost";
|
||||||
|
$username = "root";
|
||||||
|
$password = "";
|
||||||
|
$database = "ayula_store";
|
||||||
|
|
||||||
|
$conn = mysqli_connect($host, $username, $password, $database);
|
||||||
|
if (!$conn) {
|
||||||
|
die("Connection failed: " . mysqli_connect_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default criteria weights
|
||||||
|
$default_weights = [
|
||||||
|
'stock_level' => 0.4, // Lower stock is more urgent (40% importance)
|
||||||
|
'price_value' => 0.3, // Higher price might indicate higher priority (30% importance)
|
||||||
|
'turnover_rate' => 0.3 // Higher turnover rate means faster selling (30% importance)
|
||||||
|
];
|
||||||
|
|
||||||
|
// Check if form was submitted with custom weights
|
||||||
|
$weights = $default_weights;
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['submit_weights'])) {
|
||||||
|
$weights = [
|
||||||
|
'stock_level' => floatval($_POST['weight_stock']),
|
||||||
|
'price_value' => floatval($_POST['weight_price']),
|
||||||
|
'turnover_rate' => floatval($_POST['weight_turnover'])
|
||||||
|
];
|
||||||
|
|
||||||
|
// Normalize weights to ensure they sum to 1
|
||||||
|
$total = array_sum($weights);
|
||||||
|
if ($total > 0) {
|
||||||
|
foreach ($weights as $key => $value) {
|
||||||
|
$weights[$key] = $value / $total;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$weights = $default_weights;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get restocking priorities using TOPSIS
|
||||||
|
$restock_priorities = restockWithTOPSIS($conn, $weights);
|
||||||
|
|
||||||
|
// Check if any error occurred
|
||||||
|
$error_message = "";
|
||||||
|
if (isset($restock_priorities['error'])) {
|
||||||
|
$error_message = $restock_priorities['error'];
|
||||||
|
$restock_priorities = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate total products and those needing restocking (stock < 10)
|
||||||
|
$total_products = count($restock_priorities);
|
||||||
|
$low_stock_count = 0;
|
||||||
|
foreach ($restock_priorities as $product) {
|
||||||
|
if ($product['stok'] < 10) {
|
||||||
|
$low_stock_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current date and time
|
||||||
|
$current_date = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
|
// Page title
|
||||||
|
$page_title = "Analisis Restok dengan Metode TOPSIS";
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="id">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1.0, user-scalable=0" />
|
||||||
|
<meta name="description" content="POS - Bootstrap Admin Template" />
|
||||||
|
<meta
|
||||||
|
name="keywords"
|
||||||
|
content="admin, estimates, bootstrap, business, corporate, creative, invoice, html5, responsive, Projects" />
|
||||||
|
<meta name="author" content="Dreamguys - Bootstrap Admin Template" />
|
||||||
|
<meta name="robots" content="noindex, nofollow" />
|
||||||
|
<title>Dreams Pos admin template</title>
|
||||||
|
|
||||||
|
<link
|
||||||
|
rel="shortcut icon"
|
||||||
|
type="image/x-icon"
|
||||||
|
href="/ayula-store/bootstrap/assets/img/favicon.jpg" />
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/bootstrap.min.css" />
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/animate.css" />
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/plugins/select2/css/select2.min.css" />
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/dataTables.bootstrap4.min.css" />
|
||||||
|
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="/ayula-store/bootstrap/assets/plugins/fontawesome/css/fontawesome.min.css" />
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/plugins/fontawesome/css/all.min.css" />
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/style.css" />
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css">
|
||||||
|
<style>
|
||||||
|
.priority-high {
|
||||||
|
background-color: #ffdddd !important;
|
||||||
|
}
|
||||||
|
.priority-medium {
|
||||||
|
background-color: #ffffdd !important;
|
||||||
|
}
|
||||||
|
.priority-low {
|
||||||
|
background-color: #ddffdd !important;
|
||||||
|
}
|
||||||
|
.weights-form {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.dashboard-card {
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
.dashboard-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
#weightChart {
|
||||||
|
max-width: 300px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.restock-btn {
|
||||||
|
background-color: #007bff; /* Blue background */
|
||||||
|
color: white; /* White text */
|
||||||
|
border: none; /* Remove the border */
|
||||||
|
padding: 8px 16px; /* Adjust padding */
|
||||||
|
font-size: 14px; /* Adjust font size */
|
||||||
|
border-radius: 5px; /* Rounded corners */
|
||||||
|
transition: background-color 0.3s ease; /* Smooth hover effect */
|
||||||
|
}
|
||||||
|
|
||||||
|
.restock-btn:hover {
|
||||||
|
background-color: #0056b3; /* Darker blue on hover */
|
||||||
|
}
|
||||||
|
/* Table Header Styling */
|
||||||
|
.table th {
|
||||||
|
color: white !important; /* Ensures the text color in the header is white */
|
||||||
|
background-color: #343a40 !important; /* Dark background for the header */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table Cells Styling */
|
||||||
|
.table td {
|
||||||
|
color: #000000 !important; /* Text color set to black for the table cells */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Row Priority Class - High Priority (Red Background) */
|
||||||
|
.priority-high td {
|
||||||
|
background-color: #ffdddd !important; /* Red background for high priority */
|
||||||
|
color: black !important; /* Black text color */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Row Priority Class - Medium Priority (Yellow Background) */
|
||||||
|
.priority-medium td {
|
||||||
|
background-color: #ffffdd !important; /* Yellow background for medium priority */
|
||||||
|
color: black !important; /* Black text color */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Row Priority Class - Low Priority (Green Background) */
|
||||||
|
.priority-low td {
|
||||||
|
background-color: #ddffdd !important; /* Green background for low priority */
|
||||||
|
color: black !important; /* Black text color */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Optional: Styling the action buttons */
|
||||||
|
.table td button {
|
||||||
|
color: white; /* Make button text white */
|
||||||
|
background-color: #007bff; /* Blue background for buttons */
|
||||||
|
border: none;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table td button:hover {
|
||||||
|
background-color: #0056b3; /* Darker blue on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="global-loader">
|
||||||
|
<div class="whirly-loader"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="main-wrapper">
|
||||||
|
<div class="header">
|
||||||
|
<div class="header-left active">
|
||||||
|
<a href="/ayula-store/views/dashboard/" class="logo">
|
||||||
|
<img src="../../src/img/logoayula.png" alt="" />
|
||||||
|
</a>
|
||||||
|
<a href="/ayula-store/views/dashboard/" class="logo-small">
|
||||||
|
<img src="../../src/img/smallest-ayula.png" alt="" />
|
||||||
|
</a>
|
||||||
|
<a id="toggle_btn" href="javascript:void(0);"> </a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a id="mobile_btn" class="mobile_btn" href="#sidebar">
|
||||||
|
<span class="bar-icon">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<ul class="nav user-menu">
|
||||||
|
<li class="nav-item dropdown has-arrow main-drop">
|
||||||
|
<a href="javascript:void(0);" class="dropdown-toggle nav-link userset" data-bs-toggle="dropdown">
|
||||||
|
<span class="user-img"><img src="../../src/img/userprofile.png" alt="">
|
||||||
|
<span class="status online"></span></span>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu menu-drop-user">
|
||||||
|
<div class="profilename">
|
||||||
|
<div class="profileset">
|
||||||
|
<span class="user-img"><img src="../../src/img/userprofile.png" alt="">
|
||||||
|
<span class="status online"></span></span>
|
||||||
|
<div class="profilesets">
|
||||||
|
<h6><?php echo $userRole == 'admin' ? 'Admin' : 'Karyawan'; ?></h6>
|
||||||
|
<h5><?php echo htmlspecialchars($displayName); ?></h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item" href="/ayula-store/views/report-issue/">
|
||||||
|
<img src="../../src/img/warning.png" class="me-2" alt="img" /> Laporkan Masalah
|
||||||
|
</a>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item logout pb-0" href="../../views/logout.php"><img
|
||||||
|
src="../../bootstrap/assets/img/icons/log-out.svg" class="me-2" alt="img" />Keluar</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="dropdown mobile-user-menu">
|
||||||
|
<a href="javascript:void(0);" class="nav-link dropdown-toggle" data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"><i class="fa fa-ellipsis-v"></i></a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
|
<a class="dropdown-item" href="/ayula-store/views/report-issue/">
|
||||||
|
<i class="fa fa-cog me-2"></i> Laporkan Masalah
|
||||||
|
</a>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item logout pb-0" href="../../views/logout.php"><img
|
||||||
|
src="../../bootstrap/assets/img/icons/log-out.svg" class="me-2" alt="img" />Keluar</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="sidebar" id="sidebar">
|
||||||
|
<div class="sidebar-inner slimscroll">
|
||||||
|
<div id="sidebar-menu" class="sidebar-menu">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="/ayula-store/views/reporttt/report.php"><img src="../../bootstrap/assets/img/icons/dashboard.svg" alt="img" /><span>
|
||||||
|
Dashboard</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../bootstrap/assets/img/icons/product.svg" alt="img" /><span>
|
||||||
|
Barang</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/ayula-store/views/barang/productlist.php" >Daftar Barang</a></li>
|
||||||
|
<li><a href="/ayula-store/views/barang/addproduct.php">Tambah Barang</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="active">
|
||||||
|
<a href="/ayula-store/views/barang/topsis_restock_view.php"><img src="../../bootstrap/assets/img/icons/sales1.svg" alt="img" /><span>
|
||||||
|
Analisa Barang</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../bootstrap/assets/img/icons/users1.svg" alt="img" /><span>
|
||||||
|
Pengguna</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
|
||||||
|
<li><a href="/ayula-store/views/users/add-user.php">Pengguna Baru</a></li>
|
||||||
|
|
||||||
|
<li><a href="/ayula-store/views/users/">Daftar Pengguna</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<h1 class="mb-4 text-center"><?php echo $page_title; ?></h1>
|
||||||
|
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card text-white bg-primary dashboard-card h-100">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<h5 class="card-title">Total Produk</h5>
|
||||||
|
<h2><?php echo $total_products; ?></h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card text-white bg-danger dashboard-card h-100">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<h5 class="card-title">Produk Stok Rendah</h5>
|
||||||
|
<h2><?php echo $low_stock_count; ?></h2>
|
||||||
|
<small>Stok < 10</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card text-white bg-info dashboard-card h-100">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<h5 class="card-title">Tanggal Analisis</h5>
|
||||||
|
<p><?php echo $current_date; ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="weights-form">
|
||||||
|
<h4>Kustomisasi Bobot Kriteria</h4>
|
||||||
|
<form method="POST" action="" class="row g-3">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label for="weight_stock" class="form-label">Bobot Stok:</label>
|
||||||
|
<input type="number" class="form-control" id="weight_stock" name="weight_stock"
|
||||||
|
min="0" max="10" step="0.1" value="<?php echo $weights['stock_level'] * 10; ?>">
|
||||||
|
<small class="text-muted">Nilai lebih tinggi = stok rendah lebih penting</small>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label for="weight_price" class="form-label">Bobot Harga:</label>
|
||||||
|
<input type="number" class="form-control" id="weight_price" name="weight_price"
|
||||||
|
min="0" max="10" step="0.1" value="<?php echo $weights['price_value'] * 10; ?>">
|
||||||
|
<small class="text-muted">Nilai lebih tinggi = harga tinggi lebih penting</small>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label for="weight_turnover" class="form-label">Bobot Perputaran:</label>
|
||||||
|
<input type="number" class="form-control" id="weight_turnover" name="weight_turnover"
|
||||||
|
min="0" max="10" step="0.1" value="<?php echo $weights['turnover_rate'] * 10; ?>">
|
||||||
|
<small class="text-muted">Nilai lebih tinggi = penjualan cepat lebih penting</small>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 mt-3">
|
||||||
|
<button type="submit" name="submit_weights" class="btn btn-primary">Terapkan Bobot</button>
|
||||||
|
<button type="button" class="btn btn-secondary" onclick="resetWeights()">Reset ke Default</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-header">
|
||||||
|
Distribusi Bobot
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<canvas id="weightChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($error_message): ?>
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
<?php echo $error_message; ?>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<h4>Hasil Analisis TOPSIS</h4>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover table-striped">
|
||||||
|
<thead class="table-dark">
|
||||||
|
<tr>
|
||||||
|
<th>Prioritas</th>
|
||||||
|
<th>ID Barang</th>
|
||||||
|
<th>Nama Barang</th>
|
||||||
|
<th>Stok Saat Ini</th>
|
||||||
|
<th>Harga</th>
|
||||||
|
|
||||||
|
<th>Aksi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($restock_priorities as $product):
|
||||||
|
// Determine priority class based on TOPSIS score
|
||||||
|
$priority_class = '';
|
||||||
|
if ($product['topsis_score'] > 0.7) {
|
||||||
|
$priority_class = 'priority-high';
|
||||||
|
} elseif ($product['topsis_score'] > 0.4) {
|
||||||
|
$priority_class = 'priority-medium';
|
||||||
|
} else {
|
||||||
|
$priority_class = 'priority-low';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<tr class="<?php echo $priority_class; ?>">
|
||||||
|
<td><?php echo $product['rank']; ?></td>
|
||||||
|
<td><?php echo $product['id_barang']; ?></td>
|
||||||
|
<td><?php echo $product['nama_barang']; ?></td>
|
||||||
|
<td><?php echo $product['stok']; ?></td>
|
||||||
|
<td>Rp <?php echo number_format($product['harga'], 0, ',', '.'); ?></td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<button class=" restock-btn" onclick="openRestockModal(<?php echo $product['id_barang']; ?>, '<?php echo $product['nama_barang']; ?>')">
|
||||||
|
Restok
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Export Options -->
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal fade" id="restockModal" tabindex="-1" aria-labelledby="restockModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="restockModalLabel">Restok Produk</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="restockForm">
|
||||||
|
<input type="hidden" id="productId" name="productId">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="productName" class="form-label">Nama Produk</label>
|
||||||
|
<input type="text" class="form-control" id="productName" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="restockAmount" class="form-label">Jumlah Restok</label>
|
||||||
|
<input type="number" class="form-control" id="restockAmount" name="restockAmount" min="1" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="supplierNote" class="form-label">Catatan</label>
|
||||||
|
<textarea class="form-control" id="supplierNote" name="supplierNote" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
|
||||||
|
<button type="button" class="btn btn-primary" onclick="submitRestock()">Proses Restok</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- JavaScript -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/jquery-3.6.0.min.js"></script>
|
||||||
|
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/feather.min.js"></script>
|
||||||
|
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/jquery.slimscroll.min.js"></script>
|
||||||
|
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/jquery.dataTables.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/dataTables.bootstrap4.min.js"></script>
|
||||||
|
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
|
<script src="/ayula-store/bootstrap/assets/plugins/select2/js/select2.min.js"></script>
|
||||||
|
|
||||||
|
<script src="/ayula-store/bootstrap/assets/plugins/sweetalert/sweetalert2.all.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/plugins/sweetalert/sweetalerts.min.js"></script>
|
||||||
|
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/script.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Initialize modal
|
||||||
|
const restockModal = new bootstrap.Modal(document.getElementById('restockModal'));
|
||||||
|
|
||||||
|
// Open restock modal
|
||||||
|
function openRestockModal(productId, productName) {
|
||||||
|
document.getElementById('productId').value = productId;
|
||||||
|
document.getElementById('productName').value = productName;
|
||||||
|
document.getElementById('restockAmount').value = '10'; // Default value
|
||||||
|
document.getElementById('supplierNote').value = '';
|
||||||
|
restockModal.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submit restock form
|
||||||
|
function submitRestock() {
|
||||||
|
const productId = document.getElementById('productId').value;
|
||||||
|
const amount = document.getElementById('restockAmount').value;
|
||||||
|
const note = document.getElementById('supplierNote').value;
|
||||||
|
|
||||||
|
// Here you would typically send an AJAX request to update the database
|
||||||
|
alert(`Produk ID: ${productId} akan direstok dengan jumlah: ${amount}`);
|
||||||
|
|
||||||
|
// In a real implementation, you would use fetch or XMLHttpRequest to send to server
|
||||||
|
// Example:
|
||||||
|
/*
|
||||||
|
fetch('process_restock.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
body: `productId=${productId}&amount=${amount}¬e=${encodeURIComponent(note)}`
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
alert('Restok berhasil!');
|
||||||
|
window.location.reload();
|
||||||
|
} else {
|
||||||
|
alert('Error: ' + data.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('Terjadi kesalahan saat memproses restok.');
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
restockModal.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset weights to default
|
||||||
|
function resetWeights() {
|
||||||
|
document.getElementById('weight_stock').value = '4';
|
||||||
|
document.getElementById('weight_price').value = '3';
|
||||||
|
document.getElementById('weight_turnover').value = '3';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export table to CSV/PDF
|
||||||
|
function exportTable(format) {
|
||||||
|
alert(`Mengekspor tabel ke format ${format.toUpperCase()}`);
|
||||||
|
// Implement actual export functionality here
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print table
|
||||||
|
function printTable() {
|
||||||
|
window.print();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize weight chart
|
||||||
|
const ctx = document.getElementById('weightChart').getContext('2d');
|
||||||
|
const weightChart = new Chart(ctx, {
|
||||||
|
type: 'pie',
|
||||||
|
data: {
|
||||||
|
labels: ['Stok', 'Harga', 'Perputaran'],
|
||||||
|
datasets: [{
|
||||||
|
data: [
|
||||||
|
<?php echo $weights['stock_level']; ?>,
|
||||||
|
<?php echo $weights['price_value']; ?>,
|
||||||
|
<?php echo $weights['turnover_rate']; ?>
|
||||||
|
],
|
||||||
|
backgroundColor: [
|
||||||
|
'rgba(255, 99, 132, 0.7)',
|
||||||
|
'rgba(54, 162, 235, 0.7)',
|
||||||
|
'rgba(255, 206, 86, 0.7)'
|
||||||
|
],
|
||||||
|
borderColor: [
|
||||||
|
'rgba(255, 99, 132, 1)',
|
||||||
|
'rgba(54, 162, 235, 1)',
|
||||||
|
'rgba(255, 206, 86, 1)'
|
||||||
|
],
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: 'bottom',
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: function(context) {
|
||||||
|
const label = context.label || '';
|
||||||
|
const value = context.formattedValue || '';
|
||||||
|
return `${label}: ${value}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
function submitRestock() {
|
||||||
|
// Get the values from the modal form
|
||||||
|
var productName = document.getElementById('productName').value;
|
||||||
|
var restockAmount = document.getElementById('restockAmount').value;
|
||||||
|
var supplierNote = document.getElementById('supplierNote').value;
|
||||||
|
|
||||||
|
// Validate form fields before proceeding
|
||||||
|
if (!productName || !restockAmount || !supplierNote) {
|
||||||
|
alert('Please fill out all fields.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the message to be sent via WhatsApp
|
||||||
|
var message = "Restok Produk:\n";
|
||||||
|
message += "Nama Produk: " + productName + "\n";
|
||||||
|
message += "Jumlah Restok: " + restockAmount + "\n";
|
||||||
|
message += "Catatan : " + supplierNote;
|
||||||
|
|
||||||
|
// Encode the message for URL (using encodeURIComponent for proper encoding)
|
||||||
|
var encodedMessage = encodeURIComponent(message);
|
||||||
|
|
||||||
|
// Define the phone number for WhatsApp (replace with your desired phone number)
|
||||||
|
var phoneNumber = "+6287857242169"; // Replace with the recipient's phone number (without + sign)
|
||||||
|
|
||||||
|
// WhatsApp API URL
|
||||||
|
var whatsappURL = "https://wa.me/" + phoneNumber + "?text=" + encodedMessage;
|
||||||
|
|
||||||
|
// Open WhatsApp with the message
|
||||||
|
window.open(whatsappURL, "_blank");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,334 @@
|
||||||
|
<?php
|
||||||
|
// Updated transfer.php file that handles both single and bulk transfers
|
||||||
|
// Save this in the same directory as your productlist.php
|
||||||
|
|
||||||
|
// Set headers for JSON response
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
// Create log file
|
||||||
|
function log_debug($message) {
|
||||||
|
file_put_contents('transfer_debug.log', date('[Y-m-d H:i:s] ') . $message . "\n", FILE_APPEND);
|
||||||
|
}
|
||||||
|
|
||||||
|
log_debug('Script started');
|
||||||
|
log_debug('POST data: ' . json_encode($_POST));
|
||||||
|
|
||||||
|
// Database connection
|
||||||
|
$servername = "localhost";
|
||||||
|
$username = "root";
|
||||||
|
$password = "";
|
||||||
|
$database = "ayula_store";
|
||||||
|
|
||||||
|
// Connect to database
|
||||||
|
$conn = new mysqli($servername, $username, $password, $database);
|
||||||
|
if ($conn->connect_error) {
|
||||||
|
log_debug("Connection failed: " . $conn->connect_error);
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Koneksi database gagal: ' . $conn->connect_error
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_debug("Database connected successfully");
|
||||||
|
|
||||||
|
// Check if we're handling a single product or bulk transfer
|
||||||
|
$is_bulk = isset($_POST['product_ids']) && is_array($_POST['product_ids']);
|
||||||
|
|
||||||
|
if ($is_bulk) {
|
||||||
|
// Bulk transfer
|
||||||
|
log_debug("Processing bulk transfer");
|
||||||
|
|
||||||
|
$product_ids = $_POST['product_ids'];
|
||||||
|
$quantities = $_POST['quantities'];
|
||||||
|
|
||||||
|
// Validate input
|
||||||
|
if (empty($product_ids) || empty($quantities) || count($product_ids) != count($quantities)) {
|
||||||
|
log_debug("Invalid bulk data");
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Data tidak valid untuk transfer massal.'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin transaction
|
||||||
|
$conn->begin_transaction();
|
||||||
|
|
||||||
|
$success_count = 0;
|
||||||
|
$error_messages = [];
|
||||||
|
|
||||||
|
for ($i = 0; $i < count($product_ids); $i++) {
|
||||||
|
$id_barang = $product_ids[$i];
|
||||||
|
$quantity = intval($quantities[$i]);
|
||||||
|
|
||||||
|
log_debug("Processing item $i: ID=$id_barang, Quantity=$quantity");
|
||||||
|
|
||||||
|
// Skip invalid items
|
||||||
|
if (empty($id_barang) || $quantity <= 0) {
|
||||||
|
$error_messages[] = "Data tidak valid untuk produk #$i";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get product details
|
||||||
|
$stmt = $conn->prepare("SELECT * FROM barang WHERE id_barang = ?");
|
||||||
|
$stmt->bind_param("s", $id_barang);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
if ($result->num_rows == 0) {
|
||||||
|
$error_messages[] = "Produk dengan ID $id_barang tidak ditemukan";
|
||||||
|
$stmt->close();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$product = $result->fetch_assoc();
|
||||||
|
|
||||||
|
// Check stock
|
||||||
|
if ($quantity > $product['stok']) {
|
||||||
|
$error_messages[] = "Stok tidak mencukupi untuk produk {$product['nama_barang']}";
|
||||||
|
$stmt->close();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update stock in barang
|
||||||
|
$new_stock = $product['stok'] - $quantity;
|
||||||
|
$update_stmt = $conn->prepare("UPDATE barang SET stok = ? WHERE id_barang = ?");
|
||||||
|
$update_stmt->bind_param("is", $new_stock, $id_barang);
|
||||||
|
|
||||||
|
if (!$update_stmt->execute()) {
|
||||||
|
$error_messages[] = "Gagal memperbarui stok untuk produk {$product['nama_barang']}";
|
||||||
|
$stmt->close();
|
||||||
|
$update_stmt->close();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$update_stmt->close();
|
||||||
|
|
||||||
|
// Check if exists in barang_kasir
|
||||||
|
$check_stmt = $conn->prepare("SELECT * FROM barang_kasir WHERE kode_barang = ?");
|
||||||
|
$check_stmt->bind_param("s", $product['kode_barang']);
|
||||||
|
$check_stmt->execute();
|
||||||
|
$check_result = $check_stmt->get_result();
|
||||||
|
|
||||||
|
$timestamp = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
|
if ($check_result->num_rows > 0) {
|
||||||
|
// Update existing record
|
||||||
|
$kasir_product = $check_result->fetch_assoc();
|
||||||
|
$new_kasir_stock = $kasir_product['stok'] + $quantity;
|
||||||
|
|
||||||
|
$kasir_update_sql = "UPDATE barang_kasir SET stok = ?, update_at = ? WHERE id_barangK = ?";
|
||||||
|
$kasir_update_stmt = $conn->prepare($kasir_update_sql);
|
||||||
|
$kasir_update_stmt->bind_param("isi", $new_kasir_stock, $timestamp, $kasir_product['id_barangK']);
|
||||||
|
|
||||||
|
if (!$kasir_update_stmt->execute()) {
|
||||||
|
$error_messages[] = "Gagal memperbarui stok di kasir untuk produk {$product['nama_barang']}";
|
||||||
|
$stmt->close();
|
||||||
|
$check_stmt->close();
|
||||||
|
$kasir_update_stmt->close();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$kasir_update_stmt->close();
|
||||||
|
} else {
|
||||||
|
// Insert new record
|
||||||
|
// Using direct query to avoid parameter binding issues
|
||||||
|
$kode_barang = $conn->real_escape_string($product['kode_barang']);
|
||||||
|
$nama_barang = $conn->real_escape_string($product['nama_barang']);
|
||||||
|
$id_jenis = (int)$product['id_jenis'];
|
||||||
|
$harga = $conn->real_escape_string($product['harga']);
|
||||||
|
$stok = (int)$quantity;
|
||||||
|
$gambar = $conn->real_escape_string($product['image'] ?? '');
|
||||||
|
$timestamp_esc = $conn->real_escape_string($timestamp);
|
||||||
|
|
||||||
|
$insert_query = "INSERT INTO barang_kasir (kode_barang, nama_barang, id_jenis, harga, stok, gambar, created_at, update_at)
|
||||||
|
VALUES ('$kode_barang', '$nama_barang', $id_jenis, '$harga', $stok, '$gambar', '$timestamp_esc', '$timestamp_esc')";
|
||||||
|
|
||||||
|
if (!$conn->query($insert_query)) {
|
||||||
|
$error_messages[] = "Gagal menambahkan produk {$product['nama_barang']} ke kasir";
|
||||||
|
$stmt->close();
|
||||||
|
$check_stmt->close();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$check_stmt->close();
|
||||||
|
$stmt->close();
|
||||||
|
$success_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($success_count == 0) {
|
||||||
|
// If no products were successfully processed
|
||||||
|
$conn->rollback();
|
||||||
|
log_debug("Bulk transfer failed: " . implode(', ', $error_messages));
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Tidak ada produk yang berhasil dipindahkan: ' . implode(', ', $error_messages)
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
// Commit if at least one product was processed
|
||||||
|
$conn->commit();
|
||||||
|
|
||||||
|
if (count($error_messages) > 0) {
|
||||||
|
log_debug("Partial bulk transfer success: $success_count items, with errors: " . implode(', ', $error_messages));
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => "Berhasil memindahkan $success_count produk ke kasir. Beberapa produk gagal: " . implode(', ', $error_messages)
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
log_debug("Complete bulk transfer success: $success_count items");
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => "Berhasil memindahkan $success_count produk ke kasir."
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Single product transfer
|
||||||
|
if (!isset($_POST['id_barang']) || !isset($_POST['quantity'])) {
|
||||||
|
log_debug("Missing required parameters for single transfer");
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Parameter tidak lengkap'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id_barang = $_POST['id_barang'];
|
||||||
|
$quantity = (int)$_POST['quantity'];
|
||||||
|
|
||||||
|
log_debug("Processing single transfer for product ID: $id_barang, quantity: $quantity");
|
||||||
|
|
||||||
|
// Validate data
|
||||||
|
if (empty($id_barang) || $quantity <= 0) {
|
||||||
|
log_debug("Invalid parameters");
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Parameter tidak valid'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get product information
|
||||||
|
$stmt = $conn->prepare("SELECT * FROM barang WHERE id_barang = ?");
|
||||||
|
$stmt->bind_param("s", $id_barang);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
if ($result->num_rows == 0) {
|
||||||
|
log_debug("Product not found");
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Barang tidak ditemukan'
|
||||||
|
]);
|
||||||
|
$stmt->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$product = $result->fetch_assoc();
|
||||||
|
log_debug("Product found: " . json_encode($product));
|
||||||
|
|
||||||
|
// Check if enough stock
|
||||||
|
if ($quantity > $product['stok']) {
|
||||||
|
log_debug("Insufficient stock");
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Stok tidak mencukupi'
|
||||||
|
]);
|
||||||
|
$stmt->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin transaction
|
||||||
|
$conn->begin_transaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. Update stock in barang
|
||||||
|
$new_stock = $product['stok'] - $quantity;
|
||||||
|
$update_stmt = $conn->prepare("UPDATE barang SET stok = ? WHERE id_barang = ?");
|
||||||
|
$update_stmt->bind_param("is", $new_stock, $id_barang);
|
||||||
|
|
||||||
|
if (!$update_stmt->execute()) {
|
||||||
|
throw new Exception("Failed to update product stock: " . $update_stmt->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
log_debug("Updated barang stock to: $new_stock");
|
||||||
|
$update_stmt->close();
|
||||||
|
|
||||||
|
// 2. Check if product exists in barang_kasir
|
||||||
|
$check_stmt = $conn->prepare("SELECT * FROM barang_kasir WHERE kode_barang = ?");
|
||||||
|
$check_stmt->bind_param("s", $product['kode_barang']);
|
||||||
|
$check_stmt->execute();
|
||||||
|
$check_result = $check_stmt->get_result();
|
||||||
|
|
||||||
|
$timestamp = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
|
if ($check_result->num_rows > 0) {
|
||||||
|
// 3a. Update existing product
|
||||||
|
$kasir_product = $check_result->fetch_assoc();
|
||||||
|
$new_kasir_stock = $kasir_product['stok'] + $quantity;
|
||||||
|
|
||||||
|
log_debug("Product exists in cashier, updating stock to: $new_kasir_stock");
|
||||||
|
|
||||||
|
$kasir_update_stmt = $conn->prepare("UPDATE barang_kasir SET stok = ?, update_at = ? WHERE id_barangK = ?");
|
||||||
|
$kasir_update_stmt->bind_param("isi", $new_kasir_stock, $timestamp, $kasir_product['id_barangK']);
|
||||||
|
|
||||||
|
if (!$kasir_update_stmt->execute()) {
|
||||||
|
throw new Exception("Failed to update cashier stock: " . $kasir_update_stmt->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$kasir_update_stmt->close();
|
||||||
|
} else {
|
||||||
|
// 3b. Insert new product with direct query
|
||||||
|
log_debug("Product doesn't exist in cashier, inserting new record");
|
||||||
|
|
||||||
|
// Set default empty string for image
|
||||||
|
$kode_barang = $conn->real_escape_string($product['kode_barang']);
|
||||||
|
$nama_barang = $conn->real_escape_string($product['nama_barang']);
|
||||||
|
$id_jenis = (int)$product['id_jenis'];
|
||||||
|
$harga = $conn->real_escape_string($product['harga']);
|
||||||
|
$stok = (int)$quantity;
|
||||||
|
$gambar = $conn->real_escape_string($product['image'] ?? '');
|
||||||
|
$timestamp_esc = $conn->real_escape_string($timestamp);
|
||||||
|
|
||||||
|
$insert_query = "INSERT INTO barang_kasir (kode_barang, nama_barang, id_jenis, harga, stok, gambar, created_at, update_at)
|
||||||
|
VALUES ('$kode_barang', '$nama_barang', $id_jenis, '$harga', $stok, '$gambar', '$timestamp_esc', '$timestamp_esc')";
|
||||||
|
|
||||||
|
log_debug("Insert query: $insert_query");
|
||||||
|
|
||||||
|
if (!$conn->query($insert_query)) {
|
||||||
|
throw new Exception("Failed to insert product into cashier: " . $conn->error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$check_stmt->close();
|
||||||
|
|
||||||
|
// Commit transaction
|
||||||
|
$conn->commit();
|
||||||
|
|
||||||
|
log_debug("Transfer completed successfully");
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Berhasil memindahkan ' . $quantity . ' item ke kasir'
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// Rollback on error
|
||||||
|
$conn->rollback();
|
||||||
|
|
||||||
|
log_debug("Error occurred: " . $e->getMessage());
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Terjadi kesalahan: ' . $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn->close();
|
||||||
|
log_debug('Script completed');
|
||||||
|
?>
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
[2025-06-01 09:32:55] Script started
|
||||||
|
[2025-06-01 09:32:55] POST data: {"id_barang":"120","quantity":"1"}
|
||||||
|
[2025-06-01 09:32:55] Database connected successfully
|
||||||
|
[2025-06-01 09:32:55] Processing single transfer for product ID: 120, quantity: 1
|
||||||
|
[2025-06-01 09:32:55] Product found: {"id_barang":120,"kode_barang":"BRG039","nama_barang":"Produk A","stok":5,"harga":"150000","id_jenis":8,"image":"kios-Halaman-2.drawio (2).png"}
|
||||||
|
[2025-06-01 09:32:55] Updated barang stock to: 4
|
||||||
|
[2025-06-01 09:32:55] Product exists in cashier, updating stock to: 6
|
||||||
|
[2025-06-01 09:32:55] Transfer completed successfully
|
||||||
|
[2025-06-01 09:32:55] Script completed
|
||||||
|
[2025-06-01 09:33:23] Script started
|
||||||
|
[2025-06-01 09:33:23] POST data: {"product_ids":["120","121"],"quantities":["1","1"]}
|
||||||
|
[2025-06-01 09:33:23] Database connected successfully
|
||||||
|
[2025-06-01 09:33:23] Processing bulk transfer
|
||||||
|
[2025-06-01 09:33:23] Processing item 0: ID=120, Quantity=1
|
||||||
|
[2025-06-01 09:33:23] Processing item 1: ID=121, Quantity=1
|
||||||
|
[2025-06-01 09:33:23] Complete bulk transfer success: 2 items
|
||||||
|
[2025-06-01 09:33:23] Script completed
|
||||||
|
[2025-06-01 09:33:33] Script started
|
||||||
|
[2025-06-01 09:33:33] POST data: {"id_barang":"122","quantity":"1"}
|
||||||
|
[2025-06-01 09:33:33] Database connected successfully
|
||||||
|
[2025-06-01 09:33:33] Processing single transfer for product ID: 122, quantity: 1
|
||||||
|
[2025-06-01 09:33:33] Product found: {"id_barang":122,"kode_barang":"BRG041","nama_barang":"Produk B","stok":12,"harga":"75000","id_jenis":1,"image":"kios-Halaman-3.drawio (2).png"}
|
||||||
|
[2025-06-01 09:33:33] Updated barang stock to: 11
|
||||||
|
[2025-06-01 09:33:33] Product exists in cashier, updating stock to: 13
|
||||||
|
[2025-06-01 09:33:33] Transfer completed successfully
|
||||||
|
[2025-06-01 09:33:33] Script completed
|
||||||
|
[2025-06-01 21:07:57] Script started
|
||||||
|
[2025-06-01 21:07:57] POST data: {"id_barang":"120","quantity":"1"}
|
||||||
|
[2025-06-01 21:07:57] Database connected successfully
|
||||||
|
[2025-06-01 21:07:57] Processing single transfer for product ID: 120, quantity: 1
|
||||||
|
[2025-06-01 21:07:57] Product found: {"id_barang":120,"kode_barang":"BRG039","nama_barang":"Produk A","stok":3,"harga":"150000","id_jenis":8,"image":"product_1748803380.png"}
|
||||||
|
[2025-06-01 21:07:57] Updated barang stock to: 2
|
||||||
|
[2025-06-01 21:07:57] Product exists in cashier, updating stock to: 8
|
||||||
|
[2025-06-01 21:07:57] Transfer completed successfully
|
||||||
|
[2025-06-01 21:07:57] Script completed
|
||||||
|
[2025-06-01 21:08:07] Script started
|
||||||
|
[2025-06-01 21:08:07] POST data: {"product_ids":["122","124"],"quantities":["1","1"]}
|
||||||
|
[2025-06-01 21:08:07] Database connected successfully
|
||||||
|
[2025-06-01 21:08:07] Processing bulk transfer
|
||||||
|
[2025-06-01 21:08:07] Processing item 0: ID=122, Quantity=1
|
||||||
|
[2025-06-01 21:08:07] Processing item 1: ID=124, Quantity=1
|
||||||
|
[2025-06-01 21:08:07] Complete bulk transfer success: 2 items
|
||||||
|
[2025-06-01 21:08:07] Script completed
|
||||||
|
[2025-06-03 03:21:32] Script started
|
||||||
|
[2025-06-03 03:21:32] POST data: {"id_barang":"122","quantity":"1"}
|
||||||
|
[2025-06-03 03:21:32] Database connected successfully
|
||||||
|
[2025-06-03 03:21:32] Processing single transfer for product ID: 122, quantity: 1
|
||||||
|
[2025-06-03 03:21:32] Product found: {"id_barang":122,"kode_barang":"BRG041","nama_barang":"Produk B","stok":10,"harga":"75000","id_jenis":1,"image":"kios-Halaman-3.drawio (2).png"}
|
||||||
|
[2025-06-03 03:21:32] Updated barang stock to: 9
|
||||||
|
[2025-06-03 03:21:32] Product exists in cashier, updating stock to: 15
|
||||||
|
[2025-06-03 03:21:32] Transfer completed successfully
|
||||||
|
[2025-06-03 03:21:32] Script completed
|
||||||
|
|
@ -0,0 +1,216 @@
|
||||||
|
<?php
|
||||||
|
// transfer_handler.php
|
||||||
|
// File to handle individual and bulk product transfers to cashier
|
||||||
|
|
||||||
|
// Koneksi ke database
|
||||||
|
$servername = "localhost";
|
||||||
|
$username = "root"; // Sesuaikan dengan username database kamu
|
||||||
|
$password = ""; // Sesuaikan dengan password database kamu
|
||||||
|
$database = "ayula_store"; // Sesuaikan dengan nama database kamu
|
||||||
|
|
||||||
|
$conn = new mysqli($servername, $username, $password, $database);
|
||||||
|
|
||||||
|
// Periksa koneksi
|
||||||
|
if ($conn->connect_error) {
|
||||||
|
die(json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => "Koneksi gagal: " . $conn->connect_error
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fungsi untuk memvalidasi data yang diterima
|
||||||
|
function validateData($data) {
|
||||||
|
$data = trim($data);
|
||||||
|
$data = stripslashes($data);
|
||||||
|
$data = htmlspecialchars($data);
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle single product transfer
|
||||||
|
if (isset($_POST['action']) && $_POST['action'] == 'transfer_product') {
|
||||||
|
// Ambil data dari POST
|
||||||
|
$id_barang = validateData($_POST['id_barang']);
|
||||||
|
$quantity = intval($_POST['quantity']);
|
||||||
|
|
||||||
|
// Validasi input
|
||||||
|
if (empty($id_barang) || $quantity <= 0) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Data tidak valid.'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if stock is sufficient
|
||||||
|
$sql = "SELECT stok FROM barang WHERE id_barang = ?";
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
$stmt->bind_param("s", $id_barang);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
if ($result->num_rows == 0) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Barang tidak ditemukan.'
|
||||||
|
]);
|
||||||
|
$stmt->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$row = $result->fetch_assoc();
|
||||||
|
$current_stock = $row['stok'];
|
||||||
|
|
||||||
|
if ($quantity > $current_stock) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Jumlah melebihi stok tersedia.'
|
||||||
|
]);
|
||||||
|
$stmt->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin transaction
|
||||||
|
$conn->begin_transaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Update stock in barang table
|
||||||
|
$new_stock = $current_stock - $quantity;
|
||||||
|
$update_sql = "UPDATE barang SET stok = ? WHERE id_barang = ?";
|
||||||
|
$update_stmt = $conn->prepare($update_sql);
|
||||||
|
$update_stmt->bind_param("is", $new_stock, $id_barang);
|
||||||
|
$update_stmt->execute();
|
||||||
|
|
||||||
|
// TODO: Insert data to cashier table or perform other actions as needed
|
||||||
|
// This will depend on your specific requirements for transferring to cashier
|
||||||
|
|
||||||
|
// Commit the transaction
|
||||||
|
$conn->commit();
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Berhasil memindahkan ' . $quantity . ' item ke kasir.'
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// Rollback transaction on error
|
||||||
|
$conn->rollback();
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Terjadi kesalahan: ' . $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
if (isset($update_stmt)) {
|
||||||
|
$update_stmt->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle bulk product transfers
|
||||||
|
if (isset($_POST['action']) && $_POST['action'] == 'bulk_transfer_products') {
|
||||||
|
// Get product IDs and quantities
|
||||||
|
$product_ids = isset($_POST['product_ids']) ? $_POST['product_ids'] : [];
|
||||||
|
$quantities = isset($_POST['quantities']) ? $_POST['quantities'] : [];
|
||||||
|
|
||||||
|
// Validate input
|
||||||
|
if (empty($product_ids) || empty($quantities) || count($product_ids) != count($quantities)) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Data tidak valid.'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin transaction
|
||||||
|
$conn->begin_transaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$success_count = 0;
|
||||||
|
$error_messages = [];
|
||||||
|
|
||||||
|
// Process each product
|
||||||
|
for ($i = 0; $i < count($product_ids); $i++) {
|
||||||
|
$id_barang = validateData($product_ids[$i]);
|
||||||
|
$quantity = intval($quantities[$i]);
|
||||||
|
|
||||||
|
if (empty($id_barang) || $quantity <= 0) {
|
||||||
|
$error_messages[] = "Data tidak valid untuk produk #$i";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if stock is sufficient
|
||||||
|
$sql = "SELECT stok FROM barang WHERE id_barang = ?";
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
$stmt->bind_param("s", $id_barang);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
if ($result->num_rows == 0) {
|
||||||
|
$error_messages[] = "Barang dengan ID $id_barang tidak ditemukan";
|
||||||
|
$stmt->close();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$row = $result->fetch_assoc();
|
||||||
|
$current_stock = $row['stok'];
|
||||||
|
|
||||||
|
if ($quantity > $current_stock) {
|
||||||
|
$error_messages[] = "Jumlah melebihi stok tersedia untuk produk ID $id_barang";
|
||||||
|
$stmt->close();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update stock in barang table
|
||||||
|
$new_stock = $current_stock - $quantity;
|
||||||
|
$update_sql = "UPDATE barang SET stok = ? WHERE id_barang = ?";
|
||||||
|
$update_stmt = $conn->prepare($update_sql);
|
||||||
|
$update_stmt->bind_param("is", $new_stock, $id_barang);
|
||||||
|
$update_stmt->execute();
|
||||||
|
$update_stmt->close();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
// TODO: Insert data to cashier table or perform other actions as needed
|
||||||
|
// This will depend on your specific requirements for transferring to cashier
|
||||||
|
|
||||||
|
$success_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($success_count == 0) {
|
||||||
|
// If no products were successfully processed, rollback and return error
|
||||||
|
$conn->rollback();
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Tidak ada produk yang berhasil dipindahkan. ' . implode('; ', $error_messages)
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
// Commit the transaction if at least one product was successfully processed
|
||||||
|
$conn->commit();
|
||||||
|
|
||||||
|
if (count($error_messages) > 0) {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => "Berhasil memindahkan $success_count produk ke kasir. Beberapa produk gagal: " . implode('; ', $error_messages)
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => "Berhasil memindahkan $success_count produk ke kasir."
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// Rollback transaction on error
|
||||||
|
$conn->rollback();
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Terjadi kesalahan: ' . $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close connection
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
// Include database connection
|
||||||
|
include('../../routes/db_conn.php');
|
||||||
|
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||||
|
$id_barang = $_POST['id_barang'];
|
||||||
|
$nama_barang = $_POST['nama_barang'];
|
||||||
|
$brand = $_POST['brand'];
|
||||||
|
$stok = $_POST['stok'];
|
||||||
|
$harga = $_POST['harga'];
|
||||||
|
$id_jenis = $_POST['id_jenis']; // Ambil id_jenis dari POST
|
||||||
|
$existing_image = $_POST['existing_image']; // Menyimpan nama gambar yang ada
|
||||||
|
|
||||||
|
// Cek apakah id_jenis yang dipilih valid di tabel jenis_barang
|
||||||
|
$query_jenis = "SELECT * FROM jenis_barang WHERE id_jenis = '$id_jenis'";
|
||||||
|
$result_jenis_check = mysqli_query($conn, $query_jenis);
|
||||||
|
|
||||||
|
if (mysqli_num_rows($result_jenis_check) == 0) {
|
||||||
|
echo "Error: Kategori yang dipilih tidak valid.";
|
||||||
|
exit; // Hentikan skrip jika id_jenis tidak valid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Periksa apakah ada gambar yang diunggah
|
||||||
|
if (isset($_FILES['image']) && $_FILES['image']['error'] == 0) {
|
||||||
|
// Menangani unggahan file gambar
|
||||||
|
$image_name = $_FILES['image']['name'];
|
||||||
|
$image_tmp = $_FILES['image']['tmp_name'];
|
||||||
|
$image_dir = "image/" . $image_name;
|
||||||
|
|
||||||
|
// Pindahkan file yang diunggah ke direktori gambar
|
||||||
|
move_uploaded_file($image_tmp, $image_dir);
|
||||||
|
} else {
|
||||||
|
// Jika tidak ada gambar baru, gunakan gambar yang ada
|
||||||
|
$image_name = $existing_image;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Membuat query UPDATE
|
||||||
|
$query = "UPDATE barang SET
|
||||||
|
nama_barang = '$nama_barang',
|
||||||
|
brand = '$brand',
|
||||||
|
stok = '$stok',
|
||||||
|
harga = '$harga',
|
||||||
|
id_jenis = '$id_jenis',
|
||||||
|
image = '$image_name'
|
||||||
|
WHERE id_barang = $id_barang";
|
||||||
|
|
||||||
|
// Debug: Tampilkan query SQL yang akan dijalankan
|
||||||
|
echo $query;
|
||||||
|
exit; // Hentikan eksekusi untuk melihat query SQL
|
||||||
|
|
||||||
|
// Eksekusi query UPDATE
|
||||||
|
if (mysqli_query($conn, $query)) {
|
||||||
|
echo "Produk berhasil diperbarui!";
|
||||||
|
header("Location: productlist.php"); // Redirect setelah pembaruan berhasil
|
||||||
|
} else {
|
||||||
|
echo "Error memperbarui produk: " . mysqli_error($conn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
@ -0,0 +1,703 @@
|
||||||
|
<?php
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
// Ambil informasi user yang sedang login
|
||||||
|
$userRole = $_SESSION['role']; // 'user' atau 'admin'
|
||||||
|
$username = $_SESSION['username']; // Menambahkan username dari session
|
||||||
|
|
||||||
|
// Jika username adalah root, tampilkan nama yang lebih presentable
|
||||||
|
$displayName = ($username === 'root') ? 'Admin' : $username;
|
||||||
|
|
||||||
|
// Cek apakah session 'user_id' ada, yang berarti pengguna sudah login
|
||||||
|
if (!isset($_SESSION['user_id'])) {
|
||||||
|
// Jika session tidak ada, arahkan pengguna ke halaman login
|
||||||
|
header("Location: /ayula-store/index.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Database connection
|
||||||
|
include('../../routes/db_conn.php');
|
||||||
|
|
||||||
|
// Get today's date in MySQL format
|
||||||
|
$today = date('Y-m-d');
|
||||||
|
$yesterday = date('Y-m-d', strtotime('-1 day'));
|
||||||
|
$startOfWeek = date('Y-m-d', strtotime('this week monday'));
|
||||||
|
$endOfWeek = date('Y-m-d', strtotime('this week sunday'));
|
||||||
|
|
||||||
|
// Function to get employee-specific dashboard metrics
|
||||||
|
function getEmployeeDashboardData($conn) {
|
||||||
|
$today = date('Y-m-d');
|
||||||
|
$data = array();
|
||||||
|
|
||||||
|
// Today's transactions count
|
||||||
|
$query = "SELECT COUNT(*) as count FROM transaksi WHERE DATE(tanggal) = '$today'";
|
||||||
|
$result = $conn->query($query);
|
||||||
|
$data['today_transactions'] = ($result->num_rows > 0) ? $result->fetch_assoc()['count'] : 0;
|
||||||
|
|
||||||
|
// Today's items sold
|
||||||
|
$query = "SELECT SUM(total_item) as count FROM transaksi WHERE DATE(tanggal) = '$today'";
|
||||||
|
$result = $conn->query($query);
|
||||||
|
$data['today_items'] = ($result->num_rows > 0) ? $result->fetch_assoc()['count'] : 0;
|
||||||
|
if ($data['today_items'] === NULL) $data['today_items'] = 0;
|
||||||
|
|
||||||
|
// This week's transactions
|
||||||
|
$startOfWeek = date('Y-m-d', strtotime('this week monday'));
|
||||||
|
$query = "SELECT COUNT(*) as count FROM transaksi WHERE tanggal BETWEEN '$startOfWeek' AND '$today'";
|
||||||
|
$result = $conn->query($query);
|
||||||
|
$data['week_transactions'] = ($result->num_rows > 0) ? $result->fetch_assoc()['count'] : 0;
|
||||||
|
|
||||||
|
// Total products in inventory
|
||||||
|
$query = "SELECT COUNT(*) as count FROM barang";
|
||||||
|
$result = $conn->query($query);
|
||||||
|
$data['total_products'] = ($result->num_rows > 0) ? $result->fetch_assoc()['count'] : 0;
|
||||||
|
|
||||||
|
// Count low stock items (stok <= 10)
|
||||||
|
$query = "SELECT COUNT(*) as count FROM barang WHERE stok <= 10";
|
||||||
|
$result = $conn->query($query);
|
||||||
|
$data['low_stock_count'] = ($result->num_rows > 0) ? $result->fetch_assoc()['count'] : 0;
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to get recent transactions
|
||||||
|
function getRecentTransactions($conn, $limit = 5) {
|
||||||
|
$query = "SELECT t.kode_transaksi, t.tanggal, t.total_item, t.status
|
||||||
|
FROM transaksi t
|
||||||
|
ORDER BY t.tanggal DESC
|
||||||
|
LIMIT $limit";
|
||||||
|
|
||||||
|
$result = $conn->query($query);
|
||||||
|
$transactions = array();
|
||||||
|
|
||||||
|
if ($result->num_rows > 0) {
|
||||||
|
while($row = $result->fetch_assoc()) {
|
||||||
|
$transactions[] = $row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $transactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to get popular products - FIXED to ensure it gets the most recent data
|
||||||
|
function getPopularProducts($conn, $limit = 10) {
|
||||||
|
// Use current date to ensure we get the latest data
|
||||||
|
$today = date('Y-m-d');
|
||||||
|
$startOfWeek = date('Y-m-d', strtotime('this week monday'));
|
||||||
|
|
||||||
|
// Modified query to join with detail_transaksi directly on id_transaksi
|
||||||
|
$query = "SELECT b.kode_barang, b.nama_barang, SUM(dt.jumlah) as total_quantity
|
||||||
|
FROM detail_transaksi dt
|
||||||
|
JOIN transaksi t ON dt.id_transaksi = t.id_transaksi
|
||||||
|
JOIN barang b ON dt.id_barang = b.id_barang
|
||||||
|
WHERE t.tanggal BETWEEN '$startOfWeek' AND NOW()
|
||||||
|
GROUP BY b.id_barang
|
||||||
|
ORDER BY total_quantity DESC
|
||||||
|
LIMIT $limit";
|
||||||
|
|
||||||
|
$result = $conn->query($query);
|
||||||
|
$products = array();
|
||||||
|
|
||||||
|
if ($result && $result->num_rows > 0) {
|
||||||
|
while($row = $result->fetch_assoc()) {
|
||||||
|
$products[] = $row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $products;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to get low stock items
|
||||||
|
function getLowStockItems($conn, $threshold = 10, $limit = 5) {
|
||||||
|
$query = "SELECT kode_barang, nama_barang, stok
|
||||||
|
FROM barang
|
||||||
|
WHERE stok <= $threshold
|
||||||
|
ORDER BY stok ASC
|
||||||
|
LIMIT $limit";
|
||||||
|
|
||||||
|
$result = $conn->query($query);
|
||||||
|
$lowStockItems = array();
|
||||||
|
|
||||||
|
if ($result->num_rows > 0) {
|
||||||
|
while($row = $result->fetch_assoc()) {
|
||||||
|
$lowStockItems[] = $row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $lowStockItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get dashboard data
|
||||||
|
$dashboardData = getEmployeeDashboardData($conn);
|
||||||
|
$recentTransactions = getRecentTransactions($conn);
|
||||||
|
$popularProducts = getPopularProducts($conn);
|
||||||
|
$lowStockItems = getLowStockItems($conn);
|
||||||
|
|
||||||
|
// Get user activity count (simplified)
|
||||||
|
$userActivityQuery = "SELECT COUNT(*) as count FROM transaksi WHERE tanggal > DATE_SUB(NOW(), INTERVAL 30 DAY)";
|
||||||
|
$userActivityResult = $conn->query($userActivityQuery);
|
||||||
|
$userActivity = ($userActivityResult->num_rows > 0) ? $userActivityResult->fetch_assoc()['count'] : 0;
|
||||||
|
|
||||||
|
// Recent products
|
||||||
|
$recentProductsQuery = "SELECT kode_barang, nama_barang, harga FROM barang ORDER BY created_at DESC LIMIT 5";
|
||||||
|
$recentProductsResult = $conn->query($recentProductsQuery);
|
||||||
|
$recentProducts = array();
|
||||||
|
if ($recentProductsResult->num_rows > 0) {
|
||||||
|
while($row = $recentProductsResult->fetch_assoc()) {
|
||||||
|
$recentProducts[] = $row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close database connection
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0" />
|
||||||
|
<meta name="description" content="POS - Bootstrap Admin Template" />
|
||||||
|
<meta name="keywords"
|
||||||
|
content="admin, estimates, bootstrap, business, corporate, creative, management, minimal, modern, html5, responsive" />
|
||||||
|
<meta name="robots" content="noindex, nofollow" />
|
||||||
|
<title>Ayula Store - Dashboard</title>
|
||||||
|
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../src/img/smallest-ayula.png" />
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="../../bootstrap/assets/css/bootstrap.min.css" />
|
||||||
|
<link rel="stylesheet" href="../../bootstrap/assets/css/animate.css" />
|
||||||
|
<link rel="stylesheet" href="../../bootstrap/assets/css/dataTables.bootstrap4.min.css" />
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="../../bootstrap/assets/plugins/fontawesome/css/fontawesome.min.css" />
|
||||||
|
<link rel="stylesheet" href="../../bootstrap/assets/plugins/fontawesome/css/all.min.css" />
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="../../bootstrap/assets/css/style.css" />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.dash-count {
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 15px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
.dash-count:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.product-card {
|
||||||
|
border-radius: 10px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
.product-card:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.transaction-item {
|
||||||
|
padding: 12px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
.transaction-item:hover {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
.activity-list .activity-item {
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
padding-left: 30px;
|
||||||
|
border-left: 2px solid #e9ecef;
|
||||||
|
}
|
||||||
|
.activity-list .activity-item:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: -7px;
|
||||||
|
top: 0;
|
||||||
|
background-color: #7367f0;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
.low-stock-alert {
|
||||||
|
border-left: 4px solid #ff9f43;
|
||||||
|
}
|
||||||
|
.activity-date {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
.metric-subtitle {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
.welcome-message {
|
||||||
|
background: linear-gradient(to right, #1b2850, #344e9c);
|
||||||
|
color: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
.welcome-message h4 {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom 20% width column for 5 products in a row */
|
||||||
|
.col-md-20p {
|
||||||
|
-ms-flex: 0 0 20%;
|
||||||
|
flex: 0 0 20%;
|
||||||
|
max-width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.col-md-20p {
|
||||||
|
-ms-flex: 0 0 50%;
|
||||||
|
flex: 0 0 50%;
|
||||||
|
max-width: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.col-md-20p {
|
||||||
|
-ms-flex: 0 0 100%;
|
||||||
|
flex: 0 0 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="global-loader">
|
||||||
|
<div class="whirly-loader"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="main-wrapper">
|
||||||
|
<div class="header">
|
||||||
|
<div class="header-left active">
|
||||||
|
<a href="/ayula-store/views/dashboard/" class="logo">
|
||||||
|
<img src="../../src/img/logoayula.png" alt="" />
|
||||||
|
</a>
|
||||||
|
<a href="/ayula-store/views/dashboard/" class="logo-small">
|
||||||
|
<img src="../../src/img/smallest-ayula.png" alt="" />
|
||||||
|
</a>
|
||||||
|
<a id="toggle_btn" href="javascript:void(0);"> </a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a id="mobile_btn" class="mobile_btn" href="#sidebar">
|
||||||
|
<span class="bar-icon">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<ul class="nav user-menu">
|
||||||
|
<li class="nav-item dropdown has-arrow main-drop">
|
||||||
|
<a href="javascript:void(0);" class="dropdown-toggle nav-link userset" data-bs-toggle="dropdown">
|
||||||
|
<span class="user-img"><img src="../../src/img/userprofile.png" alt="">
|
||||||
|
<span class="status online"></span></span>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu menu-drop-user">
|
||||||
|
<div class="profilename">
|
||||||
|
<div class="profileset">
|
||||||
|
<span class="user-img"><img src="../../src/img/userprofile.png" alt="">
|
||||||
|
<span class="status online"></span></span>
|
||||||
|
<div class="profilesets">
|
||||||
|
<h6><?php echo $userRole == 'admin' ? 'Admin' : 'Karyawan'; ?></h6>
|
||||||
|
<h5><?php echo htmlspecialchars($displayName); ?></h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item" href="/ayula-store/views/report-issue/">
|
||||||
|
<img src="../../src/img/warning.png" class="me-2" alt="img" /> Laporkan Masalah
|
||||||
|
</a>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item logout pb-0" href="../../views/logout.php"><img
|
||||||
|
src="../../bootstrap/assets/img/icons/log-out.svg" class="me-2" alt="img" />Keluar</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="dropdown mobile-user-menu">
|
||||||
|
<a href="javascript:void(0);" class="nav-link dropdown-toggle" data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"><i class="fa fa-ellipsis-v"></i></a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
|
<a class="dropdown-item" href="/ayula-store/views/report-issue/">
|
||||||
|
<i class="fa fa-cog me-2"></i> Laporkan Masalah
|
||||||
|
</a>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item logout pb-0" href="../../views/logout.php"><img
|
||||||
|
src="../../bootstrap/assets/img/icons/log-out.svg" class="me-2" alt="img" />Keluar</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sidebar" id="sidebar">
|
||||||
|
<div class="sidebar-inner slimscroll">
|
||||||
|
<div id="sidebar-menu" class="sidebar-menu">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="/ayula-store/views/dashboard/" class="active"><img
|
||||||
|
src="../../bootstrap/assets/img/icons/dashboard.svg" alt="img" /><span>
|
||||||
|
Dashboard</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/ayula-store/views/transaction/"><img src="../../bootstrap/assets/img/icons/sales1.svg"
|
||||||
|
alt="img" /><span>
|
||||||
|
POS</span></a>
|
||||||
|
</li>
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../bootstrap/assets/img/icons/product.svg" alt="img" /><span>
|
||||||
|
Produk</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/ayula-store/views/barang/productlist.php">Daftar Produk</a></li>
|
||||||
|
<!-- <li><a href="/ayula-store/views/barang/addproduct.php">Tambah Produk</a></li>
|
||||||
|
<li><a href="categorylist.html">Daftar Kategori</a></li>
|
||||||
|
<li><a href="addcategory.html">Tambah Kategori</a></li> -->
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<!-- <li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../bootstrap/assets/img/icons/purchase1.svg" alt="img" /><span>
|
||||||
|
Pembelian</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="purchaselist.html">Daftar Pembelian</a></li>
|
||||||
|
<li><a href="addpurchase.html">Tambah Pembelian</a></li>
|
||||||
|
<li><a href="importpurchase.html">Import Pembelian</a></li>
|
||||||
|
</ul>
|
||||||
|
</li> -->
|
||||||
|
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../bootstrap/assets/img/icons/time.svg" alt="img" /><span>
|
||||||
|
Laporan</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<!-- <a href="purchaseorderreport.html">Laporan Order Pembelian</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="inventoryreport.html">Laporan Inventaris</a></li> -->
|
||||||
|
<li><a href="/ayula-store/views/report/sales-report/">Laporan Penjualan</a></li>
|
||||||
|
<?php if ($userRole == 'admin') { ?>
|
||||||
|
<li><a href="/ayula-store/views/report/popular-products/">Produk Terlaris</a></li>
|
||||||
|
<?php } ?>
|
||||||
|
<!-- <li><a href="invoicereport.html">Laporan Faktur</a></li>
|
||||||
|
<li><a href="purchasereport.html">Laporan Pembelian</a></li>
|
||||||
|
<li><a href="supplierreport.html">Laporan Pemasok</a></li>
|
||||||
|
<li><a href="customerreport.html">Laporan Pelanggan</a></li> -->
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../bootstrap/assets/img/icons/users1.svg" alt="img" /><span>
|
||||||
|
Pengguna</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<?php if ($userRole == 'admin') { ?>
|
||||||
|
<li><a href="/ayula-store/views/users/add-user.php">Pengguna Baru</a></li>
|
||||||
|
<?php } ?>
|
||||||
|
<li><a href="/ayula-store/views/users/">Daftar Pengguna</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="content">
|
||||||
|
<!-- Welcome message -->
|
||||||
|
<div class="welcome-message">
|
||||||
|
<div class="row align-items-center">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<h4>Selamat Datang, <?php echo htmlspecialchars($displayName); ?>!</h4>
|
||||||
|
<p class="mb-0">Ringkasan aktivitas toko hari ini, <?php echo date('d F Y'); ?></p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 text-end">
|
||||||
|
<i class="fas fa-store fa-3x"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main metrics -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-3 col-sm-6 col-12 d-flex">
|
||||||
|
<div class="dash-count das1 flex-fill">
|
||||||
|
<div class="dash-counts">
|
||||||
|
<h4><?php echo number_format($dashboardData['today_transactions']); ?></h4>
|
||||||
|
<h5>Transaksi Hari Ini</h5>
|
||||||
|
<p class="metric-subtitle mb-0">
|
||||||
|
<i class="fas fa-calendar-day me-1"></i> <?php echo date('d M Y'); ?>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="dash-imgs">
|
||||||
|
<i data-feather="shopping-cart"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-3 col-sm-6 col-12 d-flex">
|
||||||
|
<div class="dash-count flex-fill">
|
||||||
|
<div class="dash-counts">
|
||||||
|
<h4><?php echo number_format($dashboardData['today_items']); ?></h4>
|
||||||
|
<h5>Item Terjual Hari Ini</h5>
|
||||||
|
<p class="metric-subtitle mb-0">
|
||||||
|
<i class="fas fa-box me-1"></i> Total unit terjual
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="dash-imgs">
|
||||||
|
<i data-feather="package"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-3 col-sm-6 col-12 d-flex">
|
||||||
|
<div class="dash-count das2 flex-fill">
|
||||||
|
<div class="dash-counts">
|
||||||
|
<h4><?php echo number_format($dashboardData['week_transactions']); ?></h4>
|
||||||
|
<h5>Transaksi Minggu Ini</h5>
|
||||||
|
<p class="metric-subtitle mb-0">
|
||||||
|
<i class="fas fa-calendar-week me-1"></i> <?php echo date('d M', strtotime($startOfWeek)) . ' - ' . date('d M', strtotime($endOfWeek)); ?>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="dash-imgs">
|
||||||
|
<i data-feather="file-text"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-3 col-sm-6 col-12 d-flex">
|
||||||
|
<div class="dash-count das3 flex-fill">
|
||||||
|
<div class="dash-counts">
|
||||||
|
<h4><?php echo number_format($dashboardData['low_stock_count']); ?></h4>
|
||||||
|
<h5>Produk Stok Menipis</h5>
|
||||||
|
<p class="metric-subtitle mb-0">
|
||||||
|
<i class="fas fa-exclamation-triangle me-1"></i> Perlu perhatian
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="dash-imgs">
|
||||||
|
<i data-feather="alert-triangle"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content Cards -->
|
||||||
|
<div class="row">
|
||||||
|
<!-- Recent Transactions -->
|
||||||
|
<div class="col-lg-6 col-sm-12 col-12 d-flex">
|
||||||
|
<div class="card flex-fill">
|
||||||
|
<div class="card-header pb-0 d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="card-title mb-0">Transaksi Terbaru</h5>
|
||||||
|
<a href="/ayula-store/views/report/sales-report/" class="btn btn-sm btn-primary">Lihat Semua</a>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (count($recentTransactions) > 0): ?>
|
||||||
|
<div class="transaction-list">
|
||||||
|
<?php foreach($recentTransactions as $transaction): ?>
|
||||||
|
<div class="transaction-item d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<h6 class="mb-1"><?php echo $transaction['kode_transaksi']; ?></h6>
|
||||||
|
<p class="mb-0 text-muted"><?php echo $transaction['total_item']; ?> item</p>
|
||||||
|
</div>
|
||||||
|
<div class="text-end">
|
||||||
|
<span class="badge bg-success"><?php echo $transaction['status']; ?></span>
|
||||||
|
<p class="mb-0 text-muted"><?php echo date('d M, H:i', strtotime($transaction['tanggal'])); ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="text-center py-5">
|
||||||
|
<i class="fas fa-receipt fa-3x text-muted mb-3"></i>
|
||||||
|
<h6>Belum ada transaksi hari ini</h6>
|
||||||
|
<p class="text-muted">Transaksi baru akan muncul di sini</p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Low Stock Items -->
|
||||||
|
<div class="col-lg-6 col-sm-12 col-12 d-flex">
|
||||||
|
<div class="card flex-fill">
|
||||||
|
<div class="card-header pb-0 d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="card-title mb-0">Stok Menipis</h5>
|
||||||
|
<a href="/ayula-store/views/barang/productlist.php" class="btn btn-sm btn-primary">Lihat Semua</a>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (count($lowStockItems) > 0): ?>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Kode</th>
|
||||||
|
<th>Nama Produk</th>
|
||||||
|
<th>Stok</th>
|
||||||
|
<th>Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach($lowStockItems as $item): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo $item['kode_barang']; ?></td>
|
||||||
|
<td><?php echo $item['nama_barang']; ?></td>
|
||||||
|
<td><?php echo $item['stok']; ?></td>
|
||||||
|
<td>
|
||||||
|
<?php if ($item['stok'] <= 5): ?>
|
||||||
|
<span class="badge bg-danger">Kritis</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge bg-warning">Menipis</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="text-center py-5">
|
||||||
|
<i class="fas fa-box-open fa-3x text-muted mb-3"></i>
|
||||||
|
<h6>Semua Produk Memiliki Stok Memadai</h6>
|
||||||
|
<p class="text-muted">Tidak ada produk yang stoknya menipis saat ini</p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<!-- Popular Products - Full Width -->
|
||||||
|
<div class="col-12 d-flex">
|
||||||
|
<div class="card flex-fill">
|
||||||
|
<div class="card-header pb-0 d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="card-title mb-0">10 Produk Terlaris Minggu Ini</h5>
|
||||||
|
<?php if ($userRole == 'admin'): ?>
|
||||||
|
<a href="/ayula-store/views/report/popular-products/" class="btn btn-sm btn-primary">Detail Lengkap</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (count($popularProducts) > 0): ?>
|
||||||
|
<div class="row">
|
||||||
|
<?php
|
||||||
|
// Split the products into two rows for better display
|
||||||
|
$totalProducts = count($popularProducts);
|
||||||
|
$firstRowCount = min(5, $totalProducts);
|
||||||
|
$secondRowCount = $totalProducts - $firstRowCount;
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!-- First row of products (top 5) -->
|
||||||
|
<?php for($i = 0; $i < $firstRowCount; $i++):
|
||||||
|
$product = $popularProducts[$i];
|
||||||
|
?>
|
||||||
|
<div class="col-md-20p mb-3">
|
||||||
|
<div class="product-card p-3 border rounded h-100">
|
||||||
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
|
<div>
|
||||||
|
<h6 class="mb-1"><?php echo $product['nama_barang']; ?></h6>
|
||||||
|
<p class="mb-1 text-muted small"><?php echo $product['kode_barang']; ?></p>
|
||||||
|
</div>
|
||||||
|
<span class="badge bg-primary">#<?php echo $i + 1; ?></span>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<span>Total Terjual:</span>
|
||||||
|
<span class="fw-bold"><?php echo number_format($product['total_quantity']); ?> unit</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress mt-2" style="height: 6px;">
|
||||||
|
<div class="progress-bar bg-success" role="progressbar"
|
||||||
|
style="width: <?php echo min(100, ($product['total_quantity'] / ($popularProducts[0]['total_quantity'] ?: 1)) * 100); ?>%">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endfor; ?>
|
||||||
|
|
||||||
|
<!-- Second row of products (6-10) -->
|
||||||
|
<?php if($secondRowCount > 0): ?>
|
||||||
|
<?php for($i = $firstRowCount; $i < $totalProducts; $i++):
|
||||||
|
$product = $popularProducts[$i];
|
||||||
|
?>
|
||||||
|
<div class="col-md-20p mb-3">
|
||||||
|
<div class="product-card p-3 border rounded h-100">
|
||||||
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
|
<div>
|
||||||
|
<h6 class="mb-1"><?php echo $product['nama_barang']; ?></h6>
|
||||||
|
<p class="mb-1 text-muted small"><?php echo $product['kode_barang']; ?></p>
|
||||||
|
</div>
|
||||||
|
<span class="badge bg-primary">#<?php echo $i + 1; ?></span>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<span>Total Terjual:</span>
|
||||||
|
<span class="fw-bold"><?php echo number_format($product['total_quantity']); ?> unit</span>
|
||||||
|
</div>
|
||||||
|
<div class="progress mt-2" style="height: 6px;">
|
||||||
|
<div class="progress-bar bg-success" role="progressbar"
|
||||||
|
style="width: <?php echo min(100, ($product['total_quantity'] / ($popularProducts[0]['total_quantity'] ?: 1)) * 100); ?>%">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endfor; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="text-center py-5">
|
||||||
|
<i class="fas fa-chart-line fa-3x text-muted mb-3"></i>
|
||||||
|
<h6>Belum Ada Data Penjualan</h6>
|
||||||
|
<p class="text-muted">Produk terlaris akan ditampilkan saat ada penjualan</p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="../../bootstrap/assets/js/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="../../bootstrap/assets/js/feather.min.js"></script>
|
||||||
|
<script src="../../bootstrap/assets/js/jquery.slimscroll.min.js"></script>
|
||||||
|
<script src="../../bootstrap/assets/js/jquery.dataTables.min.js"></script>
|
||||||
|
<script src="../../bootstrap/assets/js/dataTables.bootstrap4.min.js"></script>
|
||||||
|
<script src="../../bootstrap/assets/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="../../bootstrap/assets/plugins/apexchart/apexcharts.min.js"></script>
|
||||||
|
<script src="../../bootstrap/assets/plugins/apexchart/chart-data.js"></script>
|
||||||
|
<script src="../../bootstrap/assets/js/script.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Hide loader when page is fully loaded
|
||||||
|
$(window).on('load', function() {
|
||||||
|
setTimeout(function() {
|
||||||
|
$("#global-loader").fadeOut('slow');
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize any DataTables
|
||||||
|
$(document).ready(function() {
|
||||||
|
if ($.fn.DataTable.isDataTable('.datatable') === false) {
|
||||||
|
$('.datatable').DataTable({
|
||||||
|
language: {
|
||||||
|
search: "Cari:",
|
||||||
|
lengthMenu: "Tampilkan _MENU_ data",
|
||||||
|
info: "Menampilkan _START_ sampai _END_ dari _TOTAL_ data",
|
||||||
|
infoEmpty: "Menampilkan 0 sampai 0 dari 0 data",
|
||||||
|
infoFiltered: "(disaring dari _MAX_ total data)",
|
||||||
|
zeroRecords: "Tidak ada data yang cocok",
|
||||||
|
paginate: {
|
||||||
|
first: "Pertama",
|
||||||
|
last: "Terakhir",
|
||||||
|
next: "Selanjutnya",
|
||||||
|
previous: "Sebelumnya"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
include('../routes/db_conn.php'); // Menyertakan koneksi database
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||||
|
// Menangkap data yang dikirimkan dari form
|
||||||
|
$username = mysqli_real_escape_string($conn, $_POST['username']);
|
||||||
|
$new_password = mysqli_real_escape_string($conn, $_POST['new_password']);
|
||||||
|
$confirm_password = mysqli_real_escape_string($conn, $_POST['confirm_password']);
|
||||||
|
|
||||||
|
// Validasi apakah password baru dan konfirmasi password cocok
|
||||||
|
if ($new_password !== $confirm_password) {
|
||||||
|
$error_message = "Passwords do not match!";
|
||||||
|
} else {
|
||||||
|
// Hash password untuk keamanan
|
||||||
|
$hashed_password = password_hash($new_password, PASSWORD_DEFAULT);
|
||||||
|
|
||||||
|
// Query untuk mengecek apakah username ada di database
|
||||||
|
$query = "SELECT * FROM kasir WHERE username = '$username' LIMIT 1";
|
||||||
|
$result = mysqli_query($conn, $query);
|
||||||
|
|
||||||
|
// Jika username ditemukan
|
||||||
|
if (mysqli_num_rows($result) > 0) {
|
||||||
|
// Query untuk memperbarui password di database
|
||||||
|
$query = "UPDATE kasir SET password = '$hashed_password' WHERE username = '$username'";
|
||||||
|
|
||||||
|
if (mysqli_query($conn, $query)) {
|
||||||
|
// Jika berhasil, alihkan ke halaman login
|
||||||
|
$_SESSION['username'] = $username;
|
||||||
|
header("Location: index.php");
|
||||||
|
exit();
|
||||||
|
} else {
|
||||||
|
$error_message = "Gagal memperbarui password!";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$error_message = "Username tidak ditemukan!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0" />
|
||||||
|
<meta name="description" content="POS - Bootstrap Admin Template" />
|
||||||
|
<meta name="keywords" content="admin, estimates, bootstrap, business, corporate, creative, invoice, html5, responsive, Projects" />
|
||||||
|
<meta name="author" content="Dreamguys - Bootstrap Admin Template" />
|
||||||
|
<meta name="robots" content="noindex, nofollow" />
|
||||||
|
<title>Reset Password - Ayula Store</title>
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../src/img/smallest-ayula.png" />
|
||||||
|
<link rel="stylesheet" href="../bootstrap/assets/css/bootstrap.min.css" />
|
||||||
|
<link rel="stylesheet" href="../bootstrap/assets/plugins/fontawesome/css/fontawesome.min.css" />
|
||||||
|
<link rel="stylesheet" href="../bootstrap/assets/plugins/fontawesome/css/all.min.css" />
|
||||||
|
<link rel="stylesheet" href="../bootstrap/assets/css/style.css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="account-page">
|
||||||
|
<div class="main-wrapper">
|
||||||
|
<div class="account-content">
|
||||||
|
<div class="login-wrapper">
|
||||||
|
<div class="login-content">
|
||||||
|
<div class="login-userset">
|
||||||
|
<div class="login-userheading">
|
||||||
|
<h3>Reset Password</h3>
|
||||||
|
<h4>Enter your new password below.</h4>
|
||||||
|
</div>
|
||||||
|
<form method="POST" action="forgot-password.php">
|
||||||
|
<div class="form-login">
|
||||||
|
<label>Username</label>
|
||||||
|
<div class="form-addons">
|
||||||
|
<input type="text" name="username" placeholder="Enter your username" required />
|
||||||
|
<img src="../bootstrap/assets/img/icons/users1.svg" alt="img" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-login">
|
||||||
|
<label>New Password</label>
|
||||||
|
<div class="pass-group">
|
||||||
|
<input type="password" name="new_password" class="pass-input" placeholder="Enter your new password" required />
|
||||||
|
<span class="fas toggle-password fa-eye-slash"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-login">
|
||||||
|
<label>Confirm New Password</label>
|
||||||
|
<div class="pass-group">
|
||||||
|
<input type="password" name="confirm_password" class="pass-input" placeholder="Confirm your new password" required />
|
||||||
|
<span class="fas toggle-password fa-eye-slash"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php if (isset($error_message)) { ?>
|
||||||
|
<div class="error-message">
|
||||||
|
<p style="color: red;"><?php echo $error_message; ?></p>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
<div class="form-login">
|
||||||
|
<button type="submit" class="btn btn-login">Reset Password</button>
|
||||||
|
</div>
|
||||||
|
<div class="signinform text-center">
|
||||||
|
<h4>Remembered? <a href="index.php" class="hover-a">Sign In</a></h4>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="login-img">
|
||||||
|
<img src="../bootstrap/assets/img/login.jpg" alt="img" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="../bootstrap/assets/js/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="../bootstrap/assets/js/feather.min.js"></script>
|
||||||
|
<script src="../bootstrap/assets/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="../bootstrap/assets/js/script.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,278 @@
|
||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
include('../routes/db_conn.php'); // Menyertakan koneksi database
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||||
|
// Menangkap data dari form login
|
||||||
|
$username = mysqli_real_escape_string($conn, $_POST['username']);
|
||||||
|
$password = $_POST['password']; // Password yang dimasukkan oleh pengguna
|
||||||
|
|
||||||
|
// Query untuk mencari pengguna berdasarkan username
|
||||||
|
$query = "SELECT * FROM kasir WHERE username = '$username'";
|
||||||
|
$result = mysqli_query($conn, $query);
|
||||||
|
|
||||||
|
// Mengecek apakah user ditemukan
|
||||||
|
if (mysqli_num_rows($result) > 0) {
|
||||||
|
$user = mysqli_fetch_assoc($result);
|
||||||
|
|
||||||
|
// Verifikasi password menggunakan password_verify()
|
||||||
|
if (password_verify($password, $user['password'])) {
|
||||||
|
// Jika password valid, login berhasil
|
||||||
|
$_SESSION['user_id'] = $user['id_kasir'];
|
||||||
|
$_SESSION['username'] = $user['username'];
|
||||||
|
$_SESSION['role'] = $user['role'];
|
||||||
|
|
||||||
|
// Jika role adalah admin, tampilkan modal pilihan
|
||||||
|
if ($user['role'] == 'admin') {
|
||||||
|
// Set flag untuk menampilkan modal
|
||||||
|
$showRoleModal = true;
|
||||||
|
} else {
|
||||||
|
// Jika bukan admin, redirect langsung ke dashboard
|
||||||
|
header("Location: /ayula-store/views/dashboard/");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Jika password salah
|
||||||
|
$error_message = "Password salah!";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Jika username tidak ditemukan
|
||||||
|
$error_message = "Username tidak ditemukan!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proses pilihan role dari modal
|
||||||
|
if (isset($_POST['role_choice'])) {
|
||||||
|
if ($_POST['role_choice'] == 'gudang') {
|
||||||
|
header("Location: /ayula-store/views/reporttt/report.php");
|
||||||
|
exit();
|
||||||
|
} else if ($_POST['role_choice'] == 'kasir') {
|
||||||
|
header("Location: /ayula-store/views/dashboard/");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proses logout jika tombol close ditekan
|
||||||
|
if (isset($_POST['logout'])) {
|
||||||
|
// Hapus semua data session
|
||||||
|
session_unset();
|
||||||
|
session_destroy();
|
||||||
|
// Redirect ke halaman login
|
||||||
|
header("Location: index.php");
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0" />
|
||||||
|
<meta name="description" content="POS - Bootstrap Admin Template" />
|
||||||
|
<meta name="keywords" content="admin, estimates, bootstrap, business, corporate, creative, invoice, html5, responsive, Projects" />
|
||||||
|
<meta name="author" content="Dreamguys - Bootstrap Admin Template" />
|
||||||
|
<meta name="robots" content="noindex, nofollow" />
|
||||||
|
<title>Login - Ayula Store</title>
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../src/img/smallest-ayula.png" />
|
||||||
|
<link rel="stylesheet" href="../bootstrap/assets/css/bootstrap.min.css" />
|
||||||
|
<link rel="stylesheet" href="../bootstrap/assets/plugins/fontawesome/css/fontawesome.min.css" />
|
||||||
|
<link rel="stylesheet" href="../bootstrap/assets/plugins/fontawesome/css/all.min.css" />
|
||||||
|
<link rel="stylesheet" href="../bootstrap/assets/css/style.css" />
|
||||||
|
<style>
|
||||||
|
.role-btn {
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 150px;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
border: none;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-btn:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-btn i {
|
||||||
|
font-size: 40px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gudang-btn {
|
||||||
|
background-color: #ff9f43;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gudang-btn:hover {
|
||||||
|
background-color: #ffb63f;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kasir-btn {
|
||||||
|
background-color: #1b2850;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kasir-btn:hover {
|
||||||
|
background-color: #344e9c;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-weight: 700;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
border-bottom: 2px solid #f0f0f0;
|
||||||
|
padding: 20px 25px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-text {
|
||||||
|
font-size: 16px;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-container {
|
||||||
|
padding: 0 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #888;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
padding: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn:hover {
|
||||||
|
color: #ff6b6b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="account-page">
|
||||||
|
<div class="main-wrapper">
|
||||||
|
<div class="account-content">
|
||||||
|
<div class="login-wrapper">
|
||||||
|
<div class="login-content">
|
||||||
|
<div class="login-userset">
|
||||||
|
<div class="login-userheading">
|
||||||
|
<h3>Masuk</h3>
|
||||||
|
<h4>Silakan masuk ke akun Anda</h4>
|
||||||
|
</div>
|
||||||
|
<form method="POST" action="index.php">
|
||||||
|
<div class="form-login">
|
||||||
|
<label>Username</label>
|
||||||
|
<div class="form-addons">
|
||||||
|
<input type="text" name="username" placeholder="Enter your username" required />
|
||||||
|
<img src="../bootstrap/assets/img/icons/users1.svg" alt="img" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-login">
|
||||||
|
<label>Password</label>
|
||||||
|
<div class="pass-group">
|
||||||
|
<input type="password" name="password" class="pass-input" placeholder="Enter your password" required />
|
||||||
|
<span class="fas toggle-password fa-eye-slash"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-login">
|
||||||
|
<div class="alreadyuser">
|
||||||
|
<h4>
|
||||||
|
<a href="forgot-password.php" class="hover-a">Lupa Password?</a>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php if (isset($error_message)) { ?>
|
||||||
|
<div class="error-message">
|
||||||
|
<p style="color: red;"><?php echo $error_message; ?></p>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
<div class="form-login">
|
||||||
|
<button type="submit" class="btn btn-login">Masuk</button>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="signinform text-center">
|
||||||
|
<h4>Don't have an account? <a href="register.php" class="hover-a">Sign Up</a></h4>
|
||||||
|
</div> -->
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="login-img">
|
||||||
|
<img src="../bootstrap/assets/img/login.jpg" alt="img" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Pilihan Role untuk Admin -->
|
||||||
|
<div class="modal fade" id="roleModal" tabindex="-1" role="dialog" aria-labelledby="roleModalLabel" aria-hidden="true" data-backdrop="static" data-keyboard="false">
|
||||||
|
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="roleModalLabel">Pilih Akses</h5>
|
||||||
|
<form method="POST" action="" id="logoutForm">
|
||||||
|
<input type="hidden" name="logout" value="1">
|
||||||
|
<button type="submit" class="close-btn" title="Keluar">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p class="welcome-text">Selamat datang, <strong><?php echo isset($_SESSION['username']) ? $_SESSION['username'] : ''; ?></strong>! <br>Silakan pilih akses yang ingin Anda gunakan:</p>
|
||||||
|
<form method="POST" action="">
|
||||||
|
<div class="row role-container">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<button type="submit" name="role_choice" value="gudang" class="role-btn gudang-btn">
|
||||||
|
<i class="fas fa-warehouse"></i>
|
||||||
|
<span>Gudang</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<button type="submit" name="role_choice" value="kasir" class="role-btn kasir-btn">
|
||||||
|
<i class="fas fa-cash-register"></i>
|
||||||
|
<span>Kasir</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="../bootstrap/assets/js/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="../bootstrap/assets/js/feather.min.js"></script>
|
||||||
|
<script src="../bootstrap/assets/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="../bootstrap/assets/js/script.js"></script>
|
||||||
|
|
||||||
|
<?php if (isset($showRoleModal) && $showRoleModal): ?>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('#roleModal').modal('show');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<?php endif; ?>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
// Start the session
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
// Unset all session variables
|
||||||
|
$_SESSION = array();
|
||||||
|
|
||||||
|
// If it's desired to kill the session, also delete the session cookie.
|
||||||
|
// Note: This will destroy the session, and not just the session data!
|
||||||
|
if (ini_get("session.use_cookies")) {
|
||||||
|
$params = session_get_cookie_params();
|
||||||
|
setcookie(session_name(), '', time() - 42000,
|
||||||
|
$params["path"], $params["domain"],
|
||||||
|
$params["secure"], $params["httponly"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, destroy the session
|
||||||
|
session_destroy();
|
||||||
|
|
||||||
|
// Redirect to login page
|
||||||
|
header("Location: /ayula-store/views/index.php");
|
||||||
|
exit;
|
||||||
|
?>
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
include('../routes/db_conn.php'); // Menyertakan koneksi database
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||||
|
// Menangkap data yang dikirimkan dari form
|
||||||
|
$username = mysqli_real_escape_string($conn, $_POST['username']);
|
||||||
|
$password = mysqli_real_escape_string($conn, $_POST['password']);
|
||||||
|
$phone = mysqli_real_escape_string($conn, $_POST['phone']);
|
||||||
|
|
||||||
|
// Hash password untuk keamanan
|
||||||
|
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||||
|
|
||||||
|
// Query untuk mengecek apakah username sudah ada di database
|
||||||
|
$query = "SELECT * FROM kasir WHERE username = '$username' LIMIT 1";
|
||||||
|
$result = mysqli_query($conn, $query);
|
||||||
|
|
||||||
|
// Jika username sudah ada
|
||||||
|
if (mysqli_num_rows($result) > 0) {
|
||||||
|
$error_message = "Username sudah terdaftar!";
|
||||||
|
} else {
|
||||||
|
// Query untuk memasukkan data baru ke dalam database
|
||||||
|
$query = "INSERT INTO kasir (username, password, phone, role) VALUES ('$username', '$hashed_password', '$phone', 'user')";
|
||||||
|
|
||||||
|
if (mysqli_query($conn, $query)) {
|
||||||
|
// Jika berhasil, alihkan ke halaman login
|
||||||
|
$_SESSION['username'] = $username;
|
||||||
|
header("Location: index.php");
|
||||||
|
exit();
|
||||||
|
} else {
|
||||||
|
$error_message = "Gagal melakukan registrasi!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0" />
|
||||||
|
<meta name="description" content="POS - Bootstrap Admin Template" />
|
||||||
|
<meta name="keywords" content="admin, estimates, bootstrap, business, corporate, creative, invoice, html5, responsive, Projects" />
|
||||||
|
<meta name="author" content="Dreamguys - Bootstrap Admin Template" />
|
||||||
|
<meta name="robots" content="noindex, nofollow" />
|
||||||
|
<title>Register - Ayula Store</title>
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../src/img/smallest-ayula.png" />
|
||||||
|
<link rel="stylesheet" href="../bootstrap/assets/css/bootstrap.min.css" />
|
||||||
|
<link rel="stylesheet" href="../bootstrap/assets/plugins/fontawesome/css/fontawesome.min.css" />
|
||||||
|
<link rel="stylesheet" href="../bootstrap/assets/plugins/fontawesome/css/all.min.css" />
|
||||||
|
<link rel="stylesheet" href="../bootstrap/assets/css/style.css" />
|
||||||
|
</head>
|
||||||
|
<body class="account-page">
|
||||||
|
<div class="main-wrapper">
|
||||||
|
<div class="account-content">
|
||||||
|
<div class="login-wrapper">
|
||||||
|
<div class="login-content">
|
||||||
|
<div class="login-userset">
|
||||||
|
<div class="login-userheading">
|
||||||
|
<h3>Create an Account</h3>
|
||||||
|
<h4>Continue where you left off</h4>
|
||||||
|
</div>
|
||||||
|
<form method="POST" action="register.php">
|
||||||
|
<div class="form-login">
|
||||||
|
<label>Username</label>
|
||||||
|
<div class="form-addons">
|
||||||
|
<input type="text" name="username" placeholder="Enter your username" required />
|
||||||
|
<img src="../bootstrap/assets/img/icons/users1.svg" alt="img" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-login">
|
||||||
|
<label>Password</label>
|
||||||
|
<div class="pass-group">
|
||||||
|
<input type="password" name="password" class="pass-input" placeholder="Enter your password" required />
|
||||||
|
<span class="fas toggle-password fa-eye-slash"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-login">
|
||||||
|
<label>Phone number</label>
|
||||||
|
<div class="form-addons">
|
||||||
|
<input type="text" name="phone" placeholder="Enter your phone number" required />
|
||||||
|
<img src="../bootstrap/assets/img/icons/telephone.png" alt="img" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php if (isset($error_message)) { ?>
|
||||||
|
<div class="error-message">
|
||||||
|
<p style="color: red;"><?php echo $error_message; ?></p>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
<div class="form-login">
|
||||||
|
<button type="submit" class="btn btn-login">Sign Up</button>
|
||||||
|
</div>
|
||||||
|
<div class="signinform text-center">
|
||||||
|
<h4>Already a user? <a href="index.php" class="hover-a">Sign In</a></h4>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="login-img">
|
||||||
|
<img src="../bootstrap/assets/img/login.jpg" alt="img" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="../bootstrap/assets/js/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="../bootstrap/assets/js/feather.min.js"></script>
|
||||||
|
<script src="../bootstrap/assets/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="../bootstrap/assets/js/script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,384 @@
|
||||||
|
<?php
|
||||||
|
// Start session to get user information
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
// Include database connection if needed for user validation
|
||||||
|
include('../../routes/db_conn.php');
|
||||||
|
|
||||||
|
// Get user role and name if available
|
||||||
|
$userRole = isset($_SESSION['role']) ? $_SESSION['role'] : 'guest';
|
||||||
|
$username = isset($_SESSION['username']) ? $_SESSION['username'] : 'Unknown User';
|
||||||
|
$isAdmin = ($userRole === 'admin');
|
||||||
|
|
||||||
|
// WhatsApp number (with country code format for Indonesia)
|
||||||
|
$waNumber = "6287857242169"; // 62 + 87704632355 (removing the leading 0)
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="id">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0">
|
||||||
|
<meta name="description" content="Ayula Store - Laporkan Masalah">
|
||||||
|
<meta name="keywords" content="admin, reports, issues, support, ayula store">
|
||||||
|
<meta name="author" content="Ayula Store Developer">
|
||||||
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
<title>Ayula Store POS - Laporkan Masalah</title>
|
||||||
|
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../src/img/smallest-ayula.png">
|
||||||
|
<link rel="stylesheet" href="../../bootstrap/assets/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="../../bootstrap/assets/css/animate.css">
|
||||||
|
<link rel="stylesheet" href="../../bootstrap/assets/plugins/select2/css/select2.min.css">
|
||||||
|
<link rel="stylesheet" href="../../bootstrap/assets/plugins/fontawesome/css/fontawesome.min.css">
|
||||||
|
<link rel="stylesheet" href="../../bootstrap/assets/plugins/fontawesome/css/all.min.css">
|
||||||
|
<link rel="stylesheet" href="../../bootstrap/assets/css/style.css">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.report-card {
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-header {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 20px;
|
||||||
|
border-bottom: 1px solid #e9ecef;
|
||||||
|
border-radius: 10px 10px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-body {
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wa-button {
|
||||||
|
background-color: #25D366;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 12px 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wa-button:hover {
|
||||||
|
background-color: #128C7E;
|
||||||
|
color: white;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wa-icon {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.issue-type-container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.issue-type-btn {
|
||||||
|
flex: 1 0 calc(33.333% - 10px);
|
||||||
|
min-width: 150px;
|
||||||
|
padding: 15px;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 5px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.issue-type-btn:hover {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-color: #ced4da;
|
||||||
|
}
|
||||||
|
|
||||||
|
.issue-type-btn.active {
|
||||||
|
background-color: #ff9f43;
|
||||||
|
color: white;
|
||||||
|
border-color: #ff9f43;
|
||||||
|
}
|
||||||
|
|
||||||
|
.issue-type-btn i {
|
||||||
|
display: block;
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.issue-type-btn {
|
||||||
|
flex: 1 0 calc(50% - 10px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.issue-type-btn {
|
||||||
|
flex: 1 0 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="<?php echo $isAdmin ? 'admin' : 'employee'; ?>">
|
||||||
|
<div id="global-loader">
|
||||||
|
<div class="whirly-loader"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="main-wrapper">
|
||||||
|
<div class="header">
|
||||||
|
<div class="header-left active">
|
||||||
|
<a href="/ayula-store/views/dashboard/" class="logo">
|
||||||
|
<img src="../../src/img/logoayula.png" alt="" />
|
||||||
|
</a>
|
||||||
|
<a href="/ayula-store/views/dashboard/" class="logo-small">
|
||||||
|
<img src="../../src/img/smallest-ayula.png" alt="" />
|
||||||
|
</a>
|
||||||
|
<a id="toggle_btn" href="javascript:void(0);"> </a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a id="mobile_btn" class="mobile_btn" href="#sidebar">
|
||||||
|
<span class="bar-icon">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<ul class="nav user-menu">
|
||||||
|
<li class="nav-item dropdown has-arrow main-drop">
|
||||||
|
<a href="javascript:void(0);" class="dropdown-toggle nav-link userset" data-bs-toggle="dropdown">
|
||||||
|
<span class="user-img"><img src="../../src/img/userprofile.png" alt="">
|
||||||
|
<span class="status online"></span></span>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu menu-drop-user">
|
||||||
|
<div class="profilename">
|
||||||
|
<div class="profileset">
|
||||||
|
<span class="user-img"><img src="../../src/img/userprofile.png" alt="">
|
||||||
|
<span class="status online"></span></span>
|
||||||
|
<div class="profilesets">
|
||||||
|
<h6><?php echo $userRole == 'admin' ? 'Admin' : 'Karyawan'; ?></h6>
|
||||||
|
<h5><?php echo htmlspecialchars($displayName); ?></h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item" href="/ayula-store/views/report-issue/">
|
||||||
|
<img src="../../src/img/warning.png" class="me-2" alt="img" /> Laporkan Masalah
|
||||||
|
</a>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item logout pb-0" href="../../views/logout.php"><img
|
||||||
|
src="../../bootstrap/assets/img/icons/log-out.svg" class="me-2" alt="img" />Keluar</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="dropdown mobile-user-menu">
|
||||||
|
<a href="javascript:void(0);" class="nav-link dropdown-toggle" data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"><i class="fa fa-ellipsis-v"></i></a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
|
<a class="dropdown-item" href="/ayula-store/views/report-issue/">
|
||||||
|
<i class="fa fa-cog me-2"></i> Laporkan Masalah
|
||||||
|
</a>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item logout pb-0" href="../../views/logout.php"><img
|
||||||
|
src="../../bootstrap/assets/img/icons/log-out.svg" class="me-2" alt="img" />Keluar</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sidebar" id="sidebar">
|
||||||
|
<div class="sidebar-inner slimscroll">
|
||||||
|
<div id="sidebar-menu" class="sidebar-menu">
|
||||||
|
<ul>
|
||||||
|
<li class="active">
|
||||||
|
<a href="/ayula-store/views/reporttt/report.php"><img src="../../bootstrap/assets/img/icons/dashboard.svg" alt="img" /><span>
|
||||||
|
Dashboard</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../bootstrap/assets/img/icons/product.svg" alt="img" /><span>
|
||||||
|
Barang</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/ayula-store/views/barang/productlist.php" >Daftar Barang</a></li>
|
||||||
|
<li><a href="/ayula-store/views/barang/addproduct.php">Tambah Barang</a></li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li >
|
||||||
|
<a href="/ayula-store/views/barang/topsis_restock_view.php"><img src="../../bootstrap/assets/img/icons/sales1.svg" alt="img" /><span>
|
||||||
|
Analisa Barang</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../bootstrap/assets/img/icons/users1.svg" alt="img" /><span>
|
||||||
|
Pengguna</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<?php if ($userRole == 'admin') { ?>
|
||||||
|
<li><a href="/ayula-store/views/users/add-user.php">Pengguna Baru</a></li>
|
||||||
|
<?php } ?>
|
||||||
|
<li><a href="/ayula-store/views/users/">Daftar Pengguna</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="content">
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="page-title">
|
||||||
|
<h4>Laporkan Masalah</h4>
|
||||||
|
<h6>Laporkan masalah atau saran untuk pengembangan sistem</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card report-card">
|
||||||
|
<div class="card-header report-header">
|
||||||
|
<h5 class="card-title">Hubungi Developer via WhatsApp</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body report-body">
|
||||||
|
<div class="alert alert-info mb-4">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>
|
||||||
|
Isi formulir di bawah ini untuk melaporkan masalah. Pesan akan dikirim langsung ke developer melalui WhatsApp.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form id="issue-report-form">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label">Jenis Masalah</label>
|
||||||
|
<div class="issue-type-container">
|
||||||
|
<div class="issue-type-btn" data-type="Bug/Error">
|
||||||
|
<i class="fas fa-bug"></i>
|
||||||
|
<span>Bug/Error</span>
|
||||||
|
</div>
|
||||||
|
<div class="issue-type-btn" data-type="Fitur Tidak Berfungsi">
|
||||||
|
<i class="fas fa-exclamation-triangle"></i>
|
||||||
|
<span>Fitur Tidak Berfungsi</span>
|
||||||
|
</div>
|
||||||
|
<div class="issue-type-btn" data-type="Permintaan Fitur">
|
||||||
|
<i class="fas fa-lightbulb"></i>
|
||||||
|
<span>Permintaan Fitur</span>
|
||||||
|
</div>
|
||||||
|
<div class="issue-type-btn" data-type="Optimasi Kinerja">
|
||||||
|
<i class="fas fa-tachometer-alt"></i>
|
||||||
|
<span>Optimasi Kinerja</span>
|
||||||
|
</div>
|
||||||
|
<div class="issue-type-btn" data-type="UI/UX">
|
||||||
|
<i class="fas fa-palette"></i>
|
||||||
|
<span>UI/UX</span>
|
||||||
|
</div>
|
||||||
|
<div class="issue-type-btn" data-type="Lainnya">
|
||||||
|
<i class="fas fa-question-circle"></i>
|
||||||
|
<span>Lainnya</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" id="issue-type" name="issue-type" value="">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="issue-location" class="form-label">Lokasi Masalah</label>
|
||||||
|
<select class="form-select" id="issue-location" name="issue-location">
|
||||||
|
<option value="">Pilih Halaman/Fitur...</option>
|
||||||
|
<option value="Login">Login</option>
|
||||||
|
<option value="Dashboard">Dashboard</option>
|
||||||
|
<option value="POS/Transaksi">Barang</option>
|
||||||
|
<option value="Produk">Analisa barang</option>
|
||||||
|
<option value="Pengguna">Pengguna</option>
|
||||||
|
<option value="Lainnya">Lainnya</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="issue-priority" class="form-label">Prioritas</label>
|
||||||
|
<select class="form-select" id="issue-priority" name="issue-priority">
|
||||||
|
<option value="Rendah">Rendah - Tidak mengganggu operasi utama</option>
|
||||||
|
<option value="Sedang" selected>Sedang - Mengganggu tapi ada solusi</option>
|
||||||
|
<option value="Tinggi">Tinggi - Menghambat pekerjaan</option>
|
||||||
|
<option value="Kritis">Kritis - Sistem tidak dapat digunakan</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="issue-description" class="form-label">Deskripsi Masalah</label>
|
||||||
|
<textarea class="form-control" id="issue-description" name="issue-description" rows="5" placeholder="Jelaskan masalah dengan detail. Berikan langkah-langkah untuk mereproduksi masalah (jika ada)..."></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="issue-contact" class="form-label">Kontak Anda (Opsional)</label>
|
||||||
|
<input type="text" class="form-control" id="issue-contact" name="issue-contact" placeholder="Nomor telepon atau email untuk follow-up" value="<?php echo htmlspecialchars($username); ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center mt-5">
|
||||||
|
<button type="button" id="send-wa-btn" class="btn wa-button btn-lg">
|
||||||
|
<i class="fab fa-whatsapp wa-icon"></i>
|
||||||
|
Kirim Laporan via WhatsApp
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="../../bootstrap/assets/js/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="../../bootstrap/assets/js/feather.min.js"></script>
|
||||||
|
<script src="../../bootstrap/assets/js/jquery.slimscroll.min.js"></script>
|
||||||
|
<script src="../../bootstrap/assets/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="../../bootstrap/assets/plugins/select2/js/select2.min.js"></script>
|
||||||
|
<script src="../../bootstrap/assets/js/script.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
// Handle issue type selection with animation
|
||||||
|
$('.issue-type-btn').on('click', function() {
|
||||||
|
// Remove active class from all buttons with animation
|
||||||
|
$('.issue-type-btn').removeClass('active').fadeOut(200).fadeIn(200);
|
||||||
|
$(this).addClass('active');
|
||||||
|
|
||||||
|
// Set the value in the hidden input
|
||||||
|
$('#issue-type').val($(this).data('type'));
|
||||||
|
});
|
||||||
|
|
||||||
|
// WhatsApp send button functionality with custom message
|
||||||
|
$('#send-wa-btn').on('click', function() {
|
||||||
|
var issueType = $('#issue-type').val() || 'Tidak ditentukan';
|
||||||
|
var issueLocation = $('#issue-location').val() || 'Tidak ditentukan';
|
||||||
|
var issuePriority = $('#issue-priority').val() || 'Sedang';
|
||||||
|
var issueDescription = $('#issue-description').val() || 'Tidak ada deskripsi';
|
||||||
|
var issueContact = $('#issue-contact').val() || '<?php echo htmlspecialchars($username); ?>';
|
||||||
|
|
||||||
|
// Validation check before sending
|
||||||
|
if (!issueType) {
|
||||||
|
alert('Mohon pilih jenis masalah terlebih dahulu.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!issueDescription) {
|
||||||
|
alert('Mohon isi deskripsi masalah terlebih dahulu.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = "🔴 *LAPORAN MASALAH AYULA STORE* 🔴\n\n" +
|
||||||
|
"*User:* <?php echo htmlspecialchars($username); ?> (<?php echo htmlspecialchars($userRole); ?>)\n" +
|
||||||
|
"*Jenis Masalah:* " + issueType + "\n" +
|
||||||
|
"*Lokasi:* " + issueLocation + "\n" +
|
||||||
|
"*Prioritas:* " + issuePriority + "\n\n" +
|
||||||
|
"*Deskripsi:*\n" + issueDescription + "\n\n" +
|
||||||
|
"*Kontak:* " + issueContact;
|
||||||
|
|
||||||
|
var encodedMessage = encodeURIComponent(message);
|
||||||
|
var waLink = "https://wa.me/<?php echo $waNumber; ?>?text=" + encodedMessage;
|
||||||
|
|
||||||
|
window.open(waLink, '_blank');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,949 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// session_start();
|
||||||
|
// Include the configuration file with database functions and user permissions
|
||||||
|
include('popular-products-config.php');
|
||||||
|
|
||||||
|
// Assuming the user role is stored in session after login
|
||||||
|
$userRole = getUserRole();
|
||||||
|
$username = getUsername();
|
||||||
|
|
||||||
|
// Get appropriate date presets based on user role
|
||||||
|
$isAdmin = isAdmin();
|
||||||
|
$datePresets = getDatePresets($isAdmin);
|
||||||
|
|
||||||
|
// Handle reset request
|
||||||
|
if (isset($_GET['reset']) && $_GET['reset'] == 1) {
|
||||||
|
// If reset is requested, redirect to same page with default dates
|
||||||
|
header("Location: index.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If user is not admin, limit historical data access
|
||||||
|
if (!$isAdmin && empty($_GET['preset']) && empty($_GET['start_date'])) {
|
||||||
|
// Default non-admin users to current month if no dates specified
|
||||||
|
$_GET['preset'] = 'this_month';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if preset is selected
|
||||||
|
$activePreset = isset($_GET['preset']) && !empty($_GET['preset']) ? $_GET['preset'] : '';
|
||||||
|
|
||||||
|
// Apply date ranges
|
||||||
|
$startDate = getStartDate($datePresets, $activePreset);
|
||||||
|
$endDate = getEndDate($datePresets, $activePreset);
|
||||||
|
|
||||||
|
// Ensure start date is not after end date
|
||||||
|
if (strtotime($startDate) > strtotime($endDate)) {
|
||||||
|
$temp = $startDate;
|
||||||
|
$startDate = $endDate;
|
||||||
|
$endDate = $temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default limit for top products
|
||||||
|
$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 20;
|
||||||
|
if ($limit < 5) $limit = 5;
|
||||||
|
if ($limit > 100) $limit = 100;
|
||||||
|
|
||||||
|
// Default sort type (quantity or revenue)
|
||||||
|
$sortBy = isset($_GET['sort_by']) ? $_GET['sort_by'] : 'quantity';
|
||||||
|
if (!in_array($sortBy, ['quantity', 'revenue'])) {
|
||||||
|
$sortBy = 'quantity';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the popular products based on user role
|
||||||
|
$queryData = getPopularProducts($startDate, $endDate, $limit, $sortBy, $isAdmin);
|
||||||
|
$result = $queryData['result'];
|
||||||
|
$summary = $queryData['summary'];
|
||||||
|
$categories = $queryData['categories'];
|
||||||
|
|
||||||
|
// Handle PDF export request if admin
|
||||||
|
if (isset($_POST['export_pdf']) || (isset($_GET['export']) && $_GET['export'] == 'pdf')) {
|
||||||
|
exportToPDF($startDate, $endDate, $limit, $sortBy, $isAdmin);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0">
|
||||||
|
<meta name="description" content="POS - Bootstrap Admin Template">
|
||||||
|
<meta name="keywords" content="admin, estimates, bootstrap, business, corporate, creative, invoice, html5, responsive, Projects">
|
||||||
|
<meta name="author" content="Dreamguys - Bootstrap Admin Template">
|
||||||
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
<title>Ayula Store POS - Produk Terlaris</title>
|
||||||
|
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../../src/img/smallest-ayula.png">
|
||||||
|
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="../../../bootstrap/assets/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="../../../bootstrap/assets/css/bootstrap-datetimepicker.min.css">
|
||||||
|
<link rel="stylesheet" href="../../../bootstrap/assets/css/animate.css">
|
||||||
|
<link rel="stylesheet" href="../../../bootstrap/assets/plugins/select2/css/select2.min.css">
|
||||||
|
<link rel="stylesheet" href="../../../bootstrap/assets/css/dataTables.bootstrap4.min.css">
|
||||||
|
<link rel="stylesheet" href="../../../bootstrap/assets/plugins/fontawesome/css/fontawesome.min.css">
|
||||||
|
<link rel="stylesheet" href="../../../bootstrap/assets/plugins/fontawesome/css/all.min.css">
|
||||||
|
<link rel="stylesheet" href="../../../bootstrap/assets/css/style.css">
|
||||||
|
|
||||||
|
<!-- Custom CSS -->
|
||||||
|
<link rel="stylesheet" href="../salesreport.css">
|
||||||
|
<link rel="stylesheet" href="popular-products.css">
|
||||||
|
<!-- Chart.js for visualizations -->
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="<?php echo $isAdmin ? 'admin' : 'employee'; ?>">
|
||||||
|
<!-- Loading Overlay -->
|
||||||
|
<div id="loading-overlay">
|
||||||
|
<div class="loading-spinner"></div>
|
||||||
|
<div class="loading-text">Memuat data produk terlaris...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="global-loader">
|
||||||
|
<div class="whirly-loader"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Header (similar to sales report) -->
|
||||||
|
<div class="main-wrapper">
|
||||||
|
<div class="header">
|
||||||
|
<div class="header-left active">
|
||||||
|
<a href="/ayula-store/views/dashboard/" class="logo">
|
||||||
|
<img src="../../../src/img/logoayula.png" alt="" />
|
||||||
|
</a>
|
||||||
|
<a href="/ayula-store/views/dashboard/" class="logo-small">
|
||||||
|
<img src="../../../src/img/smallest-ayula.png" alt="" />
|
||||||
|
</a>
|
||||||
|
<a id="toggle_btn" href="javascript:void(0);"> </a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a id="mobile_btn" class="mobile_btn" href="#sidebar">
|
||||||
|
<span class="bar-icon">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<ul class="nav user-menu">
|
||||||
|
<li class="nav-item dropdown has-arrow main-drop">
|
||||||
|
<a href="javascript:void(0);" class="dropdown-toggle nav-link userset" data-bs-toggle="dropdown">
|
||||||
|
<span class="user-img"><img src="../../../src/img/userprofile.png" alt="">
|
||||||
|
<span class="status online"></span></span>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu menu-drop-user">
|
||||||
|
<div class="profilename">
|
||||||
|
<div class="profileset">
|
||||||
|
<span class="user-img"><img src="../../../src/img/userprofile.png" alt="">
|
||||||
|
<span class="status online"></span></span>
|
||||||
|
<div class="profilesets">
|
||||||
|
<h6><?php echo $isAdmin ? 'Admin' : 'Karyawan'; ?></h6>
|
||||||
|
<h5><?php echo htmlspecialchars($username); ?></h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item" href="/ayula-store/views/report-issue/">
|
||||||
|
<img src="../../../src/img/warning.png" class="me-2" alt="img" /> Laporkan Masalah
|
||||||
|
</a>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item logout pb-0" href="../../../views/logout.php"><img
|
||||||
|
src="../../../bootstrap/assets/img/icons/log-out.svg"
|
||||||
|
class="me-2"
|
||||||
|
alt="img" />Keluar</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="dropdown mobile-user-menu">
|
||||||
|
<a href="javascript:void(0);" class="nav-link dropdown-toggle" data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"><i class="fa fa-ellipsis-v"></i></a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
|
<a class="dropdown-item" href="/ayula-store/views/report-issue/">
|
||||||
|
<i class="fa fa-cog me-2"></i> Laporkan Masalah
|
||||||
|
</a>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item logout pb-0" href="../../views/logout.php"><img
|
||||||
|
src="../../bootstrap/assets/img/icons/log-out.svg"
|
||||||
|
class="me-2"
|
||||||
|
alt="img" />Keluar</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Sidebar (similar to sales report) -->
|
||||||
|
<div class="sidebar" id="sidebar">
|
||||||
|
<div class="sidebar-inner slimscroll">
|
||||||
|
<div id="sidebar-menu" class="sidebar-menu">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="/ayula-store/views/dashboard/"><img src="../../../bootstrap/assets/img/icons/dashboard.svg" alt="img" /><span>
|
||||||
|
Dashboard</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/ayula-store/views/transaction/"><img src="../../../bootstrap/assets/img/icons/sales1.svg" alt="img" /><span>
|
||||||
|
POS</span></a>
|
||||||
|
</li>
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../../bootstrap/assets/img/icons/product.svg" alt="img" /><span>
|
||||||
|
Produk</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/ayula-store/views/barang/productlist.php">Daftar Produk</a></li>
|
||||||
|
<li><a href="/ayula-store/views/barang/addproduct.php">Tambah Produk</a></li>
|
||||||
|
<li><a href="categorylist.html">Daftar Kategori</a></li>
|
||||||
|
<li><a href="addcategory.html">Tambah Kategori</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../../bootstrap/assets/img/icons/purchase1.svg" alt="img" /><span>
|
||||||
|
Pembelian</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="purchaselist.html">Daftar Pembelian</a></li>
|
||||||
|
<li><a href="addpurchase.html">Tambah Pembelian</a></li>
|
||||||
|
<li><a href="importpurchase.html">Import Pembelian</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../../bootstrap/assets/img/icons/time.svg" alt="img" /><span>
|
||||||
|
Laporan</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="purchaseorderreport.html">Laporan Order Pembelian</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="inventoryreport.html">Laporan Inventaris</a></li>
|
||||||
|
<li><a href="/ayula-store/views/report/sales-report/">Laporan Penjualan</a></li>
|
||||||
|
<?php if ($userRole == 'admin') { ?>
|
||||||
|
<li><a href="/ayula-store/views/report/popular-products/">Produk Terlaris</a></li>
|
||||||
|
<?php } ?>
|
||||||
|
<li><a href="invoicereport.html">Laporan Faktur</a></li>
|
||||||
|
<li><a href="purchasereport.html">Laporan Pembelian</a></li>
|
||||||
|
<li><a href="supplierreport.html">Laporan Pemasok</a></li>
|
||||||
|
<li><a href="customerreport.html">Laporan Pelanggan</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../../bootstrap/assets/img/icons/users1.svg" alt="img" /><span>
|
||||||
|
Pengguna</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<?php if ($userRole == 'admin') { ?>
|
||||||
|
<li><a href="/ayula-store/views/users/add-user.php">Pengguna Baru</a></li>
|
||||||
|
<?php } ?>
|
||||||
|
<li><a href="/ayula-store/views/users/">Daftar Pengguna</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="content">
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="page-title">
|
||||||
|
<h4>Laporan Produk Terlaris</h4>
|
||||||
|
<h6>Lihat dan analisis produk paling populer</h6>
|
||||||
|
<?php if (!$isAdmin): ?>
|
||||||
|
<div class="alert alert-info mt-2 employee-warning">
|
||||||
|
<small><i class="fa fa-info-circle me-1"></i> Anda melihat laporan ini dengan akses karyawan. Beberapa data mungkin dibatasi.</small>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dashboard stat widgets -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-4 col-sm-6 col-12 d-flex">
|
||||||
|
<div class="dash-widget flex-fill">
|
||||||
|
<div class="dash-widgetimg">
|
||||||
|
<span class="dash-widget-icon"><i class="fa fa-box"></i></span>
|
||||||
|
</div>
|
||||||
|
<div class="dash-widgetcontent">
|
||||||
|
<h5>Total <span class="counters"><?php echo number_format($summary['total_products']); ?></span></h5>
|
||||||
|
<h6>Produk Terjual</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-4 col-sm-6 col-12 d-flex">
|
||||||
|
<div class="dash-widget flex-fill">
|
||||||
|
<div class="dash-widgetimg">
|
||||||
|
<span class="dash-widget-icon"><i class="fa fa-shopping-basket"></i></span>
|
||||||
|
</div>
|
||||||
|
<div class="dash-widgetcontent">
|
||||||
|
<h5>Total <span class="counters"><?php echo number_format($summary['total_items_sold']); ?></span></h5>
|
||||||
|
<h6>Item Terjual</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php if ($isAdmin || canAccessFeature('view_financial')): ?>
|
||||||
|
<div class="col-lg-4 col-sm-6 col-12 d-flex">
|
||||||
|
<div class="dash-widget flex-fill">
|
||||||
|
<div class="dash-widgetimg">
|
||||||
|
<span class="dash-widget-icon"><i class="fa fa-money-bill-alt"></i></span>
|
||||||
|
</div>
|
||||||
|
<div class="dash-widgetcontent">
|
||||||
|
<h5>Rp. <span class="counters"><?php echo number_format($summary['total_revenue']); ?></span></h5>
|
||||||
|
<h6>Total Pendapatan</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<!-- Employee placeholder for layout consistency -->
|
||||||
|
<div class="col-lg-4 col-sm-6 col-12 d-flex">
|
||||||
|
<div class="dash-widget flex-fill">
|
||||||
|
<div class="dash-widgetimg">
|
||||||
|
<span class="dash-widget-icon"><i class="fa fa-chart-line"></i></span>
|
||||||
|
</div>
|
||||||
|
<div class="dash-widgetcontent">
|
||||||
|
<h5>Laporan Aktivitas</h5>
|
||||||
|
<h6>Hubungi admin untuk detail keuangan</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Enhanced Date Filter with Presets -->
|
||||||
|
<!-- Enhanced Date Filter with Presets -->
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="card-title">Filter Tanggal</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form action="" method="GET" id="date-filter-form">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="preset">Rentang Waktu:</label>
|
||||||
|
<select name="preset" id="preset" class="form-control">
|
||||||
|
<?php if ($isAdmin): ?>
|
||||||
|
<option value="">Kustom</option>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php
|
||||||
|
// Buat array terjemahan untuk label preset yang lebih familiar
|
||||||
|
$presetLabels = [
|
||||||
|
'today' => 'Hari Ini',
|
||||||
|
'yesterday' => 'Kemarin',
|
||||||
|
'this_week' => 'Minggu Ini',
|
||||||
|
'last_week' => 'Minggu Lalu',
|
||||||
|
'this_month' => 'Bulan Ini',
|
||||||
|
'last_month' => 'Bulan Lalu',
|
||||||
|
// 'last_90_days' => '3 Bulan Terakhir',
|
||||||
|
'this_year' => 'Tahun Ini',
|
||||||
|
'all_time' => 'Seluruh Waktu'
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($datePresets as $key => $preset):
|
||||||
|
$label = isset($presetLabels[$key]) ? $presetLabels[$key] : $preset['label'];
|
||||||
|
?>
|
||||||
|
<option value="<?php echo $key; ?>" <?php echo ($activePreset === $key) ? 'selected' : ''; ?>>
|
||||||
|
<?php echo $label; ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($isAdmin): ?>
|
||||||
|
<!-- Custom Date Inputs - Admin Only -->
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="row custom-date-inputs" id="custom-date-inputs" <?php echo !empty($activePreset) ? 'style="display:none;"' : ''; ?>>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="start_date">Dari Tanggal:</label>
|
||||||
|
<input type="date" id="start_date" name="start_date" class="form-control"
|
||||||
|
value="<?php echo $startDate; ?>">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="end_date">Sampai Tanggal:</label>
|
||||||
|
<input type="date" id="end_date" name="end_date" class="form-control"
|
||||||
|
value="<?php echo $endDate; ?>">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<label> </label>
|
||||||
|
<button type="submit" class="btn btn-primary w-100" id="search-button">
|
||||||
|
<i class="fas fa-search"></i> Cari
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<!-- For employees - simpler view -->
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="limit">Jumlah Produk:</label>
|
||||||
|
<select name="limit" id="limit" class="form-control">
|
||||||
|
<option value="5" <?php echo ($limit == 5) ? 'selected' : ''; ?>>Top 5</option>
|
||||||
|
<option value="10" <?php echo ($limit == 10) ? 'selected' : ''; ?>>Top 10</option>
|
||||||
|
<option value="20" <?php echo ($limit == 20) ? 'selected' : ''; ?>>Top 20</option>
|
||||||
|
<option value="50" <?php echo ($limit == 50) ? 'selected' : ''; ?>>Top 50</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<label> </label>
|
||||||
|
<button type="submit" class="btn btn-primary" id="search-button">
|
||||||
|
<i class="fas fa-search"></i> Terapkan Filter
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<!-- Sorting options (for both admin and employee) -->
|
||||||
|
<div class="col-md-4 <?php echo (!$isAdmin) ? 'd-none d-md-block' : ''; ?>">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="sort_by">Urutkan Berdasarkan:</label>
|
||||||
|
<select name="sort_by" id="sort_by" class="form-control">
|
||||||
|
<option value="quantity" <?php echo ($sortBy == 'quantity') ? 'selected' : ''; ?>>Jumlah Terjual</option>
|
||||||
|
<option value="revenue" <?php echo ($sortBy == 'revenue') ? 'selected' : ''; ?>>Total Pendapatan</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($isAdmin): ?>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="limit">Jumlah Produk:</label>
|
||||||
|
<select name="limit" id="limit" class="form-control">
|
||||||
|
<option value="5" <?php echo ($limit == 5) ? 'selected' : ''; ?>>Top 5</option>
|
||||||
|
<option value="10" <?php echo ($limit == 10) ? 'selected' : ''; ?>>Top 10</option>
|
||||||
|
<option value="20" <?php echo ($limit == 20) ? 'selected' : ''; ?>>Top 20</option>
|
||||||
|
<option value="50" <?php echo ($limit == 50) ? 'selected' : ''; ?>>Top 50</option>
|
||||||
|
<option value="100" <?php echo ($limit == 100) ? 'selected' : ''; ?>>Top 100</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-12 d-flex justify-content-end">
|
||||||
|
<a href="?reset=1" class="btn btn-secondary me-2">
|
||||||
|
<i class="fas fa-redo"></i> Atur Ulang
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- <?php if ($isAdmin || canAccessFeature('print_report')): ?>
|
||||||
|
<a href="#" class="btn btn-info me-2 print-report">
|
||||||
|
<i class="fas fa-print"></i> Cetak
|
||||||
|
</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<a href="#" class="btn btn-info me-2 employee-print request-only"
|
||||||
|
data-action="print_attempt">
|
||||||
|
<i class="fas fa-print"></i> Cetak
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($isAdmin || canAccessFeature('export_excel') || canAccessFeature('export_pdf')): ?>
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn btn-success dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="fas fa-download"></i> Ekspor
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<?php if ($isAdmin || canAccessFeature('export_excel')): ?>
|
||||||
|
<li><a class="dropdown-item excel-export" href="#"><i class="fas fa-file-excel me-2"></i> Excel</a></li>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ($isAdmin || canAccessFeature('export_pdf')): ?>
|
||||||
|
<li><a class="dropdown-item pdf-export" href="#"><i class="fas fa-file-pdf me-2"></i> PDF</a></li>
|
||||||
|
<?php endif; ?>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<!-- Employee-limited export (with logging) -->
|
||||||
|
<a href="#" class="btn btn-success request-only employee-export"
|
||||||
|
data-action="export_attempt">
|
||||||
|
<i class="fas fa-download"></i> Export
|
||||||
|
</a>
|
||||||
|
<?php endif; ?> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Current Date Range Display -->
|
||||||
|
<div class="alert alert-info mb-4">
|
||||||
|
<strong>Rentang Tanggal:</strong> <?php echo date('d M Y', strtotime($startDate)); ?> sampai
|
||||||
|
<?php echo date('d M Y', strtotime($endDate)); ?>
|
||||||
|
<?php if (!empty($activePreset)): ?>
|
||||||
|
<span class="badge bg-primary ms-2"><?php echo $datePresets[$activePreset]['label']; ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
<div class="small mt-1">
|
||||||
|
<?php
|
||||||
|
$daysDiff = (strtotime($endDate) - strtotime($startDate)) / (60 * 60 * 24) + 1;
|
||||||
|
echo "Menampilkan data untuk " . number_format($daysDiff) . " hari";
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Category Distribution Charts (for both admin and employee) -->
|
||||||
|
<?php if ($queryData['has_data'] && $categories && mysqli_num_rows($categories) > 0): ?>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="card-title">Kategori Produk Terlaris</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="chart-container">
|
||||||
|
<canvas id="categoryPieChart" height="300"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="card-title">Distribusi Penjualan per Kategori</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="chart-container">
|
||||||
|
<canvas id="categoryBarChart" height="300"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- Top Products Section -->
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="card-title mb-0">
|
||||||
|
<?php
|
||||||
|
echo "Top " . $limit . " Produk";
|
||||||
|
echo ($sortBy == 'revenue') ? " (Berdasarkan Pendapatan)" : " (Berdasarkan Kuantitas)";
|
||||||
|
?>
|
||||||
|
</h5>
|
||||||
|
<div class="view-toggle btn-group">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary active" data-view="grid">
|
||||||
|
<i class="fas fa-th-large"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary" data-view="table">
|
||||||
|
<i class="fas fa-list"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (!$queryData['has_data']): ?>
|
||||||
|
<!-- Tampilan No Data Found (Tidak ada data) -->
|
||||||
|
<div class="no-data-container">
|
||||||
|
<div class="no-data-icon">
|
||||||
|
<i class="fas fa-box-open"></i>
|
||||||
|
</div>
|
||||||
|
<h4 class="no-data-message">Tidak ada data penjualan produk untuk periode yang dipilih</h4>
|
||||||
|
<p class="no-data-help">
|
||||||
|
<?php if ($activePreset == 'last_year'): ?>
|
||||||
|
Tidak ada transaksi penjualan yang tercatat untuk tahun lalu (<?php echo date('Y') - 1; ?>).
|
||||||
|
<?php else: ?>
|
||||||
|
Coba pilih rentang tanggal atau preset tanggal yang berbeda.
|
||||||
|
<?php endif; ?>
|
||||||
|
</p>
|
||||||
|
<a href="?reset=1" class="btn btn-primary">
|
||||||
|
<i class="fas fa-redo"></i> Reset Filter
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<!-- Grid View (Default) - FIXED VERSION -->
|
||||||
|
<div class="view-content" id="grid-view">
|
||||||
|
<div class="top-products-grid">
|
||||||
|
<?php
|
||||||
|
mysqli_data_seek($result, 0); // Reset pointer
|
||||||
|
$rank = 1;
|
||||||
|
while ($product = mysqli_fetch_assoc($result)):
|
||||||
|
?>
|
||||||
|
<div class="product-card">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="rank-badge <?php echo ($rank <= 3) ? 'top-3' : ''; ?>">
|
||||||
|
<?php echo $rank++; ?>
|
||||||
|
</div>
|
||||||
|
<div class="top-product position-relative ">
|
||||||
|
<!-- Move badge outside card-body but still inside top-product -->
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title"><?php echo $product['nama_barang']; ?></h5>
|
||||||
|
<p class="card-text text-muted">Kode: <?php echo $product['kode_barang']; ?></p>
|
||||||
|
<p class="card-text"><span class="badge bg-info"><?php echo $product['kategori']; ?></span></p>
|
||||||
|
<div class="product-stats">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<span>Terjual:</span>
|
||||||
|
<span class="fw-bold"><?php echo number_format($product['total_quantity']); ?> unit</span>
|
||||||
|
</div>
|
||||||
|
<?php if ($isAdmin || canAccessFeature('view_financial')): ?>
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<span>Harga:</span>
|
||||||
|
<span class="fw-bold">Rp. <?php echo number_format($product['harga']); ?></span>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<span>Total:</span>
|
||||||
|
<span class="fw-bold">Rp. <?php echo number_format($product['total_revenue']); ?></span>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- Progress bar showing percentage of total sales -->
|
||||||
|
<div class="progress product-progress mt-2">
|
||||||
|
<div class="progress-bar bg-success" role="progressbar"
|
||||||
|
style="width: <?php echo min(100, ($product['total_quantity'] / $summary['total_items_sold']) * 100); ?>%"
|
||||||
|
aria-valuenow="<?php echo ($product['total_quantity'] / $summary['total_items_sold']) * 100; ?>"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<small class="text-muted mt-1 d-block">
|
||||||
|
<?php echo number_format(($product['total_quantity'] / $summary['total_items_sold']) * 100, 1); ?>% dari total penjualan
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endwhile; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Table View (Hidden by default) - FIXED VERSION -->
|
||||||
|
<div class="view-content" id="table-view" style="display: none;">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered table-striped datanew">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="70">Rank</th>
|
||||||
|
<th>Kode</th>
|
||||||
|
<th>Nama Produk</th>
|
||||||
|
<th>Kategori</th>
|
||||||
|
<th>Jumlah Terjual</th>
|
||||||
|
<?php if ($isAdmin || canAccessFeature('view_financial')): ?>
|
||||||
|
<th>Harga Satuan</th>
|
||||||
|
<th>Total Pendapatan</th>
|
||||||
|
<?php endif; ?>
|
||||||
|
<th>% dari Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php
|
||||||
|
mysqli_data_seek($result, 0); // Reset pointer
|
||||||
|
$rank = 1;
|
||||||
|
while ($product = mysqli_fetch_assoc($result)):
|
||||||
|
$percentOfTotal = ($product['total_quantity'] / $summary['total_items_sold']) * 100;
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center">
|
||||||
|
<?php if ($rank <= 3): ?>
|
||||||
|
<span class="badge bg-warning">#<?php echo $rank++; ?></span>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php echo $rank++; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td><?php echo $product['kode_barang']; ?></td>
|
||||||
|
<td><?php echo $product['nama_barang']; ?></td>
|
||||||
|
<td><span class="badge bg-info"><?php echo $product['kategori']; ?></span></td>
|
||||||
|
<td><?php echo number_format($product['total_quantity']); ?></td>
|
||||||
|
<?php if ($isAdmin || canAccessFeature('view_financial')): ?>
|
||||||
|
<td>Rp. <?php echo number_format($product['harga']); ?></td>
|
||||||
|
<td>Rp. <?php echo number_format($product['total_revenue']); ?></td>
|
||||||
|
<?php endif; ?>
|
||||||
|
<td>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="progress flex-grow-1 me-2" style="height: 6px;">
|
||||||
|
<div class="progress-bar bg-success" role="progressbar"
|
||||||
|
style="width: <?php echo min(100, $percentOfTotal); ?>%"
|
||||||
|
aria-valuenow="<?php echo $percentOfTotal; ?>"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span><?php echo number_format($percentOfTotal, 1); ?>%</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endwhile; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Summary Section - Admin Only -->
|
||||||
|
<?php if ($isAdmin || canAccessFeature('view_summary')): ?>
|
||||||
|
<div class="row mt-4 no-print">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="card-title">Rangkuman untuk Periode Terpilih</h5>
|
||||||
|
<h6><?php echo date('d M Y', strtotime($startDate)); ?> - <?php echo date('d M Y', strtotime($endDate)); ?></h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="report-summary">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="report-summary-item">
|
||||||
|
<span>Total Produk Terjual:</span>
|
||||||
|
<strong><?php echo number_format($summary['total_products']); ?> produk</strong>
|
||||||
|
</div>
|
||||||
|
<div class="report-summary-item">
|
||||||
|
<span>Total Unit Terjual:</span>
|
||||||
|
<strong><?php echo number_format($summary['total_items_sold']); ?> unit</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="report-summary-item">
|
||||||
|
<span>Rata-rata Per Hari:</span>
|
||||||
|
<strong><?php
|
||||||
|
$daysDiff = (strtotime($endDate) - strtotime($startDate)) / (60 * 60 * 24) + 1;
|
||||||
|
$dailyAvgUnits = $daysDiff > 0 ? $summary['total_items_sold'] / $daysDiff : 0;
|
||||||
|
echo number_format($dailyAvgUnits, 1);
|
||||||
|
?> unit/hari</strong>
|
||||||
|
</div>
|
||||||
|
<div class="report-summary-item">
|
||||||
|
<span>Pendapatan Rata-rata Per Hari:</span>
|
||||||
|
<strong>Rp. <?php
|
||||||
|
$dailyAvgRevenue = $daysDiff > 0 ? $summary['total_revenue'] / $daysDiff : 0;
|
||||||
|
echo number_format($dailyAvgRevenue);
|
||||||
|
?></strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="report-summary-item">
|
||||||
|
<span>Total Pendapatan:</span>
|
||||||
|
<strong>Rp. <?php echo number_format($summary['total_revenue']); ?></strong>
|
||||||
|
</div>
|
||||||
|
<div class="report-summary-item">
|
||||||
|
<span>Rata-rata Pendapatan Per Produk:</span>
|
||||||
|
<strong>Rp. <?php
|
||||||
|
$avgRevenuePerProduct = $summary['total_products'] > 0 ? $summary['total_revenue'] / $summary['total_products'] : 0;
|
||||||
|
echo number_format($avgRevenuePerProduct);
|
||||||
|
?></strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<!-- Employee-only section: Activity log -->
|
||||||
|
<?php if (!$isAdmin): ?>
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="card-title">Ringkasan Aktivitas Anda</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="fa fa-info-circle me-2"></i>
|
||||||
|
Untuk laporan keuangan terperinci atau untuk mengekspor data, silakan hubungi administrator Anda.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="report-summary">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="report-summary-item">
|
||||||
|
<span>Produk Dilihat:</span>
|
||||||
|
<strong><?php echo $limit; ?> produk teratas</strong>
|
||||||
|
</div>
|
||||||
|
<div class="report-summary-item">
|
||||||
|
<span>Total Unit Terjual (Diproses):</span>
|
||||||
|
<strong><?php echo number_format($summary['total_items_sold']); ?> unit</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="report-summary-item">
|
||||||
|
<span>Laporan Dibuat:</span>
|
||||||
|
<strong><?php echo date('d M Y H:i'); ?></strong>
|
||||||
|
</div>
|
||||||
|
<div class="report-summary-item">
|
||||||
|
<span>Level Akses:</span>
|
||||||
|
<strong>Karyawan</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Employee Permission Modal -->
|
||||||
|
<div class="modal fade" id="permissionModal" tabindex="-1" aria-labelledby="permissionModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-warning">
|
||||||
|
<h5 class="modal-title" id="permissionModalLabel">
|
||||||
|
<i class="fas fa-exclamation-triangle me-2"></i> Akses Dibatasi
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<i class="fas fa-lock" style="font-size: 3rem; color: #ff9f43; margin-bottom: 15px;"></i>
|
||||||
|
<h4>Fitur Dibatasi</h4>
|
||||||
|
</div>
|
||||||
|
<p>Fitur ini hanya tersedia untuk administrator dan personel yang berwenang.</p>
|
||||||
|
<p>Akun karyawan tidak memiliki akses ke fungsi cetak atau ekspor.</p>
|
||||||
|
<div class="alert alert-info mt-3" id="actionDetails">
|
||||||
|
<!-- Will be filled dynamically -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Tutup</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- JavaScript libraries -->
|
||||||
|
<script src="../../../bootstrap/assets/js/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="../../../bootstrap/assets/js/feather.min.js"></script>
|
||||||
|
<script src="../../../bootstrap/assets/js/jquery.slimscroll.min.js"></script>
|
||||||
|
<script src="../../../bootstrap/assets/js/jquery.dataTables.min.js"></script>
|
||||||
|
<script src="../../../bootstrap/assets/js/dataTables.bootstrap4.min.js"></script>
|
||||||
|
<script src="../../../bootstrap/assets/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="../../../bootstrap/assets/js/moment.min.js"></script>
|
||||||
|
<script src="../../../bootstrap/assets/js/bootstrap-datetimepicker.min.js"></script>
|
||||||
|
<script src="../../../bootstrap/assets/plugins/select2/js/select2.min.js"></script>
|
||||||
|
<script src="../../../bootstrap/assets/plugins/sweetalert/sweetalert2.all.min.js"></script>
|
||||||
|
<script src="../../../bootstrap/assets/plugins/sweetalert/sweetalerts.min.js"></script>
|
||||||
|
<script src="../../../bootstrap/assets/js/script.js"></script>
|
||||||
|
|
||||||
|
<!-- Chart.js for visualizations -->
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js"></script>
|
||||||
|
|
||||||
|
<!-- Initialize Charts for Category Data -->
|
||||||
|
<script>
|
||||||
|
// Function to initialize Chart.js charts
|
||||||
|
function initializeCharts() {
|
||||||
|
<?php if ($queryData['has_data'] && $categories && mysqli_num_rows($categories) > 0): ?>
|
||||||
|
// Prepare category data for charts
|
||||||
|
var categoryData = {
|
||||||
|
labels: [],
|
||||||
|
quantities: [],
|
||||||
|
revenues: [],
|
||||||
|
products: [],
|
||||||
|
colors: [
|
||||||
|
'#FF9F43', '#7367F0', '#00CFE8', '#28C76F', '#EA5455',
|
||||||
|
'#9F44D3', '#1E9FF2', '#F6416C', '#28a745', '#17a2b8',
|
||||||
|
'#fd7e14', '#6c757d', '#343a40', '#20c997', '#6610f2'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
<?php
|
||||||
|
mysqli_data_seek($categories, 0); // Reset pointer
|
||||||
|
$categoryCount = 0;
|
||||||
|
while ($category = mysqli_fetch_assoc($categories)):
|
||||||
|
$categoryCount++;
|
||||||
|
?>
|
||||||
|
categoryData.labels.push('<?php echo $category['kategori']; ?>');
|
||||||
|
categoryData.quantities.push(<?php echo $category['total_quantity']; ?>);
|
||||||
|
categoryData.revenues.push(<?php echo $category['total_revenue']; ?>);
|
||||||
|
categoryData.products.push(<?php echo $category['unique_products']; ?>);
|
||||||
|
<?php endwhile; ?>
|
||||||
|
|
||||||
|
// Fill in any missing colors needed
|
||||||
|
while (categoryData.colors.length < categoryData.labels.length) {
|
||||||
|
const randomColor = '#' + Math.floor(Math.random() * 16777215).toString(16);
|
||||||
|
categoryData.colors.push(randomColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pie Chart for category distribution
|
||||||
|
var pieCtx = document.getElementById('categoryPieChart').getContext('2d');
|
||||||
|
var categoryPieChart = new Chart(pieCtx, {
|
||||||
|
type: 'pie',
|
||||||
|
data: {
|
||||||
|
labels: categoryData.labels,
|
||||||
|
datasets: [{
|
||||||
|
data: categoryData.quantities,
|
||||||
|
backgroundColor: categoryData.colors,
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
legend: {
|
||||||
|
position: 'right',
|
||||||
|
labels: {
|
||||||
|
boxWidth: 15,
|
||||||
|
padding: 15
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltips: {
|
||||||
|
callbacks: {
|
||||||
|
label: function(tooltipItem, data) {
|
||||||
|
const category = data.labels[tooltipItem.index];
|
||||||
|
const quantity = data.datasets[0].data[tooltipItem.index];
|
||||||
|
const totalQuantity = data.datasets[0].data.reduce((a, b) => a + b, 0);
|
||||||
|
const percentage = ((quantity / totalQuantity) * 100).toFixed(1);
|
||||||
|
|
||||||
|
return `${category}: ${quantity} unit (${percentage}%)`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Distribusi Unit Terjual per Kategori'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Bar Chart for quantity comparison
|
||||||
|
var barCtx = document.getElementById('categoryBarChart').getContext('2d');
|
||||||
|
var categoryBarChart = new Chart(barCtx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: categoryData.labels,
|
||||||
|
datasets: [{
|
||||||
|
label: 'Unit Terjual',
|
||||||
|
data: categoryData.quantities,
|
||||||
|
backgroundColor: categoryData.colors,
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
yAxes: [{
|
||||||
|
ticks: {
|
||||||
|
beginAtZero: true
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
display: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
<?php endif; ?>
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Custom JavaScript for popular products report -->
|
||||||
|
<script src="popular-products.js"></script>
|
||||||
|
<script>
|
||||||
|
// Disable console logs and warnings
|
||||||
|
if (window.location.hostname === 'localhost') {
|
||||||
|
console.log = function() {}; // Disable console logs
|
||||||
|
console.warn = function() {}; // Disable console warnings
|
||||||
|
console.error = function() {}; // Disable console errors
|
||||||
|
window.alert = function() {}; // Disable alert popups
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,421 @@
|
||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
// Include database connection
|
||||||
|
include('../../../routes/db_conn.php');
|
||||||
|
|
||||||
|
// Check if user is logged in, redirect to login page if not
|
||||||
|
if (!isset($_SESSION['user_id']) || !isset($_SESSION['role'])) {
|
||||||
|
header('Location: /ayula-store/views/login/');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current logged-in user's information
|
||||||
|
function getCurrentUser() {
|
||||||
|
global $conn;
|
||||||
|
if (isset($_SESSION['user_id'])) {
|
||||||
|
$stmt = mysqli_prepare($conn, "SELECT id_kasir, username, role, phone FROM kasir WHERE id_kasir = ?");
|
||||||
|
mysqli_stmt_bind_param($stmt, "i", $_SESSION['user_id']);
|
||||||
|
mysqli_stmt_execute($stmt);
|
||||||
|
$result = mysqli_stmt_get_result($stmt);
|
||||||
|
$user = mysqli_fetch_assoc($result);
|
||||||
|
mysqli_stmt_close($stmt);
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure username is available in session
|
||||||
|
if (isset($_SESSION['user_id']) && !isset($_SESSION['username'])) {
|
||||||
|
$currentUser = getCurrentUser();
|
||||||
|
if ($currentUser) {
|
||||||
|
$_SESSION['username'] = $currentUser['username'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user role from session
|
||||||
|
function getUserRole() {
|
||||||
|
return isset($_SESSION['role']) ? $_SESSION['role'] : 'employee';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get username from session
|
||||||
|
function getUsername() {
|
||||||
|
return isset($_SESSION['username']) ? $_SESSION['username'] : 'Unknown User';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if current user is admin
|
||||||
|
function isAdmin() {
|
||||||
|
return getUserRole() === 'admin';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define date presets with role-based options
|
||||||
|
function getDatePresets($isAdmin = false) {
|
||||||
|
$presets = [
|
||||||
|
'today' => [
|
||||||
|
'label' => 'Hari Ini',
|
||||||
|
'start' => date('Y-m-d'),
|
||||||
|
'end' => date('Y-m-d')
|
||||||
|
],
|
||||||
|
'yesterday' => [
|
||||||
|
'label' => 'Kemarin',
|
||||||
|
'start' => date('Y-m-d', strtotime('-1 day')),
|
||||||
|
'end' => date('Y-m-d', strtotime('-1 day'))
|
||||||
|
],
|
||||||
|
'this_week' => [
|
||||||
|
'label' => 'Minggu Ini',
|
||||||
|
'start' => date('Y-m-d', strtotime('monday this week')),
|
||||||
|
'end' => date('Y-m-d')
|
||||||
|
],
|
||||||
|
'last_week' => [
|
||||||
|
'label' => 'Minggu Lalu',
|
||||||
|
'start' => date('Y-m-d', strtotime('monday last week')),
|
||||||
|
'end' => date('Y-m-d', strtotime('sunday last week'))
|
||||||
|
],
|
||||||
|
'this_month' => [
|
||||||
|
'label' => 'Bulan Ini',
|
||||||
|
'start' => date('Y-m-01'),
|
||||||
|
'end' => date('Y-m-d')
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
// Admin-only presets
|
||||||
|
if ($isAdmin) {
|
||||||
|
$presets['last_month'] = [
|
||||||
|
'label' => 'Bulan Lalu',
|
||||||
|
'start' => date('Y-m-d', strtotime('first day of last month')),
|
||||||
|
'end' => date('Y-m-d', strtotime('last day of last month'))
|
||||||
|
];
|
||||||
|
|
||||||
|
$presets['last_90_days'] = [
|
||||||
|
'label' => '90 Hari Terakhir',
|
||||||
|
'start' => date('Y-m-d', strtotime('-90 days')),
|
||||||
|
'end' => date('Y-m-d')
|
||||||
|
];
|
||||||
|
|
||||||
|
$presets['this_year'] = [
|
||||||
|
'label' => 'Tahun Ini',
|
||||||
|
'start' => date('Y') . '-01-01',
|
||||||
|
'end' => date('Y-m-d')
|
||||||
|
];
|
||||||
|
|
||||||
|
$presets['all_time'] = [
|
||||||
|
'label' => 'Sepanjang Waktu',
|
||||||
|
'start' => '2000-01-01', // Set a reasonable start date
|
||||||
|
'end' => date('Y-m-d')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $presets;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to check if an employee has permission for a specific report feature
|
||||||
|
function canAccessFeature($feature) {
|
||||||
|
// Default permissions based on role
|
||||||
|
$permissions = [
|
||||||
|
'admin' => [
|
||||||
|
'export_excel' => true,
|
||||||
|
'export_pdf' => true,
|
||||||
|
'print_report' => true,
|
||||||
|
'view_financial' => true,
|
||||||
|
'view_payment_methods' => true,
|
||||||
|
'view_all_time' => true,
|
||||||
|
'view_summary' => true,
|
||||||
|
'view_popular_products' => true
|
||||||
|
],
|
||||||
|
'manager' => [
|
||||||
|
'export_excel' => true,
|
||||||
|
'export_pdf' => false,
|
||||||
|
'print_report' => true,
|
||||||
|
'view_financial' => true,
|
||||||
|
'view_payment_methods' => true,
|
||||||
|
'view_all_time' => false,
|
||||||
|
'view_summary' => true,
|
||||||
|
'view_popular_products' => true
|
||||||
|
],
|
||||||
|
'employee' => [
|
||||||
|
'export_excel' => false,
|
||||||
|
'export_pdf' => false,
|
||||||
|
'print_report' => false,
|
||||||
|
'view_financial' => false,
|
||||||
|
'view_payment_methods' => false,
|
||||||
|
'view_all_time' => false,
|
||||||
|
'view_summary' => false,
|
||||||
|
'view_popular_products' => false
|
||||||
|
],
|
||||||
|
'user' => [
|
||||||
|
'export_excel' => false,
|
||||||
|
'export_pdf' => false,
|
||||||
|
'print_report' => false,
|
||||||
|
'view_financial' => false,
|
||||||
|
'view_payment_methods' => false,
|
||||||
|
'view_all_time' => false,
|
||||||
|
'view_summary' => false,
|
||||||
|
'view_popular_products' => false
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$role = getUserRole();
|
||||||
|
|
||||||
|
// If role doesn't exist in our permissions, default to employee
|
||||||
|
if (!isset($permissions[$role])) {
|
||||||
|
$role = 'employee';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return permission status
|
||||||
|
return isset($permissions[$role][$feature]) ? $permissions[$role][$feature] : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to get popular products
|
||||||
|
function getPopularProducts($startDate, $endDate, $limit = 20, $sortBy = 'quantity', $isAdmin = false) {
|
||||||
|
global $conn;
|
||||||
|
|
||||||
|
// Add time components to make the date range inclusive
|
||||||
|
$startDateTime = $startDate . ' 00:00:00';
|
||||||
|
$endDateTime = $endDate . ' 23:59:59';
|
||||||
|
|
||||||
|
// Base query for popular products - modified to join jenis_barang for category
|
||||||
|
$baseQuery = "
|
||||||
|
SELECT
|
||||||
|
b.id_barang,
|
||||||
|
b.kode_barang,
|
||||||
|
b.nama_barang,
|
||||||
|
b.harga,
|
||||||
|
j.nama_jenis as kategori,
|
||||||
|
SUM(dt.jumlah) as total_quantity,
|
||||||
|
SUM(dt.total_harga) as total_revenue
|
||||||
|
FROM
|
||||||
|
barang b
|
||||||
|
JOIN
|
||||||
|
jenis_barang j ON b.id_jenis = j.id_jenis
|
||||||
|
JOIN
|
||||||
|
detail_transaksi dt ON b.id_barang = dt.id_barang
|
||||||
|
JOIN
|
||||||
|
transaksi t ON dt.id_transaksi = t.id_transaksi
|
||||||
|
WHERE
|
||||||
|
t.tanggal BETWEEN ? AND ?
|
||||||
|
GROUP BY
|
||||||
|
b.id_barang
|
||||||
|
ORDER BY
|
||||||
|
";
|
||||||
|
|
||||||
|
// Add the appropriate ORDER BY clause
|
||||||
|
if ($sortBy == 'revenue') {
|
||||||
|
$baseQuery .= "total_revenue DESC";
|
||||||
|
} else {
|
||||||
|
$baseQuery .= "total_quantity DESC";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add limit
|
||||||
|
$baseQuery .= " LIMIT ?";
|
||||||
|
|
||||||
|
// Prepare and execute query
|
||||||
|
$stmt = mysqli_prepare($conn, $baseQuery);
|
||||||
|
|
||||||
|
if (!$stmt) {
|
||||||
|
return [
|
||||||
|
'result' => false,
|
||||||
|
'error' => 'Failed to prepare query: ' . mysqli_error($conn),
|
||||||
|
'has_data' => false
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_stmt_bind_param($stmt, "ssi", $startDateTime, $endDateTime, $limit);
|
||||||
|
$execResult = mysqli_stmt_execute($stmt);
|
||||||
|
|
||||||
|
if (!$execResult) {
|
||||||
|
return [
|
||||||
|
'result' => false,
|
||||||
|
'error' => 'Failed to execute query: ' . mysqli_stmt_error($stmt),
|
||||||
|
'has_data' => false
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = mysqli_stmt_get_result($stmt);
|
||||||
|
$rowCount = mysqli_num_rows($result);
|
||||||
|
|
||||||
|
// Get summary totals
|
||||||
|
$summaryQuery = "
|
||||||
|
SELECT
|
||||||
|
COUNT(DISTINCT dt.id_barang) as total_products,
|
||||||
|
SUM(dt.jumlah) as total_items_sold,
|
||||||
|
SUM(dt.total_harga) as total_revenue
|
||||||
|
FROM
|
||||||
|
detail_transaksi dt
|
||||||
|
JOIN
|
||||||
|
transaksi t ON dt.id_transaksi = t.id_transaksi
|
||||||
|
WHERE
|
||||||
|
t.tanggal BETWEEN ? AND ?
|
||||||
|
";
|
||||||
|
|
||||||
|
$summaryStmt = mysqli_prepare($conn, $summaryQuery);
|
||||||
|
|
||||||
|
if (!$summaryStmt) {
|
||||||
|
return [
|
||||||
|
'result' => $result,
|
||||||
|
'summary' => null,
|
||||||
|
'error' => 'Failed to prepare summary query: ' . mysqli_error($conn),
|
||||||
|
'has_data' => ($rowCount > 0)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_stmt_bind_param($summaryStmt, "ss", $startDateTime, $endDateTime);
|
||||||
|
$summaryExecResult = mysqli_stmt_execute($summaryStmt);
|
||||||
|
|
||||||
|
if (!$summaryExecResult) {
|
||||||
|
return [
|
||||||
|
'result' => $result,
|
||||||
|
'summary' => null,
|
||||||
|
'error' => 'Failed to execute summary query: ' . mysqli_stmt_error($summaryStmt),
|
||||||
|
'has_data' => ($rowCount > 0)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$summaryResult = mysqli_stmt_get_result($summaryStmt);
|
||||||
|
$summary = mysqli_fetch_assoc($summaryResult);
|
||||||
|
|
||||||
|
// Handle case where no data is found
|
||||||
|
if (!$summary) {
|
||||||
|
$summary = [
|
||||||
|
'total_products' => 0,
|
||||||
|
'total_items_sold' => 0,
|
||||||
|
'total_revenue' => 0
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// For non-admin users, potentially mask certain financial data
|
||||||
|
if (!$isAdmin) {
|
||||||
|
// Keep the summary accessible but hide financial details if needed
|
||||||
|
if (!canAccessFeature('view_financial')) {
|
||||||
|
$summary['total_revenue'] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get category breakdown for pie chart - using jenis_barang table
|
||||||
|
$categoryQuery = "
|
||||||
|
SELECT
|
||||||
|
j.nama_jenis as kategori,
|
||||||
|
SUM(dt.jumlah) as total_quantity,
|
||||||
|
SUM(dt.total_harga) as total_revenue,
|
||||||
|
COUNT(DISTINCT b.id_barang) as unique_products
|
||||||
|
FROM
|
||||||
|
barang b
|
||||||
|
JOIN
|
||||||
|
jenis_barang j ON b.id_jenis = j.id_jenis
|
||||||
|
JOIN
|
||||||
|
detail_transaksi dt ON b.id_barang = dt.id_barang
|
||||||
|
JOIN
|
||||||
|
transaksi t ON dt.id_transaksi = t.id_transaksi
|
||||||
|
WHERE
|
||||||
|
t.tanggal BETWEEN ? AND ?
|
||||||
|
GROUP BY
|
||||||
|
j.nama_jenis
|
||||||
|
ORDER BY
|
||||||
|
total_quantity DESC
|
||||||
|
";
|
||||||
|
|
||||||
|
$categoryStmt = mysqli_prepare($conn, $categoryQuery);
|
||||||
|
|
||||||
|
if (!$categoryStmt) {
|
||||||
|
return [
|
||||||
|
'result' => $result,
|
||||||
|
'summary' => $summary,
|
||||||
|
'categories' => null,
|
||||||
|
'error' => 'Failed to prepare category query: ' . mysqli_error($conn),
|
||||||
|
'has_data' => ($rowCount > 0)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_stmt_bind_param($categoryStmt, "ss", $startDateTime, $endDateTime);
|
||||||
|
$categoryExecResult = mysqli_stmt_execute($categoryStmt);
|
||||||
|
|
||||||
|
if (!$categoryExecResult) {
|
||||||
|
return [
|
||||||
|
'result' => $result,
|
||||||
|
'summary' => $summary,
|
||||||
|
'categories' => null,
|
||||||
|
'error' => 'Failed to execute category query: ' . mysqli_stmt_error($categoryStmt),
|
||||||
|
'has_data' => ($rowCount > 0)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$categoryResult = mysqli_stmt_get_result($categoryStmt);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'result' => $result,
|
||||||
|
'summary' => $summary,
|
||||||
|
'categories' => $categoryResult,
|
||||||
|
'has_data' => ($rowCount > 0)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to apply date ranges based on preset or manual selection
|
||||||
|
function getStartDate($datePresets, $activePreset) {
|
||||||
|
// If preset is selected, use preset dates
|
||||||
|
if (!empty($activePreset) && isset($datePresets[$activePreset])) {
|
||||||
|
return $datePresets[$activePreset]['start'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise use the date parameters if provided
|
||||||
|
$startDate = isset($_GET['start_date']) && !empty($_GET['start_date'])
|
||||||
|
? $_GET['start_date']
|
||||||
|
: date('Y-m-01'); // Default to first day of current month
|
||||||
|
|
||||||
|
// Make sure date is in YYYY-MM-DD format
|
||||||
|
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $startDate)) {
|
||||||
|
$startDate = date('Y-m-01');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $startDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEndDate($datePresets, $activePreset) {
|
||||||
|
// If preset is selected, use preset dates
|
||||||
|
if (!empty($activePreset) && isset($datePresets[$activePreset])) {
|
||||||
|
return $datePresets[$activePreset]['end'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise use the date parameters if provided
|
||||||
|
$endDate = isset($_GET['end_date']) && !empty($_GET['end_date'])
|
||||||
|
? $_GET['end_date']
|
||||||
|
: date('Y-m-d'); // Default to today
|
||||||
|
|
||||||
|
// Make sure date is in YYYY-MM-DD format
|
||||||
|
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $endDate)) {
|
||||||
|
$endDate = date('Y-m-d');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $endDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle PDF export functionality
|
||||||
|
function exportToPDF($startDate, $endDate, $limit, $sortBy, $isAdmin) {
|
||||||
|
// Check permissions
|
||||||
|
if (!($isAdmin || canAccessFeature('export_pdf'))) {
|
||||||
|
die("Access denied. You don't have permission to export to PDF.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the data
|
||||||
|
$queryData = getPopularProducts($startDate, $endDate, $limit, $sortBy, $isAdmin);
|
||||||
|
|
||||||
|
// Here you would implement PDF generation with a library like FPDF or TCPDF
|
||||||
|
// For example:
|
||||||
|
/*
|
||||||
|
require('fpdf/fpdf.php');
|
||||||
|
|
||||||
|
$pdf = new FPDF();
|
||||||
|
$pdf->AddPage();
|
||||||
|
$pdf->SetFont('Arial', 'B', 16);
|
||||||
|
$pdf->Cell(40, 10, 'Ayula Store - Laporan Produk Terlaris');
|
||||||
|
|
||||||
|
// Add more PDF generation code here
|
||||||
|
|
||||||
|
$pdf->Output('D', 'produk_terlaris.pdf');
|
||||||
|
exit;
|
||||||
|
*/
|
||||||
|
|
||||||
|
// For now, just return a message
|
||||||
|
echo "<h1>PDF Export</h1>";
|
||||||
|
echo "<p>This functionality would generate a PDF for the selected date range: $startDate to $endDate</p>";
|
||||||
|
echo "<p>Implementation with a PDF library is required to complete this feature.</p>";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,305 @@
|
||||||
|
/* Custom styles for popular products report */
|
||||||
|
.product-card {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.05);
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Move rank badge outside card body */
|
||||||
|
.rank-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px; /* Slightly above card */
|
||||||
|
left: 15px;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background-color: #7367f0;
|
||||||
|
color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 18px;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Special style for top 3 rankings */
|
||||||
|
.top-3 {
|
||||||
|
background-color: #ff9f43;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure card title and content don't overlap */
|
||||||
|
.top-product .card-body {
|
||||||
|
padding-top: 60px; /* Push content below rank badge */
|
||||||
|
padding-left: 15px;
|
||||||
|
padding-right: 15px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-product .card-title {
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 10px 0;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-bottom: 1px solid #e9ecef;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add spacing between card content */
|
||||||
|
.product-stats {
|
||||||
|
margin-top: 20px; /* Ensures enough space between content and stats */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure the progress bar has some margin */
|
||||||
|
.product-progress {
|
||||||
|
height: 8px;
|
||||||
|
margin-top: 10px; /* Space out progress bar from content */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Top products grid styles */
|
||||||
|
.top-products-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustment */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.top-products-grid {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.rank-badge {
|
||||||
|
width: 35px;
|
||||||
|
height: 35px;
|
||||||
|
font-size: 16px;
|
||||||
|
top: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card .card-body {
|
||||||
|
padding-top: 70px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure cards are aligned properly on small screens */
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.product-card .card {
|
||||||
|
min-height: 250px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Progress bar styles */
|
||||||
|
.product-progress {
|
||||||
|
height: 8px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Top products grid styles */
|
||||||
|
.top-products-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Memastikan card memiliki tinggi minimum */
|
||||||
|
.product-card .card {
|
||||||
|
min-height: 200px;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%; /* Pastikan lebar penuh */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-left: auto; /* Pindahkan kartu ke kanan dengan margin kiri otomatis */
|
||||||
|
margin-right: 0; /* Menjaga margin kanan tidak tumpang tindih */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom styles for view toggle buttons */
|
||||||
|
.view-toggle .btn {
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-toggle .btn i {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Chart container styles */
|
||||||
|
.chart-container {
|
||||||
|
position: relative;
|
||||||
|
height: 300px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Employee-specific warning styles */
|
||||||
|
.employee-warning {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.employee .employee-warning {
|
||||||
|
display: block;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #fff8e1;
|
||||||
|
border-left: 4px solid #ffc107;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Role-based styling */
|
||||||
|
.admin-only {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin .admin-only {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.employee .sensitive-financial {
|
||||||
|
filter: blur(4px);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.employee .sensitive-financial:hover::after {
|
||||||
|
content: "Data terbatas";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
background: rgba(255,0,0,0.2);
|
||||||
|
padding: 2px 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disabled buttons for employees */
|
||||||
|
.employee .disabled-for-employee {
|
||||||
|
opacity: 0.6;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.employee .request-only {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin .request-only {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styling untuk tabel view */
|
||||||
|
#table-view th:first-child {
|
||||||
|
width: 70px; /* Lebar tetap untuk kolom ranking */
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#table-view .badge {
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 5px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Print specific styles */
|
||||||
|
@media print {
|
||||||
|
.sidebar, .header, .no-print, #filter_inputs, .wordset, .search-set {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-wrapper {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
box-shadow: none !important;
|
||||||
|
margin-bottom: 1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table th, .table td {
|
||||||
|
padding: 0.25rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-header {
|
||||||
|
display: block !important;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-header h2 {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Force table view in print mode */
|
||||||
|
#table-view {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#grid-view {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide chart canvases as they don't print well */
|
||||||
|
canvas {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.top-products-grid {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dash-widget {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
height: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rank-badge {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rank-badge.top-3 {
|
||||||
|
width: 35px;
|
||||||
|
height: 35px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-product .card-title {
|
||||||
|
font-size: 14px;
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make the table more compact on smaller screens */
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.datanew th, .datanew td {
|
||||||
|
padding: 0.5rem 0.25rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-toggle {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,460 @@
|
||||||
|
/**
|
||||||
|
* popular-products.js - Script for handling product popularity report functionality
|
||||||
|
*
|
||||||
|
* This script manages:
|
||||||
|
* - DataTable initialization
|
||||||
|
* - Date filtering and form handling
|
||||||
|
* - View toggling (grid vs table)
|
||||||
|
* - Export functionality (PDF, Excel)
|
||||||
|
* - Chart initialization
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Document ready function
|
||||||
|
$(document).ready(function () {
|
||||||
|
// Initialize datatable with error handling
|
||||||
|
initializeDataTable();
|
||||||
|
|
||||||
|
// Handle date preset selection
|
||||||
|
handleDatePresets();
|
||||||
|
|
||||||
|
// Handle limit and sort changes
|
||||||
|
handleFilterChanges();
|
||||||
|
|
||||||
|
// Form validation
|
||||||
|
setupFormValidation();
|
||||||
|
|
||||||
|
// View toggle
|
||||||
|
setupViewToggle();
|
||||||
|
|
||||||
|
// Export functionality
|
||||||
|
setupExportFunctionality();
|
||||||
|
|
||||||
|
// Initialize Chart.js charts if they exist
|
||||||
|
if (typeof initializeCharts === 'function') {
|
||||||
|
initializeCharts();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide loading overlay when page is fully loaded
|
||||||
|
hideLoadingWithDelay(500);
|
||||||
|
|
||||||
|
// Check user role for hiding UI elements
|
||||||
|
checkUserPermissions();
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check user permissions and adjust UI accordingly
|
||||||
|
*/
|
||||||
|
function checkUserPermissions() {
|
||||||
|
// Check if body has class 'employee' or 'user'
|
||||||
|
if ($('body').hasClass('employee') || $('body').hasClass('user')) {
|
||||||
|
// Hide any admin-only elements that might not have been hidden by PHP
|
||||||
|
$('.admin-only').hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the DataTable with appropriate settings
|
||||||
|
*/
|
||||||
|
function initializeDataTable() {
|
||||||
|
try {
|
||||||
|
$('.datanew').DataTable({
|
||||||
|
responsive: true,
|
||||||
|
language: {
|
||||||
|
search: '<span>Cari:</span> _INPUT_',
|
||||||
|
searchPlaceholder: 'Cari produk...',
|
||||||
|
lengthMenu: '<span>Tampilkan:</span> _MENU_',
|
||||||
|
paginate: {
|
||||||
|
'first': 'Pertama',
|
||||||
|
'last': 'Terakhir',
|
||||||
|
'next': 'Berikutnya',
|
||||||
|
'previous': 'Sebelumnya'
|
||||||
|
},
|
||||||
|
info: "Menampilkan _START_ sampai _END_ dari _TOTAL_ entri",
|
||||||
|
infoEmpty: "Menampilkan 0 sampai 0 dari 0 entri",
|
||||||
|
infoFiltered: "(disaring dari _MAX_ entri total)",
|
||||||
|
emptyTable: "Tidak ada data tersedia untuk periode yang dipilih",
|
||||||
|
zeroRecords: "Tidak ditemukan catatan yang cocok"
|
||||||
|
},
|
||||||
|
dom: '<"top"fl>rt<"bottom"ip><"clear">',
|
||||||
|
lengthMenu: [10, 25, 50, 100],
|
||||||
|
pageLength: 10,
|
||||||
|
drawCallback: function() {
|
||||||
|
hideLoadingOverlay();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error("DataTable initialization error:", e);
|
||||||
|
hideLoadingOverlay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle date preset selection changes
|
||||||
|
*/
|
||||||
|
function handleDatePresets() {
|
||||||
|
$('#preset').on('change', function() {
|
||||||
|
if ($(this).val() === '') {
|
||||||
|
// Custom date range selected - show custom date inputs
|
||||||
|
$('#custom-date-inputs').show();
|
||||||
|
} else {
|
||||||
|
// Preset selected - hide custom date inputs and show loading overlay
|
||||||
|
$('#custom-date-inputs').hide();
|
||||||
|
showLoadingOverlay();
|
||||||
|
|
||||||
|
// Add a small delay before submitting to ensure loading overlay is visible
|
||||||
|
setTimeout(function() {
|
||||||
|
$('#date-filter-form').submit();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle limit and sort_by changes (auto-submit form)
|
||||||
|
*/
|
||||||
|
function handleFilterChanges() {
|
||||||
|
$('#limit, #sort_by').on('change', function() {
|
||||||
|
showLoadingOverlay();
|
||||||
|
|
||||||
|
// Add a small delay before submitting
|
||||||
|
setTimeout(function() {
|
||||||
|
$('#date-filter-form').submit();
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle reset button - show loading overlay
|
||||||
|
$('a[href="?reset=1"]').on('click', function() {
|
||||||
|
showLoadingOverlay();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup form validation
|
||||||
|
*/
|
||||||
|
function setupFormValidation() {
|
||||||
|
$('#date-filter-form').on('submit', function(e) {
|
||||||
|
showLoadingOverlay();
|
||||||
|
|
||||||
|
// Only validate if custom date range is selected
|
||||||
|
if ($('#preset').val() === '') {
|
||||||
|
var startDate = $('#start_date').val();
|
||||||
|
var endDate = $('#end_date').val();
|
||||||
|
|
||||||
|
if (!startDate || !endDate) {
|
||||||
|
alert('Harap pilih tanggal awal dan akhir');
|
||||||
|
hideLoadingOverlay();
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new Date(startDate) > new Date(endDate)) {
|
||||||
|
alert('Tanggal awal tidak boleh lebih besar dari tanggal akhir');
|
||||||
|
hideLoadingOverlay();
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup view toggle between grid and table
|
||||||
|
*/
|
||||||
|
function setupViewToggle() {
|
||||||
|
$('.view-toggle button').on('click', function() {
|
||||||
|
const viewType = $(this).data('view');
|
||||||
|
|
||||||
|
// Update active button
|
||||||
|
$('.view-toggle button').removeClass('active');
|
||||||
|
$(this).addClass('active');
|
||||||
|
|
||||||
|
// Show/hide appropriate view
|
||||||
|
if (viewType === 'grid') {
|
||||||
|
$('#grid-view').show();
|
||||||
|
$('#table-view').hide();
|
||||||
|
} else {
|
||||||
|
$('#grid-view').hide();
|
||||||
|
$('#table-view').show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup export functionality
|
||||||
|
*/
|
||||||
|
function setupExportFunctionality() {
|
||||||
|
// PDF Export
|
||||||
|
$('.pdf-export').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
showLoadingOverlay();
|
||||||
|
|
||||||
|
// Check if the user has permission (controlled by CSS class)
|
||||||
|
if ($(this).hasClass('disabled-for-employee')) {
|
||||||
|
showPermissionModal('Ekspor PDF', 'Laporan ini tidak dapat diekspor ke PDF oleh akun karyawan.');
|
||||||
|
hideLoadingOverlay();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have permission, proceed with export
|
||||||
|
exportToPDF();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Excel Export
|
||||||
|
$('.excel-export').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
showLoadingOverlay();
|
||||||
|
|
||||||
|
// Check if the user has permission
|
||||||
|
if ($(this).hasClass('disabled-for-employee')) {
|
||||||
|
showPermissionModal('Ekspor Excel', 'Laporan ini tidak dapat diekspor ke Excel oleh akun karyawan.');
|
||||||
|
hideLoadingOverlay();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have permission, proceed with export
|
||||||
|
exportToExcel();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Print Report
|
||||||
|
$('.print-report').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Check if the user has permission
|
||||||
|
if ($(this).hasClass('disabled-for-employee')) {
|
||||||
|
showPermissionModal('Cetak Laporan', 'Pencetakan laporan dibatasi hanya untuk akun administrator.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have permission, proceed with print
|
||||||
|
printReport();
|
||||||
|
});
|
||||||
|
|
||||||
|
// For employee access - handle restricted action attempts
|
||||||
|
$('.employee-print, .employee-export').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Get the action type from data attribute or default
|
||||||
|
var actionType = $(this).data('action') === 'print_attempt' ? 'Cetak Laporan' : 'Ekspor Laporan';
|
||||||
|
var message = $(this).data('action') === 'print_attempt'
|
||||||
|
? 'Pencetakan laporan dibatasi hanya untuk akun administrator.'
|
||||||
|
: 'Ekspor data dibatasi hanya untuk akun administrator.';
|
||||||
|
|
||||||
|
showPermissionModal(actionType, message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show permission denied modal
|
||||||
|
*/
|
||||||
|
function showPermissionModal(actionType, message) {
|
||||||
|
// Set the action details
|
||||||
|
$('#actionDetails').html(
|
||||||
|
'<strong>Tindakan yang Dicoba:</strong> ' + actionType + '<br>' +
|
||||||
|
'<small>' + message + '</small>'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Show the modal
|
||||||
|
var permissionModal = new bootstrap.Modal(document.getElementById('permissionModal'));
|
||||||
|
permissionModal.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print the current report
|
||||||
|
*/
|
||||||
|
function printReport() {
|
||||||
|
// Add print-specific styling
|
||||||
|
$('<style>')
|
||||||
|
.attr('type', 'text/css')
|
||||||
|
.html('@media print { ' +
|
||||||
|
'.no-print, .dataTables_filter, .dataTables_length, .dataTables_paginate, .sidebar, .header, #sidebar, #mobile_btn { display: none !important; } ' +
|
||||||
|
'.page-wrapper { margin-left: 0 !important; padding: 20px !important; } ' +
|
||||||
|
'.card { border: none !important; box-shadow: none !important; } ' +
|
||||||
|
'thead { background-color: #f8f9fa !important; -webkit-print-color-adjust: exact !important; color-adjust: exact !important; } ' +
|
||||||
|
'table { width: 100% !important; } ' +
|
||||||
|
'.table th, .table td { padding: 0.25rem !important; } ' +
|
||||||
|
'.report-header { text-align: center; margin-bottom: 20px; } ' +
|
||||||
|
'.report-header h3 { margin-bottom: 5px; } ' +
|
||||||
|
'#table-view { display: block !important; } ' +
|
||||||
|
'#grid-view { display: none !important; } ' +
|
||||||
|
'}')
|
||||||
|
.appendTo('head');
|
||||||
|
|
||||||
|
// Add a report header for printing
|
||||||
|
if ($('.report-header').length === 0) {
|
||||||
|
// Get date range for report header
|
||||||
|
var startDate = $('#start_date').val() || $('.alert-info strong').next().text().split(' sampai ')[0];
|
||||||
|
var endDate = $('#end_date').val() || $('.alert-info strong').next().text().split(' sampai ')[1];
|
||||||
|
|
||||||
|
$('.content').prepend(
|
||||||
|
'<div class="report-header no-screen" style="display:none;">' +
|
||||||
|
'<h2>Ayula Store - Laporan Produk Terlaris</h2>' +
|
||||||
|
'<p>Periode: ' + startDate + ' sampai ' + endDate + '</p>' +
|
||||||
|
'<p>Dibuat pada: ' + new Date().toLocaleDateString() + '</p>' +
|
||||||
|
'</div>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force table view for printing
|
||||||
|
$('#grid-view').hide();
|
||||||
|
$('#table-view').show();
|
||||||
|
|
||||||
|
window.print();
|
||||||
|
|
||||||
|
// Restore the original view after printing
|
||||||
|
if ($('.view-toggle button[data-view="grid"]').hasClass('active')) {
|
||||||
|
$('#grid-view').show();
|
||||||
|
$('#table-view').hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export table to Excel (CSV)
|
||||||
|
*/
|
||||||
|
function exportToExcel() {
|
||||||
|
// Switch to table view temporarily
|
||||||
|
$('#grid-view').hide();
|
||||||
|
$('#table-view').show();
|
||||||
|
|
||||||
|
// Get the table data
|
||||||
|
var table = $('.datanew').DataTable();
|
||||||
|
var data = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get all rows from the table (not just the current page)
|
||||||
|
table.rows().every(function() {
|
||||||
|
data.push(this.data());
|
||||||
|
});
|
||||||
|
|
||||||
|
var headers = [];
|
||||||
|
|
||||||
|
// Get headers
|
||||||
|
$('.datanew thead th').each(function () {
|
||||||
|
headers.push($(this).text());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create CSV content
|
||||||
|
var csvContent = "data:text/csv;charset=utf-8," + headers.join(",") + "\n";
|
||||||
|
|
||||||
|
// Add data rows
|
||||||
|
for (var i = 0; i < data.length; i++) {
|
||||||
|
var row = [];
|
||||||
|
for (var j = 0; j < data[i].length; j++) {
|
||||||
|
// Clean the data (remove HTML tags)
|
||||||
|
var cellData = data[i][j].toString().replace(/<[^>]*>/g, '').trim();
|
||||||
|
// Replace multiple spaces with a single space
|
||||||
|
cellData = cellData.replace(/\s+/g, ' ');
|
||||||
|
// Wrap in quotes and escape internal quotes
|
||||||
|
row.push('"' + cellData.replace(/"/g, '""') + '"');
|
||||||
|
}
|
||||||
|
csvContent += row.join(",") + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create download link
|
||||||
|
var encodedUri = encodeURI(csvContent);
|
||||||
|
var link = document.createElement("a");
|
||||||
|
link.setAttribute("href", encodedUri);
|
||||||
|
|
||||||
|
// Use the current date in the filename
|
||||||
|
var today = new Date();
|
||||||
|
var dateStr = today.getFullYear() + '-' +
|
||||||
|
('0' + (today.getMonth()+1)).slice(-2) + '-' +
|
||||||
|
('0' + today.getDate()).slice(-2);
|
||||||
|
|
||||||
|
link.setAttribute("download", "produk_terlaris_" + dateStr + ".csv");
|
||||||
|
document.body.appendChild(link);
|
||||||
|
|
||||||
|
// Download the file
|
||||||
|
link.click();
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
document.body.removeChild(link);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error exporting to Excel:", e);
|
||||||
|
alert("Terjadi kesalahan saat mengekspor. Silakan coba lagi.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide loading overlay when export is complete
|
||||||
|
hideLoadingOverlay();
|
||||||
|
|
||||||
|
// Restore the original view after exporting
|
||||||
|
if ($('.view-toggle button[data-view="grid"]').hasClass('active')) {
|
||||||
|
$('#grid-view').show();
|
||||||
|
$('#table-view').hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export to PDF via server
|
||||||
|
*/
|
||||||
|
function exportToPDF() {
|
||||||
|
try {
|
||||||
|
// Create a form for server-side generation
|
||||||
|
var form = $('<form action="" method="post" target="_blank"></form>');
|
||||||
|
form.append('<input type="hidden" name="export_pdf" value="1">');
|
||||||
|
form.append('<input type="hidden" name="start_date" value="' + $('#start_date').val() + '">');
|
||||||
|
form.append('<input type="hidden" name="end_date" value="' + $('#end_date').val() + '">');
|
||||||
|
form.append('<input type="hidden" name="limit" value="' + $('#limit').val() + '">');
|
||||||
|
form.append('<input type="hidden" name="sort_by" value="' + $('#sort_by').val() + '">');
|
||||||
|
|
||||||
|
// Add the form to the document and submit it
|
||||||
|
$('body').append(form);
|
||||||
|
form.submit();
|
||||||
|
form.remove();
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error exporting to PDF:", e);
|
||||||
|
alert("Terjadi kesalahan saat mengekspor ke PDF. Silakan coba lagi.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide loading overlay after 2 seconds (since PDF generation happens in new tab)
|
||||||
|
setTimeout(function() {
|
||||||
|
hideLoadingOverlay();
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show loading overlay
|
||||||
|
*/
|
||||||
|
function showLoadingOverlay() {
|
||||||
|
if (document.getElementById('loading-overlay')) {
|
||||||
|
document.getElementById('loading-overlay').style.display = 'flex';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide loading overlay
|
||||||
|
*/
|
||||||
|
function hideLoadingOverlay() {
|
||||||
|
if (document.getElementById('loading-overlay')) {
|
||||||
|
document.getElementById('loading-overlay').style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide loading overlay with delay
|
||||||
|
*/
|
||||||
|
function hideLoadingWithDelay(delay) {
|
||||||
|
setTimeout(function() {
|
||||||
|
hideLoadingOverlay();
|
||||||
|
}, delay || 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide loading overlay when page is loaded
|
||||||
|
window.addEventListener('load', function() {
|
||||||
|
hideLoadingWithDelay(500);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Failsafe timeout to hide loading overlay after 10 seconds
|
||||||
|
setTimeout(function() {
|
||||||
|
hideLoadingOverlay();
|
||||||
|
}, 10000);
|
||||||
|
|
||||||
|
// Disable console logs in production
|
||||||
|
if (window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') {
|
||||||
|
console.log = function() {}; // Disable console logs
|
||||||
|
console.warn = function() {}; // Disable console warnings
|
||||||
|
console.error = function() {}; // Disable console errors
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,745 @@
|
||||||
|
<?php
|
||||||
|
// Include configuration and database queries
|
||||||
|
require_once 'salesreport-config.php';
|
||||||
|
|
||||||
|
$username = getUsername();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="id">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0">
|
||||||
|
<meta name="description" content="POS - Bootstrap Admin Template">
|
||||||
|
<meta name="keywords" content="admin, estimates, bootstrap, business, corporate, creative, invoice, html5, responsive, Projects">
|
||||||
|
<meta name="author" content="Dreamguys - Bootstrap Admin Template">
|
||||||
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
<title>Ayula Store POS - Laporan Penjualan</title>
|
||||||
|
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../../src/img/smallest-ayula.png">
|
||||||
|
<link rel="stylesheet" href="../../../bootstrap/assets/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="../../../bootstrap/assets/css/bootstrap-datetimepicker.min.css">
|
||||||
|
<link rel="stylesheet" href="../../../bootstrap/assets/css/animate.css">
|
||||||
|
<link rel="stylesheet" href="../../../bootstrap/assets/plugins/select2/css/select2.min.css">
|
||||||
|
<link rel="stylesheet" href="../../../bootstrap/assets/css/dataTables.bootstrap4.min.css">
|
||||||
|
<link rel="stylesheet" href="../../../bootstrap/assets/plugins/fontawesome/css/fontawesome.min.css">
|
||||||
|
<link rel="stylesheet" href="../../../bootstrap/assets/plugins/fontawesome/css/all.min.css">
|
||||||
|
<link rel="stylesheet" href="../../../bootstrap/assets/css/style.css">
|
||||||
|
|
||||||
|
<!-- Custom CSS -->
|
||||||
|
<link rel="stylesheet" href="salesreport.css">
|
||||||
|
<!-- Tambahkan kode CSS ini di bagian head -->
|
||||||
|
<style>
|
||||||
|
/* Loading overlay styles */
|
||||||
|
#loading-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
border: 5px solid #f3f3f3;
|
||||||
|
border-top: 5px solid #7367f0;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No data message styles */
|
||||||
|
.no-data-container {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-data-icon {
|
||||||
|
font-size: 3rem;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-data-message {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #343a40;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-data-help {
|
||||||
|
color: #6c757d;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="<?php echo $isAdmin ? 'admin' : 'employee'; ?>">
|
||||||
|
<div id="global-loader">
|
||||||
|
<div class="whirly-loader"> </div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Loading overlay -->
|
||||||
|
<div id="loading-overlay">
|
||||||
|
<div class="loading-spinner"></div>
|
||||||
|
<div class="loading-text">Memuat data...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="main-wrapper">
|
||||||
|
<div class="header">
|
||||||
|
<div class="header-left active">
|
||||||
|
<a href="/ayula-store/views/dashboard/" class="logo">
|
||||||
|
<img src="../../../src/img/logoayula.png" alt="" />
|
||||||
|
</a>
|
||||||
|
<a href="/ayula-store/views/dashboard/" class="logo-small">
|
||||||
|
<img src="../../../src/img/smallest-ayula.png" alt="" />
|
||||||
|
</a>
|
||||||
|
<a id="toggle_btn" href="javascript:void(0);"> </a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a id="mobile_btn" class="mobile_btn" href="#sidebar">
|
||||||
|
<span class="bar-icon">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<ul class="nav user-menu">
|
||||||
|
<li class="nav-item dropdown has-arrow main-drop">
|
||||||
|
<a href="javascript:void(0);" class="dropdown-toggle nav-link userset" data-bs-toggle="dropdown">
|
||||||
|
<span class="user-img"><img src="../../../src/img/userprofile.png" alt="">
|
||||||
|
<span class="status online"></span></span>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu menu-drop-user">
|
||||||
|
<div class="profilename">
|
||||||
|
<div class="profileset">
|
||||||
|
<span class="user-img"><img src="../../../src/img/userprofile.png" alt="">
|
||||||
|
<span class="status online"></span></span>
|
||||||
|
<div class="profilesets">
|
||||||
|
<h6><?php echo $isAdmin ? 'Admin' : 'Karyawan'; ?></h6>
|
||||||
|
<h5><?php echo htmlspecialchars($username); ?></h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item" href="/ayula-store/views/report-issue/">
|
||||||
|
<img src="../../../src/img/warning.png" class="me-2" alt="img" /> Laporkan Masalah
|
||||||
|
</a>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item logout pb-0" href="../../../views/logout.php"><img
|
||||||
|
src="../../../bootstrap/assets/img/icons/log-out.svg"
|
||||||
|
class="me-2"
|
||||||
|
alt="img" />Keluar</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="dropdown mobile-user-menu">
|
||||||
|
<a href="javascript:void(0);" class="nav-link dropdown-toggle" data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"><i class="fa fa-ellipsis-v"></i></a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
|
<a class="dropdown-item" href="/ayula-store/views/report-issue/">
|
||||||
|
<i class="fa fa-cog me-2"></i> Laporkan Masalah
|
||||||
|
</a>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item logout pb-0" href="../../../views/logout.php"><img
|
||||||
|
src="../../../bootstrap/assets/img/icons/log-out.svg"
|
||||||
|
class="me-2"
|
||||||
|
alt="img" />Keluar</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sidebar" id="sidebar">
|
||||||
|
<div class="sidebar-inner slimscroll">
|
||||||
|
<div id="sidebar-menu" class="sidebar-menu">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="/ayula-store/views/dashboard/"><img src="../../../bootstrap/assets/img/icons/dashboard.svg" alt="img" /><span>
|
||||||
|
Dashboard</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/ayula-store/views/transaction/"><img src="../../../bootstrap/assets/img/icons/sales1.svg" alt="img" /><span>
|
||||||
|
POS</span></a>
|
||||||
|
</li>
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../../bootstrap/assets/img/icons/product.svg" alt="img" /><span>
|
||||||
|
Produk</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/ayula-store/views/barang/productlist.php">Daftar Produk</a></li>
|
||||||
|
<li><a href="/ayula-store/views/barang/addproduct.php">Tambah Produk</a></li>
|
||||||
|
<li><a href="categorylist.html">Daftar Kategori</a></li>
|
||||||
|
<li><a href="addcategory.html">Tambah Kategori</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../../bootstrap/assets/img/icons/purchase1.svg" alt="img" /><span>
|
||||||
|
Pembelian</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="purchaselist.html">Daftar Pembelian</a></li>
|
||||||
|
<li><a href="addpurchase.html">Tambah Pembelian</a></li>
|
||||||
|
<li><a href="importpurchase.html">Import Pembelian</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../../bootstrap/assets/img/icons/time.svg" alt="img" /><span>
|
||||||
|
Laporan</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="purchaseorderreport.html">Laporan Order Pembelian</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="inventoryreport.html">Laporan Inventaris</a></li>
|
||||||
|
<li><a href="/ayula-store/views/report/sales-report/" class="active">Laporan Penjualan</a></li>
|
||||||
|
<?php if ($userRole == 'admin') { ?>
|
||||||
|
<li><a href="/ayula-store/views/report/popular-products/">Produk Terlaris</a></li>
|
||||||
|
<?php } ?>
|
||||||
|
<li><a href="invoicereport.html">Laporan Faktur</a></li>
|
||||||
|
<li><a href="purchasereport.html">Laporan Pembelian</a></li>
|
||||||
|
<li><a href="supplierreport.html">Laporan Pemasok</a></li>
|
||||||
|
<li><a href="customerreport.html">Laporan Pelanggan</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../../bootstrap/assets/img/icons/users1.svg" alt="img" /><span>
|
||||||
|
Pengguna</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<?php if ($userRole == 'admin') { ?>
|
||||||
|
<li><a href="/ayula-store/views/users/add-user.php">Pengguna Baru</a></li>
|
||||||
|
<?php } ?>
|
||||||
|
<li><a href="/ayula-store/views/users/">Daftar Pengguna</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="content">
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="page-title">
|
||||||
|
<h4>Laporan Penjualan</h4>
|
||||||
|
<h6>Kelola Laporan Penjualan Anda</h6>
|
||||||
|
<?php if (!$isAdmin): ?>
|
||||||
|
<div class="alert alert-info mt-2 employee-warning">
|
||||||
|
<small><i class="fa fa-info-circle me-1"></i> Anda melihat laporan ini dengan akses karyawan. Beberapa data mungkin dibatasi.</small>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dashboard stat widgets -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-4 col-sm-6 col-12 d-flex">
|
||||||
|
<div class="dash-widget flex-fill">
|
||||||
|
<div class="dash-widgetimg">
|
||||||
|
<span class="dash-widget-icon "><i class="fa fa-shopping-cart"></i></span>
|
||||||
|
</div>
|
||||||
|
<div class="dash-widgetcontent">
|
||||||
|
<h5>Total <span class="counters"><?php echo number_format($totals['total_transactions']); ?></span></h5>
|
||||||
|
<h6>Transaksi</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-4 col-sm-6 col-12 d-flex">
|
||||||
|
<div class="dash-widget flex-fill">
|
||||||
|
<div class="dash-widgetimg">
|
||||||
|
<span class="dash-widget-icon"><i class="fa fa-cubes"></i></span>
|
||||||
|
</div>
|
||||||
|
<div class="dash-widgetcontent">
|
||||||
|
<h5>Total <span class="counters"><?php echo number_format($totals['total_items']); ?></span></h5>
|
||||||
|
<h6>Item Terjual</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php if ($isAdmin || canAccessFeature('view_financial')): ?>
|
||||||
|
<div class="col-lg-4 col-sm-6 col-12 d-flex">
|
||||||
|
<div class="dash-widget flex-fill">
|
||||||
|
<div class="dash-widgetimg">
|
||||||
|
<span class="dash-widget-icon"><i class="fa fa-money-bill-alt"></i></span>
|
||||||
|
</div>
|
||||||
|
<div class="dash-widgetcontent">
|
||||||
|
<h5>Rp. <span class="counters"><?php echo number_format($totals['grand_total']); ?></span></h5>
|
||||||
|
<h6>Total Penjualan</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<!-- Employee placeholders for layout consistency -->
|
||||||
|
<div class="col-lg-6 col-sm-12 col-12 d-flex">
|
||||||
|
<div class="dash-widget flex-fill">
|
||||||
|
<div class="dash-widgetimg">
|
||||||
|
<span class="dash-widget-icon"><i class="fa fa-chart-line"></i></span>
|
||||||
|
</div>
|
||||||
|
<div class="dash-widgetcontent">
|
||||||
|
<h5>Laporan Aktivitas</h5>
|
||||||
|
<h6>Hubungi admin untuk detail keuangan</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Enhanced Date Filter with Presets -->
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="card-title">Filter Tanggal</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form action="" method="GET" id="date-filter-form">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="preset">Rentang Tanggal:</label>
|
||||||
|
<select name="preset" id="preset" class="form-control">
|
||||||
|
<?php if ($isAdmin): ?>
|
||||||
|
<option value="">Kustom</option>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php
|
||||||
|
// Buat array terjemahan untuk label preset
|
||||||
|
$presetLabels = [
|
||||||
|
'today' => 'Hari Ini',
|
||||||
|
'yesterday' => 'Kemarin',
|
||||||
|
'this_week' => 'Minggu Ini',
|
||||||
|
'last_week' => 'Minggu Lalu',
|
||||||
|
'this_month' => 'Bulan Ini',
|
||||||
|
'last_month' => 'Bulan Lalu',
|
||||||
|
// 'last_90_days' => '90 Hari Terakhir',
|
||||||
|
'all_time' => 'Sepanjang Waktu'
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($datePresets as $key => $preset):
|
||||||
|
$label = isset($presetLabels[$key]) ? $presetLabels[$key] : $preset['label'];
|
||||||
|
?>
|
||||||
|
<option value="<?php echo $key; ?>" <?php echo ($activePreset === $key) ? 'selected' : ''; ?>>
|
||||||
|
<?php echo $label; ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($isAdmin): ?>
|
||||||
|
<!-- Custom Date Inputs - Admin Only -->
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="row custom-date-inputs" id="custom-date-inputs" <?php echo !empty($activePreset) ? 'style="display:none;"' : ''; ?>>
|
||||||
|
<div class="col-md-5">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="start_date">Dari Tanggal:</label>
|
||||||
|
<input type="date" id="start_date" name="start_date" class="form-control"
|
||||||
|
value="<?php echo $startDate; ?>">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-5">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="end_date">Sampai Tanggal:</label>
|
||||||
|
<input type="date" id="end_date" name="end_date" class="form-control"
|
||||||
|
value="<?php echo $endDate; ?>">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="form-group">
|
||||||
|
<label> </label>
|
||||||
|
<button type="submit" class="btn btn-primary w-100" id="search-button">
|
||||||
|
<i class="fas fa-search"></i> Cari
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<!-- For employees - simpler view -->
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="form-group">
|
||||||
|
<label> </label>
|
||||||
|
<button type="submit" class="btn btn-primary" id="search-button">
|
||||||
|
<i class="fas fa-search"></i> Terapkan Filter
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-12 d-flex justify-content-end">
|
||||||
|
<a href="?reset=1" class="btn btn-secondary me-2">
|
||||||
|
<i class="fas fa-redo"></i> Reset
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<?php if ($isAdmin || canAccessFeature('print_report')): ?>
|
||||||
|
<a href="#" class="btn btn-info me-2 print-report">
|
||||||
|
<i class="fas fa-print"></i> Cetak
|
||||||
|
</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<a href="#" class="btn btn-info me-2 employee-print request-only"
|
||||||
|
data-action="print_attempt">
|
||||||
|
<i class="fas fa-print"></i> Cetak
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($isAdmin || canAccessFeature('export_excel') || canAccessFeature('export_pdf')): ?>
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn btn-success dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="fas fa-download"></i> Ekspor
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<?php if ($isAdmin || canAccessFeature('export_excel')): ?>
|
||||||
|
<li><a class="dropdown-item excel-export" href="#"><i class="fas fa-file-excel me-2"></i> Excel</a></li>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ($isAdmin || canAccessFeature('export_pdf')): ?>
|
||||||
|
<li><a class="dropdown-item pdf-export" href="#"><i class="fas fa-file-pdf me-2"></i> PDF</a></li>
|
||||||
|
<?php endif; ?>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<!-- Employee-limited export (with logging) -->
|
||||||
|
<a href="#" class="btn btn-success request-only employee-export"
|
||||||
|
data-action="export_attempt">
|
||||||
|
<i class="fas fa-download"></i> Ekspor
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Current Date Range Display -->
|
||||||
|
<div class="alert alert-info mb-4">
|
||||||
|
<strong>Rentang Tanggal Saat Ini:</strong> <?php echo date('d M Y', strtotime($startDate)); ?> sampai
|
||||||
|
<?php echo date('d M Y', strtotime($endDate)); ?>
|
||||||
|
<?php if (!empty($activePreset)):
|
||||||
|
$presetLabels = [
|
||||||
|
'today' => 'Hari Ini',
|
||||||
|
'yesterday' => 'Kemarin',
|
||||||
|
'this_week' => 'Minggu Ini',
|
||||||
|
'last_week' => 'Minggu Lalu',
|
||||||
|
'this_month' => 'Bulan Ini',
|
||||||
|
'last_30_days' => '30 Hari Terakhir',
|
||||||
|
'last_month' => 'Bulan Lalu',
|
||||||
|
'last_90_days' => '90 Hari Terakhir',
|
||||||
|
'last_quarter' => 'Kuartal Terakhir',
|
||||||
|
'last_year' => 'Tahun Lalu',
|
||||||
|
'all_time' => 'Sepanjang Waktu'
|
||||||
|
];
|
||||||
|
$label = isset($presetLabels[$activePreset]) ? $presetLabels[$activePreset] : $datePresets[$activePreset]['label'];
|
||||||
|
?>
|
||||||
|
<span class="badge bg-primary ms-2"><?php echo $label; ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
<div class="small mt-1">
|
||||||
|
<?php
|
||||||
|
$daysDiff = (strtotime($endDate) - strtotime($startDate)) / (60 * 60 * 24) + 1;
|
||||||
|
echo "Menampilkan data untuk " . number_format($daysDiff) . " hari";
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sales Report Table -->
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="card-title">Transaksi Penjualan</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (isset($queryData['has_data']) && $queryData['has_data'] === false): ?>
|
||||||
|
<!-- Tampilan No Data Found (Tidak ada data) -->
|
||||||
|
<div class="no-data-container">
|
||||||
|
<div class="no-data-icon">
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
</div>
|
||||||
|
<h4 class="no-data-message">Tidak ada transaksi ditemukan untuk periode yang dipilih</h4>
|
||||||
|
<p class="no-data-help">
|
||||||
|
<?php if ($activePreset == 'last_year'): ?>
|
||||||
|
Tidak ada transaksi penjualan yang tercatat untuk tahun lalu (<?php echo date('Y') - 1; ?>).
|
||||||
|
<?php else: ?>
|
||||||
|
Coba pilih rentang tanggal atau preset tanggal yang berbeda.
|
||||||
|
<?php endif; ?>
|
||||||
|
</p>
|
||||||
|
<a href="?reset=1" class="btn btn-primary">
|
||||||
|
<i class="fas fa-redo"></i> Reset Filter
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<!-- Tampilan Table Normal (Ada data) -->
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table datanew">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID Transaksi</th>
|
||||||
|
<th>Tanggal</th>
|
||||||
|
<th>Item</th>
|
||||||
|
<th>Produk</th>
|
||||||
|
<?php if ($isAdmin || canAccessFeature('view_financial')): ?>
|
||||||
|
<th>Total</th>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ($isAdmin || canAccessFeature('view_financial')): ?>
|
||||||
|
<th>Tunai</th>
|
||||||
|
<th>Kembalian</th>
|
||||||
|
<?php else: ?>
|
||||||
|
<th>Status</th>
|
||||||
|
<?php endif; ?>
|
||||||
|
<th class="text-end no-print">Aksi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if ($result && mysqli_num_rows($result) > 0): ?>
|
||||||
|
<?php mysqli_data_seek($result, 0); // Reset pointer to beginning of result set
|
||||||
|
?>
|
||||||
|
<?php while ($row = mysqli_fetch_assoc($result)): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo $row['kode_transaksi']; ?></td>
|
||||||
|
<td><?php echo date('d M Y H:i', strtotime($row['tanggal'])); ?></td>
|
||||||
|
<td><?php echo $row['total_item']; ?></td>
|
||||||
|
<td><?php echo $row['total_products']; ?></td>
|
||||||
|
|
||||||
|
<?php if ($isAdmin || canAccessFeature('view_financial')): ?>
|
||||||
|
<!-- Admin sees all financial details -->
|
||||||
|
<td>Rp. <?php echo number_format($row['total']); ?></td>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($isAdmin || canAccessFeature('view_financial')): ?>
|
||||||
|
<td>Rp. <?php echo number_format($row['cash_amount']); ?></td>
|
||||||
|
<td>Rp. <?php echo number_format($row['change_amount']); ?></td>
|
||||||
|
<?php else: ?>
|
||||||
|
<!-- Employee sees limited details -->
|
||||||
|
<td><span class="badge bg-success">Selesai</span></td>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<td class="text-end no-print">
|
||||||
|
<a class="btn btn-sm btn-secondary" href="../../transaction/transaction_success.php?id=<?php echo $row['id_transaksi']; ?>">
|
||||||
|
<i class="fas fa-eye"></i> Lihat
|
||||||
|
</a>
|
||||||
|
<?php if ($isAdmin || canAccessFeature('print_report')): ?>
|
||||||
|
<a class="btn btn-sm btn-primary" href="#" onclick="printReceipt(<?php echo $row['id_transaksi']; ?>); return false;">
|
||||||
|
<i class="fas fa-print"></i> Cetak
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endwhile; ?>
|
||||||
|
<?php else: ?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="9" class="text-center">Tidak ada transaksi ditemukan untuk periode yang dipilih</td>
|
||||||
|
</tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Period Summary Report - Admin Only -->
|
||||||
|
<?php if ($isAdmin || canAccessFeature('view_summary')): ?>
|
||||||
|
<div class="row mt-4 no-print">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="card-title">Ringkasan untuk Periode yang Dipilih</h5>
|
||||||
|
<h6><?php echo date('d M Y', strtotime($startDate)); ?> - <?php echo date('d M Y', strtotime($endDate)); ?></h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="report-summary">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="report-summary-item">
|
||||||
|
<span>Total Transaksi:</span>
|
||||||
|
<strong><?php echo number_format($totals['total_transactions']); ?></strong>
|
||||||
|
</div>
|
||||||
|
<div class="report-summary-item">
|
||||||
|
<span>Total Item Terjual:</span>
|
||||||
|
<strong><?php echo number_format($totals['total_items']); ?></strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="report-summary-item">
|
||||||
|
<span>Total:</span>
|
||||||
|
<strong>Rp. <?php echo number_format($totals['grand_total']); ?></strong>
|
||||||
|
</div>
|
||||||
|
<div class="report-summary-item">
|
||||||
|
<span>Rata-rata Harian:</span>
|
||||||
|
<strong>Rp. <?php
|
||||||
|
$daysDiff = (strtotime($endDate) - strtotime($startDate)) / (60 * 60 * 24) + 1;
|
||||||
|
$dailyAvg = $daysDiff > 0 ? $totals['grand_total'] / $daysDiff : 0;
|
||||||
|
echo number_format($dailyAvg);
|
||||||
|
?></strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- Employee-only section: Activity log -->
|
||||||
|
<?php if (!$isAdmin): ?>
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="card-title">Ringkasan Aktivitas Anda</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="fa fa-info-circle me-2"></i>
|
||||||
|
Untuk laporan keuangan yang lebih rinci atau untuk mengekspor data, silakan hubungi administrator Anda.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="report-summary">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="report-summary-item">
|
||||||
|
<span>Transaksi Dilihat:</span>
|
||||||
|
<strong><?php echo $totals['total_transactions']; ?></strong>
|
||||||
|
</div>
|
||||||
|
<div class="report-summary-item">
|
||||||
|
<span>Item Diproses:</span>
|
||||||
|
<strong><?php echo $totals['total_items']; ?></strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="report-summary-item">
|
||||||
|
<span>Laporan Dibuat:</span>
|
||||||
|
<strong><?php echo date('d M Y H:i'); ?></strong>
|
||||||
|
</div>
|
||||||
|
<div class="report-summary-item">
|
||||||
|
<span>Level Akses:</span>
|
||||||
|
<strong>Karyawan</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Employee Permission Modal -->
|
||||||
|
<div class="modal fade" id="permissionModal" tabindex="-1" aria-labelledby="permissionModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-warning">
|
||||||
|
<h5 class="modal-title" id="permissionModalLabel">
|
||||||
|
<i class="fas fa-exclamation-triangle me-2"></i> Akses Dibatasi
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<i class="fas fa-lock" style="font-size: 3rem; color: #ff9f43; margin-bottom: 15px;"></i>
|
||||||
|
<h4>Fitur Dibatasi</h4>
|
||||||
|
</div>
|
||||||
|
<p>Fitur ini hanya tersedia untuk administrator dan personel yang berwenang.</p>
|
||||||
|
<p>Akun karyawan tidak memiliki akses ke fungsi cetak atau ekspor.</p>
|
||||||
|
<div class="alert alert-info mt-3" id="actionDetails">
|
||||||
|
<!-- Akan diisi secara dinamis -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Tutup</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- JavaScript libraries -->
|
||||||
|
<script src="../../../bootstrap/assets/js/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="../../../bootstrap/assets/js/feather.min.js"></script>
|
||||||
|
<script src="../../../bootstrap/assets/js/jquery.slimscroll.min.js"></script>
|
||||||
|
<script src="../../../bootstrap/assets/js/jquery.dataTables.min.js"></script>
|
||||||
|
<script src="../../../bootstrap/assets/js/dataTables.bootstrap4.min.js"></script>
|
||||||
|
<script src="../../../bootstrap/assets/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="../../../bootstrap/assets/js/moment.min.js"></script>
|
||||||
|
<script src="../../../bootstrap/assets/js/bootstrap-datetimepicker.min.js"></script>
|
||||||
|
<script src="../../../bootstrap/assets/plugins/select2/js/select2.min.js"></script>
|
||||||
|
<script src="../../../bootstrap/assets/plugins/sweetalert/sweetalert2.all.min.js"></script>
|
||||||
|
<script src="../../../bootstrap/assets/plugins/sweetalert/sweetalerts.min.js"></script>
|
||||||
|
<script src="../../../bootstrap/assets/js/script.js"></script>
|
||||||
|
|
||||||
|
<!-- Custom JavaScript -->
|
||||||
|
<script src="salesreport.js"></script>
|
||||||
|
<script>
|
||||||
|
// Fungsi untuk menyembunyikan loading overlay saat halaman dimuat
|
||||||
|
window.addEventListener('load', function() {
|
||||||
|
setTimeout(function() {
|
||||||
|
document.getElementById('loading-overlay').style.display = 'none';
|
||||||
|
}, 500); // 500ms delay untuk memastikan semuanya dimuat
|
||||||
|
});
|
||||||
|
|
||||||
|
// Jika halaman terlalu lama dimuat, sembunyikan loading overlay setelah 10 detik
|
||||||
|
setTimeout(function() {
|
||||||
|
document.getElementById('loading-overlay').style.display = 'none';
|
||||||
|
}, 10000);
|
||||||
|
|
||||||
|
// Tambahan untuk penanganan filter dengan preset
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const presetSelect = document.getElementById('preset');
|
||||||
|
const customDateInputs = document.getElementById('custom-date-inputs');
|
||||||
|
|
||||||
|
if (presetSelect && customDateInputs) {
|
||||||
|
presetSelect.addEventListener('change', function() {
|
||||||
|
if (this.value === '') {
|
||||||
|
customDateInputs.style.display = 'flex';
|
||||||
|
} else {
|
||||||
|
// Tampilkan loading overlay saat memilih preset
|
||||||
|
document.getElementById('loading-overlay').style.display = 'flex';
|
||||||
|
// Tambahkan timeout kecil untuk memastikan loading screen muncul
|
||||||
|
setTimeout(function() {
|
||||||
|
document.getElementById('date-filter-form').submit();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
// Nonaktifkan console logs dan warnings
|
||||||
|
if (window.location.hostname === 'localhost') {
|
||||||
|
console.log = function() {}; // Nonaktifkan console logs
|
||||||
|
console.warn = function() {}; // Nonaktifkan console warnings
|
||||||
|
console.error = function() {}; // Nonaktifkan console errors
|
||||||
|
window.alert = function() {}; // Nonaktifkan alert popups
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,515 @@
|
||||||
|
<?php
|
||||||
|
// Mulai sesi untuk menangani keranjang
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
// Include database connection
|
||||||
|
include('../../../routes/db_conn.php');
|
||||||
|
|
||||||
|
// Check if user is logged in, redirect to login page if not
|
||||||
|
if (!isset($_SESSION['user_id']) || !isset($_SESSION['role'])) {
|
||||||
|
header('Location: /ayula-store/views/login/');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current logged-in user's information
|
||||||
|
function getCurrentUser() {
|
||||||
|
global $conn;
|
||||||
|
if (isset($_SESSION['user_id'])) {
|
||||||
|
$stmt = mysqli_prepare($conn, "SELECT id_kasir, username, role, phone FROM kasir WHERE id_kasir = ?");
|
||||||
|
mysqli_stmt_bind_param($stmt, "i", $_SESSION['user_id']);
|
||||||
|
mysqli_stmt_execute($stmt);
|
||||||
|
$result = mysqli_stmt_get_result($stmt);
|
||||||
|
$user = mysqli_fetch_assoc($result);
|
||||||
|
mysqli_stmt_close($stmt);
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure username is available in session
|
||||||
|
if (isset($_SESSION['user_id']) && !isset($_SESSION['username'])) {
|
||||||
|
$currentUser = getCurrentUser();
|
||||||
|
if ($currentUser) {
|
||||||
|
$_SESSION['username'] = $currentUser['username'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user role from session
|
||||||
|
function getUserRole() {
|
||||||
|
return isset($_SESSION['role']) ? $_SESSION['role'] : 'employee';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get username from session
|
||||||
|
function getUsername() {
|
||||||
|
return isset($_SESSION['username']) ? $_SESSION['username'] : 'Unknown User';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if current user is admin
|
||||||
|
function isAdmin() {
|
||||||
|
return getUserRole() === 'admin';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Definisikan preset tanggal dengan opsi berbasis peran
|
||||||
|
function getDatePresets($isAdmin = false) {
|
||||||
|
$presets = [
|
||||||
|
'today' => [
|
||||||
|
'label' => 'Hari Ini',
|
||||||
|
'start' => date('Y-m-d'),
|
||||||
|
'end' => date('Y-m-d')
|
||||||
|
],
|
||||||
|
'yesterday' => [
|
||||||
|
'label' => 'Kemarin',
|
||||||
|
'start' => date('Y-m-d', strtotime('-1 day')),
|
||||||
|
'end' => date('Y-m-d', strtotime('-1 day'))
|
||||||
|
],
|
||||||
|
'this_week' => [
|
||||||
|
'label' => 'Minggu Ini',
|
||||||
|
'start' => date('Y-m-d', strtotime('monday this week')),
|
||||||
|
'end' => date('Y-m-d')
|
||||||
|
],
|
||||||
|
'last_week' => [
|
||||||
|
'label' => 'Minggu Lalu',
|
||||||
|
'start' => date('Y-m-d', strtotime('monday last week')),
|
||||||
|
'end' => date('Y-m-d', strtotime('sunday last week'))
|
||||||
|
],
|
||||||
|
'this_month' => [
|
||||||
|
'label' => 'Bulan Ini',
|
||||||
|
'start' => date('Y-m-01'),
|
||||||
|
'end' => date('Y-m-d')
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
// Preset hanya untuk admin
|
||||||
|
if ($isAdmin) {
|
||||||
|
$presets['last_month'] = [
|
||||||
|
'label' => 'Bulan Lalu',
|
||||||
|
'start' => date('Y-m-d', strtotime('first day of last month')),
|
||||||
|
'end' => date('Y-m-d', strtotime('last day of last month'))
|
||||||
|
];
|
||||||
|
|
||||||
|
$presets['last_90_days'] = [
|
||||||
|
'label' => '90 Hari Terakhir',
|
||||||
|
'start' => date('Y-m-d', strtotime('-90 days')),
|
||||||
|
'end' => date('Y-m-d')
|
||||||
|
];
|
||||||
|
|
||||||
|
$presets['all_time'] = [
|
||||||
|
'label' => 'Sepanjang Waktu',
|
||||||
|
'start' => '2000-01-01', // Tetapkan tanggal mulai yang masuk akal
|
||||||
|
'end' => date('Y-m-d')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $presets;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dapatkan preset tanggal yang sesuai berdasarkan peran pengguna
|
||||||
|
$isAdmin = isAdmin();
|
||||||
|
$datePresets = getDatePresets($isAdmin);
|
||||||
|
|
||||||
|
// Tangani permintaan reset
|
||||||
|
if (isset($_GET['reset']) && $_GET['reset'] == 1) {
|
||||||
|
// Jika reset diminta, alihkan ke halaman yang sama dengan tanggal default
|
||||||
|
header("Location: index.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jika pengguna bukan admin, batasi akses data historis
|
||||||
|
if (!$isAdmin && empty($_GET['preset']) && empty($_GET['start_date'])) {
|
||||||
|
// Default pengguna non-admin ke minggu ini jika tidak ada tanggal yang ditentukan
|
||||||
|
$_GET['preset'] = 'this_week';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Periksa apakah preset dipilih
|
||||||
|
$activePreset = isset($_GET['preset']) && !empty($_GET['preset']) ? $_GET['preset'] : '';
|
||||||
|
|
||||||
|
// Terapkan rentang tanggal berdasarkan preset atau pemilihan manual
|
||||||
|
function getStartDate() {
|
||||||
|
global $datePresets, $activePreset;
|
||||||
|
|
||||||
|
// Jika preset dipilih, gunakan tanggal preset
|
||||||
|
if (!empty($activePreset) && isset($datePresets[$activePreset])) {
|
||||||
|
return $datePresets[$activePreset]['start'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jika tidak, gunakan parameter tanggal jika disediakan
|
||||||
|
$startDate = isset($_GET['start_date']) && !empty($_GET['start_date'])
|
||||||
|
? $_GET['start_date']
|
||||||
|
: date('Y-m-01'); // Default ke hari pertama bulan ini
|
||||||
|
|
||||||
|
// Pastikan tanggal dalam format YYYY-MM-DD
|
||||||
|
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $startDate)) {
|
||||||
|
$startDate = date('Y-m-01');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $startDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEndDate() {
|
||||||
|
global $datePresets, $activePreset;
|
||||||
|
|
||||||
|
// Jika preset dipilih, gunakan tanggal preset
|
||||||
|
if (!empty($activePreset) && isset($datePresets[$activePreset])) {
|
||||||
|
return $datePresets[$activePreset]['end'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jika tidak, gunakan parameter tanggal jika disediakan
|
||||||
|
$endDate = isset($_GET['end_date']) && !empty($_GET['end_date'])
|
||||||
|
? $_GET['end_date']
|
||||||
|
: date('Y-m-d'); // Default ke hari ini
|
||||||
|
|
||||||
|
// Pastikan tanggal dalam format YYYY-MM-DD
|
||||||
|
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $endDate)) {
|
||||||
|
$endDate = date('Y-m-d');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $endDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
$startDate = getStartDate();
|
||||||
|
$endDate = getEndDate();
|
||||||
|
|
||||||
|
// Pastikan tanggal mulai tidak setelah tanggal akhir
|
||||||
|
if (strtotime($startDate) > strtotime($endDate)) {
|
||||||
|
$temp = $startDate;
|
||||||
|
$startDate = $endDate;
|
||||||
|
$endDate = $temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fungsi untuk mendapatkan kueri yang sesuai dengan peran dengan penanganan tidak ada data yang lebih baik
|
||||||
|
function getReportQueries($startDate, $endDate, $isAdmin = false) {
|
||||||
|
global $conn;
|
||||||
|
|
||||||
|
// Tambahkan komponen waktu untuk membuat rentang tanggal inklusif
|
||||||
|
$startDateTime = $startDate . ' 00:00:00';
|
||||||
|
$endDateTime = $endDate . ' 23:59:59';
|
||||||
|
|
||||||
|
// Siapkan kueri transaksi dasar dengan filter tanggal
|
||||||
|
$baseQuery = "SELECT t.id_transaksi, t.kode_transaksi, t.tanggal, t.total_item,
|
||||||
|
t.subtotal, t.total, t.metode_pembayaran, t.cash_amount, t.change_amount,
|
||||||
|
COUNT(DISTINCT dt.id_barang) as total_products
|
||||||
|
FROM transaksi t
|
||||||
|
LEFT JOIN detail_transaksi dt ON t.id_transaksi = dt.id_transaksi
|
||||||
|
WHERE t.tanggal BETWEEN ? AND ?";
|
||||||
|
|
||||||
|
// Admin melihat semuanya
|
||||||
|
if ($isAdmin) {
|
||||||
|
$transactionQuery = $baseQuery . " GROUP BY t.id_transaksi ORDER BY t.tanggal DESC";
|
||||||
|
}
|
||||||
|
// Karyawan melihat data terbatas dan hanya transaksi terbaru
|
||||||
|
else {
|
||||||
|
// Untuk karyawan, kami membatasi jumlah catatan
|
||||||
|
$transactionQuery = $baseQuery . " GROUP BY t.id_transaksi ORDER BY t.tanggal DESC LIMIT 100";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Siapkan dan jalankan kueri transaksi
|
||||||
|
$stmt = mysqli_prepare($conn, $transactionQuery);
|
||||||
|
|
||||||
|
if (!$stmt) {
|
||||||
|
// Tangani kesalahan persiapan kueri
|
||||||
|
return [
|
||||||
|
'result' => false,
|
||||||
|
'paymentResult' => null,
|
||||||
|
'totals' => [
|
||||||
|
'total_transactions' => 0,
|
||||||
|
'total_items' => 0,
|
||||||
|
'total_subtotal' => 0,
|
||||||
|
'grand_total' => 0
|
||||||
|
],
|
||||||
|
'error' => 'Gagal menyiapkan kueri: ' . mysqli_error($conn),
|
||||||
|
'has_data' => false
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_stmt_bind_param($stmt, "ss", $startDateTime, $endDateTime);
|
||||||
|
$execResult = mysqli_stmt_execute($stmt);
|
||||||
|
|
||||||
|
if (!$execResult) {
|
||||||
|
// Tangani kesalahan eksekusi kueri
|
||||||
|
return [
|
||||||
|
'result' => false,
|
||||||
|
'paymentResult' => null,
|
||||||
|
'totals' => [
|
||||||
|
'total_transactions' => 0,
|
||||||
|
'total_items' => 0,
|
||||||
|
'total_subtotal' => 0,
|
||||||
|
'grand_total' => 0
|
||||||
|
],
|
||||||
|
'error' => 'Gagal mengeksekusi kueri: ' . mysqli_stmt_error($stmt),
|
||||||
|
'has_data' => false
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = mysqli_stmt_get_result($stmt);
|
||||||
|
$rowCount = mysqli_num_rows($result);
|
||||||
|
|
||||||
|
// Dapatkan total untuk ringkasan
|
||||||
|
$totalQuery = "SELECT
|
||||||
|
COUNT(id_transaksi) as total_transactions,
|
||||||
|
SUM(total_item) as total_items,
|
||||||
|
SUM(subtotal) as total_subtotal,
|
||||||
|
SUM(total) as grand_total
|
||||||
|
FROM transaksi
|
||||||
|
WHERE tanggal BETWEEN ? AND ?";
|
||||||
|
|
||||||
|
$totalStmt = mysqli_prepare($conn, $totalQuery);
|
||||||
|
|
||||||
|
if (!$totalStmt) {
|
||||||
|
// Tangani kesalahan persiapan kueri total
|
||||||
|
return [
|
||||||
|
'result' => $result,
|
||||||
|
'paymentResult' => null,
|
||||||
|
'totals' => [
|
||||||
|
'total_transactions' => 0,
|
||||||
|
'total_items' => 0,
|
||||||
|
'total_subtotal' => 0,
|
||||||
|
'grand_total' => 0
|
||||||
|
],
|
||||||
|
'error' => 'Gagal menyiapkan kueri total: ' . mysqli_error($conn),
|
||||||
|
'has_data' => ($rowCount > 0)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_stmt_bind_param($totalStmt, "ss", $startDateTime, $endDateTime);
|
||||||
|
$totalExecResult = mysqli_stmt_execute($totalStmt);
|
||||||
|
|
||||||
|
if (!$totalExecResult) {
|
||||||
|
// Tangani kesalahan eksekusi kueri total
|
||||||
|
return [
|
||||||
|
'result' => $result,
|
||||||
|
'paymentResult' => null,
|
||||||
|
'totals' => [
|
||||||
|
'total_transactions' => 0,
|
||||||
|
'total_items' => 0,
|
||||||
|
'total_subtotal' => 0,
|
||||||
|
'grand_total' => 0
|
||||||
|
],
|
||||||
|
'error' => 'Gagal mengeksekusi kueri total: ' . mysqli_stmt_error($totalStmt),
|
||||||
|
'has_data' => ($rowCount > 0)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$totalResult = mysqli_stmt_get_result($totalStmt);
|
||||||
|
$totals = mysqli_fetch_assoc($totalResult);
|
||||||
|
|
||||||
|
// Tangani kasus di mana tidak ada data yang ditemukan
|
||||||
|
if (!$totals) {
|
||||||
|
$totals = [
|
||||||
|
'total_transactions' => 0,
|
||||||
|
'total_items' => 0,
|
||||||
|
'total_subtotal' => 0,
|
||||||
|
'grand_total' => 0
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Untuk pengguna non-admin, masker data keuangan tertentu
|
||||||
|
if (!$isAdmin && $totals) {
|
||||||
|
// Hanya tampilkan kuantitas, sembunyikan angka keuangan
|
||||||
|
if (!canAccessFeature('view_financial')) {
|
||||||
|
$totals['total_subtotal'] = 0;
|
||||||
|
$totals['grand_total'] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dapatkan breakdown metode pembayaran - hanya untuk pengguna dengan izin yang tepat
|
||||||
|
$paymentResult = null;
|
||||||
|
if (($isAdmin || canAccessFeature('view_payment_methods')) && $rowCount > 0) {
|
||||||
|
$paymentQuery = "SELECT
|
||||||
|
metode_pembayaran,
|
||||||
|
COUNT(*) as count,
|
||||||
|
SUM(total) as total_amount
|
||||||
|
FROM transaksi
|
||||||
|
WHERE tanggal BETWEEN ? AND ?
|
||||||
|
GROUP BY metode_pembayaran
|
||||||
|
ORDER BY total_amount DESC";
|
||||||
|
|
||||||
|
$paymentStmt = mysqli_prepare($conn, $paymentQuery);
|
||||||
|
if ($paymentStmt) {
|
||||||
|
mysqli_stmt_bind_param($paymentStmt, "ss", $startDateTime, $endDateTime);
|
||||||
|
$paymentExecResult = mysqli_stmt_execute($paymentStmt);
|
||||||
|
|
||||||
|
if ($paymentExecResult) {
|
||||||
|
$paymentResult = mysqli_stmt_get_result($paymentStmt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'result' => $result,
|
||||||
|
'paymentResult' => $paymentResult,
|
||||||
|
'totals' => $totals,
|
||||||
|
'has_data' => ($rowCount > 0)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fungsi untuk memeriksa apakah karyawan memiliki izin untuk fitur laporan tertentu
|
||||||
|
function canAccessFeature($feature) {
|
||||||
|
// Izin default berdasarkan peran
|
||||||
|
$permissions = [
|
||||||
|
'admin' => [
|
||||||
|
'export_excel' => true,
|
||||||
|
'export_pdf' => true,
|
||||||
|
'print_report' => true,
|
||||||
|
'view_financial' => true,
|
||||||
|
'view_payment_methods' => true,
|
||||||
|
'view_all_time' => true,
|
||||||
|
'view_summary' => true
|
||||||
|
],
|
||||||
|
'manager' => [
|
||||||
|
'export_excel' => true,
|
||||||
|
'export_pdf' => false,
|
||||||
|
'print_report' => true,
|
||||||
|
'view_financial' => true,
|
||||||
|
'view_payment_methods' => true,
|
||||||
|
'view_all_time' => false,
|
||||||
|
'view_summary' => true
|
||||||
|
],
|
||||||
|
'employee' => [
|
||||||
|
'export_excel' => false,
|
||||||
|
'export_pdf' => false,
|
||||||
|
'print_report' => false,
|
||||||
|
'view_financial' => false,
|
||||||
|
'view_payment_methods' => false,
|
||||||
|
'view_all_time' => false,
|
||||||
|
'view_summary' => false
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$role = getUserRole();
|
||||||
|
|
||||||
|
// Jika peran tidak ada dalam izin kami, default ke karyawan
|
||||||
|
if (!isset($permissions[$role])) {
|
||||||
|
$role = 'employee';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kembalikan status izin
|
||||||
|
return isset($permissions[$role][$feature]) ? $permissions[$role][$feature] : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dapatkan kueri yang sesuai berdasarkan peran pengguna
|
||||||
|
$queryData = getReportQueries($startDate, $endDate, $isAdmin);
|
||||||
|
$result = $queryData['result'];
|
||||||
|
$paymentResult = $queryData['paymentResult'];
|
||||||
|
$totals = $queryData['totals'];
|
||||||
|
|
||||||
|
// Dapatkan peran pengguna dari sesi untuk izin menu
|
||||||
|
$userRole = getUserRole();
|
||||||
|
|
||||||
|
// Menangani permintaan ekspor PDF
|
||||||
|
if (isset($_POST['export_pdf']) || (isset($_GET['export']) && $_GET['export'] == 'pdf')) {
|
||||||
|
// Periksa izin
|
||||||
|
if (!($isAdmin || canAccessFeature('export_pdf'))) {
|
||||||
|
die("Akses ditolak. Anda tidak memiliki izin untuk mengekspor ke PDF.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dapatkan parameter tanggal
|
||||||
|
$exportStartDate = isset($_POST['start_date']) ? $_POST['start_date'] : (isset($_GET['start_date']) ? $_GET['start_date'] : $startDate);
|
||||||
|
$exportEndDate = isset($_POST['end_date']) ? $_POST['end_date'] : (isset($_GET['end_date']) ? $_GET['end_date'] : $endDate);
|
||||||
|
|
||||||
|
// Tetapkan header untuk PDF (memaksa unduhan)
|
||||||
|
header('Content-Type: text/html; charset=utf-8');
|
||||||
|
|
||||||
|
// Mulai output buffering
|
||||||
|
ob_start();
|
||||||
|
|
||||||
|
// Konten HTML ramah PDF
|
||||||
|
echo '<!DOCTYPE html>';
|
||||||
|
echo '<html>';
|
||||||
|
echo '<head>';
|
||||||
|
echo '<meta charset="UTF-8">';
|
||||||
|
echo '<title>Laporan Penjualan</title>';
|
||||||
|
echo '<style>';
|
||||||
|
echo 'body { font-family: Arial, sans-serif; margin: 20px; }';
|
||||||
|
echo 'table { border-collapse: collapse; width: 100%; margin-bottom: 20px; }';
|
||||||
|
echo 'th, td { border: 1px solid #000; padding: 8px; text-align: left; }';
|
||||||
|
echo 'th { background-color: #f2f2f2; font-weight: bold; }';
|
||||||
|
echo '.header { text-align: center; margin-bottom: 30px; }';
|
||||||
|
echo '.summary { margin: 20px 0; }';
|
||||||
|
echo '.footer { text-align: center; margin-top: 30px; font-size: 12px; color: #666; }';
|
||||||
|
echo '@media print { .no-print { display: none; } }';
|
||||||
|
echo '</style>';
|
||||||
|
echo '</head>';
|
||||||
|
echo '<body>';
|
||||||
|
|
||||||
|
// Tambahkan tombol cetak
|
||||||
|
echo '<div class="no-print" style="text-align: right; margin-bottom: 20px;">';
|
||||||
|
echo '<button onclick="window.print()">Cetak laporan ini</button>';
|
||||||
|
echo '<p>Setelah mencetak, gunakan dialog cetak browser Anda untuk menyimpan sebagai PDF</p>';
|
||||||
|
echo '</div>';
|
||||||
|
|
||||||
|
// Header laporan
|
||||||
|
echo '<div class="header">';
|
||||||
|
echo '<h1>Ayula Store - Laporan Penjualan</h1>';
|
||||||
|
echo '<h3>Periode: ' . date('d M Y', strtotime($exportStartDate)) . ' sampai ' . date('d M Y', strtotime($exportEndDate)) . '</h3>';
|
||||||
|
echo '<p>Dibuat pada: ' . date('d M Y H:i:s') . '</p>';
|
||||||
|
echo '<p>Dibuat oleh: ' . ($userRole) . '</p>';
|
||||||
|
echo '</div>';
|
||||||
|
|
||||||
|
// Jalankan kembali kueri untuk mendapatkan data baru
|
||||||
|
$exportQueryData = getReportQueries($exportStartDate, $exportEndDate, true);
|
||||||
|
$exportResult = $exportQueryData['result'];
|
||||||
|
$exportTotals = $exportQueryData['totals'];
|
||||||
|
|
||||||
|
// Bagian ringkasan
|
||||||
|
echo '<div class="summary">';
|
||||||
|
echo '<h2>Ringkasan</h2>';
|
||||||
|
echo '<table>';
|
||||||
|
echo '<tr><th style="width: 200px;">Total Transaksi</th><td>' . number_format($exportTotals['total_transactions']) . '</td></tr>';
|
||||||
|
echo '<tr><th>Total Item Terjual</th><td>' . number_format($exportTotals['total_items']) . '</td></tr>';
|
||||||
|
echo '<tr><th>Subtotal</th><td>Rp. ' . number_format($exportTotals['total_subtotal']) . '</td></tr>';
|
||||||
|
echo '<tr><th>Total</th><td>Rp. ' . number_format($exportTotals['grand_total']) . '</td></tr>';
|
||||||
|
|
||||||
|
$daysDiff = (strtotime($exportEndDate) - strtotime($exportStartDate)) / (60 * 60 * 24) + 1;
|
||||||
|
$dailyAvg = $daysDiff > 0 ? $exportTotals['grand_total'] / $daysDiff : 0;
|
||||||
|
echo '<tr><th>Rata-rata Harian</th><td>Rp. ' . number_format($dailyAvg) . '</td></tr>';
|
||||||
|
echo '</table>';
|
||||||
|
echo '</div>';
|
||||||
|
|
||||||
|
// Tabel transaksi
|
||||||
|
echo '<h2>Transaksi</h2>';
|
||||||
|
echo '<table>';
|
||||||
|
echo '<thead>';
|
||||||
|
echo '<tr>';
|
||||||
|
echo '<th>ID Transaksi</th>';
|
||||||
|
echo '<th>Tanggal</th>';
|
||||||
|
echo '<th>Item</th>';
|
||||||
|
echo '<th>Subtotal</th>';
|
||||||
|
echo '<th>Total</th>';
|
||||||
|
echo '<th>Pembayaran</th>';
|
||||||
|
echo '</tr>';
|
||||||
|
echo '</thead>';
|
||||||
|
echo '<tbody>';
|
||||||
|
|
||||||
|
if ($exportResult && mysqli_num_rows($exportResult) > 0) {
|
||||||
|
mysqli_data_seek($exportResult, 0); // Reset pointer ke awal set hasil
|
||||||
|
while ($row = mysqli_fetch_assoc($exportResult)) {
|
||||||
|
echo '<tr>';
|
||||||
|
echo '<td>' . $row['kode_transaksi'] . '</td>';
|
||||||
|
echo '<td>' . date('d M Y H:i', strtotime($row['tanggal'])) . '</td>';
|
||||||
|
echo '<td>' . $row['total_item'] . '</td>';
|
||||||
|
echo '<td>Rp. ' . number_format($row['subtotal']) . '</td>';
|
||||||
|
echo '<td>Rp. ' . number_format($row['total']) . '</td>';
|
||||||
|
echo '<td>' . $row['metode_pembayaran'] . '</td>';
|
||||||
|
echo '</tr>';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo '<tr><td colspan="7" align="center">Tidak ada transaksi ditemukan</td></tr>';
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '</tbody>';
|
||||||
|
echo '</table>';
|
||||||
|
|
||||||
|
// Footer
|
||||||
|
echo '<div class="footer">';
|
||||||
|
echo '<p>Ayula Store © ' . date('Y') . ' - Hak Cipta Dilindungi</p>';
|
||||||
|
echo '<p>Laporan ini bersifat rahasia dan ditujukan hanya untuk personel yang berwenang.</p>';
|
||||||
|
echo '</div>';
|
||||||
|
|
||||||
|
echo '</body>';
|
||||||
|
echo '</html>';
|
||||||
|
|
||||||
|
// Keluarkan buffer dan akhiri
|
||||||
|
ob_end_flush();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
@ -0,0 +1,270 @@
|
||||||
|
/* Custom styles for toolbar */
|
||||||
|
.multi-select-toolbar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0; /* Position at the top */
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #ff9f43; /* Brand color */
|
||||||
|
color: #ffffff; /* White text */
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 15px;
|
||||||
|
display: none;
|
||||||
|
z-index: 9999; /* Highest z-index to ensure it's above everything else */
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multi-select-toolbar.active {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjust all other page content when toolbar is active */
|
||||||
|
body.toolbar-active .header {
|
||||||
|
margin-top: 60px; /* Height of toolbar + padding */
|
||||||
|
}
|
||||||
|
|
||||||
|
body.toolbar-active .sidebar {
|
||||||
|
padding-top: 60px; /* Push sidebar content down */
|
||||||
|
}
|
||||||
|
|
||||||
|
body.toolbar-active .page-wrapper {
|
||||||
|
padding-top: 60px; /* Push main content down when toolbar is visible */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make sure text is fully visible in toolbar */
|
||||||
|
.multi-select-toolbar .selected-count {
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 5px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multi-select-toolbar .toolbar-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multi-select-toolbar .toolbar-actions button {
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: 8px 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
/* Ensure the date picker stays on top */
|
||||||
|
.datetimepicker {
|
||||||
|
z-index: 999999 !important; /* Ensure it's on top of other elements */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom styles for dashboard stats */
|
||||||
|
.dash-widget-icon {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 24px;
|
||||||
|
background-color: rgba(255, 159, 67, 0.2);
|
||||||
|
color: #ff9f43;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dash-count {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dash-widget {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dash-widget:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Report specific styles */
|
||||||
|
.report-card {
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 5px 15px rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-card .card-header {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-summary {
|
||||||
|
background-color: #f8fbff;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-summary-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 8px 0;
|
||||||
|
border-bottom: 1px dotted #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-summary-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Role-based styling */
|
||||||
|
.admin-only {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin .admin-only {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.employee .sensitive-financial {
|
||||||
|
filter: blur(4px);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.employee .sensitive-financial:hover::after {
|
||||||
|
content: "Restricted data";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
background: rgba(255,0,0,0.2);
|
||||||
|
padding: 2px 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Employee specific warning */
|
||||||
|
.employee-warning {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.employee .employee-warning {
|
||||||
|
display: block;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #fff8e1;
|
||||||
|
border-left: 4px solid #ffc107;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disabled buttons for employees */
|
||||||
|
.employee .disabled-for-employee {
|
||||||
|
opacity: 0.6;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.employee .request-only {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin .request-only {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal styling for employee permissions */
|
||||||
|
#permissionModal .modal-header {
|
||||||
|
border-bottom: 3px solid #dc3545;
|
||||||
|
background-color: #fff8f8;
|
||||||
|
color: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
#permissionModal .modal-body {
|
||||||
|
padding: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#permissionModal .modal-footer {
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
#permissionModal .fas.fa-lock {
|
||||||
|
color: #dc3545;
|
||||||
|
animation: pulse 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
#permissionModal .btn-secondary {
|
||||||
|
background-color: #6c757d;
|
||||||
|
border-color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
#permissionModal .btn-secondary:hover {
|
||||||
|
background-color: #5a6268;
|
||||||
|
border-color: #545b62;
|
||||||
|
}
|
||||||
|
|
||||||
|
#permissionModal #actionDetails {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-left: 4px solid #dc3545;
|
||||||
|
padding: 10px 15px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.1);
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInScale {
|
||||||
|
from {
|
||||||
|
transform: scale(0.5);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Print specific styles */
|
||||||
|
@media print {
|
||||||
|
.sidebar, .header, .no-print, #filter_inputs, .wordset, .search-set {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-wrapper {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
box-shadow: none !important;
|
||||||
|
margin-bottom: 1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table th, .table td {
|
||||||
|
padding: 0.25rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-header {
|
||||||
|
display: block !important;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-header h2 {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,358 @@
|
||||||
|
// salesreport.js - Versi yang ditingkatkan dengan penanganan tidak ada data
|
||||||
|
$(document).ready(function () {
|
||||||
|
// Inisialisasi datatable dengan penanganan kesalahan
|
||||||
|
try {
|
||||||
|
$('.datanew').DataTable({
|
||||||
|
responsive: true,
|
||||||
|
language: {
|
||||||
|
search: '<span>Cari:</span> _INPUT_',
|
||||||
|
searchPlaceholder: 'Cari transaksi...',
|
||||||
|
lengthMenu: '<span>Tampilkan:</span> _MENU_',
|
||||||
|
paginate: {
|
||||||
|
'first': 'Pertama',
|
||||||
|
'last': 'Terakhir',
|
||||||
|
'next': '>',
|
||||||
|
'previous': '<'
|
||||||
|
},
|
||||||
|
info: "Menampilkan _START_ sampai _END_ dari _TOTAL_ entri",
|
||||||
|
infoEmpty: "Menampilkan 0 sampai 0 dari 0 entri",
|
||||||
|
infoFiltered: "(disaring dari _MAX_ entri total)",
|
||||||
|
emptyTable: "Tidak ada data tersedia untuk periode yang dipilih",
|
||||||
|
zeroRecords: "Tidak ditemukan catatan yang cocok",
|
||||||
|
processing: "Memproses..."
|
||||||
|
},
|
||||||
|
dom: '<"top"fl>rt<"bottom"ip><"clear">',
|
||||||
|
lengthMenu: [10, 25, 50, 100],
|
||||||
|
pageLength: 10,
|
||||||
|
// Penanganan kesalahan untuk DataTables
|
||||||
|
drawCallback: function() {
|
||||||
|
// Sembunyikan loading overlay saat data siap
|
||||||
|
if (document.getElementById('loading-overlay')) {
|
||||||
|
document.getElementById('loading-overlay').style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Kesalahan inisialisasi DataTable:", e);
|
||||||
|
// Sembunyikan loading overlay jika terjadi kesalahan
|
||||||
|
if (document.getElementById('loading-overlay')) {
|
||||||
|
document.getElementById('loading-overlay').style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Menangani pemilihan preset tanggal
|
||||||
|
$('#preset').on('change', function() {
|
||||||
|
if ($(this).val() === '') {
|
||||||
|
// Rentang tanggal kustom dipilih - tampilkan input tanggal kustom
|
||||||
|
$('#custom-date-inputs').show();
|
||||||
|
} else {
|
||||||
|
// Preset dipilih - sembunyikan input tanggal kustom dan tampilkan loading overlay
|
||||||
|
$('#custom-date-inputs').hide();
|
||||||
|
|
||||||
|
// Tampilkan loading overlay sebelum mengirimkan
|
||||||
|
if (document.getElementById('loading-overlay')) {
|
||||||
|
document.getElementById('loading-overlay').style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tambahkan penundaan kecil sebelum mengirimkan untuk memastikan loading overlay terlihat
|
||||||
|
setTimeout(function() {
|
||||||
|
$('#date-filter-form').submit();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Validasi form dasar
|
||||||
|
$('#date-filter-form').on('submit', function(e) {
|
||||||
|
// Tampilkan loading overlay
|
||||||
|
if (document.getElementById('loading-overlay')) {
|
||||||
|
document.getElementById('loading-overlay').style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hanya validasi jika rentang tanggal kustom dipilih
|
||||||
|
if ($('#preset').val() === '') {
|
||||||
|
var startDate = $('#start_date').val();
|
||||||
|
var endDate = $('#end_date').val();
|
||||||
|
|
||||||
|
if (!startDate || !endDate) {
|
||||||
|
alert('Silakan pilih tanggal mulai dan tanggal akhir');
|
||||||
|
// Sembunyikan loading overlay jika validasi gagal
|
||||||
|
if (document.getElementById('loading-overlay')) {
|
||||||
|
document.getElementById('loading-overlay').style.display = 'none';
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new Date(startDate) > new Date(endDate)) {
|
||||||
|
alert('Tanggal mulai tidak boleh lebih besar dari tanggal akhir');
|
||||||
|
// Sembunyikan loading overlay jika validasi gagal
|
||||||
|
if (document.getElementById('loading-overlay')) {
|
||||||
|
document.getElementById('loading-overlay').style.display = 'none';
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fungsi ekspor
|
||||||
|
$('.pdf-export').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Tampilkan loading overlay
|
||||||
|
if (document.getElementById('loading-overlay')) {
|
||||||
|
document.getElementById('loading-overlay').style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Periksa apakah pengguna memiliki izin (dikontrol oleh kelas CSS)
|
||||||
|
if ($(this).hasClass('disabled-for-employee')) {
|
||||||
|
showPermissionModal('Ekspor PDF', 'Laporan ini tidak dapat diekspor ke PDF oleh akun karyawan.');
|
||||||
|
// Sembunyikan loading overlay jika pemeriksaan izin gagal
|
||||||
|
if (document.getElementById('loading-overlay')) {
|
||||||
|
document.getElementById('loading-overlay').style.display = 'none';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jika kita memiliki izin, lanjutkan dengan ekspor
|
||||||
|
exportTableToPDF();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.excel-export').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Tampilkan loading overlay
|
||||||
|
if (document.getElementById('loading-overlay')) {
|
||||||
|
document.getElementById('loading-overlay').style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Periksa apakah pengguna memiliki izin (dikontrol oleh kelas CSS)
|
||||||
|
if ($(this).hasClass('disabled-for-employee')) {
|
||||||
|
showPermissionModal('Ekspor Excel', 'Laporan ini tidak dapat diekspor ke Excel oleh akun karyawan.');
|
||||||
|
// Sembunyikan loading overlay jika pemeriksaan izin gagal
|
||||||
|
if (document.getElementById('loading-overlay')) {
|
||||||
|
document.getElementById('loading-overlay').style.display = 'none';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jika kita memiliki izin, lanjutkan dengan ekspor
|
||||||
|
exportTableToExcel();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.print-report').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Periksa apakah pengguna memiliki izin (dikontrol oleh kelas CSS)
|
||||||
|
if ($(this).hasClass('disabled-for-employee')) {
|
||||||
|
showPermissionModal('Cetak Laporan', 'Pencetakan laporan dibatasi hanya untuk akun administrator.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jika kita memiliki izin, lanjutkan dengan cetak
|
||||||
|
printReport();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Untuk akses karyawan - menangani upaya tindakan terbatas
|
||||||
|
$('.employee-print, .employee-export').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Dapatkan jenis tindakan dari atribut data atau default
|
||||||
|
var actionType = $(this).data('action') === 'print_attempt' ? 'Cetak Laporan' : 'Ekspor Laporan';
|
||||||
|
var message = $(this).data('action') === 'print_attempt'
|
||||||
|
? 'Pencetakan laporan dibatasi hanya untuk akun administrator.'
|
||||||
|
: 'Ekspor data dibatasi hanya untuk akun administrator.';
|
||||||
|
|
||||||
|
showPermissionModal(actionType, message);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Menangani tombol reset - tampilkan loading overlay
|
||||||
|
$('a[href="?reset=1"]').on('click', function() {
|
||||||
|
if (document.getElementById('loading-overlay')) {
|
||||||
|
document.getElementById('loading-overlay').style.display = 'flex';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fungsi untuk menampilkan modal izin
|
||||||
|
function showPermissionModal(actionType, message) {
|
||||||
|
// Atur detail tindakan
|
||||||
|
$('#actionDetails').html(
|
||||||
|
'<strong>Tindakan yang Dicoba:</strong> ' + actionType + '<br>' +
|
||||||
|
'<small>' + message + '</small>'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Tampilkan modal
|
||||||
|
var permissionModal = new bootstrap.Modal(document.getElementById('permissionModal'));
|
||||||
|
permissionModal.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fungsi untuk mencetak tanda terima
|
||||||
|
function printReceipt(transactionId) {
|
||||||
|
// Buka tanda terima di jendela baru untuk pencetakan
|
||||||
|
var printWindow = window.open('../../transaction/transaction_success.php?id=' + transactionId + '&print=true', '_blank', 'width=400,height=600');
|
||||||
|
|
||||||
|
// Cetak otomatis setelah konten dimuat
|
||||||
|
printWindow.onload = function () {
|
||||||
|
setTimeout(function () {
|
||||||
|
printWindow.print();
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fungsi untuk mencetak laporan saat ini
|
||||||
|
function printReport() {
|
||||||
|
// Tambahkan gaya khusus untuk pencetakan
|
||||||
|
$('<style>')
|
||||||
|
.attr('type', 'text/css')
|
||||||
|
.html('@media print { ' +
|
||||||
|
'.no-print, .dataTables_filter, .dataTables_length, .dataTables_paginate, .sidebar, .header, #sidebar, #mobile_btn { display: none !important; } ' +
|
||||||
|
'.page-wrapper { margin-left: 0 !important; padding: 20px !important; } ' +
|
||||||
|
'.card { border: none !important; box-shadow: none !important; } ' +
|
||||||
|
'thead { background-color: #f8f9fa !important; -webkit-print-color-adjust: exact !important; color-adjust: exact !important; } ' +
|
||||||
|
'table { width: 100% !important; } ' +
|
||||||
|
'.table th, .table td { padding: 0.25rem !important; } ' +
|
||||||
|
'.report-header { text-align: center; margin-bottom: 20px; } ' +
|
||||||
|
'.report-header h3 { margin-bottom: 5px; } ' +
|
||||||
|
'}')
|
||||||
|
.appendTo('head');
|
||||||
|
|
||||||
|
// Tambahkan header laporan untuk pencetakan
|
||||||
|
if ($('.report-header').length === 0) {
|
||||||
|
// Dapatkan rentang tanggal untuk header laporan
|
||||||
|
var startDate = $('#start_date').val() || $('.alert-info strong').next().text().split(' sampai ')[0];
|
||||||
|
var endDate = $('#end_date').val() || $('.alert-info strong').next().text().split(' sampai ')[1];
|
||||||
|
|
||||||
|
$('.content').prepend(
|
||||||
|
'<div class="report-header no-screen" style="display:none;">' +
|
||||||
|
'<h2>Ayula Store - Laporan Penjualan</h2>' +
|
||||||
|
'<p>Periode: ' + startDate + ' sampai ' + endDate + '</p>' +
|
||||||
|
'<p>Dibuat pada: ' + new Date().toLocaleDateString() + '</p>' +
|
||||||
|
'</div>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.print();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fungsi untuk mengekspor tabel ke Excel
|
||||||
|
function exportTableToExcel() {
|
||||||
|
// Dapatkan data tabel
|
||||||
|
var table = $('.datanew').DataTable();
|
||||||
|
var data = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Dapatkan semua baris dari tabel (tidak hanya halaman saat ini)
|
||||||
|
table.rows().every(function() {
|
||||||
|
data.push(this.data());
|
||||||
|
});
|
||||||
|
|
||||||
|
var headers = [];
|
||||||
|
|
||||||
|
// Dapatkan header
|
||||||
|
$('.datanew thead th').each(function () {
|
||||||
|
// Lewati kolom Aksi
|
||||||
|
if ($(this).text() !== 'Aksi') {
|
||||||
|
headers.push($(this).text());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Buat konten CSV
|
||||||
|
var csvContent = "data:text/csv;charset=utf-8," + headers.join(",") + "\n";
|
||||||
|
|
||||||
|
// Tambahkan baris data
|
||||||
|
for (var i = 0; i < data.length; i++) {
|
||||||
|
var row = [];
|
||||||
|
for (var j = 0; j < data[i].length - 1; j++) { // Lewati kolom terakhir (Aksi)
|
||||||
|
// Bersihkan data (hapus tag HTML)
|
||||||
|
var cellData = data[i][j].toString().replace(/<[^>]*>/g, '').trim();
|
||||||
|
// Ganti beberapa spasi dengan satu spasi
|
||||||
|
cellData = cellData.replace(/\s+/g, ' ');
|
||||||
|
// Bungkus dalam tanda kutip dan hindari tanda kutip internal
|
||||||
|
row.push('"' + cellData.replace(/"/g, '""') + '"');
|
||||||
|
}
|
||||||
|
csvContent += row.join(",") + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buat tautan unduhan
|
||||||
|
var encodedUri = encodeURI(csvContent);
|
||||||
|
var link = document.createElement("a");
|
||||||
|
link.setAttribute("href", encodedUri);
|
||||||
|
|
||||||
|
// Gunakan tanggal saat ini dalam nama file
|
||||||
|
var today = new Date();
|
||||||
|
var dateStr = today.getFullYear() + '-' +
|
||||||
|
('0' + (today.getMonth()+1)).slice(-2) + '-' +
|
||||||
|
('0' + today.getDate()).slice(-2);
|
||||||
|
|
||||||
|
link.setAttribute("download", "laporan_penjualan_" + dateStr + ".csv");
|
||||||
|
document.body.appendChild(link);
|
||||||
|
|
||||||
|
// Unduh file
|
||||||
|
link.click();
|
||||||
|
|
||||||
|
// Bersihkan
|
||||||
|
document.body.removeChild(link);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error mengekspor ke Excel:", e);
|
||||||
|
alert("Terjadi kesalahan saat mengekspor. Silakan coba lagi.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sembunyikan loading overlay saat ekspor selesai
|
||||||
|
if (document.getElementById('loading-overlay')) {
|
||||||
|
document.getElementById('loading-overlay').style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fungsi untuk mengekspor tabel ke PDF
|
||||||
|
function exportTableToPDF() {
|
||||||
|
try {
|
||||||
|
// Buat form untuk pembuatan di sisi server
|
||||||
|
var form = $('<form action="" method="post" target="_blank"></form>');
|
||||||
|
form.append('<input type="hidden" name="export_pdf" value="1">');
|
||||||
|
form.append('<input type="hidden" name="start_date" value="' + $('#start_date').val() + '">');
|
||||||
|
form.append('<input type="hidden" name="end_date" value="' + $('#end_date').val() + '">');
|
||||||
|
|
||||||
|
// Tambahkan form ke dokumen dan kirimkan
|
||||||
|
$('body').append(form);
|
||||||
|
form.submit();
|
||||||
|
form.remove();
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error mengekspor ke PDF:", e);
|
||||||
|
alert("Terjadi kesalahan saat mengekspor ke PDF. Silakan coba lagi.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sembunyikan loading overlay setelah 2 detik (karena pembuatan PDF terjadi di tab baru)
|
||||||
|
setTimeout(function() {
|
||||||
|
if (document.getElementById('loading-overlay')) {
|
||||||
|
document.getElementById('loading-overlay').style.display = 'none';
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otomatis sembunyikan loading overlay saat halaman dimuat sepenuhnya
|
||||||
|
$(window).on('load', function() {
|
||||||
|
if (document.getElementById('loading-overlay')) {
|
||||||
|
setTimeout(function() {
|
||||||
|
document.getElementById('loading-overlay').style.display = 'none';
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Failsafe timeout untuk menyembunyikan loading overlay setelah 10 detik
|
||||||
|
setTimeout(function() {
|
||||||
|
if (document.getElementById('loading-overlay')) {
|
||||||
|
document.getElementById('loading-overlay').style.display = 'none';
|
||||||
|
}
|
||||||
|
}, 10000);
|
||||||
|
|
||||||
|
// Nonaktifkan console logs dalam produksi
|
||||||
|
if (window.location.hostname !== 'localhost') {
|
||||||
|
console.log = function() {}; // Nonaktifkan console logs
|
||||||
|
console.warn = function() {}; // Nonaktifkan console warnings
|
||||||
|
console.error = function() {}; // Nonaktifkan console errors
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,265 @@
|
||||||
|
/**
|
||||||
|
* Styling for Report Module
|
||||||
|
* Ayula Store
|
||||||
|
*/
|
||||||
|
|
||||||
|
.card-stats {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-stats:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-icon {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 24px;
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-widget-three {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-widget-three .order-summary-content h2 {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-widget-three .order-summary-content span {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-filter-card {
|
||||||
|
background: #f9f9f9;
|
||||||
|
border-radius: 10px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-filter-card:hover {
|
||||||
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-filter-card .card-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-filter-card .form-group label {
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-filter-card .form-control {
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: none;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-filter-card .input-group-text {
|
||||||
|
background-color: #f0f3f5;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table th {
|
||||||
|
background-color: #5682d6 !important;
|
||||||
|
color: white !important;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
vertical-align: middle;
|
||||||
|
padding: 12px 15px;
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table td {
|
||||||
|
vertical-align: middle;
|
||||||
|
padding: 12px 15px;
|
||||||
|
border-color: #f0f0f0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table tbody tr:hover {
|
||||||
|
background-color: #f9fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receipt-thumbnail {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
border: 2px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receipt-thumbnail:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
border-color: #5682d6;
|
||||||
|
}
|
||||||
|
|
||||||
|
#receipt-modal .modal-img {
|
||||||
|
width: 100%;
|
||||||
|
max-height: 80vh;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
position: relative;
|
||||||
|
height: 300px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-btn {
|
||||||
|
margin-top: 32px;
|
||||||
|
height: 44px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-export-btn {
|
||||||
|
margin-right: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-export-btn i {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.daterangepicker {
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.daterangepicker .ranges li.active {
|
||||||
|
background-color: #5682d6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.daterangepicker td.active,
|
||||||
|
.daterangepicker td.active:hover {
|
||||||
|
background-color: #5682d6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.productimgname {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.productimgname .product-img {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-right: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.productimgname .product-img img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.productimgname a {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.productimgname a:hover {
|
||||||
|
color: #5682d6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-responsive {
|
||||||
|
overflow-x: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #ddd #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-responsive::-webkit-scrollbar {
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-responsive::-webkit-scrollbar-track {
|
||||||
|
background: #f9f9f9;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-responsive::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-selection--single {
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-selection--single .select2-selection__rendered {
|
||||||
|
line-height: 40px;
|
||||||
|
padding-left: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-container--default .select2-selection--single .select2-selection__arrow {
|
||||||
|
height: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animation for loading elements */
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate3d(0, 40px, 0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fadeInUp {
|
||||||
|
animation: fadeInUp 0.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Print styles */
|
||||||
|
@media print {
|
||||||
|
body * {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#print-template, #print-template * {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
#print-template {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,486 @@
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
// Initialize Select2 for dropdowns
|
||||||
|
$('.select').select2({
|
||||||
|
width: '100%',
|
||||||
|
placeholder: "Pilih opsi...",
|
||||||
|
allowClear: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Safe initialization of DataTable - check if it's already initialized
|
||||||
|
var reportTable;
|
||||||
|
if (!$.fn.DataTable.isDataTable('#report-table')) {
|
||||||
|
reportTable = $('#report-table').DataTable({
|
||||||
|
dom: "<'row'<'col-sm-12 col-md-6'l><'col-sm-12 col-md-6'f>>" +
|
||||||
|
"<'row'<'col-sm-12'tr>>" +
|
||||||
|
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>",
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
extend: 'copy',
|
||||||
|
text: '<i class="fas fa-copy"></i> Copy',
|
||||||
|
className: 'btn btn-sm btn-secondary'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
extend: 'csv',
|
||||||
|
text: '<i class="fas fa-file-csv"></i> CSV',
|
||||||
|
className: 'btn btn-sm btn-secondary'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
extend: 'excel',
|
||||||
|
text: '<i class="fas fa-file-excel"></i> Excel',
|
||||||
|
className: 'btn btn-sm btn-success'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
extend: 'pdf',
|
||||||
|
text: '<i class="fas fa-file-pdf"></i> PDF',
|
||||||
|
className: 'btn btn-sm btn-danger'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
extend: 'print',
|
||||||
|
text: '<i class="fas fa-print"></i> Print',
|
||||||
|
className: 'btn btn-sm btn-primary'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pageLength": 10,
|
||||||
|
"lengthMenu": [[10, 25, 50, -1], [10, 25, 50, "All"]],
|
||||||
|
"language": {
|
||||||
|
"paginate": {
|
||||||
|
"previous": "<i class='fas fa-chevron-left'></i>",
|
||||||
|
"next": "<i class='fas fa-chevron-right'></i>"
|
||||||
|
},
|
||||||
|
"search": "Search:",
|
||||||
|
"emptyTable": "Tidak ada data laporan yang tersedia",
|
||||||
|
"info": "Menampilkan _START_ sampai _END_ dari _TOTAL_ entri",
|
||||||
|
"infoEmpty": "Menampilkan 0 sampai 0 dari 0 entri",
|
||||||
|
"infoFiltered": "(difilter dari _MAX_ total entri)",
|
||||||
|
"lengthMenu": "Tampilkan _MENU_ entri",
|
||||||
|
"zeroRecords": "Tidak ditemukan data yang sesuai"
|
||||||
|
},
|
||||||
|
"order": [[1, 'desc']] // Order by date column descending
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add the DataTable buttons to the custom button container only if not already added
|
||||||
|
reportTable.buttons().container().appendTo($('.wordset ul'));
|
||||||
|
} else {
|
||||||
|
// If already initialized, just get the existing instance
|
||||||
|
reportTable = $('#report-table').DataTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search functionality
|
||||||
|
$('#search-input').on('keyup', function() {
|
||||||
|
reportTable.search(this.value).draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Filter Type Change Handler
|
||||||
|
$('#filter-type').on('change', function() {
|
||||||
|
const filterType = $(this).val();
|
||||||
|
$('.filter-option').hide();
|
||||||
|
|
||||||
|
if (filterType === 'date_range') {
|
||||||
|
$('#date-range-filter').show();
|
||||||
|
} else if (filterType === 'month') {
|
||||||
|
$('#month-filter').show();
|
||||||
|
$('#year-filter').show();
|
||||||
|
} else if (filterType === 'year') {
|
||||||
|
$('#year-filter').show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize Date Range Picker
|
||||||
|
if ($('#daterange').length && !$('#daterange').data('daterangepicker')) {
|
||||||
|
$('#daterange').daterangepicker({
|
||||||
|
opens: 'left',
|
||||||
|
autoUpdateInput: true,
|
||||||
|
locale: {
|
||||||
|
format: 'DD/MM/YYYY',
|
||||||
|
applyLabel: 'Terapkan',
|
||||||
|
cancelLabel: 'Batal',
|
||||||
|
fromLabel: 'Dari',
|
||||||
|
toLabel: 'Sampai',
|
||||||
|
customRangeLabel: 'Kustom',
|
||||||
|
daysOfWeek: ['Min', 'Sen', 'Sel', 'Rab', 'Kam', 'Jum', 'Sab'],
|
||||||
|
monthNames: ['Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni', 'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember']
|
||||||
|
},
|
||||||
|
ranges: {
|
||||||
|
'Hari Ini': [moment(), moment()],
|
||||||
|
'Kemarin': [moment().subtract(1, 'days'), moment().subtract(1, 'days')],
|
||||||
|
'7 Hari Terakhir': [moment().subtract(6, 'days'), moment()],
|
||||||
|
'30 Hari Terakhir': [moment().subtract(29, 'days'), moment()],
|
||||||
|
'Bulan Ini': [moment().startOf('month'), moment().endOf('month')],
|
||||||
|
'Bulan Lalu': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
|
||||||
|
}
|
||||||
|
}, function(start, end, label) {
|
||||||
|
$('#start_date').val(start.format('YYYY-MM-DD'));
|
||||||
|
$('#end_date').val(end.format('YYYY-MM-DD'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle receipt image modal
|
||||||
|
$('#receipt-modal').on('show.bs.modal', function (event) {
|
||||||
|
const button = $(event.relatedTarget);
|
||||||
|
const imgSrc = button.data('img');
|
||||||
|
const title = button.data('title');
|
||||||
|
|
||||||
|
const modal = $(this);
|
||||||
|
modal.find('.modal-title').text(title);
|
||||||
|
modal.find('#receipt-img').attr('src', imgSrc);
|
||||||
|
|
||||||
|
// Set download link
|
||||||
|
$('#download-receipt').off('click').on('click', function() {
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = imgSrc;
|
||||||
|
a.download = 'receipt_' + title.replace('Nota #', '') + '.jpg';
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Print functionality
|
||||||
|
$('#print-btn, #table-print').off('click').on('click', function() {
|
||||||
|
printReport();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Export to PDF button
|
||||||
|
$('#export-pdf').off('click').on('click', function() {
|
||||||
|
exportReport('pdf');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Export to Excel button
|
||||||
|
$('#export-excel').off('click').on('click', function() {
|
||||||
|
exportReport('excel');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize Category Distribution Chart
|
||||||
|
initCategoryChart();
|
||||||
|
|
||||||
|
// Initialize Value Distribution Chart
|
||||||
|
initValueChart();
|
||||||
|
|
||||||
|
// Refresh charts when filter form is submitted
|
||||||
|
$('#report-filter-form').off('submit').on('submit', function() {
|
||||||
|
refreshCharts();
|
||||||
|
return true; // Continue with form submission
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Function to print the report
|
||||||
|
function printReport() {
|
||||||
|
// Set period text
|
||||||
|
let periodText = '';
|
||||||
|
const filterType = $('#filter-type').val();
|
||||||
|
|
||||||
|
if (filterType === 'date_range') {
|
||||||
|
periodText = $('#daterange').val();
|
||||||
|
} else if (filterType === 'month') {
|
||||||
|
const month = $('select[name="filter_month"] option:selected').text();
|
||||||
|
const year = $('select[name="filter_year"]').val();
|
||||||
|
periodText = month + ' ' + year;
|
||||||
|
} else if (filterType === 'year') {
|
||||||
|
periodText = 'Tahun ' + $('select[name="filter_year"]').val();
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#print-period').text(periodText);
|
||||||
|
|
||||||
|
// Populate table body
|
||||||
|
const tableBody = $('#print-table-body');
|
||||||
|
tableBody.empty();
|
||||||
|
|
||||||
|
// Get data from the visible table
|
||||||
|
$('#report-table tbody tr').each(function() {
|
||||||
|
const cells = $(this).find('td');
|
||||||
|
|
||||||
|
// Skip if it's the "no data" row
|
||||||
|
if (cells.length <= 1) return;
|
||||||
|
|
||||||
|
const id = cells.eq(0).text();
|
||||||
|
const date = cells.eq(1).text();
|
||||||
|
const product = cells.eq(2).find('a').text();
|
||||||
|
const category = cells.eq(3).text();
|
||||||
|
const qty = cells.eq(4).text();
|
||||||
|
const price = cells.eq(5).text();
|
||||||
|
const total = cells.eq(6).text();
|
||||||
|
|
||||||
|
tableBody.append(`
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 8px; border: 1px solid #ddd;">${id}</td>
|
||||||
|
<td style="padding: 8px; border: 1px solid #ddd;">${date}</td>
|
||||||
|
<td style="padding: 8px; border: 1px solid #ddd;">${product}</td>
|
||||||
|
<td style="padding: 8px; border: 1px solid #ddd;">${category}</td>
|
||||||
|
<td style="padding: 8px; border: 1px solid #ddd;">${qty}</td>
|
||||||
|
<td style="padding: 8px; border: 1px solid #ddd;">${price}</td>
|
||||||
|
<td style="padding: 8px; border: 1px solid #ddd;">${total}</td>
|
||||||
|
</tr>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Open print dialog
|
||||||
|
const printContent = document.getElementById('print-template').innerHTML;
|
||||||
|
const printWindow = window.open('', '_blank');
|
||||||
|
printWindow.document.write(`
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Print Report</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
color: #666;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
tfoot td {
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
${printContent}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`);
|
||||||
|
printWindow.document.close();
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
printWindow.print();
|
||||||
|
printWindow.close();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to export report (PDF or Excel)
|
||||||
|
function exportReport(type) {
|
||||||
|
// Get current filter values
|
||||||
|
const filterForm = $('#report-filter-form');
|
||||||
|
const filterType = $('#filter-type').val();
|
||||||
|
const startDate = $('#start_date').val();
|
||||||
|
const endDate = $('#end_date').val();
|
||||||
|
const filterMonth = $('select[name="filter_month"]').val();
|
||||||
|
const filterYear = $('select[name="filter_year"]').val();
|
||||||
|
const productId = $('select[name="product_id"]').val();
|
||||||
|
const categoryId = $('select[name="category_id"]').val();
|
||||||
|
|
||||||
|
// Construct URL with parameters
|
||||||
|
let url = 'report_export.php?export=' + type;
|
||||||
|
|
||||||
|
if (filterType === 'date_range') {
|
||||||
|
url += '&filter_type=date_range&start_date=' + startDate + '&end_date=' + endDate;
|
||||||
|
} else if (filterType === 'month') {
|
||||||
|
url += '&filter_type=month&filter_month=' + filterMonth + '&filter_year=' + filterYear;
|
||||||
|
} else if (filterType === 'year') {
|
||||||
|
url += '&filter_type=year&filter_year=' + filterYear;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (productId) {
|
||||||
|
url += '&product_id=' + productId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (categoryId) {
|
||||||
|
url += '&category_id=' + categoryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open in new window/tab
|
||||||
|
window.open(url, '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize Category Distribution Chart
|
||||||
|
function initCategoryChart() {
|
||||||
|
const categoryCtx = document.getElementById('categoryChart');
|
||||||
|
|
||||||
|
if (!categoryCtx) return;
|
||||||
|
|
||||||
|
// Destroy existing chart if it exists
|
||||||
|
if (window.categoryChart) {
|
||||||
|
window.categoryChart.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.categoryChart = new Chart(categoryCtx.getContext('2d'), {
|
||||||
|
type: 'pie',
|
||||||
|
data: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Jumlah Items',
|
||||||
|
data: [],
|
||||||
|
backgroundColor: [
|
||||||
|
'rgba(255, 99, 132, 0.7)',
|
||||||
|
'rgba(54, 162, 235, 0.7)',
|
||||||
|
'rgba(255, 206, 86, 0.7)',
|
||||||
|
'rgba(75, 192, 192, 0.7)',
|
||||||
|
'rgba(153, 102, 255, 0.7)',
|
||||||
|
'rgba(255, 159, 64, 0.7)',
|
||||||
|
'rgba(199, 199, 199, 0.7)',
|
||||||
|
'rgba(83, 102, 255, 0.7)',
|
||||||
|
'rgba(40, 159, 64, 0.7)',
|
||||||
|
'rgba(210, 199, 199, 0.7)'
|
||||||
|
],
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: 'right',
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: function(context) {
|
||||||
|
const label = context.label || '';
|
||||||
|
const value = context.raw || 0;
|
||||||
|
const total = context.dataset.data.reduce((a, b) => a + b, 0);
|
||||||
|
const percentage = Math.round((value / total) * 100);
|
||||||
|
return `${label}: ${value} (${percentage}%)`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load initial data
|
||||||
|
const labels = [];
|
||||||
|
const data = [];
|
||||||
|
|
||||||
|
// Get data from PHP-rendered elements with special data attributes
|
||||||
|
const dataElements = document.querySelectorAll('[data-category-name]');
|
||||||
|
dataElements.forEach(element => {
|
||||||
|
labels.push(element.getAttribute('data-category-name'));
|
||||||
|
data.push(parseInt(element.getAttribute('data-category-count')));
|
||||||
|
});
|
||||||
|
|
||||||
|
// If no data elements found, try to parse from the script
|
||||||
|
if (labels.length === 0) {
|
||||||
|
try {
|
||||||
|
// This assumes there's PHP-rendered data in the original script
|
||||||
|
const categoryData = JSON.parse(document.getElementById('category-data').textContent);
|
||||||
|
categoryData.forEach(item => {
|
||||||
|
labels.push(item.name);
|
||||||
|
data.push(item.count);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Could not load category chart data', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update chart with data
|
||||||
|
window.categoryChart.data.labels = labels;
|
||||||
|
window.categoryChart.data.datasets[0].data = data;
|
||||||
|
window.categoryChart.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize Value Distribution Chart
|
||||||
|
function initValueChart() {
|
||||||
|
const valueCtx = document.getElementById('valueChart');
|
||||||
|
|
||||||
|
if (!valueCtx) return;
|
||||||
|
|
||||||
|
// Destroy existing chart if it exists
|
||||||
|
if (window.valueChart) {
|
||||||
|
window.valueChart.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.valueChart = new Chart(valueCtx.getContext('2d'), {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Nilai (Rp)',
|
||||||
|
data: [],
|
||||||
|
backgroundColor: 'rgba(54, 162, 235, 0.6)',
|
||||||
|
borderColor: 'rgba(54, 162, 235, 1)',
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
ticks: {
|
||||||
|
callback: function(value) {
|
||||||
|
return 'Rp ' + value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: function(context) {
|
||||||
|
let label = context.dataset.label || '';
|
||||||
|
if (label) {
|
||||||
|
label += ': ';
|
||||||
|
}
|
||||||
|
if (context.parsed.y !== null) {
|
||||||
|
label += 'Rp ' + context.parsed.y.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load initial data
|
||||||
|
const labels = [];
|
||||||
|
const data = [];
|
||||||
|
|
||||||
|
// Get data from PHP-rendered elements with special data attributes
|
||||||
|
const dataElements = document.querySelectorAll('[data-category-name]');
|
||||||
|
dataElements.forEach(element => {
|
||||||
|
labels.push(element.getAttribute('data-category-name'));
|
||||||
|
data.push(parseInt(element.getAttribute('data-category-amount')));
|
||||||
|
});
|
||||||
|
|
||||||
|
// If no data elements found, try to parse from the script
|
||||||
|
if (labels.length === 0) {
|
||||||
|
try {
|
||||||
|
// This assumes there's PHP-rendered data in the original script
|
||||||
|
const categoryData = JSON.parse(document.getElementById('category-data').textContent);
|
||||||
|
categoryData.forEach(item => {
|
||||||
|
labels.push(item.name);
|
||||||
|
data.push(item.amount);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Could not load value chart data', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update chart with data
|
||||||
|
window.valueChart.data.labels = labels;
|
||||||
|
window.valueChart.data.datasets[0].data = data;
|
||||||
|
window.valueChart.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh chart data from server
|
||||||
|
function refreshCharts() {
|
||||||
|
// Try to update charts with data present in the page first
|
||||||
|
try {
|
||||||
|
initCategoryChart();
|
||||||
|
initValueChart();
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Could not refresh charts with page data', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,470 @@
|
||||||
|
<?php
|
||||||
|
// Include database connection
|
||||||
|
require_once '../../routes/db_conn.php';
|
||||||
|
|
||||||
|
|
||||||
|
// Function to generate PDF report
|
||||||
|
function generatePDF($filters) {
|
||||||
|
require_once '../vendor/autoload.php'; // Require TCPDF or FPDF library
|
||||||
|
|
||||||
|
// Create new PDF document
|
||||||
|
$pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);
|
||||||
|
|
||||||
|
// Set document information
|
||||||
|
$pdf->SetCreator('Ayula Store');
|
||||||
|
$pdf->SetAuthor('Ayula Store');
|
||||||
|
$pdf->SetTitle('Laporan Inventaris');
|
||||||
|
$pdf->SetSubject('Laporan Inventaris');
|
||||||
|
$pdf->SetKeywords('Laporan, Inventaris, Ayula Store');
|
||||||
|
|
||||||
|
// Set default header data
|
||||||
|
$pdf->SetHeaderData('logo.png', 30, 'Ayula Store', 'Laporan Inventaris', array(0,64,255), array(0,64,128));
|
||||||
|
$pdf->setFooterData(array(0,64,0), array(0,64,128));
|
||||||
|
|
||||||
|
// Set header and footer fonts
|
||||||
|
$pdf->setHeaderFont(Array(PDF_FONT_NAME_MAIN, '', PDF_FONT_SIZE_MAIN));
|
||||||
|
$pdf->setFooterFont(Array(PDF_FONT_NAME_DATA, '', PDF_FONT_SIZE_DATA));
|
||||||
|
|
||||||
|
// Set default monospaced font
|
||||||
|
$pdf->SetDefaultMonospacedFont(PDF_FONT_MONOSPACED);
|
||||||
|
|
||||||
|
// Set margins
|
||||||
|
$pdf->SetMargins(PDF_MARGIN_LEFT, PDF_MARGIN_TOP, PDF_MARGIN_RIGHT);
|
||||||
|
$pdf->SetHeaderMargin(PDF_MARGIN_HEADER);
|
||||||
|
$pdf->SetFooterMargin(PDF_MARGIN_FOOTER);
|
||||||
|
|
||||||
|
// Set auto page breaks
|
||||||
|
$pdf->SetAutoPageBreak(TRUE, PDF_MARGIN_BOTTOM);
|
||||||
|
|
||||||
|
// Set image scale factor
|
||||||
|
$pdf->setImageScale(PDF_IMAGE_SCALE_RATIO);
|
||||||
|
|
||||||
|
// Add a page
|
||||||
|
$pdf->AddPage();
|
||||||
|
|
||||||
|
// Set font
|
||||||
|
$pdf->SetFont('helvetica', '', 10);
|
||||||
|
|
||||||
|
// Get report data
|
||||||
|
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
|
||||||
|
if ($conn->connect_error) {
|
||||||
|
die("Connection failed: " . $conn->connect_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build query based on filters
|
||||||
|
$sql = "SELECT r.id_report, r.tanggal, r.jumlah, r.harga, r.image,
|
||||||
|
b.nama_barang, b.kode_barang,
|
||||||
|
jb.nama_jenis
|
||||||
|
FROM report r
|
||||||
|
JOIN barang b ON r.id_barang = b.id_barang
|
||||||
|
JOIN jenis_barang jb ON b.id_jenis = jb.id_jenis
|
||||||
|
WHERE 1=1";
|
||||||
|
|
||||||
|
$params = array();
|
||||||
|
$types = "";
|
||||||
|
|
||||||
|
if (!empty($filters['start_date']) && !empty($filters['end_date'])) {
|
||||||
|
$sql .= " AND DATE(r.tanggal) BETWEEN ? AND ?";
|
||||||
|
$params[] = $filters['start_date'];
|
||||||
|
$params[] = $filters['end_date'];
|
||||||
|
$types .= "ss";
|
||||||
|
} elseif (!empty($filters['month']) && !empty($filters['year'])) {
|
||||||
|
$sql .= " AND MONTH(r.tanggal) = ? AND YEAR(r.tanggal) = ?";
|
||||||
|
$params[] = $filters['month'];
|
||||||
|
$params[] = $filters['year'];
|
||||||
|
$types .= "ss";
|
||||||
|
} elseif (!empty($filters['year'])) {
|
||||||
|
$sql .= " AND YEAR(r.tanggal) = ?";
|
||||||
|
$params[] = $filters['year'];
|
||||||
|
$types .= "s";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($filters['product_id'])) {
|
||||||
|
$sql .= " AND b.id_barang = ?";
|
||||||
|
$params[] = $filters['product_id'];
|
||||||
|
$types .= "i";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($filters['category_id'])) {
|
||||||
|
$sql .= " AND b.id_jenis = ?";
|
||||||
|
$params[] = $filters['category_id'];
|
||||||
|
$types .= "i";
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= " ORDER BY r.tanggal DESC";
|
||||||
|
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
|
||||||
|
if (!empty($params)) {
|
||||||
|
$stmt->bind_param($types, ...$params);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
// Add report title
|
||||||
|
$title = 'Laporan Inventaris Ayula Store';
|
||||||
|
$period = '';
|
||||||
|
|
||||||
|
if (!empty($filters['start_date']) && !empty($filters['end_date'])) {
|
||||||
|
$start = date('d M Y', strtotime($filters['start_date']));
|
||||||
|
$end = date('d M Y', strtotime($filters['end_date']));
|
||||||
|
$period = "Periode: $start - $end";
|
||||||
|
} elseif (!empty($filters['month']) && !empty($filters['year'])) {
|
||||||
|
$month = date('F', mktime(0, 0, 0, $filters['month'], 1));
|
||||||
|
$period = "Periode: $month {$filters['year']}";
|
||||||
|
} elseif (!empty($filters['year'])) {
|
||||||
|
$period = "Periode: Tahun {$filters['year']}";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add title and period
|
||||||
|
$pdf->SetFont('helvetica', 'B', 15);
|
||||||
|
$pdf->Cell(0, 10, $title, 0, 1, 'C');
|
||||||
|
$pdf->SetFont('helvetica', '', 12);
|
||||||
|
$pdf->Cell(0, 10, $period, 0, 1, 'C');
|
||||||
|
$pdf->Ln(5);
|
||||||
|
|
||||||
|
// Add summary information
|
||||||
|
$pdf->SetFont('helvetica', 'B', 12);
|
||||||
|
$pdf->Cell(0, 10, 'Ringkasan', 0, 1, 'L');
|
||||||
|
$pdf->SetFont('helvetica', '', 10);
|
||||||
|
|
||||||
|
$total_items = 0;
|
||||||
|
$total_amount = 0;
|
||||||
|
$reports_count = $result->num_rows;
|
||||||
|
|
||||||
|
// Create table headers
|
||||||
|
$pdf->SetFont('helvetica', 'B', 9);
|
||||||
|
$pdf->SetFillColor(230, 230, 230);
|
||||||
|
$pdf->Cell(15, 7, 'ID', 1, 0, 'C', 1);
|
||||||
|
$pdf->Cell(25, 7, 'Tanggal', 1, 0, 'C', 1);
|
||||||
|
$pdf->Cell(50, 7, 'Produk', 1, 0, 'C', 1);
|
||||||
|
$pdf->Cell(30, 7, 'Kategori', 1, 0, 'C', 1);
|
||||||
|
$pdf->Cell(15, 7, 'Jumlah', 1, 0, 'C', 1);
|
||||||
|
$pdf->Cell(25, 7, 'Harga', 1, 0, 'C', 1);
|
||||||
|
$pdf->Cell(30, 7, 'Total', 1, 1, 'C', 1);
|
||||||
|
|
||||||
|
// Add data rows
|
||||||
|
$pdf->SetFont('helvetica', '', 8);
|
||||||
|
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$total = $row['jumlah'] * $row['harga'];
|
||||||
|
$total_items += $row['jumlah'];
|
||||||
|
$total_amount += $total;
|
||||||
|
|
||||||
|
$pdf->Cell(15, 6, $row['id_report'], 1, 0, 'C');
|
||||||
|
$pdf->Cell(25, 6, date('d M Y', strtotime($row['tanggal'])), 1, 0, 'C');
|
||||||
|
$pdf->Cell(50, 6, $row['nama_barang'], 1, 0, 'L');
|
||||||
|
$pdf->Cell(30, 6, $row['nama_jenis'], 1, 0, 'L');
|
||||||
|
$pdf->Cell(15, 6, $row['jumlah'], 1, 0, 'C');
|
||||||
|
$pdf->Cell(25, 6, 'Rp ' . number_format($row['harga'], 0, ',', '.'), 1, 0, 'R');
|
||||||
|
$pdf->Cell(30, 6, 'Rp ' . number_format($total, 0, ',', '.'), 1, 1, 'R');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add totals row
|
||||||
|
$pdf->SetFont('helvetica', 'B', 9);
|
||||||
|
$pdf->Cell(120, 7, 'TOTAL', 1, 0, 'R', 1);
|
||||||
|
$pdf->Cell(15, 7, $total_items, 1, 0, 'C', 1);
|
||||||
|
$pdf->Cell(25, 7, '', 1, 0, 'C', 1);
|
||||||
|
$pdf->Cell(30, 7, 'Rp ' . number_format($total_amount, 0, ',', '.'), 1, 1, 'R', 1);
|
||||||
|
|
||||||
|
// Add summary before table
|
||||||
|
$pdf->SetY(60); // Position at beginning
|
||||||
|
$pdf->SetFont('helvetica', '', 10);
|
||||||
|
$pdf->Cell(60, 7, 'Total Laporan: ' . $reports_count, 0, 1, 'L');
|
||||||
|
$pdf->Cell(60, 7, 'Total Items: ' . $total_items, 0, 1, 'L');
|
||||||
|
$pdf->Cell(60, 7, 'Total Nilai: Rp ' . number_format($total_amount, 0, ',', '.'), 0, 1, 'L');
|
||||||
|
$pdf->Ln(5);
|
||||||
|
|
||||||
|
// Close and output PDF
|
||||||
|
$pdf->Output('Laporan_Inventaris_' . date('Y-m-d') . '.pdf', 'I');
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conn->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to export to Excel
|
||||||
|
function exportExcel($filters) {
|
||||||
|
require_once '../vendor/autoload.php'; // Require PhpSpreadsheet
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||||
|
|
||||||
|
// Create new Spreadsheet object
|
||||||
|
$spreadsheet = new Spreadsheet();
|
||||||
|
$sheet = $spreadsheet->getActiveSheet();
|
||||||
|
|
||||||
|
// Set document properties
|
||||||
|
$spreadsheet->getProperties()
|
||||||
|
->setCreator('Ayula Store')
|
||||||
|
->setLastModifiedBy('Ayula Store')
|
||||||
|
->setTitle('Laporan Inventaris')
|
||||||
|
->setSubject('Laporan Inventaris Ayula Store')
|
||||||
|
->setDescription('Laporan Inventaris Ayula Store');
|
||||||
|
|
||||||
|
// Get report data
|
||||||
|
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
|
||||||
|
if ($conn->connect_error) {
|
||||||
|
die("Connection failed: " . $conn->connect_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build query based on filters
|
||||||
|
$sql = "SELECT r.id_report, r.tanggal, r.jumlah, r.harga, r.image,
|
||||||
|
b.nama_barang, b.kode_barang,
|
||||||
|
jb.nama_jenis
|
||||||
|
FROM report r
|
||||||
|
JOIN barang b ON r.id_barang = b.id_barang
|
||||||
|
JOIN jenis_barang jb ON b.id_jenis = jb.id_jenis
|
||||||
|
WHERE 1=1";
|
||||||
|
|
||||||
|
$params = array();
|
||||||
|
$types = "";
|
||||||
|
|
||||||
|
if (!empty($filters['start_date']) && !empty($filters['end_date'])) {
|
||||||
|
$sql .= " AND DATE(r.tanggal) BETWEEN ? AND ?";
|
||||||
|
$params[] = $filters['start_date'];
|
||||||
|
$params[] = $filters['end_date'];
|
||||||
|
$types .= "ss";
|
||||||
|
} elseif (!empty($filters['month']) && !empty($filters['year'])) {
|
||||||
|
$sql .= " AND MONTH(r.tanggal) = ? AND YEAR(r.tanggal) = ?";
|
||||||
|
$params[] = $filters['month'];
|
||||||
|
$params[] = $filters['year'];
|
||||||
|
$types .= "ss";
|
||||||
|
} elseif (!empty($filters['year'])) {
|
||||||
|
$sql .= " AND YEAR(r.tanggal) = ?";
|
||||||
|
$params[] = $filters['year'];
|
||||||
|
$types .= "s";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($filters['product_id'])) {
|
||||||
|
$sql .= " AND b.id_barang = ?";
|
||||||
|
$params[] = $filters['product_id'];
|
||||||
|
$types .= "i";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($filters['category_id'])) {
|
||||||
|
$sql .= " AND b.id_jenis = ?";
|
||||||
|
$params[] = $filters['category_id'];
|
||||||
|
$types .= "i";
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= " ORDER BY r.tanggal DESC";
|
||||||
|
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
|
||||||
|
if (!empty($params)) {
|
||||||
|
$stmt->bind_param($types, ...$params);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
// Add title
|
||||||
|
$sheet->setCellValue('A1', 'LAPORAN INVENTARIS AYULA STORE');
|
||||||
|
$sheet->mergeCells('A1:G1');
|
||||||
|
$sheet->getStyle('A1')->getFont()->setBold(true)->setSize(14);
|
||||||
|
$sheet->getStyle('A1')->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER);
|
||||||
|
|
||||||
|
// Add period
|
||||||
|
$period = '';
|
||||||
|
if (!empty($filters['start_date']) && !empty($filters['end_date'])) {
|
||||||
|
$start = date('d M Y', strtotime($filters['start_date']));
|
||||||
|
$end = date('d M Y', strtotime($filters['end_date']));
|
||||||
|
$period = "Periode: $start - $end";
|
||||||
|
} elseif (!empty($filters['month']) && !empty($filters['year'])) {
|
||||||
|
$month = date('F', mktime(0, 0, 0, $filters['month'], 1));
|
||||||
|
$period = "Periode: $month {$filters['year']}";
|
||||||
|
} elseif (!empty($filters['year'])) {
|
||||||
|
$period = "Periode: Tahun {$filters['year']}";
|
||||||
|
}
|
||||||
|
|
||||||
|
$sheet->setCellValue('A2', $period);
|
||||||
|
$sheet->mergeCells('A2:G2');
|
||||||
|
$sheet->getStyle('A2')->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER);
|
||||||
|
|
||||||
|
// Add summary information
|
||||||
|
$total_items = 0;
|
||||||
|
$total_amount = 0;
|
||||||
|
$reports_count = $result->num_rows;
|
||||||
|
|
||||||
|
// Calculate totals
|
||||||
|
$data = [];
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$data[] = $row;
|
||||||
|
$total_items += $row['jumlah'];
|
||||||
|
$total_amount += ($row['jumlah'] * $row['harga']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add summary section
|
||||||
|
$sheet->setCellValue('A4', 'Ringkasan:');
|
||||||
|
$sheet->getStyle('A4')->getFont()->setBold(true);
|
||||||
|
|
||||||
|
$sheet->setCellValue('A5', 'Total Laporan:');
|
||||||
|
$sheet->setCellValue('B5', $reports_count);
|
||||||
|
|
||||||
|
$sheet->setCellValue('A6', 'Total Items:');
|
||||||
|
$sheet->setCellValue('B6', $total_items);
|
||||||
|
|
||||||
|
$sheet->setCellValue('A7', 'Total Nilai:');
|
||||||
|
$sheet->setCellValue('B7', 'Rp ' . number_format($total_amount, 0, ',', '.'));
|
||||||
|
|
||||||
|
// Add table headers
|
||||||
|
$sheet->setCellValue('A9', 'ID');
|
||||||
|
$sheet->setCellValue('B9', 'Tanggal');
|
||||||
|
$sheet->setCellValue('C9', 'Produk');
|
||||||
|
$sheet->setCellValue('D9', 'Kategori');
|
||||||
|
$sheet->setCellValue('E9', 'Jumlah');
|
||||||
|
$sheet->setCellValue('F9', 'Harga');
|
||||||
|
$sheet->setCellValue('G9', 'Total');
|
||||||
|
|
||||||
|
// Style the headers
|
||||||
|
$sheet->getStyle('A9:G9')->getFont()->setBold(true);
|
||||||
|
$sheet->getStyle('A9:G9')->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)->getStartColor()->setRGB('CCCCCC');
|
||||||
|
$sheet->getStyle('A9:G9')->getAlignment()->setHorizontal(\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER);
|
||||||
|
|
||||||
|
// Add data rows
|
||||||
|
$row_num = 10;
|
||||||
|
foreach ($data as $row) {
|
||||||
|
$total = $row['jumlah'] * $row['harga'];
|
||||||
|
|
||||||
|
$sheet->setCellValue('A' . $row_num, $row['id_report']);
|
||||||
|
$sheet->setCellValue('B' . $row_num, date('d M Y', strtotime($row['tanggal'])));
|
||||||
|
$sheet->setCellValue('C' . $row_num, $row['nama_barang']);
|
||||||
|
$sheet->setCellValue('D' . $row_num, $row['nama_jenis']);
|
||||||
|
$sheet->setCellValue('E' . $row_num, $row['jumlah']);
|
||||||
|
$sheet->setCellValue('F' . $row_num, 'Rp ' . number_format($row['harga'], 0, ',', '.'));
|
||||||
|
$sheet->setCellValue('G' . $row_num, 'Rp ' . number_format($total, 0, ',', '.'));
|
||||||
|
|
||||||
|
$row_num++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add totals row
|
||||||
|
$sheet->setCellValue('A' . $row_num, 'TOTAL');
|
||||||
|
$sheet->mergeCells('A' . $row_num . ':D' . $row_num);
|
||||||
|
$sheet->setCellValue('E' . $row_num, $total_items);
|
||||||
|
$sheet->setCellValue('G' . $row_num, 'Rp ' . number_format($total_amount, 0, ',', '.'));
|
||||||
|
|
||||||
|
$sheet->getStyle('A' . $row_num . ':G' . $row_num)->getFont()->setBold(true);
|
||||||
|
$sheet->getStyle('A' . $row_num . ':G' . $row_num)->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)->getStartColor()->setRGB('EEEEEE');
|
||||||
|
|
||||||
|
// Autosize columns
|
||||||
|
foreach(range('A', 'G') as $col) {
|
||||||
|
$sheet->getColumnDimension($col)->setAutoSize(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a border for the table
|
||||||
|
$styleArray = [
|
||||||
|
'borders' => [
|
||||||
|
'allBorders' => [
|
||||||
|
'borderStyle' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
$sheet->getStyle('A9:G' . $row_num)->applyFromArray($styleArray);
|
||||||
|
|
||||||
|
// Output excel file
|
||||||
|
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||||
|
header('Content-Disposition: attachment;filename="Laporan_Inventaris_' . date('Y-m-d') . '.xlsx"');
|
||||||
|
header('Cache-Control: max-age=0');
|
||||||
|
|
||||||
|
$writer = new Xlsx($spreadsheet);
|
||||||
|
$writer->save('php://output');
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conn->close();
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process report export request
|
||||||
|
if (isset($_GET['export']) && in_array($_GET['export'], ['pdf', 'excel'])) {
|
||||||
|
$filters = [
|
||||||
|
'start_date' => isset($_GET['start_date']) ? $_GET['start_date'] : '',
|
||||||
|
'end_date' => isset($_GET['end_date']) ? $_GET['end_date'] : '',
|
||||||
|
'month' => isset($_GET['filter_month']) ? $_GET['filter_month'] : '',
|
||||||
|
'year' => isset($_GET['filter_year']) ? $_GET['filter_year'] : '',
|
||||||
|
'product_id' => isset($_GET['product_id']) ? $_GET['product_id'] : '',
|
||||||
|
'category_id' => isset($_GET['category_id']) ? $_GET['category_id'] : ''
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($_GET['export'] == 'pdf') {
|
||||||
|
generatePDF($filters);
|
||||||
|
} else {
|
||||||
|
exportExcel($filters);
|
||||||
|
}
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle AJAX request for chart data
|
||||||
|
if (isset($_GET['action']) && $_GET['action'] == 'get_chart_data') {
|
||||||
|
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
|
||||||
|
if ($conn->connect_error) {
|
||||||
|
die(json_encode(['error' => 'Connection failed: ' . $conn->connect_error]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build query based on filters
|
||||||
|
$sql = "SELECT jb.nama_jenis, SUM(r.jumlah) as total_items, SUM(r.jumlah * r.harga) as total_value
|
||||||
|
FROM report r
|
||||||
|
JOIN barang b ON r.id_barang = b.id_barang
|
||||||
|
JOIN jenis_barang jb ON b.id_jenis = jb.id_jenis
|
||||||
|
WHERE 1=1";
|
||||||
|
|
||||||
|
$params = array();
|
||||||
|
$types = "";
|
||||||
|
|
||||||
|
if (!empty($_GET['start_date']) && !empty($_GET['end_date'])) {
|
||||||
|
$sql .= " AND DATE(r.tanggal) BETWEEN ? AND ?";
|
||||||
|
$params[] = $_GET['start_date'];
|
||||||
|
$params[] = $_GET['end_date'];
|
||||||
|
$types .= "ss";
|
||||||
|
} elseif (!empty($_GET['filter_month']) && !empty($_GET['filter_year'])) {
|
||||||
|
$sql .= " AND MONTH(r.tanggal) = ? AND YEAR(r.tanggal) = ?";
|
||||||
|
$params[] = $_GET['filter_month'];
|
||||||
|
$params[] = $_GET['filter_year'];
|
||||||
|
$types .= "ss";
|
||||||
|
} elseif (!empty($_GET['filter_year'])) {
|
||||||
|
$sql .= " AND YEAR(r.tanggal) = ?";
|
||||||
|
$params[] = $_GET['filter_year'];
|
||||||
|
$types .= "s";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($_GET['product_id'])) {
|
||||||
|
$sql .= " AND b.id_barang = ?";
|
||||||
|
$params[] = $_GET['product_id'];
|
||||||
|
$types .= "i";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($_GET['category_id'])) {
|
||||||
|
$sql .= " AND b.id_jenis = ?";
|
||||||
|
$params[] = $_GET['category_id'];
|
||||||
|
$types .= "i";
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= " GROUP BY jb.nama_jenis";
|
||||||
|
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
|
||||||
|
if (!empty($params)) {
|
||||||
|
$stmt->bind_param($types, ...$params);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
$chart_data = [
|
||||||
|
'labels' => [],
|
||||||
|
'item_counts' => [],
|
||||||
|
'values' => []
|
||||||
|
];
|
||||||
|
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$chart_data['labels'][] = $row['nama_jenis'];
|
||||||
|
$chart_data['item_counts'][] = (int)$row['total_items'];
|
||||||
|
$chart_data['values'][] = (float)$row['total_value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($chart_data);
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conn->close();
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,160 @@
|
||||||
|
/* Barcode Scanner Styles */
|
||||||
|
.barcode-scanner-container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 100;
|
||||||
|
background: linear-gradient(45deg, #ff9f4380, #ff7f5080);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barcode-scanner-container.sticky-scanner {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: calc(100% - 30px);
|
||||||
|
max-width: 1140px;
|
||||||
|
border-radius: 0 0 10px 10px;
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
||||||
|
padding: 10px 15px;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barcode-scanner-container.collapsed {
|
||||||
|
padding: 5px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barcode-scanner-container.collapsed .barcode-content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barcode-scanner-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barcode-scanner-container.collapsed .barcode-scanner-header {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barcode-scanner-header h5 {
|
||||||
|
margin: 0;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 600;
|
||||||
|
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.barcode-toggle-btn {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barcode-content {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barcode-input-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.barcode-input-group .input-group-text {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#barcode-input {
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
padding: 10px 15px;
|
||||||
|
font-size: 16px;
|
||||||
|
background-color: #fff;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#barcode-input:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#barcode-status {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #6c757d;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scanning animation */
|
||||||
|
.barcode-scanner-container.scanning {
|
||||||
|
background: linear-gradient(45deg, #ff9f4330, #ff7f5030);
|
||||||
|
}
|
||||||
|
|
||||||
|
.barcode-scanner-container.scanning .barcode-input-group {
|
||||||
|
box-shadow: 0 0 0 2px #ff9f43;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Highlight product in grid */
|
||||||
|
.productset.highlight-product {
|
||||||
|
border: 2px solid #28a745;
|
||||||
|
box-shadow: 0 0 15px rgba(40, 167, 69, 0.5);
|
||||||
|
animation: highlight-pulse 1.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes highlight-pulse {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
box-shadow: 0 0 0 rgba(40, 167, 69, 0.5);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.05);
|
||||||
|
box-shadow: 0 0 15px rgba(40, 167, 69, 0.8);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
box-shadow: 0 0 0 rgba(40, 167, 69, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Product Added Notification */
|
||||||
|
.barcode-notification {
|
||||||
|
position: fixed;
|
||||||
|
top: 70px;
|
||||||
|
right: 20px;
|
||||||
|
background-color: #28a745;
|
||||||
|
color: white;
|
||||||
|
padding: 10px 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
z-index: 9999;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(50px);
|
||||||
|
transition: opacity 0.3s, transform 0.3s;
|
||||||
|
font-weight: bold;
|
||||||
|
max-width: 280px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barcode-notification.show {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,453 @@
|
||||||
|
/**
|
||||||
|
* Barcode Scanner Feature
|
||||||
|
* Adds barcode scanning functionality to Ayula Store POS System
|
||||||
|
*/
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
// Initialize barcode scanner functionality
|
||||||
|
initBarcodeScanner();
|
||||||
|
|
||||||
|
// Check for barcode scanner toggle
|
||||||
|
setupBarcodeScannerToggle();
|
||||||
|
|
||||||
|
// Force update the clear cart button state
|
||||||
|
forceUpdateClearCartButton();
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force update the clear cart button state based on actual cart content
|
||||||
|
*/
|
||||||
|
function forceUpdateClearCartButton() {
|
||||||
|
// Check if cart actually has items
|
||||||
|
const hasItems = $('.product-lists').length > 0;
|
||||||
|
console.log('Force updating clear cart button. Cart has items:', hasItems);
|
||||||
|
|
||||||
|
const clearCartBtn = $('#clear-cart-btn');
|
||||||
|
|
||||||
|
if (hasItems) {
|
||||||
|
clearCartBtn.attr('href', 'javascript:void(0);')
|
||||||
|
.removeClass('disabled')
|
||||||
|
.css({
|
||||||
|
'opacity': '1',
|
||||||
|
'cursor': 'pointer'
|
||||||
|
})
|
||||||
|
.off('click')
|
||||||
|
.on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (typeof bootstrap !== 'undefined' && $('#clearCartModal').length) {
|
||||||
|
try {
|
||||||
|
const clearModal = new bootstrap.Modal(document.getElementById('clearCartModal'));
|
||||||
|
clearModal.show();
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Modal error, using fallback:', error);
|
||||||
|
if (confirm('Apakah Anda yakin ingin menghapus semua item dari keranjang Anda?')) {
|
||||||
|
window.location.href = 'index.php?clear_cart=1';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (confirm('Apakah Anda yakin ingin menghapus semua item dari keranjang Anda?')) {
|
||||||
|
window.location.href = 'index.php?clear_cart=1';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
clearCartBtn.attr('href', '#')
|
||||||
|
.addClass('disabled')
|
||||||
|
.css({
|
||||||
|
'opacity': '0.5',
|
||||||
|
'cursor': 'not-allowed'
|
||||||
|
})
|
||||||
|
.off('click');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize barcode scanner functionality
|
||||||
|
*/
|
||||||
|
function initBarcodeScanner() {
|
||||||
|
const barcodeInput = $('#barcode-input');
|
||||||
|
|
||||||
|
// Focus barcode input when barcode scanner container is clicked
|
||||||
|
$('.barcode-scanner-container').on('click', function(e) {
|
||||||
|
// Don't focus if clicking on a button or another interactive element
|
||||||
|
if (!$(e.target).is('button, a, input')) {
|
||||||
|
barcodeInput.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Make barcode scanner sticky at top
|
||||||
|
makeBarcodeSticky();
|
||||||
|
|
||||||
|
// Handle barcode input
|
||||||
|
barcodeInput.on('keydown', function(e) {
|
||||||
|
// If Enter key is pressed, process the barcode
|
||||||
|
if (e.keyCode === 13) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const barcode = $(this).val().trim();
|
||||||
|
if (barcode) {
|
||||||
|
processBarcode(barcode);
|
||||||
|
$(this).val(''); // Clear input after processing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto focus the barcode input when page loads
|
||||||
|
setTimeout(function() {
|
||||||
|
barcodeInput.focus();
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
// Handle window focus to automatically focus barcode input
|
||||||
|
$(window).on('focus', function() {
|
||||||
|
// Only focus if scanner is not collapsed
|
||||||
|
if (!$('.barcode-scanner-container').hasClass('collapsed')) {
|
||||||
|
setTimeout(function() {
|
||||||
|
barcodeInput.focus();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make barcode scanner sticky at top of page
|
||||||
|
*/
|
||||||
|
function makeBarcodeSticky() {
|
||||||
|
const barcodeContainer = $('.barcode-scanner-container');
|
||||||
|
if (!barcodeContainer.length) return;
|
||||||
|
|
||||||
|
const originalPosition = barcodeContainer.offset() ? barcodeContainer.offset().top : 0;
|
||||||
|
const headerHeight = $('.header').outerHeight() || 0;
|
||||||
|
|
||||||
|
$(window).on('scroll', function() {
|
||||||
|
const scrollPosition = $(window).scrollTop();
|
||||||
|
|
||||||
|
if (scrollPosition > originalPosition - headerHeight) {
|
||||||
|
barcodeContainer.addClass('sticky-scanner');
|
||||||
|
barcodeContainer.css('top', headerHeight + 'px');
|
||||||
|
} else {
|
||||||
|
barcodeContainer.removeClass('sticky-scanner');
|
||||||
|
barcodeContainer.css('top', '');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup toggle for barcode scanner container
|
||||||
|
*/
|
||||||
|
function setupBarcodeScannerToggle() {
|
||||||
|
$('#toggle-barcode-scanner').on('click', function() {
|
||||||
|
$('.barcode-scanner-container').toggleClass('collapsed');
|
||||||
|
$(this).find('i').toggleClass('fa-chevron-up fa-chevron-down');
|
||||||
|
|
||||||
|
// Focus the input when expanded
|
||||||
|
if (!$('.barcode-scanner-container').hasClass('collapsed')) {
|
||||||
|
$('#barcode-input').focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process barcode and add product to cart
|
||||||
|
*/
|
||||||
|
function processBarcode(barcode) {
|
||||||
|
// Show loading indicator
|
||||||
|
$('.barcode-scanner-container').addClass('scanning');
|
||||||
|
$('#barcode-status').html('<i class="fa fa-spinner fa-spin"></i> Memindai...');
|
||||||
|
|
||||||
|
// Keep focus on the barcode input field
|
||||||
|
const barcodeInput = $('#barcode-input');
|
||||||
|
|
||||||
|
// Send AJAX request to get product by barcode
|
||||||
|
$.ajax({
|
||||||
|
url: 'get_product_by_barcode.php',
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
barcode: barcode
|
||||||
|
},
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(response) {
|
||||||
|
$('.barcode-scanner-container').removeClass('scanning');
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
// Show success message
|
||||||
|
$('#barcode-status').html(
|
||||||
|
'<div class="text-success"><i class="fa fa-check-circle"></i> Ditambahkan: ' +
|
||||||
|
response.product.name + '</div>'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Highlight the product in the grid if visible (without scrolling)
|
||||||
|
highlightProductInGrid(response.product.id);
|
||||||
|
|
||||||
|
// Update cart section via AJAX
|
||||||
|
if (response.cart_html) {
|
||||||
|
// Update the cart HTML
|
||||||
|
$('.product-table').html(response.cart_html);
|
||||||
|
|
||||||
|
// Update cart totals and related UI elements
|
||||||
|
updateAllCartElements(response.cart_totals);
|
||||||
|
|
||||||
|
// Make sure quantity controls and other event handlers are properly set up
|
||||||
|
if (typeof setupQuantityControls === 'function') {
|
||||||
|
setupQuantityControls();
|
||||||
|
}
|
||||||
|
if (typeof setupCartDeletionConfirmation === 'function') {
|
||||||
|
setupCartDeletionConfirmation();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force update the clear cart button
|
||||||
|
forceUpdateClearCartButton();
|
||||||
|
|
||||||
|
// Trigger custom event for other scripts to respond to cart updates
|
||||||
|
document.dispatchEvent(new CustomEvent('cartUpdated'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Show error message
|
||||||
|
$('#barcode-status').html(
|
||||||
|
'<div class="text-danger"><i class="fa fa-exclamation-circle"></i> ' +
|
||||||
|
response.message + '</div>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset status message after delay
|
||||||
|
setTimeout(function() {
|
||||||
|
$('#barcode-status').html('<i class="fa fa-barcode"></i> Pindai barcode');
|
||||||
|
// Restore focus to barcode input
|
||||||
|
barcodeInput.focus();
|
||||||
|
// Try to restore cursor position
|
||||||
|
if (barcodeInput.get(0) && barcodeInput.get(0).setSelectionRange) {
|
||||||
|
barcodeInput.get(0).setSelectionRange(0, 0);
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
$('.barcode-scanner-container').removeClass('scanning');
|
||||||
|
console.error('AJAX Error:', status, error);
|
||||||
|
|
||||||
|
// Try to get more detailed error info
|
||||||
|
let errorMessage = 'Koneksi error';
|
||||||
|
try {
|
||||||
|
if (xhr.responseText) {
|
||||||
|
// Check if it's JSON
|
||||||
|
try {
|
||||||
|
const errorData = JSON.parse(xhr.responseText);
|
||||||
|
if (errorData && errorData.message) {
|
||||||
|
errorMessage = errorData.message;
|
||||||
|
}
|
||||||
|
} catch (jsonError) {
|
||||||
|
// Not valid JSON, just use generic message
|
||||||
|
errorMessage = 'Kesalahan sistem. Silakan coba lagi.';
|
||||||
|
console.log('Response was not valid JSON:', xhr.responseText.substring(0, 100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error parsing error response:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#barcode-status').html(
|
||||||
|
'<div class="text-danger"><i class="fa fa-exclamation-circle"></i> ' + errorMessage + '</div>'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Reset status message after delay
|
||||||
|
setTimeout(function() {
|
||||||
|
$('#barcode-status').html('<i class="fa fa-barcode"></i> Pindai barcode');
|
||||||
|
barcodeInput.focus();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comprehensive update of all cart-related elements
|
||||||
|
*/
|
||||||
|
function updateAllCartElements(totals) {
|
||||||
|
console.log('Updating all cart elements with totals:', totals);
|
||||||
|
|
||||||
|
// 1. Update displayed values
|
||||||
|
$('.totalitem h4').text('Total barang: ' + totals.items);
|
||||||
|
$('.total-value h6').text('Rp. ' + totals.formatted_total);
|
||||||
|
$('.btn-totallabel h6').text('Rp. ' + totals.formatted_total);
|
||||||
|
|
||||||
|
// 2. Update form inputs
|
||||||
|
$('#cash-amount').val(totals.formatted_total);
|
||||||
|
$('#hidden-cash-amount').val(totals.total);
|
||||||
|
|
||||||
|
// 3. Create and replace checkout form if doesn't exist or needs updating
|
||||||
|
if (totals.items > 0) {
|
||||||
|
// Only replace if needed
|
||||||
|
if ($('#checkout-form').length === 0 || $('#checkout-form button[type="submit"]').prop('disabled')) {
|
||||||
|
const checkoutFormHtml = `
|
||||||
|
<form id="checkout-form" method="post" action="index.php">
|
||||||
|
<input type="hidden" name="cash_amount" id="hidden-cash-amount" value="${totals.total}">
|
||||||
|
<input type="hidden" name="change_amount" id="hidden-change-amount" value="0">
|
||||||
|
<input type="hidden" name="checkout" value="1">
|
||||||
|
<button type="submit" class="btn-totallabel w-100">
|
||||||
|
<h5>Pesan Sekarang</h5>
|
||||||
|
<h6>Rp. ${totals.formatted_total}</h6>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Replace current form with new one
|
||||||
|
if ($('#checkout-form').length > 0) {
|
||||||
|
$('#checkout-form').replaceWith(checkoutFormHtml);
|
||||||
|
} else {
|
||||||
|
// If form doesn't exist, replace the disabled button
|
||||||
|
$('.btn-totallabel.w-100[disabled]').replaceWith(checkoutFormHtml);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the new form
|
||||||
|
if (typeof setupCheckoutForm === 'function') {
|
||||||
|
setupCheckoutForm();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Just enable the existing button
|
||||||
|
$('.btn-totallabel').prop('disabled', false);
|
||||||
|
$('#checkout-form button[type="submit"]').prop('disabled', false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Cart is empty, show disabled button
|
||||||
|
if ($('#checkout-form').length > 0) {
|
||||||
|
$('#checkout-form').replaceWith(`
|
||||||
|
<button class="btn-totallabel w-100" disabled>
|
||||||
|
<h5>Pesan Sekarang</h5>
|
||||||
|
<h6>Rp. 0</h6>
|
||||||
|
</button>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Update quick cash buttons
|
||||||
|
updateQuickCashButtons(totals.total);
|
||||||
|
|
||||||
|
// 5. Show/hide change display
|
||||||
|
updateChangeDisplay();
|
||||||
|
|
||||||
|
// 6. Re-initialize scrollbars and event handlers
|
||||||
|
initializeCartScrolling();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize scrolling behavior for cart
|
||||||
|
*/
|
||||||
|
function initializeCartScrolling() {
|
||||||
|
// Make sure product table has correct max height
|
||||||
|
const windowHeight = $(window).height();
|
||||||
|
const headerHeight = $('.order-list').outerHeight() || 0;
|
||||||
|
const cardHeaderHeight = $('.card-order .card-body:first-of-type').outerHeight() || 0;
|
||||||
|
const cardFooterHeight = $('.card-order .card-body:last-of-type').outerHeight() || 0;
|
||||||
|
const availableHeight = windowHeight - headerHeight - cardHeaderHeight - cardFooterHeight - 50; // 50px buffer
|
||||||
|
|
||||||
|
// Set max height for scrollable area
|
||||||
|
$('.product-table').css('max-height', availableHeight + 'px');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highlight product in grid when added via barcode
|
||||||
|
*/
|
||||||
|
function highlightProductInGrid(productId) {
|
||||||
|
const productElement = $('.productset[data-product-id="' + productId + '"]');
|
||||||
|
|
||||||
|
if (productElement.length) {
|
||||||
|
// Flash highlight effect without scrolling
|
||||||
|
productElement.addClass('highlight-product');
|
||||||
|
setTimeout(function() {
|
||||||
|
productElement.removeClass('highlight-product');
|
||||||
|
}, 1500);
|
||||||
|
|
||||||
|
// Optional: Briefly show which product was added via a popup notification
|
||||||
|
showAddedProductNotification(productElement.find('.productsetcontent h4').text());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a notification that a product was added instead of scrolling
|
||||||
|
*/
|
||||||
|
function showAddedProductNotification(productName) {
|
||||||
|
// Create notification element if it doesn't exist
|
||||||
|
if ($('#barcode-notification').length === 0) {
|
||||||
|
$('body').append('<div id="barcode-notification" class="barcode-notification"></div>');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show notification
|
||||||
|
$('#barcode-notification')
|
||||||
|
.text('Ditambahkan: ' + productName)
|
||||||
|
.addClass('show');
|
||||||
|
|
||||||
|
// Hide notification after 2 seconds
|
||||||
|
setTimeout(function() {
|
||||||
|
$('#barcode-notification').removeClass('show');
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update quick cash buttons based on total amount
|
||||||
|
*/
|
||||||
|
function updateQuickCashButtons(totalAmount) {
|
||||||
|
if (totalAmount > 0) {
|
||||||
|
// Update round-up buttons for different denominations
|
||||||
|
const round1000 = Math.ceil(totalAmount / 1000) * 1000;
|
||||||
|
const round10000 = Math.ceil(totalAmount / 10000) * 10000;
|
||||||
|
const round50000 = Math.ceil(totalAmount / 50000) * 50000;
|
||||||
|
const round100000 = Math.ceil(totalAmount / 100000) * 100000;
|
||||||
|
|
||||||
|
// Find quick cash buttons and update their values
|
||||||
|
$('.quick-cash').each(function(index) {
|
||||||
|
let newValue;
|
||||||
|
switch(index) {
|
||||||
|
case 0: newValue = round1000; break;
|
||||||
|
case 1: newValue = round10000; break;
|
||||||
|
case 2: newValue = round50000; break;
|
||||||
|
case 3: newValue = round100000; break;
|
||||||
|
default: newValue = totalAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
$(this).data('value', newValue);
|
||||||
|
$(this).text('Rp. ' + formatNumber(newValue));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show row containing quick cash buttons
|
||||||
|
$('.setvaluecash .row').show();
|
||||||
|
} else {
|
||||||
|
// Hide quick cash buttons if cart is empty
|
||||||
|
$('.setvaluecash .row').hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update change display based on current cash amount and total
|
||||||
|
*/
|
||||||
|
function updateChangeDisplay() {
|
||||||
|
const cashInput = $('#cash-amount');
|
||||||
|
const changeDisplay = $('#change-amount');
|
||||||
|
const changeContainer = $('#change-container');
|
||||||
|
const hiddenCashAmount = $('#hidden-cash-amount');
|
||||||
|
const hiddenChangeAmount = $('#hidden-change-amount');
|
||||||
|
|
||||||
|
// Get total from the displayed value or use 0 if not available
|
||||||
|
const totalText = $('.total-value h6').text();
|
||||||
|
const totalAmount = parseFloat(totalText.replace(/[^\d]/g, '')) || 0;
|
||||||
|
|
||||||
|
// Get cash value from hidden field
|
||||||
|
let cashValue = parseFloat(hiddenCashAmount.val()) || 0;
|
||||||
|
|
||||||
|
// Calculate change
|
||||||
|
const change = cashValue - totalAmount;
|
||||||
|
|
||||||
|
// Update hidden field for change amount
|
||||||
|
hiddenChangeAmount.val(Math.max(0, change));
|
||||||
|
|
||||||
|
// Format and display
|
||||||
|
if (change >= 0 && totalAmount > 0) {
|
||||||
|
changeDisplay.text('Rp. ' + formatNumber(change));
|
||||||
|
changeContainer.show();
|
||||||
|
} else {
|
||||||
|
changeContainer.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format number with thousand separator
|
||||||
|
*/
|
||||||
|
function formatNumber(number) {
|
||||||
|
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,223 @@
|
||||||
|
/**
|
||||||
|
* Ayula Store POS System - Cart Functionality
|
||||||
|
* Handles cart operations and user interactions for shopping cart
|
||||||
|
*/
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
// Setup quantity controls for cart items
|
||||||
|
setupQuantityControls();
|
||||||
|
|
||||||
|
// Setup checkout form functionality
|
||||||
|
setupCheckoutForm();
|
||||||
|
|
||||||
|
// Setup cart item deletion confirmation
|
||||||
|
setupCartDeletionConfirmation();
|
||||||
|
|
||||||
|
// Setup clear cart confirmation
|
||||||
|
setupClearCartConfirmation();
|
||||||
|
|
||||||
|
// Setup cash amount input formatting
|
||||||
|
setupCashAmountFormatting();
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up quantity controls (increment, decrement, manual input) for cart items
|
||||||
|
*/
|
||||||
|
function setupQuantityControls() {
|
||||||
|
// Handle increment button
|
||||||
|
$('.quantity-btn.increment').off('click').on('click', function() {
|
||||||
|
const input = $(this).siblings('.quantity-field');
|
||||||
|
const currentValue = parseInt(input.val()) || 1;
|
||||||
|
const maxStock = parseInt(input.data('max-stock')) || 999;
|
||||||
|
|
||||||
|
if (currentValue < maxStock) {
|
||||||
|
input.val(currentValue + 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle decrement button
|
||||||
|
$('.quantity-btn.decrement').off('click').on('click', function() {
|
||||||
|
const input = $(this).siblings('.quantity-field');
|
||||||
|
const currentValue = parseInt(input.val()) || 1;
|
||||||
|
|
||||||
|
if (currentValue > 1) {
|
||||||
|
input.val(currentValue - 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Validate manual input in quantity field
|
||||||
|
$('.quantity-field').off('change keyup').on('change keyup', function() {
|
||||||
|
let value = parseInt($(this).val()) || 1;
|
||||||
|
const maxStock = parseInt($(this).data('max-stock')) || 999;
|
||||||
|
|
||||||
|
// Ensure value is within limits
|
||||||
|
if (value < 1) value = 1;
|
||||||
|
if (value > maxStock) value = maxStock;
|
||||||
|
|
||||||
|
$(this).val(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up checkout form validation and submission
|
||||||
|
*/
|
||||||
|
function setupCheckoutForm() {
|
||||||
|
$('#checkout-form').off('submit').on('submit', function(e) {
|
||||||
|
// Get the cash amount and total
|
||||||
|
const cashAmount = parseFloat($('#hidden-cash-amount').val()) || 0;
|
||||||
|
const totalText = $('.total-value h6').text();
|
||||||
|
const totalAmount = parseFloat(totalText.replace(/[^\d]/g, '')) || 0;
|
||||||
|
|
||||||
|
// Validate that cash amount is sufficient
|
||||||
|
if (cashAmount < totalAmount) {
|
||||||
|
e.preventDefault();
|
||||||
|
alert('Jumlah tunai tidak mencukupi. Mohon masukkan nilai yang sama atau lebih besar dari total.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// All good, allow form submission
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup confirmation dialog when removing items from cart
|
||||||
|
*/
|
||||||
|
function setupCartDeletionConfirmation() {
|
||||||
|
$('.delete-cart-item').off('click').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const index = $(this).data('index');
|
||||||
|
|
||||||
|
// Set up the confirmation link to the correct index
|
||||||
|
$('#confirm-delete-btn').attr('href', 'index.php?remove_item=' + index);
|
||||||
|
|
||||||
|
// Show modal if available, otherwise use confirm dialog
|
||||||
|
if (typeof bootstrap !== 'undefined' && $('#deleteConfirmModal').length) {
|
||||||
|
try {
|
||||||
|
const modal = new bootstrap.Modal(document.getElementById('deleteConfirmModal'));
|
||||||
|
modal.show();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Modal error, using fallback:', error);
|
||||||
|
if (confirm('Apakah Anda yakin ingin menghapus item ini dari keranjang?')) {
|
||||||
|
window.location.href = 'index.php?remove_item=' + index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (confirm('Apakah Anda yakin ingin menghapus item ini dari keranjang?')) {
|
||||||
|
window.location.href = 'index.php?remove_item=' + index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup clear cart confirmation
|
||||||
|
*/
|
||||||
|
function setupClearCartConfirmation() {
|
||||||
|
// This is handled in another part of the code (index.php inline script)
|
||||||
|
// Just making sure all confirmations are set up correctly
|
||||||
|
|
||||||
|
// Clear Cart confirmation - Confirm button handler
|
||||||
|
$('#confirm-clear-cart').off('click').on('click', function(e) {
|
||||||
|
window.location.href = 'index.php?clear_cart=1';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear Cart confirmation - Cancel button handler
|
||||||
|
$('#cancel-clear-cart').off('click').on('click', function() {
|
||||||
|
if (typeof bootstrap !== 'undefined') {
|
||||||
|
try {
|
||||||
|
const clearModal = bootstrap.Modal.getInstance(document.getElementById('clearCartModal'));
|
||||||
|
if (clearModal) {
|
||||||
|
clearModal.hide();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Modal hide error:', error);
|
||||||
|
// Force modal to close
|
||||||
|
$('#clearCartModal').modal('hide');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup cash amount input formatting with thousand separator
|
||||||
|
*/
|
||||||
|
function setupCashAmountFormatting() {
|
||||||
|
const cashInput = $('#cash-amount');
|
||||||
|
const hiddenCashAmount = $('#hidden-cash-amount');
|
||||||
|
const hiddenChangeAmount = $('#hidden-change-amount');
|
||||||
|
const changeDisplay = $('#change-amount');
|
||||||
|
const changeContainer = $('#change-container');
|
||||||
|
|
||||||
|
// Format the input with thousand separator on change
|
||||||
|
cashInput.on('input', function() {
|
||||||
|
// Remove non-numeric characters
|
||||||
|
let value = $(this).val().replace(/[^\d]/g, '');
|
||||||
|
|
||||||
|
// Get the total amount
|
||||||
|
const totalText = $('.total-value h6').text();
|
||||||
|
const totalAmount = parseFloat(totalText.replace(/[^\d]/g, '')) || 0;
|
||||||
|
|
||||||
|
// Convert to number and format with thousand separator
|
||||||
|
if (value !== '') {
|
||||||
|
const numericValue = parseInt(value);
|
||||||
|
|
||||||
|
// Calculate change
|
||||||
|
const change = numericValue - totalAmount;
|
||||||
|
|
||||||
|
// Update hidden fields for form submission
|
||||||
|
hiddenCashAmount.val(numericValue);
|
||||||
|
hiddenChangeAmount.val(Math.max(0, change));
|
||||||
|
|
||||||
|
// Format display value
|
||||||
|
$(this).val(formatNumber(numericValue));
|
||||||
|
|
||||||
|
// Show change if payment is sufficient
|
||||||
|
if (change >= 0) {
|
||||||
|
changeDisplay.text('Rp. ' + formatNumber(change));
|
||||||
|
changeContainer.show();
|
||||||
|
} else {
|
||||||
|
changeContainer.hide();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$(this).val('');
|
||||||
|
changeContainer.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Quick cash buttons
|
||||||
|
$('.quick-cash').on('click', function() {
|
||||||
|
const value = $(this).data('value');
|
||||||
|
cashInput.val(formatNumber(value));
|
||||||
|
|
||||||
|
// Calculate change
|
||||||
|
const totalText = $('.total-value h6').text();
|
||||||
|
const totalAmount = parseFloat(totalText.replace(/[^\d]/g, '')) || 0;
|
||||||
|
const change = value - totalAmount;
|
||||||
|
|
||||||
|
// Update hidden fields for form submission
|
||||||
|
hiddenCashAmount.val(value);
|
||||||
|
hiddenChangeAmount.val(Math.max(0, change));
|
||||||
|
|
||||||
|
// Show change
|
||||||
|
if (change >= 0) {
|
||||||
|
changeDisplay.text('Rp. ' + formatNumber(change));
|
||||||
|
changeContainer.show();
|
||||||
|
} else {
|
||||||
|
changeContainer.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Trigger input event to initialize on page load
|
||||||
|
cashInput.trigger('input');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format number with thousand separator
|
||||||
|
*
|
||||||
|
* @param {number} number The number to format
|
||||||
|
* @return {string} Formatted number with thousand separator
|
||||||
|
*/
|
||||||
|
function formatNumber(number) {
|
||||||
|
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,484 @@
|
||||||
|
<?php
|
||||||
|
// Start session if not already started
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Include the main database connection
|
||||||
|
include('../../routes/db_conn.php');
|
||||||
|
|
||||||
|
// Check for database connection
|
||||||
|
if (!isset($conn) || $conn->connect_error) {
|
||||||
|
error_log("Database connection failed: " . ($conn ? $conn->connect_error : "Connection not established"));
|
||||||
|
die("Database connection failed. Please try again later.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set error reporting for debugging
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('log_errors', 1);
|
||||||
|
ini_set('error_log', '../../logs/php-errors.log');
|
||||||
|
|
||||||
|
// Check if user is logged in, redirect to login page if not
|
||||||
|
if (!isset($_SESSION['user_id']) || !isset($_SESSION['role'])) {
|
||||||
|
// Check if this is an AJAX request
|
||||||
|
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
|
||||||
|
// Return JSON error for AJAX requests
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Session expired. Please login again.', 'redirect' => '/ayula-store/views/login/']);
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
// Redirect for regular requests
|
||||||
|
header('Location: /ayula-store/views/login/');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set variables for use throughout the application
|
||||||
|
$userRole = $_SESSION['role'] ?? ''; // 'user' or 'admin'
|
||||||
|
$username = $_SESSION['username'] ?? 'Unknown User';
|
||||||
|
$isAdmin = ($userRole === 'admin');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all products or filter by product type and/or search query
|
||||||
|
*
|
||||||
|
* @param int|null $typeId Optional product type ID to filter by
|
||||||
|
* @param string|null $searchQuery Optional search term to filter product names
|
||||||
|
* @return array List of products
|
||||||
|
*/
|
||||||
|
function getProducts($typeId = null, $searchQuery = null) {
|
||||||
|
global $conn;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$sql = "SELECT b.*, j.nama_jenis
|
||||||
|
FROM barang_kasir b
|
||||||
|
JOIN jenis_barang j ON b.id_jenis = j.id_jenis
|
||||||
|
WHERE 1=1";
|
||||||
|
|
||||||
|
$params = [];
|
||||||
|
$types = "";
|
||||||
|
|
||||||
|
// Add type filter if specified
|
||||||
|
if ($typeId) {
|
||||||
|
$sql .= " AND b.id_jenis = ?";
|
||||||
|
$params[] = $typeId;
|
||||||
|
$types .= "i"; // integer parameter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add search filter if specified
|
||||||
|
if ($searchQuery && !empty($searchQuery)) {
|
||||||
|
$sql .= " AND (b.nama_barang LIKE ? OR b.kode_barang LIKE ?)";
|
||||||
|
$params[] = "%" . $searchQuery . "%";
|
||||||
|
$params[] = "%" . $searchQuery . "%";
|
||||||
|
$types .= "ss"; // two string parameters
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= " ORDER BY b.nama_barang ASC";
|
||||||
|
|
||||||
|
$stmt = mysqli_prepare($conn, $sql);
|
||||||
|
|
||||||
|
if (!$stmt) {
|
||||||
|
error_log("Query preparation failed: " . mysqli_error($conn));
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind parameters dynamically if we have any
|
||||||
|
if (!empty($params)) {
|
||||||
|
$bindParams = array(&$stmt, &$types);
|
||||||
|
foreach($params as $key => $value) {
|
||||||
|
$bindParams[] = &$params[$key];
|
||||||
|
}
|
||||||
|
call_user_func_array('mysqli_stmt_bind_param', $bindParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
$executed = mysqli_stmt_execute($stmt);
|
||||||
|
|
||||||
|
if (!$executed) {
|
||||||
|
error_log("Query execution failed: " . mysqli_stmt_error($stmt));
|
||||||
|
mysqli_stmt_close($stmt);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = mysqli_stmt_get_result($stmt);
|
||||||
|
|
||||||
|
$products = [];
|
||||||
|
while ($row = mysqli_fetch_assoc($result)) {
|
||||||
|
$products[] = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_stmt_close($stmt);
|
||||||
|
return $products;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Exception in getProducts: " . $e->getMessage());
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get product details by ID
|
||||||
|
*
|
||||||
|
* @param int $productId The product ID
|
||||||
|
* @return array|null Product details or null if not found
|
||||||
|
*/
|
||||||
|
function getProductById($productId) {
|
||||||
|
global $conn;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = mysqli_prepare($conn, "SELECT b.*, j.nama_jenis
|
||||||
|
FROM barang_kasir b
|
||||||
|
LEFT JOIN jenis_barang j ON b.id_jenis = j.id_jenis
|
||||||
|
WHERE b.id_barangK = ?");
|
||||||
|
|
||||||
|
if (!$stmt) {
|
||||||
|
error_log("Query preparation failed: " . mysqli_error($conn));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_stmt_bind_param($stmt, "i", $productId);
|
||||||
|
$executed = mysqli_stmt_execute($stmt);
|
||||||
|
|
||||||
|
if (!$executed) {
|
||||||
|
error_log("Query execution failed: " . mysqli_stmt_error($stmt));
|
||||||
|
mysqli_stmt_close($stmt);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = mysqli_stmt_get_result($stmt);
|
||||||
|
$product = mysqli_fetch_assoc($result);
|
||||||
|
mysqli_stmt_close($stmt);
|
||||||
|
|
||||||
|
return $product;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Exception in getProductById: " . $e->getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all product types (jenis_barang)
|
||||||
|
*
|
||||||
|
* @return array List of product types
|
||||||
|
*/
|
||||||
|
function getProductTypes() {
|
||||||
|
global $conn;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$query = "SELECT * FROM jenis_barang ORDER BY nama_jenis";
|
||||||
|
$result = mysqli_query($conn, $query);
|
||||||
|
|
||||||
|
if (!$result) {
|
||||||
|
error_log("Query failed: " . mysqli_error($conn));
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$types = [];
|
||||||
|
while ($row = mysqli_fetch_assoc($result)) {
|
||||||
|
$types[] = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $types;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Exception in getProductTypes: " . $e->getMessage());
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new transaction
|
||||||
|
*
|
||||||
|
* @param array $items Array of items with product_id and quantity
|
||||||
|
* @param float $total Total amount
|
||||||
|
* @param float $cashAmount Cash amount received from customer
|
||||||
|
* @param float $changeAmount Change amount to be returned to customer
|
||||||
|
* @return array Result with success status and transaction ID
|
||||||
|
*/
|
||||||
|
function createTransaction($items, $total, $cashAmount = 0, $changeAmount = 0) {
|
||||||
|
global $conn;
|
||||||
|
|
||||||
|
if (empty($items)) {
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'No items in cart'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$totalItems = array_sum(array_column($items, 'quantity'));
|
||||||
|
|
||||||
|
// Generate transaction code (e.g., TRX-20250416-001)
|
||||||
|
$transactionCode = 'TRX-' . date('Ymd') . '-' . rand(100, 999);
|
||||||
|
|
||||||
|
// Begin transaction
|
||||||
|
mysqli_begin_transaction($conn);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Insert into transaksi table with cash_amount and change_amount
|
||||||
|
$stmt = mysqli_prepare($conn, "INSERT INTO transaksi (kode_transaksi, total_item, total, metode_pembayaran, cash_amount, change_amount) VALUES (?, ?, ?, 'Cash', ?, ?)");
|
||||||
|
|
||||||
|
if (!$stmt) {
|
||||||
|
throw new Exception("Failed to prepare transaction statement: " . mysqli_error($conn));
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_stmt_bind_param($stmt, "siddd", $transactionCode, $totalItems, $total, $cashAmount, $changeAmount);
|
||||||
|
$executed = mysqli_stmt_execute($stmt);
|
||||||
|
|
||||||
|
if (!$executed) {
|
||||||
|
throw new Exception("Failed to execute transaction statement: " . mysqli_stmt_error($stmt));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the ID of the transaction just created
|
||||||
|
$transactionId = mysqli_insert_id($conn);
|
||||||
|
|
||||||
|
if (!$transactionId) {
|
||||||
|
throw new Exception("Failed to get transaction ID");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert each item into detail_transaksi
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$product = getProductById($item['product_id']);
|
||||||
|
|
||||||
|
if (!$product) {
|
||||||
|
throw new Exception("Product not found: ID=" . $item['product_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$itemTotal = $product['harga'] * $item['quantity'];
|
||||||
|
|
||||||
|
$stmt = mysqli_prepare($conn, "INSERT INTO detail_transaksi (id_transaksi, id_barangK, jumlah, harga_satuan, total_harga) VALUES (?, ?, ?, ?, ?)");
|
||||||
|
|
||||||
|
if (!$stmt) {
|
||||||
|
throw new Exception("Failed to prepare detail statement: " . mysqli_error($conn));
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_stmt_bind_param($stmt, "iiddd", $transactionId, $item['product_id'], $item['quantity'], $product['harga'], $itemTotal);
|
||||||
|
$executed = mysqli_stmt_execute($stmt);
|
||||||
|
|
||||||
|
if (!$executed) {
|
||||||
|
throw new Exception("Failed to execute detail statement: " . mysqli_stmt_error($stmt));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update inventory (reduce stock)
|
||||||
|
$stmt = mysqli_prepare($conn, "UPDATE barang_kasir SET stok = stok - ? WHERE id_barangK = ? AND stok >= ?");
|
||||||
|
|
||||||
|
if (!$stmt) {
|
||||||
|
throw new Exception("Failed to prepare stock update statement: " . mysqli_error($conn));
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_stmt_bind_param($stmt, "iii", $item['quantity'], $item['product_id'], $item['quantity']);
|
||||||
|
$executed = mysqli_stmt_execute($stmt);
|
||||||
|
|
||||||
|
if (!$executed) {
|
||||||
|
throw new Exception("Failed to execute stock update statement: " . mysqli_stmt_error($stmt));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if stock was actually updated (prevents overselling)
|
||||||
|
if (mysqli_affected_rows($conn) == 0) {
|
||||||
|
throw new Exception("Insufficient stock for product: " . $product['nama_barang']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit transaction
|
||||||
|
mysqli_commit($conn);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
'transaction_id' => $transactionId,
|
||||||
|
'transaction_code' => $transactionCode,
|
||||||
|
'cash_amount' => $cashAmount,
|
||||||
|
'change_amount' => $changeAmount
|
||||||
|
];
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// Rollback in case of error
|
||||||
|
mysqli_rollback($conn);
|
||||||
|
error_log("Transaction failed: " . $e->getMessage());
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get transaction by ID
|
||||||
|
*
|
||||||
|
* @param int $transactionId The transaction ID
|
||||||
|
* @return array|null Transaction details or null if not found
|
||||||
|
*/
|
||||||
|
function getTransactionById($transactionId) {
|
||||||
|
global $conn;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get transaction details
|
||||||
|
$stmt = mysqli_prepare($conn, "SELECT * FROM transaksi WHERE id_transaksi = ?");
|
||||||
|
|
||||||
|
if (!$stmt) {
|
||||||
|
error_log("Query preparation failed: " . mysqli_error($conn));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_stmt_bind_param($stmt, "i", $transactionId);
|
||||||
|
$executed = mysqli_stmt_execute($stmt);
|
||||||
|
|
||||||
|
if (!$executed) {
|
||||||
|
error_log("Query execution failed: " . mysqli_stmt_error($stmt));
|
||||||
|
mysqli_stmt_close($stmt);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = mysqli_stmt_get_result($stmt);
|
||||||
|
$transaction = mysqli_fetch_assoc($result);
|
||||||
|
|
||||||
|
if (!$transaction) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get transaction items
|
||||||
|
$stmt = mysqli_prepare($conn, "
|
||||||
|
SELECT dt.*, b.nama_barang, b.kode_barang, j.nama_jenis
|
||||||
|
FROM detail_transaksi dt
|
||||||
|
JOIN barang_kasir b ON dt.id_barangK = b.id_barangK
|
||||||
|
JOIN jenis_barang j ON b.id_jenis = j.id_jenis
|
||||||
|
WHERE dt.id_transaksi = ?
|
||||||
|
");
|
||||||
|
|
||||||
|
if (!$stmt) {
|
||||||
|
error_log("Query preparation failed: " . mysqli_error($conn));
|
||||||
|
return $transaction; // Return transaction without items
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_stmt_bind_param($stmt, "i", $transactionId);
|
||||||
|
$executed = mysqli_stmt_execute($stmt);
|
||||||
|
|
||||||
|
if (!$executed) {
|
||||||
|
error_log("Query execution failed: " . mysqli_stmt_error($stmt));
|
||||||
|
mysqli_stmt_close($stmt);
|
||||||
|
return $transaction; // Return transaction without items
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = mysqli_stmt_get_result($stmt);
|
||||||
|
|
||||||
|
$items = [];
|
||||||
|
while ($row = mysqli_fetch_assoc($result)) {
|
||||||
|
$items[] = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
$transaction['items'] = $items;
|
||||||
|
|
||||||
|
return $transaction;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Exception in getTransactionById: " . $e->getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get recent transactions
|
||||||
|
*
|
||||||
|
* @param int $limit Maximum number of transactions to return
|
||||||
|
* @return array List of recent transactions
|
||||||
|
*/
|
||||||
|
function getRecentTransactions($limit = 10) {
|
||||||
|
global $conn;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$query = "SELECT * FROM transaksi ORDER BY created_at DESC LIMIT " . intval($limit);
|
||||||
|
$result = mysqli_query($conn, $query);
|
||||||
|
|
||||||
|
if (!$result) {
|
||||||
|
error_log("Query failed: " . mysqli_error($conn));
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$transactions = [];
|
||||||
|
while ($row = mysqli_fetch_assoc($result)) {
|
||||||
|
$transactions[] = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $transactions;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Exception in getRecentTransactions: " . $e->getMessage());
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current user information
|
||||||
|
*
|
||||||
|
* @return array|null User details or null if not found
|
||||||
|
*/
|
||||||
|
function getCurrentUser() {
|
||||||
|
global $conn;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (isset($_SESSION['user_id'])) {
|
||||||
|
$stmt = mysqli_prepare($conn, "SELECT id_kasir, username, role, phone FROM kasir WHERE id_kasir = ?");
|
||||||
|
|
||||||
|
if (!$stmt) {
|
||||||
|
error_log("Query preparation failed: " . mysqli_error($conn));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_stmt_bind_param($stmt, "i", $_SESSION['user_id']);
|
||||||
|
$executed = mysqli_stmt_execute($stmt);
|
||||||
|
|
||||||
|
if (!$executed) {
|
||||||
|
error_log("Query execution failed: " . mysqli_stmt_error($stmt));
|
||||||
|
mysqli_stmt_close($stmt);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = mysqli_stmt_get_result($stmt);
|
||||||
|
$user = mysqli_fetch_assoc($result);
|
||||||
|
mysqli_stmt_close($stmt);
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Exception in getCurrentUser: " . $e->getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get product by barcode (kode_barang)
|
||||||
|
*
|
||||||
|
* @param string $barcode The product barcode
|
||||||
|
* @return array|null Product details or null if not found
|
||||||
|
*/
|
||||||
|
function getProductByBarcode($barcode) {
|
||||||
|
global $conn;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = mysqli_prepare($conn, "SELECT b.*, j.nama_jenis FROM barang_kasir b
|
||||||
|
LEFT JOIN jenis_barang j ON b.id_jenis = j.id_jenis
|
||||||
|
WHERE b.kode_barang = ?");
|
||||||
|
if (!$stmt) {
|
||||||
|
error_log("Query preparation failed: " . mysqli_error($conn));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_stmt_bind_param($stmt, "s", $barcode);
|
||||||
|
$executed = mysqli_stmt_execute($stmt);
|
||||||
|
|
||||||
|
if (!$executed) {
|
||||||
|
error_log("Query execution failed: " . mysqli_stmt_error($stmt));
|
||||||
|
mysqli_stmt_close($stmt);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = mysqli_stmt_get_result($stmt);
|
||||||
|
$product = mysqli_fetch_assoc($result);
|
||||||
|
mysqli_stmt_close($stmt);
|
||||||
|
|
||||||
|
// Log untuk debugging
|
||||||
|
if ($product) {
|
||||||
|
error_log("Product found: ID=" . $product['id_barangK'] . ", Name=" . $product['nama_barang']);
|
||||||
|
} else {
|
||||||
|
error_log("Product not found for barcode: " . $barcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $product;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Exception in getProductByBarcode: " . $e->getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,245 @@
|
||||||
|
<?php
|
||||||
|
// Pencegahan error tampil di output (sangat penting untuk AJAX)
|
||||||
|
ini_set('display_errors', 0);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
// Pastikan output hanya JSON, bukan error PHP
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
// Include transaction functions
|
||||||
|
require_once 'configtrans.php';
|
||||||
|
|
||||||
|
// Fungsi untuk mengirim respons JSON dan keluar
|
||||||
|
function sendResponse($data) {
|
||||||
|
echo json_encode($data);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tangkap semua error untuk mencegah tampil di output
|
||||||
|
function handleErrors($errno, $errstr, $errfile, $errline) {
|
||||||
|
error_log("PHP Error ($errno): $errstr in $errfile on line $errline");
|
||||||
|
sendResponse([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Terjadi kesalahan internal. Silakan coba lagi.',
|
||||||
|
'product' => null,
|
||||||
|
'cart_html' => '',
|
||||||
|
'cart_totals' => [
|
||||||
|
'items' => 0,
|
||||||
|
'total' => 0,
|
||||||
|
'formatted_total' => '0'
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
set_error_handler('handleErrors');
|
||||||
|
|
||||||
|
// Try-catch untuk menangkap semua exception
|
||||||
|
try {
|
||||||
|
// Pastikan sesi dimulai
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log request untuk debugging
|
||||||
|
error_log("Barcode scan request received: " . json_encode($_POST));
|
||||||
|
|
||||||
|
// Initialize response
|
||||||
|
$response = [
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Barcode tidak valid',
|
||||||
|
'product' => null,
|
||||||
|
'cart_html' => '',
|
||||||
|
'cart_totals' => [
|
||||||
|
'items' => 0,
|
||||||
|
'total' => 0,
|
||||||
|
'formatted_total' => '0'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
// Check if barcode is provided
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['barcode'])) {
|
||||||
|
$barcode = trim($_POST['barcode']);
|
||||||
|
|
||||||
|
// Validate barcode
|
||||||
|
if (empty($barcode)) {
|
||||||
|
$response['message'] = "Barcode tidak boleh kosong";
|
||||||
|
sendResponse($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get product by barcode (kode_barang)
|
||||||
|
$product = getProductByBarcode($barcode);
|
||||||
|
|
||||||
|
if (!$product) {
|
||||||
|
$response['message'] = "Produk tidak ditemukan untuk barcode: " . htmlspecialchars($barcode);
|
||||||
|
sendResponse($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check stock availability
|
||||||
|
if ($product['stok'] <= 0) {
|
||||||
|
$response['message'] = "Produk " . htmlspecialchars($product['nama_barang']) . " stok habis";
|
||||||
|
sendResponse($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize cart if not exists
|
||||||
|
if (!isset($_SESSION['cart']) || !is_array($_SESSION['cart'])) {
|
||||||
|
$_SESSION['cart'] = [];
|
||||||
|
error_log("Cart initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
$productId = $product['id_barangK'];
|
||||||
|
$quantity = 1; // Default quantity is 1
|
||||||
|
|
||||||
|
// Check if product already in cart, update quantity if exists
|
||||||
|
$exists = false;
|
||||||
|
foreach ($_SESSION['cart'] as $key => $item) {
|
||||||
|
if ($item['id'] == $productId) {
|
||||||
|
// Only add if it won't exceed stock
|
||||||
|
if ($item['quantity'] < $product['stok']) {
|
||||||
|
$_SESSION['cart'][$key]['quantity'] += 1;
|
||||||
|
} else {
|
||||||
|
// Stock limit reached
|
||||||
|
$response['success'] = false;
|
||||||
|
$response['message'] = "Tidak dapat menambahkan unit lagi. Batas stok tercapai.";
|
||||||
|
sendResponse($response);
|
||||||
|
}
|
||||||
|
$exists = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not exists, add to cart
|
||||||
|
if (!$exists) {
|
||||||
|
$_SESSION['cart'][] = [
|
||||||
|
'id' => $productId,
|
||||||
|
'name' => $product['nama_barang'],
|
||||||
|
'code' => $product['kode_barang'],
|
||||||
|
'price' => $product['harga'],
|
||||||
|
'quantity' => $quantity,
|
||||||
|
'max_stock' => $product['stok']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set success response
|
||||||
|
$response['success'] = true;
|
||||||
|
$response['message'] = 'Produk ditambahkan ke keranjang';
|
||||||
|
$response['product'] = [
|
||||||
|
'id' => $product['id_barangK'],
|
||||||
|
'name' => $product['nama_barang'],
|
||||||
|
'code' => $product['kode_barang'],
|
||||||
|
'price' => $product['harga'],
|
||||||
|
'stock' => $product['stok']
|
||||||
|
];
|
||||||
|
|
||||||
|
// Generate cart HTML
|
||||||
|
$response['cart_html'] = generateCartHTML();
|
||||||
|
|
||||||
|
// Calculate cart totals
|
||||||
|
$cartItems = count($_SESSION['cart']);
|
||||||
|
$total = 0;
|
||||||
|
foreach ($_SESSION['cart'] as $item) {
|
||||||
|
$total += $item['price'] * $item['quantity'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$response['cart_totals'] = [
|
||||||
|
'items' => $cartItems,
|
||||||
|
'total' => $total,
|
||||||
|
'formatted_total' => number_format($total, 0, ',', '.')
|
||||||
|
];
|
||||||
|
|
||||||
|
// Log cart state for debugging
|
||||||
|
error_log("Cart updated. Items: " . $cartItems . ", Total: " . $total);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the response
|
||||||
|
sendResponse($response);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// Log exception
|
||||||
|
error_log("Exception in get_product_by_barcode.php: " . $e->getMessage());
|
||||||
|
|
||||||
|
// Return error response
|
||||||
|
sendResponse([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Terjadi kesalahan: ' . $e->getMessage(),
|
||||||
|
'product' => null,
|
||||||
|
'cart_html' => '',
|
||||||
|
'cart_totals' => [
|
||||||
|
'items' => 0,
|
||||||
|
'total' => 0,
|
||||||
|
'formatted_total' => '0'
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate HTML for cart items
|
||||||
|
*
|
||||||
|
* @return string HTML content for cart
|
||||||
|
*/
|
||||||
|
function generateCartHTML() {
|
||||||
|
if (isset($_SESSION['cart']) && !empty($_SESSION['cart'])) {
|
||||||
|
$html = '';
|
||||||
|
foreach ($_SESSION['cart'] as $index => $item) {
|
||||||
|
$html .= '
|
||||||
|
<ul class="product-lists">
|
||||||
|
<li>
|
||||||
|
<div class="productimg">
|
||||||
|
<div class="productimgs">
|
||||||
|
<img src="../../bootstrap/assets/img/product/product30.jpg" alt="img" />
|
||||||
|
</div>
|
||||||
|
<div class="productcontet">
|
||||||
|
<h4>' . htmlspecialchars($item['name']) . '</h4>
|
||||||
|
<div class="productlinkset">
|
||||||
|
<h5>' . htmlspecialchars($item['code']) . '</h5>
|
||||||
|
</div>
|
||||||
|
<div class="increment-decrement">
|
||||||
|
<div class="input-groups">
|
||||||
|
<form method="post" action="index.php" class="cart-item-form">
|
||||||
|
<input type="hidden" name="product_id" value="' . intval($item['id']) . '">
|
||||||
|
<input type="hidden" name="update_cart" value="1">
|
||||||
|
<div class="quantity-control-container">
|
||||||
|
<button type="button" class="btn btn-sm btn-light quantity-btn decrement">-</button>
|
||||||
|
<input type="text" name="quantity" value="' . intval($item['quantity']) . '"
|
||||||
|
class="quantity-field form-control mx-1"
|
||||||
|
style="width: 45px; text-align: center;"
|
||||||
|
data-max-stock="' . intval($item['max_stock']) . '" />
|
||||||
|
<button type="button" class="btn btn-sm btn-light quantity-btn increment">+</button>
|
||||||
|
<button type="submit" class="btn btn-sm btn-primary ms-1 update-cart-btn">
|
||||||
|
<i class="fa fa-check"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li>Rp. ' . number_format($item['price'] * $item['quantity'], 0, ',', '.') . '</li>
|
||||||
|
<li>
|
||||||
|
<a href="javascript:void(0);" class="delete-cart-item" data-index="' . $index . '">
|
||||||
|
<img src="../../bootstrap/assets/img/icons/delete-2.svg" alt="img" />
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add script to enable functionalities
|
||||||
|
$html .= '
|
||||||
|
<script>
|
||||||
|
// Setup event handlers for the new cart items
|
||||||
|
if (typeof setupQuantityControls === "function") {
|
||||||
|
setupQuantityControls();
|
||||||
|
}
|
||||||
|
if (typeof setupCartDeletionConfirmation === "function") {
|
||||||
|
setupCartDeletionConfirmation();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch custom event to notify that cart was updated
|
||||||
|
document.dispatchEvent(new CustomEvent("cartUpdated"));
|
||||||
|
</script>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
} else {
|
||||||
|
return '<p class="text-center">Keranjang Anda kosong.</p>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,482 @@
|
||||||
|
/**
|
||||||
|
* Ayula Store POS System
|
||||||
|
* Main JavaScript file for transaction page functionality
|
||||||
|
*/
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
// Initialize globals
|
||||||
|
initializeGlobals();
|
||||||
|
|
||||||
|
// Setup event handlers
|
||||||
|
setupEventHandlers();
|
||||||
|
|
||||||
|
// Initialize UI components
|
||||||
|
initializeUI();
|
||||||
|
|
||||||
|
// Check and update the "Clear All" button state on page load
|
||||||
|
updateClearCartButtonInitialState();
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize global variables and state
|
||||||
|
*/
|
||||||
|
function initializeGlobals() {
|
||||||
|
// Variables to track selected products
|
||||||
|
window.selectedProducts = [];
|
||||||
|
window.multiSelectToolbar = $('.multi-select-toolbar');
|
||||||
|
|
||||||
|
// Store product stock information
|
||||||
|
window.productStock = {};
|
||||||
|
$('.productset').each(function() {
|
||||||
|
const productId = $(this).data('product-id');
|
||||||
|
const stockText = $(this).find('.productsetimg h6').text();
|
||||||
|
const stockValue = parseInt(stockText.replace('Stok: ', '')) || 0;
|
||||||
|
window.productStock[productId] = stockValue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check and update the "Clear All" button state on page load
|
||||||
|
*/
|
||||||
|
function updateClearCartButtonInitialState() {
|
||||||
|
const cartItemsCount = $('.product-lists').length;
|
||||||
|
console.log('Initial cart items count:', cartItemsCount);
|
||||||
|
|
||||||
|
// Get the clear cart button
|
||||||
|
const clearCartBtn = $('#clear-cart-btn');
|
||||||
|
|
||||||
|
if (cartItemsCount > 0) {
|
||||||
|
// Enable the clear cart button
|
||||||
|
clearCartBtn.attr('href', 'javascript:void(0);')
|
||||||
|
.removeClass('disabled')
|
||||||
|
.css({
|
||||||
|
'opacity': '1',
|
||||||
|
'cursor': 'pointer'
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Clear cart button initially enabled');
|
||||||
|
} else {
|
||||||
|
// Disable the clear cart button
|
||||||
|
clearCartBtn.attr('href', '#')
|
||||||
|
.addClass('disabled')
|
||||||
|
.css({
|
||||||
|
'opacity': '0.5',
|
||||||
|
'cursor': 'not-allowed'
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Clear cart button initially disabled');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup all event handlers for the page
|
||||||
|
*/
|
||||||
|
function setupEventHandlers() {
|
||||||
|
// Product card selection
|
||||||
|
setupProductSelection();
|
||||||
|
|
||||||
|
// Toolbar actions
|
||||||
|
setupToolbarActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize UI components
|
||||||
|
*/
|
||||||
|
function initializeUI() {
|
||||||
|
// Hide all check marks initially
|
||||||
|
$('.check-product i').hide();
|
||||||
|
|
||||||
|
// Trigger toggle button click to activate it when page loads
|
||||||
|
setTimeout(function() {
|
||||||
|
$("#toggle_btn").trigger('click');
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
// Focus search field when search icon is clicked
|
||||||
|
$('.responsive-search').on('click', function() {
|
||||||
|
setTimeout(function() {
|
||||||
|
$('input[name="search"]').focus();
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Focus the page header search field on click
|
||||||
|
$('.product-search-form input[name="search"]').on('click', function() {
|
||||||
|
$(this).focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Enable live search functionality
|
||||||
|
setupLiveSearch();
|
||||||
|
|
||||||
|
// Enable AJAX category navigation
|
||||||
|
setupCategoryNavigation();
|
||||||
|
|
||||||
|
// Initialize cash payment functionality
|
||||||
|
setupCashPayment();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup product selection functionality
|
||||||
|
*/
|
||||||
|
function setupProductSelection() {
|
||||||
|
// Click on product card to toggle selection
|
||||||
|
$('.productset').on('click', function(e) {
|
||||||
|
// Only handle clicks on the card itself, not buttons or links inside
|
||||||
|
if ($(e.target).closest('button, a, form').length === 0) {
|
||||||
|
const productId = $(this).data('product-id');
|
||||||
|
const checkbox = $(this).find('.product-checkbox');
|
||||||
|
const isChecked = checkbox.prop('checked');
|
||||||
|
|
||||||
|
console.log('Clicked product ID:', productId);
|
||||||
|
|
||||||
|
// Toggle checkbox
|
||||||
|
checkbox.prop('checked', !isChecked);
|
||||||
|
|
||||||
|
if (!isChecked) {
|
||||||
|
// Add to selected list and highlight
|
||||||
|
window.selectedProducts.push(productId);
|
||||||
|
$(this).addClass('selected');
|
||||||
|
$(this).find('.check-product i').show();
|
||||||
|
} else {
|
||||||
|
// Remove from selected list and unhighlight
|
||||||
|
window.selectedProducts = window.selectedProducts.filter(id => id !== productId);
|
||||||
|
$(this).removeClass('selected');
|
||||||
|
$(this).find('.check-product i').hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Selected products:', window.selectedProducts);
|
||||||
|
updateToolbar();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup toolbar action buttons
|
||||||
|
*/
|
||||||
|
function setupToolbarActions() {
|
||||||
|
// Cancel selection button
|
||||||
|
$('#cancel-selection').on('click', function() {
|
||||||
|
// Uncheck all checkboxes and hide check marks
|
||||||
|
$('.product-checkbox').prop('checked', false);
|
||||||
|
$('.productset').removeClass('selected');
|
||||||
|
$('.check-product i').hide();
|
||||||
|
window.selectedProducts = [];
|
||||||
|
updateToolbar();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add selected products to cart
|
||||||
|
$('#add-selected-to-cart').on('click', function() {
|
||||||
|
if (window.selectedProducts.length > 0) {
|
||||||
|
console.log('Adding products to cart:', window.selectedProducts);
|
||||||
|
|
||||||
|
// Create a form to submit selected products
|
||||||
|
const form = $('<form>', {
|
||||||
|
method: 'post',
|
||||||
|
action: 'index.php'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add each product ID as a hidden input
|
||||||
|
window.selectedProducts.forEach(function(productId) {
|
||||||
|
form.append(
|
||||||
|
$('<input>').attr({
|
||||||
|
type: 'hidden',
|
||||||
|
name: 'product_ids[]',
|
||||||
|
value: productId
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Log form contents before submission
|
||||||
|
console.log('Form contents:', form.serialize());
|
||||||
|
|
||||||
|
// Add the form to the body and submit it
|
||||||
|
$('body').append(form);
|
||||||
|
form.submit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the toolbar state based on selected products
|
||||||
|
*/
|
||||||
|
function updateToolbar() {
|
||||||
|
if (window.selectedProducts.length > 0) {
|
||||||
|
$('.selected-count').text(window.selectedProducts.length + ' item' + (window.selectedProducts.length > 1 ? 's' : '') + ' selected');
|
||||||
|
window.multiSelectToolbar.addClass('active');
|
||||||
|
} else {
|
||||||
|
window.multiSelectToolbar.removeClass('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup live search functionality
|
||||||
|
*/
|
||||||
|
function setupLiveSearch() {
|
||||||
|
// Handle both search inputs - top nav and product header search
|
||||||
|
const searchInputs = $('input[name="search"]');
|
||||||
|
|
||||||
|
// Store the original URL for reference
|
||||||
|
const originalUrl = window.location.href.split('?')[0];
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
|
// Clear search buttons functionality
|
||||||
|
$('.product-search-form a, .search-addon a').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
urlParams.delete('search');
|
||||||
|
|
||||||
|
// Build the new URL
|
||||||
|
let newUrl = originalUrl;
|
||||||
|
const paramString = urlParams.toString();
|
||||||
|
if (paramString) {
|
||||||
|
newUrl += '?' + paramString;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigate to the new URL
|
||||||
|
window.location.href = newUrl;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Custom form submission to prevent loader
|
||||||
|
$('.product-search-form form, .top-nav-search form').on('submit', function(e) {
|
||||||
|
e.preventDefault(); // Prevent the default form submission
|
||||||
|
|
||||||
|
// Get the search query
|
||||||
|
const searchQuery = $(this).find('input[name="search"]').val().trim();
|
||||||
|
|
||||||
|
// Get any type filter
|
||||||
|
const typeParam = $(this).find('input[name="type"]').val();
|
||||||
|
|
||||||
|
// Build the URL
|
||||||
|
let newUrl = originalUrl;
|
||||||
|
if (searchQuery || typeParam) {
|
||||||
|
newUrl += '?';
|
||||||
|
|
||||||
|
if (searchQuery) {
|
||||||
|
newUrl += 'search=' + encodeURIComponent(searchQuery);
|
||||||
|
if (typeParam) {
|
||||||
|
newUrl += '&';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeParam) {
|
||||||
|
newUrl += 'type=' + encodeURIComponent(typeParam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use AJAX to fetch the page content
|
||||||
|
$.ajax({
|
||||||
|
url: newUrl,
|
||||||
|
type: 'GET',
|
||||||
|
beforeSend: function() {
|
||||||
|
// Add a simple loading indicator to the product area
|
||||||
|
$('.tab_content').addClass('loading');
|
||||||
|
$('.tab_content > .row').css('opacity', '0.5');
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
// Extract and replace just the product content
|
||||||
|
const $response = $(response);
|
||||||
|
const newProductContent = $response.find('.tab_content').html();
|
||||||
|
|
||||||
|
// Update the DOM with new content
|
||||||
|
$('.tab_content').html(newProductContent);
|
||||||
|
|
||||||
|
// Update browser URL without reloading
|
||||||
|
history.pushState({}, '', newUrl);
|
||||||
|
|
||||||
|
// Update search info area if it exists
|
||||||
|
const searchInfoContent = $response.find('.search-results-info').html();
|
||||||
|
if (searchInfoContent) {
|
||||||
|
if ($('.search-results-info').length) {
|
||||||
|
$('.search-results-info').html(searchInfoContent);
|
||||||
|
} else {
|
||||||
|
$('<div class="search-results-info mb-3"></div>')
|
||||||
|
.html(searchInfoContent)
|
||||||
|
.insertAfter('.page-header');
|
||||||
|
}
|
||||||
|
$('.search-results-info').show();
|
||||||
|
} else {
|
||||||
|
$('.search-results-info').hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the other search input with the same value
|
||||||
|
searchInputs.val(searchQuery);
|
||||||
|
|
||||||
|
// Reset the loading state
|
||||||
|
$('.tab_content').removeClass('loading');
|
||||||
|
$('.tab_content > .row').css('opacity', '1');
|
||||||
|
|
||||||
|
// Reinitialize product selection for newly loaded products
|
||||||
|
setupProductSelection();
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
// If something goes wrong, just do a normal page load
|
||||||
|
window.location.href = newUrl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup AJAX-based category navigation
|
||||||
|
*/
|
||||||
|
function setupCategoryNavigation() {
|
||||||
|
// Original URL for reference
|
||||||
|
const originalUrl = window.location.href.split('?')[0];
|
||||||
|
|
||||||
|
// Add click event handlers to all category links
|
||||||
|
$('.tabs li a.category-tab').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Get category type from URL
|
||||||
|
const href = $(this).attr('href');
|
||||||
|
const url = new URL(href, window.location.origin);
|
||||||
|
const typeParam = url.searchParams.get('type');
|
||||||
|
|
||||||
|
// Get current search query if any
|
||||||
|
const currentUrl = new URL(window.location.href);
|
||||||
|
const searchQuery = currentUrl.searchParams.get('search');
|
||||||
|
|
||||||
|
// Build the target URL
|
||||||
|
let targetUrl = originalUrl;
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
// Add type parameter if set
|
||||||
|
if (typeParam) {
|
||||||
|
params.set('type', typeParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preserve search query if exists
|
||||||
|
if (searchQuery) {
|
||||||
|
params.set('search', searchQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append parameters to URL if any
|
||||||
|
const paramString = params.toString();
|
||||||
|
if (paramString) {
|
||||||
|
targetUrl += '?' + paramString;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Highlight the active category tab
|
||||||
|
$('.tabs li').removeClass('active');
|
||||||
|
$(this).closest('li').addClass('active');
|
||||||
|
|
||||||
|
// Use AJAX to fetch the category products
|
||||||
|
$.ajax({
|
||||||
|
url: targetUrl,
|
||||||
|
type: 'GET',
|
||||||
|
beforeSend: function() {
|
||||||
|
// Add loading indicator to product area
|
||||||
|
$('.tab_content').addClass('loading');
|
||||||
|
$('.tab_content > .row').css('opacity', '0.5');
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
// Extract and replace just the product content
|
||||||
|
const $response = $(response);
|
||||||
|
const newProductContent = $response.find('.tab_content').html();
|
||||||
|
|
||||||
|
// Update the DOM with new content
|
||||||
|
$('.tab_content').html(newProductContent);
|
||||||
|
|
||||||
|
// Update browser URL without reloading
|
||||||
|
history.pushState({}, '', targetUrl);
|
||||||
|
|
||||||
|
// Reset the loading state
|
||||||
|
$('.tab_content').removeClass('loading');
|
||||||
|
$('.tab_content > .row').css('opacity', '1');
|
||||||
|
|
||||||
|
// Reinitialize product selection for newly loaded products
|
||||||
|
setupProductSelection();
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
// If something goes wrong, just do a normal page load
|
||||||
|
window.location.href = targetUrl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup cash payment functionality
|
||||||
|
*/
|
||||||
|
function setupCashPayment() {
|
||||||
|
const cashInput = $('#cash-amount');
|
||||||
|
const changeDisplay = $('#change-amount');
|
||||||
|
const changeContainer = $('#change-container');
|
||||||
|
const hiddenCashAmount = $('#hidden-cash-amount');
|
||||||
|
const hiddenChangeAmount = $('#hidden-change-amount');
|
||||||
|
const totalAmount = parseFloat($('.total-value h6').text().replace(/[^\d]/g, ''));
|
||||||
|
|
||||||
|
// Format cash input with thousand separators
|
||||||
|
cashInput.on('input', function() {
|
||||||
|
// Remove non-numeric characters
|
||||||
|
let value = $(this).val().replace(/[^\d]/g, '');
|
||||||
|
|
||||||
|
// Convert to number and format with thousand separator
|
||||||
|
if (value !== '') {
|
||||||
|
const numericValue = parseInt(value);
|
||||||
|
|
||||||
|
// Calculate change
|
||||||
|
const change = numericValue - totalAmount;
|
||||||
|
|
||||||
|
// Update hidden fields for form submission
|
||||||
|
hiddenCashAmount.val(numericValue);
|
||||||
|
hiddenChangeAmount.val(Math.max(0, change));
|
||||||
|
|
||||||
|
// Format display value
|
||||||
|
$(this).val(formatNumber(numericValue));
|
||||||
|
|
||||||
|
// Show change if payment is sufficient
|
||||||
|
if (change >= 0) {
|
||||||
|
changeDisplay.text('Rp. ' + formatNumber(change));
|
||||||
|
changeContainer.show();
|
||||||
|
} else {
|
||||||
|
changeContainer.hide();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$(this).val('');
|
||||||
|
changeContainer.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Quick cash buttons
|
||||||
|
$('.quick-cash').on('click', function() {
|
||||||
|
const value = $(this).data('value');
|
||||||
|
cashInput.val(formatNumber(value));
|
||||||
|
|
||||||
|
// Calculate change
|
||||||
|
const change = value - totalAmount;
|
||||||
|
|
||||||
|
// Update hidden fields for form submission
|
||||||
|
hiddenCashAmount.val(value);
|
||||||
|
hiddenChangeAmount.val(Math.max(0, change));
|
||||||
|
|
||||||
|
// Show change
|
||||||
|
if (change >= 0) {
|
||||||
|
changeDisplay.text('Rp. ' + formatNumber(change));
|
||||||
|
changeContainer.show();
|
||||||
|
} else {
|
||||||
|
changeContainer.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Form submission check
|
||||||
|
$('#checkout-form').on('submit', function(e) {
|
||||||
|
const cashValue = parseFloat(hiddenCashAmount.val());
|
||||||
|
|
||||||
|
// Check if cash amount is sufficient
|
||||||
|
if (cashValue < totalAmount) {
|
||||||
|
e.preventDefault();
|
||||||
|
alert('Cash amount is not sufficient!');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Trigger input event to initialize on page load
|
||||||
|
cashInput.trigger('input');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format number with thousand separator
|
||||||
|
*/
|
||||||
|
function formatNumber(number) {
|
||||||
|
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,453 @@
|
||||||
|
<?php
|
||||||
|
// Memasukkan fungsi transaksi
|
||||||
|
require_once 'configtrans.php';
|
||||||
|
|
||||||
|
// Memulai sesi
|
||||||
|
// session_start();
|
||||||
|
$userRole = $_SESSION['role']; // 'user' or 'admin'
|
||||||
|
// Memeriksa apakah ID transaksi tersedia
|
||||||
|
if (!isset($_GET['id'])) {
|
||||||
|
// Arahkan ke halaman transaksi jika ID tidak ada
|
||||||
|
header('Location: index.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mendapatkan ID transaksi dari URL
|
||||||
|
$transactionId = $_GET['id'];
|
||||||
|
|
||||||
|
// Mendapatkan detail transaksi
|
||||||
|
$transaction = getTransactionById($transactionId);
|
||||||
|
|
||||||
|
// Memeriksa apakah transaksi ada
|
||||||
|
if (!$transaction) {
|
||||||
|
// Arahkan ke halaman transaksi jika transaksi tidak ditemukan
|
||||||
|
header('Location: index.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="id">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1.0, user-scalable=0"
|
||||||
|
/>
|
||||||
|
<meta name="description" content="POS - Template Admin Bootstrap" />
|
||||||
|
<meta
|
||||||
|
name="keywords"
|
||||||
|
content="admin, estimasi, bootstrap, bisnis, korporat, kreatif, faktur, html5, responsif, Proyek"
|
||||||
|
/>
|
||||||
|
<meta name="author" content="Dreamguys - Template Admin Bootstrap" />
|
||||||
|
<meta name="robots" content="noindex, nofollow" />
|
||||||
|
<title>Ayula Store POS - Transaksi Sukses</title>
|
||||||
|
|
||||||
|
<link
|
||||||
|
rel="shortcut icon"
|
||||||
|
type="image/x-icon"
|
||||||
|
href="../../bootstrap/assets/img/favicon.jpg"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="../../bootstrap/assets/css/bootstrap.min.css" />
|
||||||
|
<link rel="stylesheet" href="../../bootstrap/assets/css/animate.css" />
|
||||||
|
<link rel="stylesheet" href="../../bootstrap/assets/plugins/fontawesome/css/fontawesome.min.css" />
|
||||||
|
<link rel="stylesheet" href="../../bootstrap/assets/plugins/fontawesome/css/all.min.css" />
|
||||||
|
<link rel="stylesheet" href="../../bootstrap/assets/css/style.css" />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Gaya layar reguler */
|
||||||
|
@media screen {
|
||||||
|
.print-only {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gaya cetak */
|
||||||
|
@media print {
|
||||||
|
/* Sembunyikan semua kecuali struk */
|
||||||
|
body * {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-receipt, .print-receipt * {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-receipt {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 80mm; /* Lebar untuk struk thermal */
|
||||||
|
padding: 2mm;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 10pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sembunyikan elemen hanya untuk layar */
|
||||||
|
.screen-only {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tampilkan elemen hanya untuk cetak */
|
||||||
|
.print-only {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gaya struk */
|
||||||
|
.receipt-header {
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: 1px dashed #000;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receipt-info {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-size: 9pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receipt-table {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 8pt;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receipt-table th, .receipt-table td {
|
||||||
|
padding: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receipt-table th {
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receipt-footer {
|
||||||
|
text-align: center;
|
||||||
|
border-top: 1px dashed #000;
|
||||||
|
padding-top: 5px;
|
||||||
|
margin-top: 5px;
|
||||||
|
font-size: 8pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.receipt-total {
|
||||||
|
border-top: 1px solid #000;
|
||||||
|
margin-top: 5px;
|
||||||
|
padding-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="global-loader">
|
||||||
|
<div class="whirly-loader"></div>
|
||||||
|
</div>
|
||||||
|
<div class="main-wrapper">
|
||||||
|
<div class="header">
|
||||||
|
<div class="header-left active">
|
||||||
|
<a href="/ayula-store/views/dashboard/" class="logo">
|
||||||
|
<img src="../../src/img/logoayula.png" alt="" />
|
||||||
|
</a>
|
||||||
|
<a href="/ayula-store/views/dashboard/" class="logo-small">
|
||||||
|
<img src="../../src/img/smallest-ayula.png" alt="" />
|
||||||
|
</a>
|
||||||
|
<a id="toggle_btn" href="javascript:void(0);"> </a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a id="mobile_btn" class="mobile_btn" href="#sidebar">
|
||||||
|
<span class="bar-icon">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<ul class="nav user-menu">
|
||||||
|
<li class="nav-item dropdown has-arrow main-drop">
|
||||||
|
<a href="javascript:void(0);" class="dropdown-toggle nav-link userset" data-bs-toggle="dropdown">
|
||||||
|
<span class="user-img">
|
||||||
|
<img src="../../src/img/userprofile.png" alt="" />
|
||||||
|
<span class="status online"></span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu menu-drop-user">
|
||||||
|
<div class="profilename">
|
||||||
|
<div class="profileset">
|
||||||
|
<span class="user-img">
|
||||||
|
<img src="../../src/img/userprofile.png" alt="" />
|
||||||
|
<span class="status online"></span>
|
||||||
|
</span>
|
||||||
|
<div class="profilesets">
|
||||||
|
<h6><?php echo $isAdmin ? 'Admin' : 'Karyawan'; ?></h6>
|
||||||
|
<h5><?php echo htmlspecialchars($username); ?></h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item" href="/ayula-store/views/report-issue/">
|
||||||
|
<img src="../../src/img/warning.png" class="me-2" alt="img" /> Laporkan Masalah
|
||||||
|
</a>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item logout pb-0" href="../../views/logout.php"><img
|
||||||
|
src="../../bootstrap/assets/img/icons/log-out.svg"
|
||||||
|
class="me-2"
|
||||||
|
alt="img" />Keluar</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="dropdown mobile-user-menu">
|
||||||
|
<a href="javascript:void(0);" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="fa fa-ellipsis-v"></i>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
|
<a class="dropdown-item" href="/ayula-store/views/report-issue/">
|
||||||
|
<i class="fa fa-cog me-2"></i> Laporkan Masalah
|
||||||
|
</a>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item logout pb-0" href="../../views/logout.php"><img
|
||||||
|
src="../../bootstrap/assets/img/icons/log-out.svg"
|
||||||
|
class="me-2"
|
||||||
|
alt="img" />Keluar</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sidebar" id="sidebar">
|
||||||
|
<div class="sidebar-inner slimscroll">
|
||||||
|
<div id="sidebar-menu" class="sidebar-menu">
|
||||||
|
<ul>
|
||||||
|
<li class="active">
|
||||||
|
<a href="/ayula-store/views/dashboard/"><img src="../../bootstrap/assets/img/icons/dashboard.svg" alt="img" /><span>
|
||||||
|
Dashboard</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/ayula-store/views/transaction/"><img src="../../bootstrap/assets/img/icons/sales1.svg" alt="img" /><span>
|
||||||
|
POS</span></a>
|
||||||
|
</li>
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../bootstrap/assets/img/icons/product.svg" alt="img" /><span>
|
||||||
|
Produk</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/ayula-store/views/barang/productlist.php">Daftar Produk</a></li>
|
||||||
|
<li><a href="/ayula-store/views/barang/addproduct.php">Tambah Produk</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../bootstrap/assets/img/icons/purchase1.svg" alt="img" /><span>
|
||||||
|
Pembelian</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="purchaselist.html">Daftar Pembelian</a></li>
|
||||||
|
<li><a href="addpurchase.html">Tambah Pembelian</a></li>
|
||||||
|
<li><a href="importpurchase.html">Impor Pembelian</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../bootstrap/assets/img/icons/time.svg" alt="img" /><span>
|
||||||
|
Laporan</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="inventoryreport.html">Laporan Inventaris</a></li>
|
||||||
|
<li><a href="/ayula-store/views/report/sales-report/">Laporan Penjualan</a></li>
|
||||||
|
<li><a href="purchasereport.html">Laporan Pembelian</a></li>
|
||||||
|
<li><a href="supplierreport.html">Laporan Pemasok</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../bootstrap/assets/img/icons/users1.svg" alt="img" /><span>
|
||||||
|
Pengguna</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<?php if ($userRole == 'admin') { ?>
|
||||||
|
<li><a href="/ayula-store/views/users/add-user.php">Pengguna Baru</a></li>
|
||||||
|
<?php } ?>
|
||||||
|
<li><a href="/ayula-store/views/users/">Daftar Pengguna</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="content">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-8 col-sm-12 mx-auto">
|
||||||
|
<div class="card screen-only">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="text-center">
|
||||||
|
<h4 class="mt-2 mb-4"><i class="fa fa-check-circle text-success me-2"></i> Transaksi Berhasil</h4>
|
||||||
|
<p class="text-secondary">Transaksi Anda telah diproses dengan sukses</p>
|
||||||
|
<hr>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label text-muted">ID Transaksi</label>
|
||||||
|
<div class="form-control-static"><?php echo $transaction['kode_transaksi']; ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label text-muted">Tanggal</label>
|
||||||
|
<div class="form-control-static"><?php echo date('d M Y H:i', strtotime($transaction['tanggal'])); ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Barang</th>
|
||||||
|
<th>Jumlah</th>
|
||||||
|
<th class="text-end">Harga</th>
|
||||||
|
<th class="text-end">Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($transaction['items'] as $item): ?>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div class="d-flex">
|
||||||
|
<div>
|
||||||
|
<h6><?php echo $item['nama_barang']; ?></h6>
|
||||||
|
<p class="text-muted mb-0"><?php echo $item['kode_barang']; ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td><?php echo $item['jumlah']; ?></td>
|
||||||
|
<td class="text-end">Rp <?php echo number_format($item['harga_satuan'], 0, ',', '.'); ?></td>
|
||||||
|
<td class="text-end">Rp <?php echo number_format($item['total_harga'], 0, ',', '.'); ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row justify-content-end mt-4">
|
||||||
|
<div class="col-lg-5">
|
||||||
|
<div class="card bg-light">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between border-top pt-2">
|
||||||
|
<h5>Total</h5>
|
||||||
|
<h5>Rp <?php echo number_format($transaction['total'], 0, ',', '.'); ?></h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tambahkan informasi tunai dan kembalian -->
|
||||||
|
<?php if (isset($transaction['cash_amount']) && $transaction['cash_amount'] > 0): ?>
|
||||||
|
<div class="d-flex justify-content-between mt-3 mb-2">
|
||||||
|
<h6>Jumlah Tunai</h6>
|
||||||
|
<h6>Rp <?php echo number_format($transaction['cash_amount'], 0, ',', '.'); ?></h6>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<h6>Kembalian</h6>
|
||||||
|
<h6>Rp <?php echo number_format($transaction['change_amount'], 0, ',', '.'); ?></h6>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center mt-4 screen-only">
|
||||||
|
<a href="index.php" class="btn btn-primary me-2">Transaksi Baru</a>
|
||||||
|
<button class="btn btn-secondary" onclick="window.print()">Cetak Struk</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Template Struk Thermal - Dioptimalkan untuk lebar 80mm -->
|
||||||
|
<div class="print-receipt print-only">
|
||||||
|
<div class="receipt-header">
|
||||||
|
<h3 style="margin:0;font-size:14pt;">AYULA STORE</h3>
|
||||||
|
<p style="margin:3px 0;font-size:9pt;">Senjayan, Kec. Gondang, Kabupaten Nganjuk, </p>
|
||||||
|
<p style="margin:3px 0;font-size:9pt;">Jawa Timur 64451</p>
|
||||||
|
<p style="margin:3px 0;font-size:9pt;">Telp: 0822 3472 2000</p>
|
||||||
|
<p style="margin:3px 0;font-size:8pt;">-------------------------------------</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="receipt-info">
|
||||||
|
<table style="width:100%;font-size:9pt;">
|
||||||
|
<tr>
|
||||||
|
<td width="60%">No: <?php echo $transaction['kode_transaksi']; ?></td>
|
||||||
|
<td width="40%" style="text-align:right;">Kasir: Admin</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">Tanggal: <?php echo date('d/m/Y H:i', strtotime($transaction['tanggal'])); ?></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<p style="margin:2px 0;font-size:8pt;">-------------------------------------</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="receipt-table" style="width:100%;font-size:8pt;">
|
||||||
|
<tr>
|
||||||
|
<th style="width:50%;text-align:left;">Barang</th>
|
||||||
|
<th style="width:10%;text-align:center;">Qty</th>
|
||||||
|
<th style="width:20%;text-align:right;">Harga</th>
|
||||||
|
<th style="width:20%;text-align:right;">Total</th>
|
||||||
|
</tr>
|
||||||
|
<?php foreach ($transaction['items'] as $item): ?>
|
||||||
|
<tr>
|
||||||
|
<td style="font-size:8pt;"><?php echo $item['nama_barang']; ?></td>
|
||||||
|
<td style="text-align:center;"><?php echo $item['jumlah']; ?></td>
|
||||||
|
<td style="text-align:right;"><?php echo number_format($item['harga_satuan'], 0, ',', '.'); ?></td>
|
||||||
|
<td style="text-align:right;"><?php echo number_format($item['total_harga'], 0, ',', '.'); ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p style="margin:5px 0 2px;font-size:8pt;">-------------------------------------</p>
|
||||||
|
|
||||||
|
<table style="width:100%;font-size:9pt;">
|
||||||
|
<tr style="font-weight:bold;">
|
||||||
|
<td style="text-align:right;">TOTAL:</td>
|
||||||
|
<td style="text-align:right;">Rp <?php echo number_format($transaction['total'], 0, ',', '.'); ?></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<?php if (isset($transaction['cash_amount']) && $transaction['cash_amount'] > 0): ?>
|
||||||
|
<tr>
|
||||||
|
<td style="text-align:right;">Tunai:</td>
|
||||||
|
<td style="text-align:right;">Rp <?php echo number_format($transaction['cash_amount'], 0, ',', '.'); ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="text-align:right;">Kembali:</td>
|
||||||
|
<td style="text-align:right;">Rp <?php echo number_format($transaction['change_amount'], 0, ',', '.'); ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="receipt-footer">
|
||||||
|
<p style="margin:5px 0 2px;font-size:8pt;">-------------------------------------</p>
|
||||||
|
<p style="margin:3px 0;font-size:9pt;">Terima Kasih Atas Kunjungan Anda</p>
|
||||||
|
<p style="margin:3px 0;font-size:8pt;">Barang yang sudah dibeli tidak dapat dikembalikan</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="../../bootstrap/assets/js/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="../../bootstrap/assets/js/feather.min.js"></script>
|
||||||
|
<script src="../../bootstrap/assets/js/jquery.slimscroll.min.js"></script>
|
||||||
|
<script src="../../bootstrap/assets/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="../../bootstrap/assets/js/script.js"></script>
|
||||||
|
<script>
|
||||||
|
// Auto-print saat halaman ini dimuat - aktifkan jika perlu
|
||||||
|
// window.onload = function() {
|
||||||
|
// window.print();
|
||||||
|
// };
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
After Width: | Height: | Size: 508 KiB |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 175 KiB |
|
After Width: | Height: | Size: 479 KiB |
|
After Width: | Height: | Size: 139 KiB |
|
After Width: | Height: | Size: 248 KiB |
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 248 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 170 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 175 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
|
@ -0,0 +1,293 @@
|
||||||
|
<?php
|
||||||
|
include('../../routes/db_conn.php');
|
||||||
|
session_start();
|
||||||
|
$userRole = $_SESSION['role'];
|
||||||
|
$username = $_SESSION['username']; // Mengambil username dari session
|
||||||
|
|
||||||
|
// Register new user data if the form is submitted
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||||
|
// Get the form data
|
||||||
|
$newUsername = $_POST['username'];
|
||||||
|
$password = $_POST['password'];
|
||||||
|
$confirmPassword = $_POST['confirm_password'];
|
||||||
|
$phone = $_POST['phone'];
|
||||||
|
$role = $_POST['role'];
|
||||||
|
|
||||||
|
// Validate form fields (optional but recommended)
|
||||||
|
if (!empty($newUsername) && !empty($password) && !empty($confirmPassword) && !empty($phone) && !empty($role)) {
|
||||||
|
// Check if phone number contains only numbers
|
||||||
|
if (!preg_match('/^[0-9]+$/', $phone)) {
|
||||||
|
$showUsernameModal = true;
|
||||||
|
$modalMessage = "Nomor telepon hanya boleh berisi angka.";
|
||||||
|
}
|
||||||
|
// Check if phone number length is valid (11-13 digits for Indonesian numbers)
|
||||||
|
else if (strlen($phone) < 11 || strlen($phone) > 13) {
|
||||||
|
$showUsernameModal = true;
|
||||||
|
$modalMessage = "Nomor telepon harus terdiri dari 11 hingga 13 angka.";
|
||||||
|
}
|
||||||
|
// Check if passwords match
|
||||||
|
else if ($password !== $confirmPassword) {
|
||||||
|
$showUsernameModal = true;
|
||||||
|
$modalMessage = "Password tidak sama.";
|
||||||
|
} else {
|
||||||
|
// Check if username already exists
|
||||||
|
$checkUsername = "SELECT username FROM kasir WHERE username = ?";
|
||||||
|
$checkStmt = $conn->prepare($checkUsername);
|
||||||
|
$checkStmt->bind_param("s", $newUsername);
|
||||||
|
$checkStmt->execute();
|
||||||
|
$result = $checkStmt->get_result();
|
||||||
|
|
||||||
|
if ($result->num_rows > 0) {
|
||||||
|
// Set flag to show modal
|
||||||
|
$showUsernameModal = true;
|
||||||
|
$modalMessage = "Username sudah digunakan. Silakan pilih username lain.";
|
||||||
|
} else {
|
||||||
|
// Hash the password
|
||||||
|
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||||
|
|
||||||
|
// Insert the user information into the database
|
||||||
|
$insertSql = "INSERT INTO kasir (username, password, phone, role) VALUES (?, ?, ?, ?)";
|
||||||
|
$insertStmt = $conn->prepare($insertSql);
|
||||||
|
$insertStmt->bind_param("ssss", $newUsername, $hashed_password, $phone, $role);
|
||||||
|
$insertStmt->execute();
|
||||||
|
|
||||||
|
// Redirect to the users list after the insert
|
||||||
|
header("Location: index.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Show error if any form field is empty
|
||||||
|
$showUsernameModal = true;
|
||||||
|
$modalMessage = "Harap isi semua kolom.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variable for modal display
|
||||||
|
$showUsernameModal = isset($showUsernameModal) ? $showUsernameModal : false;
|
||||||
|
$modalMessage = isset($modalMessage) ? $modalMessage : "";
|
||||||
|
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="id">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0">
|
||||||
|
<meta name="description" content="POS - Bootstrap Admin Template">
|
||||||
|
<meta name="keywords" content="admin, estimates, bootstrap, business, corporate, creative, invoice, html5, responsive, Projects">
|
||||||
|
<meta name="author" content="Dreamguys - Bootstrap Admin Template">
|
||||||
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
<title>Ayula Store - Tambah Pengguna</title>
|
||||||
|
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../src/img/smallest-ayula.png">
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/animate.css">
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/plugins/select2/css/select2.min.css">
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/dataTables.bootstrap4.min.css">
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/plugins/fontawesome/css/fontawesome.min.css">
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/plugins/fontawesome/css/all.min.css">
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/style.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="global-loader">
|
||||||
|
<div class="whirly-loader"> </div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="main-wrapper">
|
||||||
|
<div class="header">
|
||||||
|
<div class="header-left active">
|
||||||
|
<a href="/ayula-store/views/dashboard/" class="logo">
|
||||||
|
<img src="../../src/img/logoayula.png" alt="" />
|
||||||
|
</a>
|
||||||
|
<a href="/ayula-store/views/dashboard/" class="logo-small">
|
||||||
|
<img src="../../src/img/smallest-ayula.png" alt="" />
|
||||||
|
</a>
|
||||||
|
<a id="toggle_btn" href="javascript:void(0);"> </a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a id="mobile_btn" class="mobile_btn" href="#sidebar">
|
||||||
|
<span class="bar-icon">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<ul class="nav user-menu">
|
||||||
|
<li class="nav-item dropdown has-arrow main-drop">
|
||||||
|
<a href="javascript:void(0);" class="dropdown-toggle nav-link userset" data-bs-toggle="dropdown">
|
||||||
|
<span class="user-img">
|
||||||
|
<img src="../../src/img/userprofile.png" alt="" />
|
||||||
|
<span class="status online"></span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu menu-drop-user">
|
||||||
|
<div class="profilename">
|
||||||
|
<div class="profileset">
|
||||||
|
<span class="user-img">
|
||||||
|
<img src="../../src/img/userprofile.png" alt="" />
|
||||||
|
<span class="status online"></span>
|
||||||
|
</span>
|
||||||
|
<div class="profilesets">
|
||||||
|
<h6><?php echo $userRole == 'admin' ? 'Admin' : 'Karyawan'; ?></h6>
|
||||||
|
<h5><?php echo htmlspecialchars($username); ?></h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item" href="/ayula-store/views/report-issue/">
|
||||||
|
<img src="../../src/img/warning.png" class="me-2" alt="img" /> Laporkan Masalah
|
||||||
|
</a>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item logout pb-0" href="../../views/logout.php"><img
|
||||||
|
src="../../bootstrap/assets/img/icons/log-out.svg"
|
||||||
|
class="me-2"
|
||||||
|
alt="img" />Keluar</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="sidebar" id="sidebar">
|
||||||
|
<div class="sidebar-inner slimscroll">
|
||||||
|
<div id="sidebar-menu" class="sidebar-menu">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="/ayula-store/views/reporttt/report.php"><img src="../../bootstrap/assets/img/icons/dashboard.svg" alt="img" /><span>
|
||||||
|
Dashboard</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../bootstrap/assets/img/icons/product.svg" alt="img" /><span>
|
||||||
|
Barang</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/ayula-store/views/barang/productlist.php" class="active">Daftar Barang</a></li>
|
||||||
|
<li><a href="/ayula-store/views/barang/addproduct.php">Tambah Barang</a></li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li >
|
||||||
|
<a href="/ayula-store/views/barang/topsis_restock_view.php"><img src="../../bootstrap/assets/img/icons/sales1.svg" alt="img" /><span>
|
||||||
|
Analisa Barang</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../bootstrap/assets/img/icons/users1.svg" alt="img" /><span>
|
||||||
|
Pengguna</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<?php if ($userRole == 'admin') { ?>
|
||||||
|
<li><a href="/ayula-store/views/users/add-user.php" class="active">Pengguna Baru</a></li>
|
||||||
|
<?php } ?>
|
||||||
|
<li><a href="/ayula-store/views/users/">Daftar Pengguna</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="content">
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="page-title">
|
||||||
|
<h4>Manajemen Pengguna</h4>
|
||||||
|
<h6>Tambah Pengguna Baru</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="POST" action="add-user.php">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-3 col-sm-6 col-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Nama</label>
|
||||||
|
<input type="text" name="username" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Password</label>
|
||||||
|
<input type="password" name="password" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Konfirmasi Password</label>
|
||||||
|
<input type="password" name="confirm_password" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-3 col-sm-6 col-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Telepon</label>
|
||||||
|
<input type="text" name="phone" class="form-control" required
|
||||||
|
oninput="this.value = this.value.replace(/[^0-9]/g, '');"
|
||||||
|
pattern="[0-9]{11,13}"
|
||||||
|
title="Masukkan nomor telepon yang valid (11-13 angka)"
|
||||||
|
minlength="11" maxlength="13">
|
||||||
|
<!-- <small class="form-text text-muted">Nomor telepon harus terdiri dari 11-13 angka.</small> -->
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Peran</label>
|
||||||
|
<select name="role" class="form-control" required>
|
||||||
|
<option value="admin">Admin</option>
|
||||||
|
<option value="user">Karyawan</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<button type="submit" class="btn btn-submit me-2">Daftar</button>
|
||||||
|
<a href="/ayula-store/views/users/" class="btn btn-cancel">Batal</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/feather.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/jquery.slimscroll.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/jquery.dataTables.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/dataTables.bootstrap4.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/plugins/select2/js/select2.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/script.js"></script>
|
||||||
|
|
||||||
|
<!-- Modal for notifications -->
|
||||||
|
<div class="modal fade" id="notificationModal" tabindex="-1" role="dialog" aria-labelledby="notificationModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="notificationModalLabel">Notifikasi</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="text-center">
|
||||||
|
<i class="fas fa-exclamation-circle text-warning" style="font-size: 48px;"></i>
|
||||||
|
<p class="mt-3"><?php echo $modalMessage; ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Mengerti</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($showUsernameModal): ?>
|
||||||
|
<script>
|
||||||
|
// Show the modal when the page loads
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
var notificationModal = new bootstrap.Modal(document.getElementById('notificationModal'));
|
||||||
|
notificationModal.show();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<?php endif; ?>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
include('../../routes/db_conn.php');
|
||||||
|
|
||||||
|
// Check if an ID is passed
|
||||||
|
if (isset($_GET['id'])) {
|
||||||
|
$id_kasir = $_GET['id'];
|
||||||
|
|
||||||
|
// Prepare SQL query to delete the user
|
||||||
|
$sql = "DELETE FROM kasir WHERE id_kasir = ?";
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
$stmt->bind_param("i", $id_kasir);
|
||||||
|
|
||||||
|
// Execute the query
|
||||||
|
if ($stmt->execute()) {
|
||||||
|
// Redirect back to the user list after successful deletion
|
||||||
|
header("Location: index.php");
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
// If deletion fails, show an error message
|
||||||
|
echo "Error deleting record: " . $conn->error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the connection
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
|
|
@ -0,0 +1,264 @@
|
||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
include('../../routes/db_conn.php');
|
||||||
|
$userRole = $_SESSION['role']; // 'user' or 'admin'
|
||||||
|
$username = $_SESSION['username']; // Menambahkan username dari session
|
||||||
|
|
||||||
|
// Check if the user ID is passed in the URL
|
||||||
|
if (isset($_GET['id'])) {
|
||||||
|
$id_kasir = $_GET['id'];
|
||||||
|
|
||||||
|
// Fetch user data from the database
|
||||||
|
$sql = "SELECT id_kasir, username, phone, role FROM kasir WHERE id_kasir = ?";
|
||||||
|
$stmt = $conn->prepare($sql);
|
||||||
|
$stmt->bind_param("i", $id_kasir); // Bind the id to the query
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
// Check if the user exists
|
||||||
|
if ($result->num_rows > 0) {
|
||||||
|
$user = $result->fetch_assoc();
|
||||||
|
} else {
|
||||||
|
echo "Pengguna tidak ditemukan.";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update user data if the form is submitted
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||||
|
// Get the form data
|
||||||
|
$editUsername = $_POST['username'];
|
||||||
|
$password = $_POST['password'];
|
||||||
|
$phone = $_POST['phone'];
|
||||||
|
$role = $_POST['role'];
|
||||||
|
|
||||||
|
// Validate form fields (optional but recommended)
|
||||||
|
if (!empty($editUsername) && !empty($phone) && !empty($role)) {
|
||||||
|
// Check if password is provided
|
||||||
|
if (!empty($password)) {
|
||||||
|
// Hash the password
|
||||||
|
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||||
|
|
||||||
|
// Update with new password
|
||||||
|
$updateSql = "UPDATE kasir SET username = ?, password = ?, phone = ?, role = ? WHERE id_kasir = ?";
|
||||||
|
$updateStmt = $conn->prepare($updateSql);
|
||||||
|
$updateStmt->bind_param("ssssi", $editUsername, $hashed_password, $phone, $role, $id_kasir);
|
||||||
|
} else {
|
||||||
|
// Update without changing password
|
||||||
|
$updateSql = "UPDATE kasir SET username = ?, phone = ?, role = ? WHERE id_kasir = ?";
|
||||||
|
$updateStmt = $conn->prepare($updateSql);
|
||||||
|
$updateStmt->bind_param("sssi", $editUsername, $phone, $role, $id_kasir);
|
||||||
|
}
|
||||||
|
|
||||||
|
$updateStmt->execute();
|
||||||
|
|
||||||
|
// Redirect to the users list after the update
|
||||||
|
header("Location: index.php");
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
// Show error if any form field is empty
|
||||||
|
echo "<script>alert('Harap isi semua kolom yang diperlukan.');</script>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="id">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0">
|
||||||
|
<meta name="description" content="POS - Bootstrap Admin Template">
|
||||||
|
<meta name="keywords" content="admin, estimates, bootstrap, business, corporate, creative, invoice, html5, responsive, Projects">
|
||||||
|
<meta name="author" content="Dreamguys - Bootstrap Admin Template">
|
||||||
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
<title>Ayula Store - Ubah Pengguna</title>
|
||||||
|
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../src/img/smallest-ayula.png">
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/animate.css">
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/plugins/select2/css/select2.min.css">
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/dataTables.bootstrap4.min.css">
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/plugins/fontawesome/css/fontawesome.min.css">
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/plugins/fontawesome/css/all.min.css">
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/style.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="global-loader">
|
||||||
|
<div class="whirly-loader"> </div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="main-wrapper">
|
||||||
|
<div class="header">
|
||||||
|
<div class="header-left active">
|
||||||
|
<a href="/ayula-store/views/dashboard/" class="logo">
|
||||||
|
<img src="../../src/img/logoayula.png" alt="" />
|
||||||
|
</a>
|
||||||
|
<a href="/ayula-store/views/dashboard/" class="logo-small">
|
||||||
|
<img src="../../src/img/smallest-ayula.png" alt="" />
|
||||||
|
</a>
|
||||||
|
<a id="toggle_btn" href="javascript:void(0);"> </a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a id="mobile_btn" class="mobile_btn" href="#sidebar">
|
||||||
|
<span class="bar-icon">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<ul class="nav user-menu">
|
||||||
|
<li class="nav-item dropdown has-arrow main-drop">
|
||||||
|
<a href="javascript:void(0);" class="dropdown-toggle nav-link userset" data-bs-toggle="dropdown">
|
||||||
|
<span class="user-img">
|
||||||
|
<img src="../../src/img/userprofile.png" alt="" />
|
||||||
|
<span class="status online"></span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu menu-drop-user">
|
||||||
|
<div class="profilename">
|
||||||
|
<div class="profileset">
|
||||||
|
<span class="user-img">
|
||||||
|
<img src="../../src/img/userprofile.png" alt="" />
|
||||||
|
<span class="status online"></span>
|
||||||
|
</span>
|
||||||
|
<div class="profilesets">
|
||||||
|
<h6><?php echo $userRole == 'admin' ? 'Admin' : 'Karyawan'; ?></h6>
|
||||||
|
<h5><?php echo htmlspecialchars($username); ?></h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item" href="/ayula-store/views/report-issue/">
|
||||||
|
<img src="../../src/img/warning.png" class="me-2" alt="img" /> Laporkan Masalah
|
||||||
|
</a>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item logout pb-0" href="../../views/logout.php"><img
|
||||||
|
src="../../bootstrap/assets/img/icons/log-out.svg"
|
||||||
|
class="me-2"
|
||||||
|
alt="img" />Keluar</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="dropdown mobile-user-menu">
|
||||||
|
<a href="javascript:void(0);" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="fa fa-ellipsis-v"></i>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
|
<a class="dropdown-item" href="/ayula-store/views/report-issue/">
|
||||||
|
<i class="fa fa-cog me-2"></i> Laporkan Masalah
|
||||||
|
</a>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item logout pb-0" href="../../views/logout.php"><img
|
||||||
|
src="../../bootstrap/assets/img/icons/log-out.svg"
|
||||||
|
class="me-2"
|
||||||
|
alt="img" />Keluar</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sidebar" id="sidebar">
|
||||||
|
<div class="sidebar-inner slimscroll">
|
||||||
|
<div id="sidebar-menu" class="sidebar-menu">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="/ayula-store/views/reporttt/report.php"><img src="../../bootstrap/assets/img/icons/dashboard.svg" alt="img" /><span>
|
||||||
|
Dashboard</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../bootstrap/assets/img/icons/product.svg" alt="img" /><span>
|
||||||
|
Barang</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/ayula-store/views/barang/productlist.php" class="active">Daftar Barang</a></li>
|
||||||
|
<li><a href="/ayula-store/views/barang/addproduct.php">Tambah Barang</a></li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li >
|
||||||
|
<a href="/ayula-store/views/barang/topsis_restock_view.php"><img src="../../bootstrap/assets/img/icons/sales1.svg" alt="img" /><span>
|
||||||
|
Analisa Barang</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../bootstrap/assets/img/icons/users1.svg" alt="img" /><span>
|
||||||
|
Pengguna</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<?php if ($userRole == 'admin') { ?>
|
||||||
|
<li><a href="/ayula-store/views/users/add-user.php">Pengguna Baru</a></li>
|
||||||
|
<?php } ?>
|
||||||
|
<li><a href="/ayula-store/views/users/" class="active">Daftar Pengguna</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="content">
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="page-title">
|
||||||
|
<h4>Manajemen Pengguna</h4>
|
||||||
|
<h6>Edit/Perbarui Pengguna</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="POST" action="edit-user.php?id=<?php echo $id_kasir; ?>">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-3 col-sm-6 col-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Nama Pengguna</label>
|
||||||
|
<input type="text" name="username" class="form-control" value="<?php echo htmlspecialchars($user['username']); ?>" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Password</label>
|
||||||
|
<input type="password" name="password" class="form-control" placeholder="Kosongkan jika tidak ingin mengubah">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-3 col-sm-6 col-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Telepon</label>
|
||||||
|
<input type="text" name="phone" class="form-control" value="<?php echo htmlspecialchars($user['phone']); ?>" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Peran</label>
|
||||||
|
<select name="role" class="form-control" required>
|
||||||
|
<option value="admin" <?php echo $user['role'] == 'admin' ? 'selected' : ''; ?>>Admin</option>
|
||||||
|
<option value="user" <?php echo $user['role'] == 'user' ? 'selected' : ''; ?>>Karyawan</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<button type="submit" class="btn btn-submit me-2">Simpan</button>
|
||||||
|
<a href="index.php" class="btn btn-cancel">Batal</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/feather.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/jquery.slimscroll.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/jquery.dataTables.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/dataTables.bootstrap4.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/plugins/select2/js/select2.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/script.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,296 @@
|
||||||
|
<?php
|
||||||
|
include('../../routes/db_conn.php');
|
||||||
|
|
||||||
|
// Start the session to access the logged-in user information
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
// // Ambil informasi user yang sedang login
|
||||||
|
$userRole = $_SESSION['role']; // 'user' atau 'admin' $username = $_SESSION['username']; // Menambahkan username dari session
|
||||||
|
|
||||||
|
// Query database untuk daftar kasir
|
||||||
|
$sql = "SELECT id_kasir, username, phone, role FROM kasir"; // Menghapus 'password' dari query
|
||||||
|
$result = $conn->query($sql);
|
||||||
|
|
||||||
|
if ($result->num_rows > 0) {
|
||||||
|
$kasirData = [];
|
||||||
|
while ($row = $result->fetch_assoc()) {
|
||||||
|
$kasirData[] = $row;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$kasirData = null; // Jika tidak ada data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Menutup koneksi
|
||||||
|
$conn->close();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="id">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Ayula Store - Pengguna</title>
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="../../src/img/smallest-ayula.png">
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/animate.css">
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/plugins/select2/css/select2.min.css">
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/bootstrap-datetimepicker.min.css">
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/dataTables.bootstrap4.min.css">
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/plugins/fontawesome/css/fontawesome.min.css">
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/plugins/fontawesome/css/all.min.css">
|
||||||
|
<link rel="stylesheet" href="/ayula-store/bootstrap/assets/css/style.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="global-loader">
|
||||||
|
<div class="whirly-loader"> </div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="main-wrapper">
|
||||||
|
<div class="header">
|
||||||
|
<div class="header-left active">
|
||||||
|
<a href="/ayula-store/views/dashboard/" class="logo">
|
||||||
|
<img src="../../src/img/logoayula.png" alt="" />
|
||||||
|
</a>
|
||||||
|
<a href="/ayula-store/views/dashboard/" class="logo-small">
|
||||||
|
<img src="../../src/img/smallest-ayula.png" alt="" />
|
||||||
|
</a>
|
||||||
|
<a id="toggle_btn" href="javascript:void(0);"> </a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a id="mobile_btn" class="mobile_btn" href="#sidebar">
|
||||||
|
<span class="bar-icon">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<ul class="nav user-menu">
|
||||||
|
<li class="nav-item dropdown has-arrow main-drop">
|
||||||
|
<a href="javascript:void(0);" class="dropdown-toggle nav-link userset" data-bs-toggle="dropdown">
|
||||||
|
<span class="user-img">
|
||||||
|
<img src="../../src/img/userprofile.png" alt="" />
|
||||||
|
<span class="status online"></span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu menu-drop-user">
|
||||||
|
<div class="profilename">
|
||||||
|
<div class="profileset">
|
||||||
|
<span class="user-img">
|
||||||
|
<img src="../../src/img/userprofile.png" alt="" />
|
||||||
|
<span class="status online"></span>
|
||||||
|
</span>
|
||||||
|
<div class="profilesets">
|
||||||
|
<h6><?php echo $userRole == 'admin' ? 'Admin' : 'Karyawan'; ?></h6>
|
||||||
|
<h5><?php echo htmlspecialchars($username); ?></h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item" href="/ayula-store/views/report-issue/">
|
||||||
|
<img src="../../src/img/warning.png" class="me-2" alt="img" /> Laporkan Masalah
|
||||||
|
</a>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item logout pb-0" href="../../views/logout.php"><img
|
||||||
|
src="../../bootstrap/assets/img/icons/log-out.svg"
|
||||||
|
class="me-2"
|
||||||
|
alt="img" />Keluar</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="dropdown mobile-user-menu">
|
||||||
|
<a href="javascript:void(0);" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="fa fa-ellipsis-v"></i>
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
|
<a class="dropdown-item" href="/ayula-store/views/report-issue/">
|
||||||
|
<i class="fa fa-cog me-2"></i> Laporkan Masalah
|
||||||
|
</a>
|
||||||
|
<hr class="m-0" />
|
||||||
|
<a class="dropdown-item logout pb-0" href="../../views/logout.php"><img
|
||||||
|
src="../../bootstrap/assets/img/icons/log-out.svg"
|
||||||
|
class="me-2"
|
||||||
|
alt="img" />Keluar</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sidebar" id="sidebar">
|
||||||
|
<div class="sidebar-inner slimscroll">
|
||||||
|
<div id="sidebar-menu" class="sidebar-menu">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="/ayula-store/views/reporttt/report.php"><img src="../../bootstrap/assets/img/icons/dashboard.svg" alt="img" /><span>
|
||||||
|
Dashboard</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../bootstrap/assets/img/icons/product.svg" alt="img" /><span>
|
||||||
|
Barang</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/ayula-store/views/barang/productlist.php" class="active">Daftar Barang</a></li>
|
||||||
|
<li><a href="/ayula-store/views/barang/addproduct.php">Tambah Barang</a></li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li >
|
||||||
|
<a href="/ayula-store/views/barang/topsis_restock_view.php"><img src="../../bootstrap/assets/img/icons/sales1.svg" alt="img" /><span>
|
||||||
|
Analisa Barang</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="submenu">
|
||||||
|
<a href="javascript:void(0);"><img src="../../bootstrap/assets/img/icons/users1.svg" alt="img" /><span>
|
||||||
|
Pengguna</span>
|
||||||
|
<span class="menu-arrow"></span></a>
|
||||||
|
<ul>
|
||||||
|
|
||||||
|
<li><a href="/ayula-store/views/users/add-user.php">Pengguna Baru</a></li>
|
||||||
|
|
||||||
|
<li><a href="/ayula-store/views/users/" class="active">Daftar Pengguna</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="content">
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="page-title">
|
||||||
|
<h4>Daftar Pengguna</h4>
|
||||||
|
<h6>Kelola Pengguna Anda</h6>
|
||||||
|
</div>
|
||||||
|
<div class="page-btn">
|
||||||
|
|
||||||
|
<a href="add-user.php" class="btn btn-added"><img src="/ayula-store/bootstrap/assets/img/icons/plus.svg" alt="img">Tambah Pengguna</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table datanew">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Nama</th>
|
||||||
|
<th>Telepon</th>
|
||||||
|
<th>Peran</th>
|
||||||
|
|
||||||
|
<th>Aksi</th>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php
|
||||||
|
if ($kasirData) {
|
||||||
|
foreach ($kasirData as $kasir) {
|
||||||
|
echo "<tr>";
|
||||||
|
echo "<td>" . $kasir['username'] . "</td>";
|
||||||
|
echo "<td>" . $kasir['phone'] . "</td>";
|
||||||
|
echo "<td>" . ($kasir['role'] == 'admin' ? 'Admin' : 'Karyawan') . "</td>";
|
||||||
|
|
||||||
|
echo "<td>
|
||||||
|
<a class='me-3' href='edit-user.php?id=" . $kasir['id_kasir'] . "'>
|
||||||
|
<img src='/ayula-store/bootstrap/assets/img/icons/edit.svg' alt='img'>
|
||||||
|
</a>
|
||||||
|
<a class='me-3 delete-btn' href='#' data-id='" . $kasir['id_kasir'] . "'>
|
||||||
|
<img src='/ayula-store/bootstrap/assets/img/icons/delete.svg' alt='img'>
|
||||||
|
</a>
|
||||||
|
</td>";
|
||||||
|
}
|
||||||
|
echo "</tr>";
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal for Delete Confirmation -->
|
||||||
|
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="deleteModalLabel">Hapus Pengguna</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
Apakah Anda yakin ingin menghapus pengguna ini?
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-cancel" data-bs-dismiss="modal">Batal</button>
|
||||||
|
<a id="confirmDelete" href="#" class="btn btn-submit">Hapus</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/feather.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/jquery.slimscroll.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/jquery.dataTables.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/dataTables.bootstrap4.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/plugins/select2/js/select2.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/moment.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/bootstrap-datetimepicker.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/plugins/sweetalert/sweetalert2.all.min.js"></script>
|
||||||
|
<script src="/ayula-store/bootstrap/assets/js/script.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
// Check if the table is already initialized before initializing it
|
||||||
|
if (!$.fn.dataTable.isDataTable('.datanew')) {
|
||||||
|
$('.datanew').DataTable({
|
||||||
|
language: {
|
||||||
|
search: "Cari:",
|
||||||
|
lengthMenu: "Tampilkan _MENU_ data",
|
||||||
|
info: "Menampilkan _START_ sampai _END_ dari _TOTAL_ data",
|
||||||
|
infoEmpty: "Menampilkan 0 sampai 0 dari 0 data",
|
||||||
|
infoFiltered: "(disaring dari _MAX_ total data)",
|
||||||
|
zeroRecords: "Tidak ada data yang cocok",
|
||||||
|
paginate: {
|
||||||
|
first: "Pertama",
|
||||||
|
last: "Terakhir",
|
||||||
|
next: "Selanjutnya",
|
||||||
|
previous: "Sebelumnya"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handling the delete button click
|
||||||
|
$('.delete-btn').on('click', function() {
|
||||||
|
var userId = $(this).data('id');
|
||||||
|
var deleteUrl = 'delete-user.php?id=' + userId;
|
||||||
|
|
||||||
|
// Set the delete link in the modal
|
||||||
|
$('#confirmDelete').attr('href', deleteUrl);
|
||||||
|
|
||||||
|
// Show the modal
|
||||||
|
$('#deleteModal').modal('show');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
// Disable console logs and warnings
|
||||||
|
if (window.location.hostname === 'localhost') {
|
||||||
|
console.log = function() {}; // Disable console logs
|
||||||
|
console.warn = function() {}; // Disable console warnings
|
||||||
|
console.error = function() {}; // Disable console errors
|
||||||
|
window.alert = function() {}; // Disable alert popups
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||