feat(pos): integrate design system for facturacion, contabilidad, dashboard, config, reportes
Replace 5 POS templates with updated design system pages using tokens.css. Add routes for dashboard, config, and reports pages. Create stub JS files for dashboard, config, and reports modules. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
12
pos/app.py
12
pos/app.py
@@ -66,6 +66,18 @@ def create_app():
|
||||
def pos_accounting():
|
||||
return render_template('accounting.html')
|
||||
|
||||
@app.route('/pos/dashboard')
|
||||
def pos_dashboard():
|
||||
return render_template('dashboard.html')
|
||||
|
||||
@app.route('/pos/config')
|
||||
def pos_config():
|
||||
return render_template('config.html')
|
||||
|
||||
@app.route('/pos/reports')
|
||||
def pos_reports():
|
||||
return render_template('reports.html')
|
||||
|
||||
@app.route('/pos/static/<path:filename>')
|
||||
def pos_static(filename):
|
||||
return send_from_directory('static', filename)
|
||||
|
||||
139
pos/static/js/config.js
Normal file
139
pos/static/js/config.js
Normal file
@@ -0,0 +1,139 @@
|
||||
// /home/Autopartes/pos/static/js/config.js
|
||||
// Config module: branches, employees, theme, system settings
|
||||
|
||||
const Config = (() => {
|
||||
const API = '/pos/api/config';
|
||||
|
||||
function token() {
|
||||
return localStorage.getItem('pos_token') || '';
|
||||
}
|
||||
|
||||
function checkAuth() {
|
||||
if (!token()) {
|
||||
window.location.href = '/pos/login';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function headers() {
|
||||
return { 'Authorization': `Bearer ${token()}`, 'Content-Type': 'application/json' };
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Theme switcher
|
||||
// -------------------------------------------------------------------------
|
||||
function setTheme(theme) {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
try { localStorage.setItem('nexus-theme', theme); } catch(e) {}
|
||||
|
||||
document.querySelectorAll('.theme-btn').forEach(function(btn) {
|
||||
btn.classList.toggle('is-active', btn.dataset.themeTarget === theme);
|
||||
});
|
||||
|
||||
document.querySelectorAll('.theme-option').forEach(function(opt) {
|
||||
opt.classList.remove('is-selected');
|
||||
});
|
||||
var idx = theme === 'industrial' ? 0 : 1;
|
||||
var opts = document.querySelectorAll('.theme-option');
|
||||
if (opts[idx]) opts[idx].classList.add('is-selected');
|
||||
}
|
||||
window.setTheme = setTheme;
|
||||
|
||||
function selectThemeOption(theme) {
|
||||
setTheme(theme);
|
||||
}
|
||||
window.selectThemeOption = selectThemeOption;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Live clock
|
||||
// -------------------------------------------------------------------------
|
||||
function updateClock() {
|
||||
var now = new Date();
|
||||
var hh = String(now.getHours()).padStart(2, '0');
|
||||
var mm = String(now.getMinutes()).padStart(2, '0');
|
||||
var el = document.getElementById('live-clock');
|
||||
if (el) el.textContent = hh + ':' + mm;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// API calls using existing config_bp endpoints
|
||||
// -------------------------------------------------------------------------
|
||||
async function loadBranches() {
|
||||
try {
|
||||
const res = await fetch(`${API}/branches`, { headers: headers() });
|
||||
if (!res.ok) throw new Error('Failed to load branches');
|
||||
return await res.json();
|
||||
} catch (e) {
|
||||
console.error('Config.loadBranches:', e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function loadEmployees() {
|
||||
try {
|
||||
const res = await fetch(`${API}/employees`, { headers: headers() });
|
||||
if (!res.ok) throw new Error('Failed to load employees');
|
||||
return await res.json();
|
||||
} catch (e) {
|
||||
console.error('Config.loadEmployees:', e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function saveBranch(data) {
|
||||
const res = await fetch(`${API}/branches`, {
|
||||
method: 'POST',
|
||||
headers: headers(),
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({ error: res.statusText }));
|
||||
throw new Error(err.error || 'Save failed');
|
||||
}
|
||||
return res.json();
|
||||
}
|
||||
|
||||
async function saveEmployee(data) {
|
||||
const res = await fetch(`${API}/employees`, {
|
||||
method: 'POST',
|
||||
headers: headers(),
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({ error: res.statusText }));
|
||||
throw new Error(err.error || 'Save failed');
|
||||
}
|
||||
return res.json();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Init
|
||||
// -------------------------------------------------------------------------
|
||||
function init() {
|
||||
if (!checkAuth()) return;
|
||||
|
||||
// Restore theme
|
||||
try {
|
||||
var saved = localStorage.getItem('nexus-theme');
|
||||
if (saved === 'industrial' || saved === 'modern') {
|
||||
setTheme(saved);
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
// Start clock
|
||||
updateClock();
|
||||
setInterval(updateClock, 30000);
|
||||
|
||||
// Load initial data
|
||||
loadBranches();
|
||||
loadEmployees();
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
|
||||
return {
|
||||
init, setTheme, selectThemeOption,
|
||||
loadBranches, loadEmployees, saveBranch, saveEmployee
|
||||
};
|
||||
})();
|
||||
113
pos/static/js/dashboard.js
Normal file
113
pos/static/js/dashboard.js
Normal file
@@ -0,0 +1,113 @@
|
||||
// /home/Autopartes/pos/static/js/dashboard.js
|
||||
// Dashboard module: KPIs, charts, summary data
|
||||
|
||||
const Dashboard = (() => {
|
||||
function token() {
|
||||
return localStorage.getItem('pos_token') || '';
|
||||
}
|
||||
|
||||
function checkAuth() {
|
||||
if (!token()) {
|
||||
window.location.href = '/pos/login';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function headers() {
|
||||
return { 'Authorization': `Bearer ${token()}`, 'Content-Type': 'application/json' };
|
||||
}
|
||||
|
||||
function fmt(n) {
|
||||
return parseFloat(n || 0).toLocaleString('es-MX', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Theme switcher
|
||||
// -------------------------------------------------------------------------
|
||||
function setTheme(theme) {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
try { localStorage.setItem('nexus-theme', theme); } catch(e) {}
|
||||
const btnInd = document.getElementById('btn-industrial');
|
||||
const btnMod = document.getElementById('btn-modern');
|
||||
if (btnInd) btnInd.classList.toggle('active', theme === 'industrial');
|
||||
if (btnMod) btnMod.classList.toggle('active', theme === 'modern');
|
||||
}
|
||||
window.setTheme = setTheme;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Sidebar toggle (mobile)
|
||||
// -------------------------------------------------------------------------
|
||||
function toggleSidebar() {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const overlay = document.getElementById('sidebar-overlay');
|
||||
if (!sidebar) return;
|
||||
const isOpen = sidebar.classList.contains('open');
|
||||
sidebar.classList.toggle('open', !isOpen);
|
||||
if (overlay) overlay.classList.toggle('open', !isOpen);
|
||||
document.body.style.overflow = isOpen ? '' : 'hidden';
|
||||
}
|
||||
window.toggleSidebar = toggleSidebar;
|
||||
|
||||
function closeSidebar() {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const overlay = document.getElementById('sidebar-overlay');
|
||||
if (sidebar) sidebar.classList.remove('open');
|
||||
if (overlay) overlay.classList.remove('open');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
window.closeSidebar = closeSidebar;
|
||||
|
||||
window.addEventListener('resize', function() {
|
||||
if (window.innerWidth >= 768) closeSidebar();
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Period selector
|
||||
// -------------------------------------------------------------------------
|
||||
function setPeriod(btn) {
|
||||
btn.closest('.period-selector').querySelectorAll('.period-btn').forEach(function(b) {
|
||||
b.classList.remove('active');
|
||||
});
|
||||
btn.classList.add('active');
|
||||
}
|
||||
window.setPeriod = setPeriod;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Placeholder API calls
|
||||
// -------------------------------------------------------------------------
|
||||
async function loadSalesSummary() {
|
||||
// TODO: call /pos/api/cashregister/summary or similar
|
||||
}
|
||||
|
||||
async function loadTopProducts() {
|
||||
// TODO: call /pos/api/inventory/products?sort=sold
|
||||
}
|
||||
|
||||
async function loadRecentSales() {
|
||||
// TODO: call /pos/api/cashregister/recent
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Init
|
||||
// -------------------------------------------------------------------------
|
||||
function init() {
|
||||
if (!checkAuth()) return;
|
||||
|
||||
// Restore theme
|
||||
try {
|
||||
const saved = localStorage.getItem('nexus-theme');
|
||||
if (saved === 'industrial' || saved === 'modern') {
|
||||
setTheme(saved);
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
loadSalesSummary();
|
||||
loadTopProducts();
|
||||
loadRecentSales();
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
|
||||
return { init, loadSalesSummary, loadTopProducts, loadRecentSales, setTheme };
|
||||
})();
|
||||
99
pos/static/js/reports.js
Normal file
99
pos/static/js/reports.js
Normal file
@@ -0,0 +1,99 @@
|
||||
// /home/Autopartes/pos/static/js/reports.js
|
||||
// Reports module: sales reports, inventory reports, financial reports
|
||||
|
||||
const Reports = (() => {
|
||||
function token() {
|
||||
return localStorage.getItem('pos_token') || '';
|
||||
}
|
||||
|
||||
function checkAuth() {
|
||||
if (!token()) {
|
||||
window.location.href = '/pos/login';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function headers() {
|
||||
return { 'Authorization': `Bearer ${token()}`, 'Content-Type': 'application/json' };
|
||||
}
|
||||
|
||||
function fmt(n) {
|
||||
return parseFloat(n || 0).toLocaleString('es-MX', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Theme switcher
|
||||
// -------------------------------------------------------------------------
|
||||
function setTheme(theme) {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
try { localStorage.setItem('nexus-theme', theme); } catch(e) {}
|
||||
var btnInd = document.getElementById('btn-industrial');
|
||||
var btnMod = document.getElementById('btn-modern');
|
||||
if (btnInd) btnInd.classList.toggle('is-active', theme === 'industrial');
|
||||
if (btnMod) btnMod.classList.toggle('is-active', theme === 'modern');
|
||||
}
|
||||
window.setTheme = setTheme;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Tab switcher
|
||||
// -------------------------------------------------------------------------
|
||||
function switchTab(id, btn) {
|
||||
document.querySelectorAll('.tab-panel').forEach(function(p) { p.classList.remove('is-active'); });
|
||||
document.querySelectorAll('.tab-btn').forEach(function(b) { b.classList.remove('is-active'); });
|
||||
var panel = document.getElementById('panel-' + id);
|
||||
if (panel) panel.classList.add('is-active');
|
||||
if (btn) btn.classList.add('is-active');
|
||||
}
|
||||
window.switchTab = switchTab;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Live clock
|
||||
// -------------------------------------------------------------------------
|
||||
function updateClock() {
|
||||
var el = document.getElementById('live-clock');
|
||||
if (!el) return;
|
||||
var now = new Date();
|
||||
var pad = function(n) { return String(n).padStart(2, '0'); };
|
||||
el.textContent = pad(now.getHours()) + ':' + pad(now.getMinutes()) + ':' + pad(now.getSeconds());
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Placeholder API calls
|
||||
// -------------------------------------------------------------------------
|
||||
async function loadSalesReport(params) {
|
||||
// TODO: call /pos/api/cashregister/... with date range
|
||||
}
|
||||
|
||||
async function loadInventoryReport() {
|
||||
// TODO: call /pos/api/inventory/products for stock report
|
||||
}
|
||||
|
||||
async function loadFinancialReport(params) {
|
||||
// TODO: call /pos/api/accounting/... for financial reports
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Init
|
||||
// -------------------------------------------------------------------------
|
||||
function init() {
|
||||
if (!checkAuth()) return;
|
||||
|
||||
// Restore theme
|
||||
try {
|
||||
var saved = localStorage.getItem('nexus-theme') || 'industrial';
|
||||
setTheme(saved);
|
||||
} catch(e) {}
|
||||
|
||||
// Start clock
|
||||
updateClock();
|
||||
setInterval(updateClock, 1000);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
|
||||
return {
|
||||
init, setTheme, switchTab,
|
||||
loadSalesReport, loadInventoryReport, loadFinancialReport, fmt
|
||||
};
|
||||
})();
|
||||
File diff suppressed because it is too large
Load Diff
1826
pos/templates/config.html
Normal file
1826
pos/templates/config.html
Normal file
File diff suppressed because it is too large
Load Diff
1954
pos/templates/dashboard.html
Normal file
1954
pos/templates/dashboard.html
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2920
pos/templates/reports.html
Normal file
2920
pos/templates/reports.html
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user