Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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>
|
||||||
186
index.php
186
index.php
@ -1,26 +1,19 @@
|
|||||||
<?php
|
<?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
|
// 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'] ?? '';
|
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||||
?>
|
?>
|
||||||
|
<!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): ?>
|
<?php if ($projectDescription): ?>
|
||||||
<!-- Meta description -->
|
<!-- Meta description -->
|
||||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
<meta name="description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||||
<!-- Open Graph meta tags -->
|
<!-- Open Graph meta tags -->
|
||||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||||
<!-- Twitter meta tags -->
|
<!-- Twitter meta tags -->
|
||||||
@ -32,119 +25,58 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
|||||||
<!-- Twitter image -->
|
<!-- Twitter image -->
|
||||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--bg-color-start: #6a11cb;
|
|
||||||
--bg-color-end: #2575fc;
|
|
||||||
--text-color: #ffffff;
|
|
||||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
|
||||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: 'Inter', sans-serif;
|
|
||||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
|
||||||
color: var(--text-color);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
text-align: center;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
body::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
|
||||||
animation: bg-pan 20s linear infinite;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
@keyframes bg-pan {
|
|
||||||
0% { background-position: 0% 0%; }
|
|
||||||
100% { background-position: 100% 100%; }
|
|
||||||
}
|
|
||||||
main {
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
.card {
|
|
||||||
background: var(--card-bg-color);
|
|
||||||
border: 1px solid var(--card-border-color);
|
|
||||||
border-radius: 16px;
|
|
||||||
padding: 2rem;
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
-webkit-backdrop-filter: blur(20px);
|
|
||||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
.loader {
|
|
||||||
margin: 1.25rem auto 1.25rem;
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
|
||||||
border-top-color: #fff;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
@keyframes spin {
|
|
||||||
from { transform: rotate(0deg); }
|
|
||||||
to { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
.hint {
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
.sr-only {
|
|
||||||
position: absolute;
|
|
||||||
width: 1px; height: 1px;
|
|
||||||
padding: 0; margin: -1px;
|
|
||||||
overflow: hidden;
|
|
||||||
clip: rect(0, 0, 0, 0);
|
|
||||||
white-space: nowrap; border: 0;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
font-size: 3rem;
|
|
||||||
font-weight: 700;
|
|
||||||
margin: 0 0 1rem;
|
|
||||||
letter-spacing: -1px;
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
margin: 0.5rem 0;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
code {
|
|
||||||
background: rgba(0,0,0,0.2);
|
|
||||||
padding: 2px 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
||||||
}
|
|
||||||
footer {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 1rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||||
<div class="card">
|
<div class="container-fluid">
|
||||||
<h1>Analyzing your requirements and generating your website…</h1>
|
<a class="navbar-brand" href="/">Shipments Admin</a>
|
||||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
<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="sr-only">Loading…</span>
|
<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>
|
</div>
|
||||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
|
||||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
|
||||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</nav>
|
||||||
<footer>
|
|
||||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
<div class="container mt-5">
|
||||||
</footer>
|
<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>
|
</body>
|
||||||
</html>
|
</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