kind of a mess lol but file caching and front end
This commit is contained in:
238
app/static/app.js
Normal file
238
app/static/app.js
Normal file
@ -0,0 +1,238 @@
|
||||
// API base URL
|
||||
const API_BASE_URL = '/api';
|
||||
|
||||
// Selected orders for actions
|
||||
let selectedOrders = new Set();
|
||||
|
||||
// Show toast notification
|
||||
function showToast(message, type = 'success') {
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-lg text-white ${
|
||||
type === 'success' ? 'bg-green-600' : 'bg-red-600'
|
||||
} transform translate-y-0 opacity-100 transition-all duration-300`;
|
||||
toast.textContent = message;
|
||||
document.body.appendChild(toast);
|
||||
|
||||
setTimeout(() => {
|
||||
toast.style.transform = 'translateY(100%)';
|
||||
toast.style.opacity = '0';
|
||||
setTimeout(() => toast.remove(), 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
function setLoading(isLoading) {
|
||||
const buttons = document.querySelectorAll('button');
|
||||
buttons.forEach(button => {
|
||||
if (isLoading) {
|
||||
button.disabled = true;
|
||||
button.classList.add('opacity-50', 'cursor-not-allowed');
|
||||
} else {
|
||||
button.disabled = false;
|
||||
button.classList.remove('opacity-50', 'cursor-not-allowed');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Fetch orders from the API
|
||||
async function fetchOrders() {
|
||||
try {
|
||||
setLoading(true);
|
||||
const searchRange = document.getElementById('searchRange').value;
|
||||
const openOnly = document.getElementById('openOnly').checked;
|
||||
|
||||
const response = await fetch(`${API_BASE_URL}/orders?search_range=${searchRange}&open_only=${openOnly}`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch orders');
|
||||
}
|
||||
|
||||
const orders = await response.json();
|
||||
displayOrders(orders);
|
||||
showToast('Orders loaded successfully');
|
||||
} catch (error) {
|
||||
showToast('Error fetching orders: ' + error.message, 'error');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Display orders in the UI
|
||||
function displayOrders(orders) {
|
||||
const ordersList = document.getElementById('ordersList');
|
||||
ordersList.innerHTML = '';
|
||||
|
||||
if (!orders || orders.length === 0) {
|
||||
ordersList.innerHTML = '<div class="col-span-full text-center text-gray-400 py-4">No orders found</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
orders.forEach(order => {
|
||||
const orderCard = document.createElement('div');
|
||||
orderCard.className = `bg-gray-700 rounded-lg shadow-sm p-4 border border-gray-600 hover:shadow-md transition-shadow cursor-pointer ${
|
||||
selectedOrders.has(order.orderNumber) ? 'ring-2 ring-blue-500' : ''
|
||||
}`;
|
||||
orderCard.dataset.orderId = order.orderNumber;
|
||||
orderCard.innerHTML = `
|
||||
<div class="flex flex-col h-full">
|
||||
<div class="flex justify-between items-start mb-3">
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-lg font-bold text-blue-400 truncate">#${order.orderNumber || 'N/A'}</h3>
|
||||
<p class="text-sm text-gray-400">${order.buyerName || 'N/A'}</p>
|
||||
</div>
|
||||
<span class="px-2 py-1 text-xs rounded-full ${
|
||||
order.orderStatus === 'Open' ? 'bg-green-900/50 text-green-300' : 'bg-gray-600/50 text-gray-300'
|
||||
}">${order.orderStatus || 'Unknown'}</span>
|
||||
</div>
|
||||
<div class="mt-auto">
|
||||
<div class="flex justify-between items-center">
|
||||
<p class="text-sm text-gray-400">${order.orderDate ? new Date(order.orderDate).toLocaleString() : 'N/A'}</p>
|
||||
<p class="text-lg font-bold text-white">$${order.totalAmount ? order.totalAmount.toFixed(2) : '0.00'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
ordersList.appendChild(orderCard);
|
||||
|
||||
// Add click event listener to the order card
|
||||
orderCard.addEventListener('click', () => {
|
||||
const orderId = orderCard.dataset.orderId;
|
||||
if (selectedOrders.has(orderId)) {
|
||||
selectedOrders.delete(orderId);
|
||||
orderCard.classList.remove('ring-2', 'ring-blue-500');
|
||||
} else {
|
||||
selectedOrders.add(orderId);
|
||||
orderCard.classList.add('ring-2', 'ring-blue-500');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Select all orders on the page
|
||||
function selectAllOrders() {
|
||||
const orderCards = document.querySelectorAll('[data-order-id]');
|
||||
const allSelected = orderCards.length > 0 && Array.from(orderCards).every(card => selectedOrders.has(card.dataset.orderId));
|
||||
|
||||
orderCards.forEach(card => {
|
||||
const orderId = card.dataset.orderId;
|
||||
if (allSelected) {
|
||||
selectedOrders.delete(orderId);
|
||||
card.classList.remove('ring-2', 'ring-blue-500');
|
||||
} else {
|
||||
selectedOrders.add(orderId);
|
||||
card.classList.add('ring-2', 'ring-blue-500');
|
||||
}
|
||||
});
|
||||
|
||||
showToast(allSelected ? 'All orders deselected' : 'All orders selected');
|
||||
}
|
||||
|
||||
// Generate pull sheets
|
||||
async function generatePullSheets() {
|
||||
try {
|
||||
const orderIds = Array.from(selectedOrders);
|
||||
if (orderIds.length === 0) {
|
||||
showToast('Please select at least one order', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
const response = await fetch(`${API_BASE_URL}/orders/generate-pull-sheets`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
order_ids: orderIds
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.detail || 'Failed to generate pull sheets');
|
||||
}
|
||||
|
||||
showToast('Pull sheets generated successfully');
|
||||
} catch (error) {
|
||||
showToast('Error generating pull sheets: ' + error.message, 'error');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate packing slips
|
||||
async function generatePackingSlips() {
|
||||
try {
|
||||
const orderIds = Array.from(selectedOrders);
|
||||
if (orderIds.length === 0) {
|
||||
showToast('Please select at least one order', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
const response = await fetch(`${API_BASE_URL}/orders/generate-packing-slips`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
order_ids: orderIds
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.detail || 'Failed to generate packing slips');
|
||||
}
|
||||
|
||||
showToast('Packing slips generated successfully');
|
||||
} catch (error) {
|
||||
showToast('Error generating packing slips: ' + error.message, 'error');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate address labels
|
||||
async function generateAddressLabels() {
|
||||
try {
|
||||
const orderIds = Array.from(selectedOrders);
|
||||
if (orderIds.length === 0) {
|
||||
showToast('Please select at least one order', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const labelType = document.getElementById('labelType').value;
|
||||
|
||||
setLoading(true);
|
||||
const response = await fetch(`${API_BASE_URL}/orders/generate-address-labels`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
order_ids: orderIds,
|
||||
label_type: labelType
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.detail || 'Failed to generate address labels');
|
||||
}
|
||||
|
||||
showToast('Address labels generated successfully');
|
||||
} catch (error) {
|
||||
showToast('Error generating address labels: ' + error.message, 'error');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Load orders when page loads
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
fetchOrders();
|
||||
|
||||
// Add event listeners for search range and open only checkbox
|
||||
document.getElementById('searchRange').addEventListener('change', fetchOrders);
|
||||
document.getElementById('openOnly').addEventListener('change', fetchOrders);
|
||||
});
|
78
app/static/index.html
Normal file
78
app/static/index.html
Normal file
@ -0,0 +1,78 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>TCGPlayer Order Management</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'sans-serif'],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body class="bg-gray-900 min-h-screen text-gray-100">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<div class="bg-gray-800 rounded-xl shadow-sm p-6 mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-100 mb-2">TCGPlayer Order Management</h1>
|
||||
<p class="text-gray-400">Manage your TCGPlayer orders efficiently</p>
|
||||
</div>
|
||||
|
||||
<!-- Order Actions Section -->
|
||||
<div class="bg-gray-800 rounded-xl shadow-sm p-6 mb-8">
|
||||
<h2 class="text-xl font-semibold text-gray-100 mb-6">Order Actions</h2>
|
||||
<div class="flex flex-wrap gap-4 mb-4">
|
||||
<button onclick="generatePullSheets()" class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 transition-colors">
|
||||
Generate Pull Sheets
|
||||
</button>
|
||||
<button onclick="generatePackingSlips()" class="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 transition-colors">
|
||||
Generate Packing Slips
|
||||
</button>
|
||||
<button onclick="generateAddressLabels()" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition-colors">
|
||||
Generate Address Labels
|
||||
</button>
|
||||
</div>
|
||||
<div id="labelOptions" class="bg-gray-700 rounded-lg p-4">
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Label Type</label>
|
||||
<select id="labelType" class="rounded-lg border-gray-600 bg-gray-800 text-gray-100 focus:ring-blue-500 focus:border-blue-500">
|
||||
<option value="dk1201">DK1201</option>
|
||||
<option value="dk1241">DK1241</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Order List Section -->
|
||||
<div class="bg-gray-800 rounded-xl shadow-sm p-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-xl font-semibold text-gray-100">Orders</h2>
|
||||
<div class="flex items-center space-x-4">
|
||||
<select id="searchRange" class="rounded-lg border-gray-600 bg-gray-800 text-gray-100 focus:ring-blue-500 focus:border-blue-500">
|
||||
<option value="LastWeek" selected>Last Week</option>
|
||||
<option value="LastMonth">Last Month</option>
|
||||
<option value="LastThreeMonths">Last Three Months</option>
|
||||
<option value="LastFourMonths">Last Four Months</option>
|
||||
<option value="LastTwoYears">Last Two Years</option>
|
||||
</select>
|
||||
<label class="flex items-center space-x-2">
|
||||
<input type="checkbox" id="openOnly" class="rounded border-gray-600 bg-gray-800 text-blue-600 focus:ring-blue-500" checked>
|
||||
<span class="text-gray-300">Show Open Orders Only</span>
|
||||
</label>
|
||||
<button onclick="selectAllOrders()" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors">
|
||||
Select All Orders
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ordersList" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/app.js"></script>
|
||||
</body>
|
||||
</html>
|
108
app/static/styles.css
Normal file
108
app/static/styles.css
Normal file
@ -0,0 +1,108 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #444;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 30px;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
select, button {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.orders-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.order-card {
|
||||
padding: 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.order-card h3 {
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.order-card p {
|
||||
margin-bottom: 5px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.label-options {
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.controls {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
select, button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user