6 January 2026, commit
This commit is contained in:
parent
1bcd927e19
commit
f8eb73face
BIN
assets/pasted-20260103-175650-632b87a5.png
Normal file
BIN
assets/pasted-20260103-175650-632b87a5.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
23
get_counts.php
Normal file
23
get_counts.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
try {
|
||||
$db = db();
|
||||
$tables = ['customers', 'products', 'shipments', 'lifecycle'];
|
||||
$counts = [];
|
||||
|
||||
foreach ($tables as $table) {
|
||||
$stmt = $db->query("SELECT COUNT(*) FROM `$table`");
|
||||
$counts[$table] = $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
foreach ($counts as $table => $count) {
|
||||
echo ucfirst($table) . ": $count rows
|
||||
";
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
echo "Error: " . $e->getMessage() . "
|
||||
";
|
||||
}
|
||||
?>
|
||||
22
get_schema.php
Normal file
22
get_schema.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
try {
|
||||
$pdoconn = db();
|
||||
|
||||
echo "--- customers table ---\n";
|
||||
$stmt = $pdoconn->query('DESCRIBE customers');
|
||||
print_r($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
|
||||
echo "--- lifecycle table ---\n";
|
||||
$stmt = $pdoconn->query('DESCRIBE lifecycle');
|
||||
print_r($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
|
||||
echo "--- products table ---\n";
|
||||
$stmt = $pdoconn->query('DESCRIBE products');
|
||||
print_r($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
|
||||
} catch (PDOException $e) {
|
||||
echo "Error: " . $e->getMessage();
|
||||
}
|
||||
|
||||
354
import.php
Normal file
354
import.php
Normal file
@ -0,0 +1,354 @@
|
||||
<?php
|
||||
ini_set('display_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
require_once 'db/config.php';
|
||||
|
||||
$messages = [];
|
||||
$conn = db();
|
||||
|
||||
function create_lifecycle_table($conn) {
|
||||
$messages = [];
|
||||
try {
|
||||
$sql_lifecycle = "
|
||||
CREATE TABLE IF NOT EXISTS lifecycle (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
product_id VARCHAR(255),
|
||||
custcode VARCHAR(255),
|
||||
first_ship_month VARCHAR(255),
|
||||
last_ship_month VARCHAR(255),
|
||||
total_ship INT,
|
||||
avg_ship FLOAT,
|
||||
maxstores INT,
|
||||
mock_pct FLOAT,
|
||||
store_count INT,
|
||||
runrate FLOAT
|
||||
);";
|
||||
$conn->exec($sql_lifecycle);
|
||||
$messages['success'][] = "Table 'lifecycle' created or already exists.";
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$messages['danger'][] = "Table creation failed: " . $e->getMessage();
|
||||
}
|
||||
return $messages;
|
||||
}
|
||||
|
||||
function create_customers_table($conn) {
|
||||
$messages = [];
|
||||
try {
|
||||
$sql_customers = "
|
||||
CREATE TABLE IF NOT EXISTS customers (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
custcode VARCHAR(255),
|
||||
custname VARCHAR(255),
|
||||
channel VARCHAR(255),
|
||||
country VARCHAR(255),
|
||||
maxstores INT
|
||||
);";
|
||||
$conn->exec($sql_customers);
|
||||
$messages['success'][] = "Table 'customers' created or already exists.";
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$messages['danger'][] = "Table creation failed: " . $e->getMessage();
|
||||
}
|
||||
return $messages;
|
||||
}
|
||||
|
||||
function create_products_table($conn) {
|
||||
$messages = [];
|
||||
try {
|
||||
$sql_products = "
|
||||
CREATE TABLE IF NOT EXISTS products (
|
||||
product_id VARCHAR(255) PRIMARY KEY,
|
||||
prodname VARCHAR(255),
|
||||
brand VARCHAR(255),
|
||||
subbrand VARCHAR(255),
|
||||
sub_brand_abbreviation VARCHAR(255),
|
||||
listprice FLOAT
|
||||
);";
|
||||
$conn->exec($sql_products);
|
||||
$messages['success'][] = "Table 'products' created or already exists.";
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$messages['danger'][] = "Table creation failed: " . $e->getMessage();
|
||||
}
|
||||
return $messages;
|
||||
}
|
||||
|
||||
function create_shipments_table($conn) {
|
||||
$messages = [];
|
||||
try {
|
||||
$sql_shipments = "
|
||||
CREATE TABLE IF NOT EXISTS shipments (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
product_id VARCHAR(255),
|
||||
month VARCHAR(255),
|
||||
custcode VARCHAR(255),
|
||||
ship INT
|
||||
);";
|
||||
$conn->exec($sql_shipments);
|
||||
$messages['success'][] = "Table 'shipments' created or already exists.";
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$messages['danger'][] = "Table creation failed: " . $e->getMessage();
|
||||
}
|
||||
return $messages;
|
||||
}
|
||||
|
||||
function import_csv($conn, $filePath, $tableName, $fieldMap, &$messages) {
|
||||
try {
|
||||
$conn->exec("TRUNCATE TABLE `$tableName`");
|
||||
} catch (PDOException $e) {
|
||||
$messages['danger'][] = "Error truncating table $tableName: " . $e->getMessage();
|
||||
return ['count' => 0, 'error' => "Error truncating table: " . $e->getMessage()];
|
||||
}
|
||||
|
||||
if (!file_exists($filePath) || !is_readable($filePath)) {
|
||||
return ['count' => 0, 'error' => "File not found or not readable: $filePath"];
|
||||
}
|
||||
|
||||
$file = fopen($filePath, 'r');
|
||||
if (!$file) {
|
||||
return ['count' => 0, 'error' => "Failed to open file: $filePath"];
|
||||
}
|
||||
|
||||
$header = fgetcsv($file);
|
||||
if (!$header) {
|
||||
return ['count' => 0, 'error' => "Failed to read header from file: $filePath"];
|
||||
}
|
||||
|
||||
$indices = [];
|
||||
foreach ($fieldMap as $dbField => $csvField) {
|
||||
$index = array_search($csvField, $header);
|
||||
if ($index === false) {
|
||||
// Allow for flexibility with headers that have dots
|
||||
$csvFieldWithDot = str_replace('_', '.', $csvField);
|
||||
if (in_array($csvFieldWithDot, $header)) {
|
||||
$index = array_search($csvFieldWithDot, $header);
|
||||
} else {
|
||||
return ['count' => 0, 'error' => "Column '$csvField' (or '$csvFieldWithDot') not found in $tableName CSV."];
|
||||
}
|
||||
}
|
||||
$indices[$dbField] = $index;
|
||||
}
|
||||
|
||||
$placeholders = implode(', ', array_fill(0, count($fieldMap), '?'));
|
||||
$columns = implode(', ', array_keys($fieldMap));
|
||||
|
||||
if ($tableName === 'products') {
|
||||
$sql = "INSERT IGNORE INTO $tableName ($columns) VALUES ($placeholders)";
|
||||
} else {
|
||||
$sql = "INSERT INTO $tableName ($columns) VALUES ($placeholders)";
|
||||
}
|
||||
$stmt = $conn->prepare($sql);
|
||||
|
||||
$count = 0;
|
||||
$conn->beginTransaction();
|
||||
while (($row = fgetcsv($file)) !== false) {
|
||||
$params = [];
|
||||
|
||||
foreach ($fieldMap as $dbField => $csvField) {
|
||||
$value = $row[$indices[$dbField]];
|
||||
if ($tableName === 'lifecycle') {
|
||||
if (in_array($dbField, ['total_ship', 'maxstores', 'store_count'])) {
|
||||
$params[] = (int)filter_var($value, FILTER_SANITIZE_NUMBER_INT);
|
||||
} elseif (in_array($dbField, ['avg_ship', 'mock_pct', 'runrate'])) {
|
||||
$params[] = (float)filter_var($value, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
|
||||
} else {
|
||||
$params[] = trim($value);
|
||||
}
|
||||
} elseif ($tableName === 'customers') {
|
||||
if (in_array($dbField, ['maxstores'])) {
|
||||
$params[] = (int)filter_var($value, FILTER_SANITIZE_NUMBER_INT);
|
||||
} else {
|
||||
$params[] = trim($value);
|
||||
}
|
||||
} elseif ($tableName === 'products') {
|
||||
if (in_array($dbField, ['listprice'])) {
|
||||
$params[] = (float)filter_var($value, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
|
||||
} else {
|
||||
$params[] = trim($value);
|
||||
}
|
||||
} elseif ($tableName === 'shipments') {
|
||||
if (in_array($dbField, ['ship'])) {
|
||||
$params[] = (int)filter_var($value, FILTER_SANITIZE_NUMBER_INT);
|
||||
} else {
|
||||
$params[] = trim($value);
|
||||
}
|
||||
} else {
|
||||
$params[] = trim($value);
|
||||
}
|
||||
}
|
||||
$stmt->execute($params);
|
||||
$count++;
|
||||
}
|
||||
$conn->commit();
|
||||
fclose($file);
|
||||
|
||||
return ['count' => $count, 'error' => null];
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$messages = array_merge_recursive($messages, create_lifecycle_table($conn));
|
||||
$messages = array_merge_recursive($messages, create_customers_table($conn));
|
||||
$messages = array_merge_recursive($messages, create_products_table($conn));
|
||||
$messages = array_merge_recursive($messages, create_shipments_table($conn));
|
||||
|
||||
$uploadDir = 'uploads/';
|
||||
if (!is_dir($uploadDir)) {
|
||||
mkdir($uploadDir, 0777, true);
|
||||
}
|
||||
|
||||
$files = [
|
||||
'lifecycle' => ['lifecycle', [
|
||||
'product_id' => 'product_id',
|
||||
'custcode' => 'custcode',
|
||||
'first_ship_month' => 'first_ship_month',
|
||||
'last_ship_month' => 'last_ship_month',
|
||||
'total_ship' => 'total_ship',
|
||||
'avg_ship' => 'avg_ship',
|
||||
'maxstores' => 'maxstores',
|
||||
'mock_pct' => 'mock_pct',
|
||||
'store_count' => 'store_count',
|
||||
'runrate' => 'runrate'
|
||||
]],
|
||||
'customers' => ['customers', [
|
||||
'custcode' => 'custcode',
|
||||
'custname' => 'custname',
|
||||
'channel' => 'channel',
|
||||
'country' => 'country',
|
||||
'maxstores' => 'maxstores'
|
||||
]],
|
||||
'products' => ['products', [
|
||||
'product_id' => 'product_id',
|
||||
'prodname' => 'prodname',
|
||||
'brand' => 'brand',
|
||||
'subbrand' => 'subbrand',
|
||||
'sub_brand_abbreviation' => 'sub_brand.abbreviation',
|
||||
'listprice' => 'listprice'
|
||||
]],
|
||||
'shipments' => ['shipments', [
|
||||
'product_id' => 'product_id',
|
||||
'month' => 'month',
|
||||
'custcode' => 'custcode',
|
||||
'ship' => 'ship'
|
||||
]]
|
||||
];
|
||||
|
||||
foreach ($files as $fileKey => $fileInfo) {
|
||||
if (isset($_FILES[$fileKey]) && $_FILES[$fileKey]['error'] == UPLOAD_ERR_OK) {
|
||||
$tableName = $fileInfo[0];
|
||||
$fieldMap = $fileInfo[1];
|
||||
$tmpName = $_FILES[$fileKey]['tmp_name'];
|
||||
$filePath = $uploadDir . basename($_FILES[$fileKey]['name']);
|
||||
|
||||
if (move_uploaded_file($tmpName, $filePath)) {
|
||||
$result = import_csv($conn, $filePath, $tableName, $fieldMap, $messages);
|
||||
if ($result['error']) {
|
||||
$messages['danger'][] = "Error importing $tableName: " . $result['error'];
|
||||
} else {
|
||||
$messages['success'][] = "Successfully imported " . $result['count'] . " records into '$tableName'.";
|
||||
}
|
||||
} else {
|
||||
$messages['danger'][] = "Failed to move uploaded file for $fileKey.";
|
||||
}
|
||||
} elseif (isset($_FILES[$fileKey]) && !empty($_FILES[$fileKey]['name'])) {
|
||||
$errorCode = $_FILES[$fileKey]['error'];
|
||||
$errorMessage = "An unknown error occurred.";
|
||||
if ($errorCode === UPLOAD_ERR_INI_SIZE) {
|
||||
$maxSize = ini_get('upload_max_filesize');
|
||||
$errorMessage = "The file is too large. The server's maximum upload size is {$maxSize}.";
|
||||
}
|
||||
$messages['warning'][] = "An error occurred with the '$fileKey' upload: " . $errorMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Data Importer</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/">CRUD App</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" aria-current="page" href="/import.php">Import Data</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container mt-5">
|
||||
<div class="row">
|
||||
<div class="col-md-8 offset-md-2">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h1 class="card-title text-center">Data Importer</h1>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">Upload your CSV files to populate the database. The system will create the necessary tables and import the data.</p>
|
||||
|
||||
<?php if (!empty($messages)): ?>
|
||||
<div class="mb-4">
|
||||
<?php foreach ($messages as $type => $msgs): ?>
|
||||
<?php foreach ($msgs as $msg): ?>
|
||||
<div class="alert alert-<?php echo htmlspecialchars($type); ?>" role="alert">
|
||||
<?php echo htmlspecialchars($msg); ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="import.php" method="post" enctype="multipart/form-data">
|
||||
<div class="mb-3">
|
||||
<label for="lifecycle" class="form-label">Lifecycle CSV File</label>
|
||||
<input class="form-control" type="file" id="lifecycle" name="lifecycle">
|
||||
<div class="form-text">Fields: product_id, custcode, first_ship_month, last_ship_month, total_ship, avg_ship, maxstores, mock_pct, store_count, runrate</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="customers" class="form-label">Customers CSV File</label>
|
||||
<input class="form-control" type="file" id="customers" name="customers">
|
||||
<div class="form-text">Fields: custcode, custname, channel, country, maxstores</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="products" class="form-label">Products CSV File</label>
|
||||
<input class="form-control" type="file" id="products" name="products">
|
||||
<div class="form-text">Fields: product_id, prodname, brand, subbrand, sub_brand.abbreviation, listprice</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="shipments" class="form-label">Shipments CSV File</label>
|
||||
<input class="form-control" type="file" id="shipments" name="shipments">
|
||||
<div class="form-text">Fields: product_id, month, custcode, ship</div>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-primary btn-lg"><i class="bi bi-cloud-upload-fill"></i> Preview & Import</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="card-footer text-muted">
|
||||
Ensure your CSV files have a header row that matches the specified fields.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
194
index.php
194
index.php
@ -1,150 +1,82 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
@ini_set('display_errors', '1');
|
||||
@error_reporting(E_ALL);
|
||||
@date_default_timezone_set('UTC');
|
||||
|
||||
$phpVersion = PHP_VERSION;
|
||||
$now = date('Y-m-d H:i:s');
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>New Style</title>
|
||||
<?php
|
||||
// Read project preview data from environment
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'A CRUD application for managing shipments.';
|
||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
?>
|
||||
<?php if ($projectDescription): ?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Shipments Admin</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
<?php if ($projectDescription): ?>
|
||||
<!-- Meta description -->
|
||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
||||
<meta name="description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<!-- Open Graph meta tags -->
|
||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<!-- Twitter meta tags -->
|
||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<?php endif; ?>
|
||||
<?php if ($projectImageUrl): ?>
|
||||
<?php endif; ?>
|
||||
<?php if ($projectImageUrl): ?>
|
||||
<!-- Open Graph image -->
|
||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<!-- Twitter image -->
|
||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<?php endif; ?>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-color-start: #6a11cb;
|
||||
--bg-color-end: #2575fc;
|
||||
--text-color: #ffffff;
|
||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
body::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
||||
animation: bg-pan 20s linear infinite;
|
||||
z-index: -1;
|
||||
}
|
||||
@keyframes bg-pan {
|
||||
0% { background-position: 0% 0%; }
|
||||
100% { background-position: 100% 100%; }
|
||||
}
|
||||
main {
|
||||
padding: 2rem;
|
||||
}
|
||||
.card {
|
||||
background: var(--card-bg-color);
|
||||
border: 1px solid var(--card-border-color);
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.loader {
|
||||
margin: 1.25rem auto 1.25rem;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.hint {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px; height: 1px;
|
||||
padding: 0; margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap; border: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 1rem;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
p {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
code {
|
||||
background: rgba(0,0,0,0.2);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
footer {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
<?php endif; ?>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="card">
|
||||
<h1>Analyzing your requirements and generating your website…</h1>
|
||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||
<span class="sr-only">Loading…</span>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/">Shipments Admin</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" aria-current="page" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/import.php">Import Data</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/manage_lifecycle.php">Manage Lifecycle</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
||||
</footer>
|
||||
</nav>
|
||||
|
||||
<div class="container mt-5">
|
||||
<div class="p-5 mb-4 bg-light rounded-3">
|
||||
<div class="container-fluid py-5">
|
||||
<h1 class="display-5 fw-bold">Welcome to Shipments Admin</h1>
|
||||
<p class="col-md-8 fs-4">This is your starting point for managing customers, products, and shipments. Use the navigation bar to get started.</p>
|
||||
<a href="/import.php" class="btn btn-primary btn-lg" role="button">
|
||||
<i class="bi bi-cloud-upload"></i> Start by Importing Data
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row align-items-md-stretch">
|
||||
<div class="col-md-6">
|
||||
<div class="h-100 p-5 text-white bg-dark rounded-3">
|
||||
<h2>View Data</h2>
|
||||
<p>Once imported, you will be able to browse, search, and manage your data here.</p>
|
||||
<button class="btn btn-outline-light" type="button" disabled>View Customers (Coming Soon)</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="h-100 p-5 bg-light border rounded-3">
|
||||
<h2>Next Steps</h2>
|
||||
<p>After importing your data, the next step will be to build the user interface for creating, reading, updating, and deleting records.</p>
|
||||
<p>Let me know what functionality you'd like to see first!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
104
manage_lifecycle.php
Normal file
104
manage_lifecycle.php
Normal file
@ -0,0 +1,104 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
|
||||
// Fetch all customers for the dropdown
|
||||
try {
|
||||
$pdoconn = db();
|
||||
$stmt = $pdoconn->prepare('SELECT custcode, custname FROM customers ORDER BY custname ASC');
|
||||
$stmt->execute();
|
||||
$customers = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
echo "Error fetching customers: " . $e->getMessage();
|
||||
$customers = [];
|
||||
}
|
||||
|
||||
$selected_custcode = $_GET['custcode'] ?? null;
|
||||
$lifecycle_data = [];
|
||||
|
||||
if ($selected_custcode) {
|
||||
try {
|
||||
$stmt = $pdoconn->prepare('
|
||||
SELECT l.id, l.product_id, p.prodname, DATE(l.first_ship_month) as first_ship_month, DATE(l.last_ship_month) as last_ship_month, l.store_count, l.runrate
|
||||
FROM lifecycle l
|
||||
JOIN products p ON l.product_id = p.product_id
|
||||
WHERE l.custcode = :custcode
|
||||
ORDER BY l.first_ship_month DESC
|
||||
');
|
||||
$stmt->execute(['custcode' => $selected_custcode]);
|
||||
$lifecycle_data = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
echo "Error fetching lifecycle data: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Manage Lifecycle Data</title>
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.datatables.net/1.10.25/css/jquery.dataTables.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-5">
|
||||
<h1>Manage Lifecycle Data</h1>
|
||||
<p>Select a customer to view their product lifecycle information.</p>
|
||||
|
||||
<form method="GET" action="manage_lifecycle.php" class="form-inline mb-4">
|
||||
<div class="form-group">
|
||||
<label for="custcode" class="mr-2">Select Customer:</label>
|
||||
<select name="custcode" id="custcode" class="form-control" onchange="this.form.submit()">
|
||||
<option value="">-- Select a Customer --</option>
|
||||
<?php foreach ($customers as $customer): ?>
|
||||
<option value="<?= htmlspecialchars($customer['custcode']) ?>" <?= $selected_custcode == $customer['custcode'] ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($customer['custname']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php if ($selected_custcode && !empty($lifecycle_data)): ?>
|
||||
<h3>Lifecycle Records for <?= htmlspecialchars($customers[array_search($selected_custcode, array_column($customers, 'custcode'))]['custname']) ?></h3>
|
||||
<table id="lifecycleTable" class="table table-striped table-bordered">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Product Name</th>
|
||||
<th>First Ship Month</th>
|
||||
<th>Last Ship Month</th>
|
||||
<th>Store Count</th>
|
||||
<th>Run Rate</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($lifecycle_data as $row): ?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($row['id']) ?></td>
|
||||
<td><?= htmlspecialchars($row['prodname']) ?></td>
|
||||
<td><?= htmlspecialchars($row['first_ship_month']) ?></td>
|
||||
<td><?= htmlspecialchars($row['last_ship_month']) ?></td>
|
||||
<td><?= htmlspecialchars($row['store_count']) ?></td>
|
||||
<td><?= htmlspecialchars($row['runrate']) ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php elseif ($selected_custcode && empty($lifecycle_data)): ?>
|
||||
<div class="alert alert-info">
|
||||
No lifecycle records found for the selected customer.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.5.1.js"></script>
|
||||
<script src="https://cdn.datatables.net/1.10.25/js/jquery.dataTables.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('#lifecycleTable').DataTable();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
97
uploads/customers.csv
Normal file
97
uploads/customers.csv
Normal file
@ -0,0 +1,97 @@
|
||||
custcode,custname,channel,country,maxstores
|
||||
100006,7 ELEVEN,Convenience,US,1
|
||||
100021,COUCHE TARD,Convenience,Canada,1
|
||||
100063,RACETRAC,Convenience,US,1
|
||||
100083,WALLACE & CAREY INC,Distributor,Canada,1
|
||||
6800327,Pioneer Distributing Inc,Distributor,US,1
|
||||
6803002,RR DISTRIBUTORS,Distributor,US,1
|
||||
100009,ARAMARK,Foodservice,US,1
|
||||
100088,WENDYS INTERNATIONAL,Foodservice,US,1
|
||||
100010,AVENDRA,Foodservice,US,1
|
||||
100037,FOODBUY LLC,Foodservice,US,1
|
||||
100060,PREMIER PURCHASING,Foodservice,US,1
|
||||
100091,ASG CO-OP,Grocery Retail,US,1
|
||||
100094,ASSOCIATED WSL INC,Grocery Retail,US,1
|
||||
100098,WHOLE FOODS MARKET,Grocery Retail,US,1
|
||||
100095,BTB PLANTS,Miscellaneous,US,1
|
||||
100051,MCKESSON,Miscellaneous,US,1
|
||||
100042,HILTON,Miscellaneous,US,1
|
||||
100055,INTERCOMPANY,Miscellaneous,US,1
|
||||
100043,HQ AAFES,Miscellaneous,US,1
|
||||
6803538,Ontario Natural Food Company,Miscellaneous,Canada,1
|
||||
100032,ESSENDANT CO,Miscellaneous,US,1
|
||||
100059,PILOT FLYING J,Miscellaneous,US,1
|
||||
100076,TRAVEL CENTERS,Miscellaneous,US,1
|
||||
100066,WALMART,Grocery Retail,US,4593
|
||||
100005,ALL OTHER RETAILERS,Grocery Retail,US,8000
|
||||
100020,COSTCO,Club,US,639
|
||||
100061,PUBLIX,Grocery Retail,US,1467
|
||||
100028,DOLLAR TREE,Grocery Retail,US,9007
|
||||
100081,WAKEFERN,Grocery Retail,US,372
|
||||
100011,BJ'S,Club,US,278
|
||||
100026,KROGER,Grocery Retail,US,1249
|
||||
100007,ALBERTSONS,Grocery Retail,US,2271
|
||||
100015,C&S,Grocery Retail,US,30
|
||||
100041,HEB,Grocery Retail,US,344
|
||||
100047,JETRO,Miscellaneous,US,12
|
||||
100016,AHOLD,Grocery Retail,US,7659
|
||||
100074,SYSCO,Foodservice,US,340
|
||||
100025,DEMOULAS,Grocery Retail,US,95
|
||||
100027,DOLLAR GENERAL,Grocery Retail,US,20388
|
||||
100078,US FOODSERVICE,Foodservice,US,98
|
||||
100033,FAMILY DOLLAR,Grocery Retail,US,7338
|
||||
100030,EAST COAST FOODS DIST,Foodservice,US,4500
|
||||
100073,SUPERVALU,Grocery Retail,US,43
|
||||
100008,AMAZON,E-Commerce,US,58
|
||||
100062,QUIK TRIP,Convenience,US,1195
|
||||
100052,MCLANE,Distributor,US,110000
|
||||
100075,TARGET,Grocery Retail,US,1995
|
||||
100013,BOZZUTO'S,Grocery Retail,US,250
|
||||
100019,COREMARK,Distributor,US,50000
|
||||
100023,CVS,Grocery Retail,US,8987
|
||||
100082,WALGREENS,Grocery Retail,US,8055
|
||||
100093,ASSOCIATED WSL GROCERS,Grocery Retail,US,4000
|
||||
100049,LOBLAWS INC,Grocery Retail,Canada,2000
|
||||
100024,DECA,Miscellaneous,US,300000
|
||||
100017,INDEPENDENTS,Grocery Retail,US,100000
|
||||
100072,STAPLES,Miscellaneous,US,922
|
||||
100039,GORDON FOOD SVC,Foodservice,US,199
|
||||
100077,UNFI,Miscellaneous,US,30000
|
||||
100080,VISTAR CORP,Miscellaneous,US,24
|
||||
6803637,MICHAEL LEWIS,Distributor,US,3
|
||||
100086,WEGMANS,Grocery Retail,US,114
|
||||
100054,NATIONAL DCP LLC,Foodservice,US,10000
|
||||
100084,WAWA,Convenience,US,1174
|
||||
100053,MEIJER,Grocery Retail,US,274
|
||||
100048,KRASDALE FOODS,Grocery Retail,US,2500
|
||||
100069,SHEETZ,Convenience,US,815
|
||||
100056,OFFICE DEPOT,Miscellaneous,US,827
|
||||
100012,BK MILLER CO,Distributor,US,800
|
||||
100092,ASSOCIATED GROCERS,Grocery Retail,US,166
|
||||
100022,CUMBERLAND FARMS,Convenience,US,580
|
||||
100045,J POLEP CHICOPEE,Distributor,US,4000
|
||||
100057,PFG AFI,Foodservice,US,300000
|
||||
100071,SPARTAN STORES,Grocery Retail,US,196
|
||||
100067,OVERWAITEA FOOD GROUP,Grocery Retail,Canada,145
|
||||
100031,EBY BROWN,Distributor,US,10000
|
||||
100038,GENERAL TRADING,Grocery Retail,US,1500
|
||||
6803974,FRESH DIRECT,E-Commerce,US,100
|
||||
100085,WB MASON,Miscellaneous,US,60
|
||||
100044,HT HACKNEY,Distributor,US,20000
|
||||
100087,WEIS MARKETS,Grocery Retail,US,204
|
||||
100034,SOBEYS INC,Grocery Retail,Canada,1500
|
||||
6800114,Dairyland USA,Foodservice,US,10
|
||||
100058,PIGGLY WIGGLY,Grocery Retail,US,498
|
||||
100064,REINHART,Foodservice,US,42
|
||||
6801041,Gold Star Foods,Foodservice,US,6
|
||||
100035,FEDERATED CO-OP,Grocery Retail,Canada,10
|
||||
100065,RITE AID,Grocery Retail,US,89
|
||||
100014,BROOKSHIRE GROCER,Grocery Retail,US,109
|
||||
100036,METRO RICHELIEU INC,Grocery Retail,Canada,953
|
||||
100018,CIRCLE K,Convenience,US,6831
|
||||
6801135,Industrial & Supply,Miscellaneous,US,100
|
||||
100050,LONDON DRUGS LTD,Grocery Retail,Canada,80
|
||||
100070,SODEXO,Foodservice,US,169
|
||||
100079,Imperial Dade Inc,Foodservice,US,1
|
||||
100046,J RABBA,Convenience,Canada,37
|
||||
100089,WINCO,Grocery Retail,US,144
|
||||
|
5850
uploads/lifecycle.csv
Normal file
5850
uploads/lifecycle.csv
Normal file
File diff suppressed because it is too large
Load Diff
4612
uploads/products.csv
Normal file
4612
uploads/products.csv
Normal file
File diff suppressed because it is too large
Load Diff
71999
uploads/shipments.csv
Normal file
71999
uploads/shipments.csv
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user