shop-management-pos/assets/admin.css
:root{
–topbar:#3a8db8;
–card-green:#0aa957; –card-teal:#0579cd; –card-magenta:#7c0ca6; –card-purple:#4a56d7; –card-orange:#0c1587;
–head-bg:#0b2a6c; –name-col:#081c3f; –total-col:#1d56e0; –today-col:#022e99; –week-col:#0b36a0; –month-col:#011F7B; –year-col:#071e49;
–text:#1a2a44; –text-strong:#0b1d3a; –white:#ffffff;
–panel-bg:#eaf2ff; –panel-head:#07224a;
–shadow:0 4px 12px rgba(0,0,0,.10); –border:1px solid rgba(12,64,141,.18);
–grid-font:20px; –head-font:20px; –name-font:20px;
}
/* Topbar */
.smpos-topbar{ background:var(–topbar); color:#fff; height:48px; display:flex; align-items:center; justify-content:space-between; padding:0 14px; border-bottom:1px solid rgba(255,255,255,.18); }
.smpos-topbar .tb-left .tb-logo{ font-weight:800; text-shadow:0 1px 0 rgba(0,0,0,.15); }
.smpos-topbar .tb-right{ display:flex; align-items:center; gap:14px; }
.smpos-topbar .tb-link,.smpos-topbar .tb-icon,.smpos-topbar .tb-user{ color:#fff; text-decoration:none; font-weight:700; display:flex; align-items:center; gap:6px; filter: drop-shadow(0 1px 0 rgba(0,0,0,.15)); }
.smpos-topbar .tb-link:hover,.smpos-topbar .tb-icon:hover{ background:rgba(0,0,0,.06); border-radius:6px; padding:4px 6px; }
.smpos-topbar .dashicons{ transform: translateY(1px); }
/* Wrapper */
.smpos-wrap{ margin-top:12px; }
.smpos-title{ margin:8px 0 16px; font-size:22px; color:var(–text-strong); }
/* Cards */
.smpos-cards{ display:grid; grid-template-columns: repeat(5, 1fr); gap:12px; margin-bottom:14px; }
.smpos-cards .card{ color:#fff; padding:12px 14px 36px; border-radius:10px; box-shadow:var(–shadow), inset 0 0 0 1px rgba(255,255,255,.05); position:relative; display:flex; flex-direction:column; justify-content:center; align-items:center; text-align:center; }
.card.green{ background:var(–card-green);} .card.teal{background:var(–card-teal);} .card.magenta{background:var(–card-magenta);} .card.purple{background:var(–card-purple);} .card.orange{background:var(–card-orange);}
.card .label{ font-size:24px; font-weight:800; line-height:1.1; }
.card .value{ font-size:30px; font-weight:800; margin-top:8px; line-height:1; }
.view-btn{ position:absolute; left:0; right:0; bottom:0; padding:7px 0; text-align:center; color:#ffffff; text-decoration:none; font-weight:700; background: rgba(0,0,0,.22); border-bottom-left-radius:10px; border-bottom-right-radius:10px; }
.view-btn:hover{ background: rgba(255,255,255,.65); color:#000; }
/* KPI grid */
.smpos-grid-block{ display:grid; grid-template-columns: 1.2fr repeat(5, 1fr); gap:12px; }
.smpos-grid-block .cell{ padding:12px 14px; border-radius:10px; color:#ffffff; box-shadow:var(–shadow); border:1px solid rgba(255,255,255,.12); font-size: var(–grid-font); line-height:1.25; }
.smpos-grid-block .head{ background:var(–head-bg); font-weight:800; font-size: var(–head-font); }
.smpos-grid-block .name{ background:#081c3f; font-weight:800; font-size: var(–name-font); }
.smpos-grid-block .col-total{ background:#1d56e0; } .smpos-grid-block .col-today{ background:#022e99; } .smpos-grid-block .col-week{ background:#0b36a0; } .smpos-grid-block .col-month{ background:#011F7B; } .smpos-grid-block .col-year{ background:#071e49; }
.align-right{ text-align:right; }
/* Panels */
.smpos-panels{ display:grid; grid-template-columns: 1.4fr 1fr; gap:12px; margin-top:14px; }
.panel{ background: var(–panel-bg); color: var(–text); border-radius:10px; padding:14px; box-shadow:var(–shadow); border: var(–border); }
.panel h2{ margin:0 0 10px; font-size:16px; color:var(–panel-head); }
/* Tables */
.smpos-table{ width:100%; border-collapse:separate; border-spacing:0; }
.smpos-table thead th{ text-align:left; font-size:14px; color:#ffffff; background:#07224a; padding:10px 12px; position:sticky; top:0; }
.smpos-table tbody td{ padding:10px 12px; border-bottom:1px solid rgba(12,64,141,.18); font-size:14px; color:#1a2a44; }
.smpos-table tbody tr:nth-child(even){ background:#f5f8ff; }
/* Panel footer buttons */
.panel-foot{ display:flex; justify-content:flex-end; padding-top:8px; }
.panel-view{ display:inline-flex; align-items:center; gap:6px; padding:8px 12px; border-radius:8px; background:#07224a; color:#fff; text-decoration:none; font-weight:700; font-size:13px; box-shadow:0 2px 6px rgba(0,0,0,.12); border:1px solid rgba(255,255,255,.15); transition: transform .15s ease, background .15s ease; }
.panel-view:hover{ background:#0b2a6c; transform: translateY(-1px); }
.panel-view–alt{ background:#0aa957; }
.panel-view–alt:hover{ background:#0f8b48; }
/* Trending block desktop */
.panel.trending .trend-wrap{
display:grid;
grid-template-columns: 420px minmax(0, 1fr);
gap:14px;
align-items:start;
}
.panel.trending .trend-pie .smpos-canvas{ aspect-ratio:1/1; background:transparent; }
.trend-table{ display:block; min-width:0; overflow:auto; -webkit-overflow-scrolling: touch; }
.trend-row{ display:grid; grid-template-columns: 0.5fr 1.5fr repeat(4, 1fr); gap:10px; margin-bottom:8px; }
.trend-head .t{ background:#0b2a6c; color:#fff; font-weight:700; padding:10px 12px; border-radius:8px; text-align:center; white-space:normal; line-height:1.2; }
.trend-head .t.name{ text-align:left; }
.trend-row .t{ background:#0e1f4a; color:#fff; padding:10px 12px; border-radius:8px; text-align:center; box-shadow:0 2px 8px rgba(0,0,0,.12); border:1px solid rgba(255,255,255,.1); font-size:14px; }
.trend-row .t.name{ text-align:left; background:#13275d; }
.trend-row .t.today{ background:#2f57a7; } .trend-row .t.week{ background:#2a4f9a; } .trend-row .t.month{ background:#234689; } .trend-row .t.year{ background:#1c3c78; }
/* Responsive breakpoints */
@media (max-width: 1280px){
.trend-row{ gap:8px; }
.panel.trending .trend-wrap{ grid-template-columns: 360px minmax(0,1fr); }
}
@media (max-width: 992px){
.panel.trending .trend-wrap{ grid-template-columns: 320px minmax(0,1fr); }
.trend-row{ grid-template-columns: 0.5fr 1.4fr repeat(4, 1fr); }
.trend-head .t, .trend-row .t{ font-size:13px; padding:8px 10px; }
}
@media (max-width: 768px){
.smpos-cards{ grid-template-columns: repeat(2, 1fr); }
.smpos-panels{ grid-template-columns: 1fr; }
.panel.trending .trend-wrap{ grid-template-columns: 1fr; }
.panel.trending .trend-pie .smpos-canvas{ width:100% !important; height:auto !important; aspect-ratio:1/1; }
.trend-row{ grid-template-columns: 0.6fr 1.2fr repeat(4, 0.9fr); gap:6px; }
}
@media (max-width: 480px){
.smpos-cards{ grid-template-columns: 1fr; }
.trend-head .t, .trend-row .t{ font-size:12px; padding:7px 8px; }
.trend-row{ grid-template-columns: 0.7fr 1.3fr repeat(4, 0.9fr); }
}
/* Chart canvas generic */
.smpos-canvas{ width:100%; display:block; background:transparent; }
shop-management-pos/assets/admin.js
(function(){
if(typeof SMPOS === ‘undefined’) return;
// ———- HTTP ———-
async function get(url){
const res = await fetch(SMPOS.root + url, { headers: { ‘X-WP-Nonce’: SMPOS.nonce }});
return res.json();
}
const fmt = n => Number(n||0).toLocaleString();
// ———- Canvas helpers ———-
function fitBar(canvas){
const p = canvas.parentElement, r = window.devicePixelRatio || 1;
const w = p.clientWidth, h = Math.round(w * (7/16));
canvas.style.width = w + ‘px’; canvas.style.height = h + ‘px’;
canvas.width = Math.floor(w*r); canvas.height = Math.floor(h*r);
const ctx = canvas.getContext(‘2d’,{alpha:true});
ctx.setTransform(1,0,0,1,0,0); ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.setTransform(r,0,0,r,0,0);
return ctx;
}
function fitSquare(canvas, min=420){
const p = canvas.parentElement, r = window.devicePixelRatio || 1;
// ছোট স্ক্রিনে parent width অনুযায়ী মিন সাইড অ্যাডজাস্ট
const w = Math.max(Math.min(420, p.clientWidth), Math.min(min, p.clientWidth));
canvas.style.width = w + ‘px’; canvas.style.height = w + ‘px’;
canvas.width = Math.floor(w*r); canvas.height = Math.floor(w*r);
const ctx = canvas.getContext(‘2d’,{alpha:true});
ctx.setTransform(1,0,0,1,0,0); ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.setTransform(r,0,0,r,0,0);
return ctx;
}
// ———- Utils ———-
function computeStep(maxVal){
if(maxVal <= 0) return 1;
const exp = Math.floor(Math.log10(maxVal));
const base = Math.pow(10, exp);
const candidates = [1,2,2.5,5,10].map(m=>m*base);
let best = candidates[0], bestDiff = Infinity;
for(const c of candidates){
const steps = Math.ceil(maxVal / c);
const diff = Math.abs(steps – 5);
if(diff < bestDiff){ bestDiff = diff; best = c; }
}
return best;
}
// ———- Shop meta ———-
function setDOB(){
const el = document.getElementById(‘shop-dob’);
if(!el) return;
let src = SMPOS.shopDOB || (SMPOS.startedAt ? SMPOS.startedAt.substring(0,10) : ”);
if(src){
const d = new Date(src);
if(!isNaN(d.getTime())){
const y=d.getFullYear(), m=String(d.getMonth()+1).padStart(2,’0′), day=String(d.getDate()).padStart(2,’0′);
el.textContent = `${y}-${m}-${day}`;
}else el.textContent = src;
}
}
function tickCountdown(startedAt){
const el = document.getElementById(‘shop-age’); if(!el||!startedAt) return;
function render(){
const st = new Date(startedAt);
const diff = Math.max(0, Math.floor((Date.now()-st.getTime())/1000));
const y=Math.floor(diff/31536000), d=Math.floor((diff%31536000)/86400), h=Math.floor((diff%86400)/3600), m=Math.floor((diff%3600)/60), s=diff%60;
el.textContent = `${y}y ${d}d ${h}h ${m}m ${s}s`;
} render(); setInterval(render,1000);
}
// ———- Bar Chart ———-
function initBarChart(payload){
const canvas = document.getElementById(‘ps-chart’); if(!canvas || typeof Chart===’undefined’) return;
const ctx = fitBar(canvas);
const labels = (payload.months && payload.months.length ? payload.months
: [‘January’,’February’,’March’,’April’,’May’,’June’,’July’,’August’,’September’,’October’,’November’,’December’]);
const purchase = (payload.purchase && payload.purchase.some(v=>Number(v)>0) ? payload.purchase
: [1800,3200,2200,3600,2000,4200,3150,3600,2400,3120,3250,2850]).map(Number);
const sales = (payload.sales && payload.sales.some(v=>Number(v)>0) ? payload.sales
: [1500,2900,2100,3300,2100,4000,3000,3500,2300,3000,3100,2800]).map(Number);
const maxVal = Math.max(1, …purchase, …sales);
const step = computeStep(maxVal);
const suggestedMax = Math.ceil(maxVal/step)*step;
new Chart(ctx,{
type:’bar’,
data:{
labels,
datasets:[
{ label:’Sales’, data:sales, backgroundColor:’#16a34a’, borderRadius:2, barPercentage:0.55, categoryPercentage:0.8 },
{ label:’Purchase’, data:purchase, backgroundColor:’#93c5fd’, borderRadius:2, barPercentage:0.55, categoryPercentage:0.8 }
]
},
options:{
responsive:false, /* আমরা নিজে ক্যানভাস সাইজ কন্ট্রোল করছি */
plugins:{ legend:{ display:false } },
scales:{
y:{
beginAtZero:true, suggestedMax,
ticks:{ color:’#000′, font:{ size:16, weight:’700′ }, stepSize:step, callback:(v)=>Number(v).toLocaleString() },
grid:{ color:’#cfd8e3′ }
},
x:{
ticks:{ color:’#000′, font:{ size:16, weight:’700′ }, maxRotation:55, minRotation:40 },
grid:{ color:’#dbe4f0′ }
}
},
layout:{ padding:{ top:6, right:6, bottom:6, left:6 } }
}
});
}
// ———- Pie + Trending table ———-
let TREND_STATE = { items:[], expanded:false };
function pieColorsDashboard(count){
const palette = [‘#16a34a’,’#0ea5e9′,’#7c3aed’,’#4f46e5′,’#0c1587′,’#10b981′,’#ef4444′,’#22c55e’,’#3b82f6′,’#a855f7′];
return Array.from({length:count}, (_,i)=> palette[i % palette.length]);
}
function initPie(payload){
const canvas = document.getElementById(‘trend-pie’); if(!canvas || typeof Chart===’undefined’) return;
const ctx = fitSquare(canvas, 420);
const items = (payload.items||[]).slice(0,100);
const labels = items.length ? items.map(x=>x.name) : [‘A’,’B’,’C’,’D’,’E’,’F’];
const data = items.length ? items.map(x=>x.percent) : [30,20,15,12,10,13];
const colors = pieColorsDashboard(data.length);
new Chart(ctx,{ type:’pie’, data:{ labels, datasets:[{ data, backgroundColor:colors }] }, options:{ responsive:false, plugins:{ legend:{ position:’right’ } } }});
}
function mountTrendToggle(){
let foot = document.querySelector(‘.panel.trending .panel-foot’);
if(!foot){
foot = document.createElement(‘div’);
foot.className = ‘panel-foot’;
document.querySelector(‘.panel.trending’).appendChild(foot);
}
let btn = document.getElementById(‘trend-toggle’);
if(!btn){
btn = document.createElement(‘a’);
btn.id = ‘trend-toggle’;
btn.className = ‘panel-view panel-view–alt’;
btn.href = ‘javascript:void(0)’;
foot.appendChild(btn);
}
const setLabel = ()=>{ btn.textContent = `${TREND_STATE.expanded ? ‘▴’ : ‘▾’} ${TREND_STATE.expanded ? ‘Show Less’ : ‘Show All’}`; };
setLabel();
btn.onclick = ()=>{ TREND_STATE.expanded = !TREND_STATE.expanded; renderTrendingRows(); setLabel(); };
}
function renderTrendingRows(){
const body = document.getElementById(‘trend-body’); if(!body) return;
body.innerHTML = ”;
const all = (TREND_STATE.items||[]).filter(it=>{
const nums = [it.today, it.week, it.month, it.year, it.percent].map(Number);
return nums.some(v=>v>0);
}).slice(0,100);
const showCount = TREND_STATE.expanded ? 100 : 10;
const view = all.slice(0, showCount);
for(let i=0;i<showCount;i++){
const it = view[i] || {};
const row=document.createElement(‘div’); row.className=’trend-row’;
row.innerHTML = `
<div class=”t sn”>${String(i+1).padStart(3,’0′)}</div>
<div class=”t name”>${it.name || ”}</div>
<div class=”t today”>${fmt(it.today||0)}</div>
<div class=”t week”>${fmt(it.week||0)}</div>
<div class=”t month”>${fmt(it.month||0)}</div>
<div class=”t year”>${fmt(it.year||0)}</div>
`;
body.appendChild(row);
}
}
// ———- Tables ———-
function fillTable(id, rows, cols){
const tb = document.querySelector(`#${id} tbody`); if(!tb) return; tb.innerHTML=”;
(rows||[]).forEach((r,i)=>{
const tr=document.createElement(‘tr’);
tr.innerHTML = cols.map(c=>`<td>${c===’sl’?(i+1):(r[c]??”)}</td>`).join(”);
tb.appendChild(tr);
});
}
// ———- Debounce and ResizeObserver ———-
function debounce(fn, wait=150){
let t; return (…args)=>{ clearTimeout(t); t=setTimeout(()=>fn.apply(null,args), wait); };
}
// ———- Boot ———-
async function boot(){
setDOB();
if(SMPOS.startedAt) tickCountdown(SMPOS.startedAt);
const bar = await get(‘bar-chart’); initBarChart(bar);
const trending = await get(‘trending’);
TREND_STATE.items = trending.items || [];
initPie({ items: TREND_STATE.items });
renderTrendingRows();
mountTrendToggle();
const recent = await get(‘recent-items’); fillTable(‘recent-items’, recent.items||[], [‘sl’,’name’,’price’]);
const expired = await get(‘expired-items’); fillTable(‘expired-items’, expired.items||[], [‘code’,’name’,’category’,’expire_at’]);
const alert = await get(‘stock-alert’); fillTable(‘stock-alert’, alert.items||[], [‘sl’,’name’,’category’,’stock’]);
// responsive re-fit
const onResize = debounce(()=>{
const b=document.getElementById(‘ps-chart’); if(b) fitBar(b);
const p=document.getElementById(‘trend-pie’); if(p) fitSquare(p, Math.min(420, p.parentElement.clientWidth));
}, 150);
window.addEventListener(‘resize’, onResize, {passive:true});
if (window.ResizeObserver){
const ro = new ResizeObserver(onResize);
const pieWrap = document.querySelector(‘.panel.trending .trend-pie’);
if (pieWrap) ro.observe(pieWrap);
}
}
document.addEventListener(‘DOMContentLoaded’, boot);
})();
shop-management-pos/assets/suppliers.css
/* Wrapper */
.smpos-wrap{ margin-top:10px; }
.smpos-page-head{ display:flex; justify-content:space-between; align-items:center; margin-bottom:10px; }
.smpos-page-head h1{ margin:0; font-size:20px; }
.smpos-page-head h1 small{ font-weight:600; color:#64748b; margin-left:8px; }
.smpos-card{ background:#fff; border:1px solid #e5e7eb; border-radius:8px; padding:12px; margin-bottom:12px; box-shadow:0 1px 2px rgba(0,0,0,.04); }
.btn{ display:inline-block; padding:6px 10px; border-radius:6px; background:#0b2a6c; color:#fff; text-decoration:none; font-weight:700; font-size:13px; }
.btn:hover{ background:#113883; }
.btn-primary{ background:#0aa957; }
.btn-primary:hover{ background:#0b8f4b; }
.toolbar{ display:flex; justify-content:space-between; align-items:center; gap:10px; margin-bottom:8px; }
.toolbar .left label{ font-weight:600; color:#1f2937; }
.toolbar select, .toolbar input[type=”search”]{ padding:6px 8px; border:1px solid #cbd5e1; border-radius:6px; }
.table-wrap{ overflow:auto; }
.smpos-table{ width:100%; border-collapse:separate; border-spacing:0; }
.smpos-table thead th{ background:#07224a; color:#fff; padding:10px; text-align:left; position:sticky; top:0; }
.smpos-table td{ padding:10px; border-bottom:1px solid #e5e7eb; }
.smpos-table tfoot th{ background:#f1f5f9; color:#0b1d3a; padding:10px; }
.numeric, .align-right{ text-align:right; }
.muted{ color:#64748b; text-align:center; padding:14px !important; }
.table-foot{ display:flex; justify-content:space-between; align-items:center; padding-top:8px; }
.pager .btn{ background:#0b2a6c; }
.grid.two-col{ display:grid; grid-template-columns: 1fr 1fr; gap:12px; }
.grid.two-col .col label{ display:block; font-weight:600; color:#1f2937; margin-bottom:10px; }
.grid.two-col input, .grid.two-col select, .grid.two-col textarea{ width:100%; padding:8px 10px; border:1px solid #cbd5e1; border-radius:6px; }
.req{ color:#ef4444; margin-left:4px; }
.form-actions{ margin-top:8px; display:flex; gap:8px; }
.import-row{ display:flex; align-items:center; gap:12px; }
.import-row .note{ color:#64748b; font-size:12px; }
.badge-success{ background:#10b981; color:#fff; padding:2px 8px; border-radius:999px; font-size:12px; font-weight:700; }
.badge-soft{ background:#e5e7eb; color:#111827; padding:2px 8px; border-radius:999px; font-size:12px; font-weight:700; }
.status-pill{ padding:4px 8px; border-radius:999px; font-size:12px; font-weight:700; display:inline-block; }
.status-active{ background:#22c55e; color:#fff; }
.status-inactive{ background:#e5e7eb; color:#111827; }
.action-menu .btn{ padding:4px 8px; font-size:12px; }
shop-management-pos/assets/suppliers.js
(function($){
// Helpers
const fmt = n => Number(n||0).toLocaleString();
// Suppliers List
function loadList(){
const $tbody = $(‘#suppliers-tbody’);
if(!$tbody.length) return;
// Placeholder data; বাস্তবে REST API থেকে আনুন
const rows = [
{id:’SP0001′, name:’RIDOY TELECOM’, mobile:’01911065826′, email:’dronekabir01@gmail.com’, purchase_due:0, return_due:0, status:’Active’},
];
let totalPurchase = 0, totalReturn = 0;
$tbody.empty();
rows.forEach((r, idx)=>{
totalPurchase += Number(r.purchase_due||0);
totalReturn += Number(r.return_due||0);
const tr = $(`
<tr>
<td><input type=”checkbox” class=”row-check”></td>
<td>${r.id}</td>
<td>${r.name}</td>
<td>${r.mobile||”}</td>
<td>${r.email||”}</td>
<td class=”numeric”>${fmt(r.purchase_due)}</td>
<td class=”numeric”>${fmt(r.return_due)}</td>
<td><span class=”status-pill ${r.status===’Active’?’status-active’:’status-inactive’}”>${r.status}</span></td>
<td class=”action-menu”>
<a href=”admin.php?page=smpos-suppliers-new&edit=${encodeURIComponent(r.id)}” class=”btn”>Action ▾</a>
</td>
</tr>
`);
$tbody.append(tr);
});
$(‘#foot-purchase-due’).text(fmt(totalPurchase));
$(‘#foot-return-due’).text(fmt(totalReturn));
$(‘#sup-page-info’).text(`Showing 1 to ${rows.length} of ${rows.length} entries`);
}
// New Supplier form
function bindForm(){
const $form = $(‘#supplier-form’);
if(!$form.length) return;
$form.on(‘submit’, function(e){
e.preventDefault();
const data = Object.fromEntries(new FormData(this).entries());
alert(‘Saved (demo). Replace with REST call.\n’ + JSON.stringify(data, null, 2));
window.location = ‘admin.php?page=smpos-suppliers-list’;
});
}
// Import
function bindImport(){
const $form = $(‘#sup-import-form’);
if(!$form.length) return;
$form.on(‘submit’, function(e){
e.preventDefault();
const file = this.file.files[0];
if(!file){ alert(‘Choose a CSV file’); return; }
alert(‘Import started (demo). Implement server parsing.’);
});
}
$(document).ready(function(){
loadList();
bindForm();
bindImport();
});
})(jQuery);
shop-management-pos/assets/topnav.css
/* Top horizontal nav – responsive */
.smpos-topnav{
position:sticky;
top:32px; /* WP admin bar offset */
z-index:1000;
background:#1e4e7e; color:#fff; border-bottom:1px solid rgba(255,255,255,.15);
font-size:14px; line-height:1;
}
@media (max-width:782px){ .smpos-topnav{ top:46px; } }
.smpos-topnav *{ box-sizing:border-box; }
.smpos-topnav a{ color:#fff; text-decoration:none; }
.smpos-topnav-inner{
display:flex; align-items:center; justify-content:space-between;
gap:10px; padding:6px 10px;
}
.smpos-topnav .brand{
color:#fff; font-weight:800; padding:6px 10px; border-radius:6px;
background:rgba(255,255,255,.08); display:inline-block;
}
.smpos-topnav .nav-toggle{
display:none; background:transparent; border:0; color:#fff; font-size:18px; padding:6px 8px; border-radius:6px;
cursor:pointer;
}
.smpos-topnav .nav-items{
display:flex; align-items:center; gap:6px; flex-wrap:nowrap; overflow:auto;
}
.smpos-topnav .nav-items.open{ display:flex; }
.smpos-topnav .nav-link{
font-weight:700; padding:6px 10px; border-radius:6px; white-space:nowrap; display:inline-block;
}
.smpos-topnav .nav-link:hover{ background:rgba(255,255,255,.15); }
.smpos-topnav .nav-link.has-caret{ padding-right:16px; }
.smpos-topnav .nav-actions{ display:flex; align-items:center; gap:8px; }
.smpos-topnav .ic{ font-size:14px; background:rgba(255,255,255,.08); padding:6px; border-radius:50%; display:inline-block; }
/* Dropdown */
.smpos-topnav .nav-dropdown{ position:relative; }
.smpos-topnav .dropdown-menu{
position:absolute; top:100%; left:0; min-width:180px; background:#194267; border:1px solid rgba(255,255,255,.2);
box-shadow:0 6px 16px rgba(0,0,0,.2); border-radius:8px; padding:6px; display:none;
}
.smpos-topnav .dropdown-menu.open{ display:block; }
.smpos-topnav .dropdown-item{
display:block; padding:8px 10px; border-radius:6px; white-space:nowrap;
}
.smpos-topnav .dropdown-item:hover{ background:rgba(255,255,255,.15); }
/* Responsive */
@media (max-width: 1024px){
.smpos-topnav .nav-link{ padding:6px 8px; font-size:13px; }
}
@media (max-width: 768px){
.smpos-topnav-inner{ flex-wrap:wrap; }
.smpos-topnav .nav-toggle{ display:inline-block; }
.smpos-topnav .nav-items{
width:100%; order:3; display:none; gap:6px; padding-top:6px; flex-wrap:wrap;
}
.smpos-topnav .nav-items.open{ display:flex; }
.smpos-topnav .nav-actions{ order:2; margin-left:auto; }
.smpos-topnav .brand{ order:1; }
}
@media (max-width: 480px){
.smpos-topnav .nav-link, .smpos-topnav .dropdown-item{ font-size:12px; padding:6px 8px; }
}
shop-management-pos/includes/core/autoload.php
<?php
// Prevent direct access
if (!defined(‘ABSPATH’)) {
exit;
}
spl_autoload_register(function($class){
// Only handle SMPOS classes
if (strpos($class, ‘SMPOS\\’) !== 0) {
return;
}
// Convert namespace to file path
$relative_class = str_replace([‘SMPOS\\’, ‘\\’], [”, ‘/’], $class);
$file = SMPOS_PATH . ‘includes/’ . $relative_class . ‘.php’;
// Check if file exists and require it
if (file_exists($file)) {
require_once $file;
}
});
shop-management-pos/includes/Setup/Activator.php
<?php
namespace SMPOS\Setup;
class Activator {
public static function activate(){
global $wpdb;
require_once ABSPATH . ‘wp-admin/includes/upgrade.php’;
$charset_collate = $wpdb->get_charset_collate();
$tables = [
“CREATE TABLE {$wpdb->prefix}smpos_products (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
sku VARCHAR(64) UNIQUE,
name VARCHAR(255),
brand_id BIGINT UNSIGNED,
category_id BIGINT UNSIGNED,
unit VARCHAR(32),
cost DECIMAL(18,2) DEFAULT 0,
price DECIMAL(18,2) DEFAULT 0,
tax_rate DECIMAL(8,2) DEFAULT 0,
stock_qty DECIMAL(18,3) DEFAULT 0,
expire_at DATE NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) $charset_collate;”,
“CREATE TABLE {$wpdb->prefix}smpos_brands (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(128) UNIQUE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) $charset_collate;”,
“CREATE TABLE {$wpdb->prefix}smpos_categories (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(128) UNIQUE,
parent_id BIGINT UNSIGNED DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) $charset_collate;”,
“CREATE TABLE {$wpdb->prefix}smpos_suppliers (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255),
phone VARCHAR(64),
email VARCHAR(255),
address TEXT,
opening_balance DECIMAL(18,2) DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) $charset_collate;”,
“CREATE TABLE {$wpdb->prefix}smpos_customers (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255),
phone VARCHAR(64),
email VARCHAR(255),
address TEXT,
opening_balance DECIMAL(18,2) DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) $charset_collate;”,
“CREATE TABLE {$wpdb->prefix}smpos_purchases (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
supplier_id BIGINT UNSIGNED,
invoice_no VARCHAR(64),
invoice_date DATE,
subtotal DECIMAL(18,2),
discount DECIMAL(18,2) DEFAULT 0,
tax DECIMAL(18,2) DEFAULT 0,
shipping DECIMAL(18,2) DEFAULT 0,
total DECIMAL(18,2),
paid DECIMAL(18,2) DEFAULT 0,
status VARCHAR(32) DEFAULT ‘unpaid’,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) $charset_collate;”,
“CREATE TABLE {$wpdb->prefix}smpos_purchase_items (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
purchase_id BIGINT UNSIGNED,
product_id BIGINT UNSIGNED,
qty DECIMAL(18,3),
cost DECIMAL(18,2),
total DECIMAL(18,2)
) $charset_collate;”,
“CREATE TABLE {$wpdb->prefix}smpos_sales (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
customer_id BIGINT UNSIGNED NULL,
invoice_no VARCHAR(64),
invoice_date DATE,
subtotal DECIMAL(18,2),
discount DECIMAL(18,2) DEFAULT 0,
tax DECIMAL(18,2) DEFAULT 0,
shipping DECIMAL(18,2) DEFAULT 0,
total DECIMAL(18,2),
paid DECIMAL(18,2) DEFAULT 0,
status VARCHAR(32) DEFAULT ‘unpaid’,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) $charset_collate;”,
“CREATE TABLE {$wpdb->prefix}smpos_sales_items (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
sale_id BIGINT UNSIGNED,
product_id BIGINT UNSIGNED,
qty DECIMAL(18,3),
price DECIMAL(18,2),
total DECIMAL(18,2),
cost DECIMAL(18,2) DEFAULT 0
) $charset_collate;”,
“CREATE TABLE {$wpdb->prefix}smpos_payments (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
ref_type ENUM(‘purchase’,’sale’,’expense’) NOT NULL,
ref_id BIGINT UNSIGNED,
method VARCHAR(64),
amount DECIMAL(18,2),
paid_at DATETIME,
note TEXT
) $charset_collate;”,
“CREATE TABLE {$wpdb->prefix}smpos_expenses (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
category_id BIGINT UNSIGNED,
title VARCHAR(255),
amount DECIMAL(18,2),
expense_date DATE,
cashbook ENUM(‘cash’,’bank’) DEFAULT ‘cash’,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) $charset_collate;”,
“CREATE TABLE {$wpdb->prefix}smpos_expense_categories (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(128) UNIQUE
) $charset_collate;”,
“CREATE TABLE {$wpdb->prefix}smpos_stock_ledger (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
product_id BIGINT UNSIGNED,
ref_type VARCHAR(32),
ref_id BIGINT UNSIGNED,
in_qty DECIMAL(18,3) DEFAULT 0,
out_qty DECIMAL(18,3) DEFAULT 0,
cost DECIMAL(18,2) DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) $charset_collate;”
];
foreach($tables as $sql){
dbDelta($sql);
}
// Default options
if (!get_option(‘smpos_started_at’)) {
add_option(‘smpos_started_at’, current_time(‘mysql’));
}
if (!get_option(‘smpos_week_start’)) {
add_option(‘smpos_week_start’, 6); // 6 = Saturday
}
if (!get_option(‘smpos_shop_name’)) {
add_option(‘smpos_shop_name’, get_bloginfo(‘name’) ?: ‘Shop POS’);
}
// Flush rewrite rules
flush_rewrite_rules();
}
}
shop-management-pos/includes/Setup/Assets.php
<?php
namespace SMPOS\Setup;
class Assets {
public function hooks(){
add_action(‘admin_enqueue_scripts’, array($this, ‘enqueue_admin_assets’));
}
public function enqueue_admin_assets($hook){
// Only load on our plugin pages
if (strpos($hook, ‘smpos-‘) === false && $hook !== ‘toplevel_page_smpos-dashboard’) {
return;
}
wp_enqueue_style(‘smpos-admin’, SMPOS_URL.’assets/admin.css’, array(), SMPOS_VER);
wp_enqueue_script(‘smpos-admin’, SMPOS_URL.’assets/admin.js’, array(‘wp-api’, ‘jquery’), SMPOS_VER, true);
// Ensure options exist
$startedAt = get_option(‘smpos_started_at’);
if(!$startedAt){
$startedAt = current_time(‘mysql’);
update_option(‘smpos_started_at’, $startedAt);
}
// Shop DOB
$shopDob = get_option(‘smpos_dob’);
if(!$shopDob){
$shopDob = substr($startedAt,0,10);
add_option(‘smpos_dob’,$shopDob);
}
// Shop Name
$shopName = get_option(‘smpos_shop_name’);
if(!$shopName){
$shopName = get_bloginfo(‘name’) ?: ‘Shop’;
add_option(‘smpos_shop_name’, $shopName);
}
wp_localize_script(‘smpos-admin’,’SMPOS’, array(
‘root’=>esc_url_raw(rest_url(‘smpos/v1/’)),
‘nonce’=>wp_create_nonce(‘wp_rest’),
‘weekStart’=>(int)get_option(‘smpos_week_start’,6),
‘startedAt’=>$startedAt,
‘shopDOB’=>$shopDob,
‘shopName’=>$shopName
));
// Load Chart.js for dashboard
if ($hook === ‘toplevel_page_smpos-dashboard’) {
wp_enqueue_script(‘chartjs’,’https://cdn.jsdelivr.net/npm/chart.js’,array(), ‘4.4.0’, true);
}
}
}
shop-management-pos/includes/Setup/Deactivator.php
<?php
namespace SMPOS\Setup;
class Deactivator {
public static function deactivate(){
// Clean up temporary options if needed
delete_option(‘smpos_flush_rewrite_rules’);
// Clear scheduled hooks if any
wp_clear_scheduled_hook(‘smpos_daily_maintenance’);
// Flush rewrite rules
flush_rewrite_rules();
}
}
shop-management-pos/includes/Setup/Menus.php
<?php
namespace SMPOS\Setup;
class Menus {
public function hooks(){
add_action(‘admin_menu’, array($this, ‘add_admin_menus’));
add_action(‘admin_head’, array($this, ‘highlight_current_menu’));
add_action(‘admin_footer’, array($this, ‘add_dropdown_scripts’));
add_action(‘wp_ajax_smpos_suppliers_example’, array($this, ‘suppliers_csv_example’));
}
public function add_admin_menus(){
$icon_url = ‘dashicons-cart’;
$capability = ‘manage_options’;
// Main Dashboard
add_menu_page(
‘Shop POS Dashboard’,
‘Shop POS’,
$capability,
‘smpos-dashboard’,
array($this, ‘render_dashboard’),
$icon_url,
25
);
// Dashboard
add_submenu_page(
‘smpos-dashboard’,
‘Dashboard’,
‘Dashboard’,
$capability,
‘smpos-dashboard’,
array($this, ‘render_dashboard’)
);
// Remove duplicate suppliers menu that was causing issues
remove_submenu_page(‘smpos-dashboard’, ‘smpos-suppliers’);
remove_submenu_page(‘smpos-dashboard’, ‘smpos-purchase’);
remove_submenu_page(‘smpos-dashboard’, ‘smpos-products’);
remove_submenu_page(‘smpos-dashboard’, ‘smpos-sales’);
remove_submenu_page(‘smpos-dashboard’, ‘smpos-customers’);
remove_submenu_page(‘smpos-dashboard’, ‘smpos-staff-salary’);
remove_submenu_page(‘smpos-dashboard’, ‘smpos-expenses’);
remove_submenu_page(‘smpos-dashboard’, ‘smpos-reports’);
remove_submenu_page(‘smpos-dashboard’, ‘smpos-users’);
remove_submenu_page(‘smpos-dashboard’, ‘smpos-sms’);
remove_submenu_page(‘smpos-dashboard’, ‘smpos-places’);
// Suppliers Menu Group
add_submenu_page(
‘smpos-dashboard’,
‘Suppliers’,
‘Suppliers’,
$capability,
‘smpos-suppliers-main’,
array($this, ‘render_suppliers_main’)
);
// Suppliers Submenus
add_submenu_page(
‘smpos-dashboard’,
‘New Supplier’,
‘— New Supplier’,
$capability,
‘smpos-suppliers-new’,
array($this, ‘render_suppliers_new’)
);
add_submenu_page(
‘smpos-dashboard’,
‘Suppliers List’,
‘— Suppliers List’,
$capability,
‘smpos-suppliers-list’,
array($this, ‘render_suppliers_list’)
);
add_submenu_page(
‘smpos-dashboard’,
‘Import Suppliers’,
‘— Import Suppliers’,
$capability,
‘smpos-suppliers-import’,
array($this, ‘render_suppliers_import’)
);
// Purchase Menu Group
add_submenu_page(
‘smpos-dashboard’,
‘Purchase’,
‘Purchase’,
$capability,
‘smpos-purchase-main’,
array($this, ‘render_purchase_main’)
);
// Purchase Submenus
add_submenu_page(
‘smpos-dashboard’,
‘New Purchase’,
‘— New Purchase’,
$capability,
‘smpos-purchase-new’,
array($this, ‘render_purchase_new’)
);
add_submenu_page(
‘smpos-dashboard’,
‘Purchase List’,
‘— Purchase List’,
$capability,
‘smpos-purchase-list’,
array($this, ‘render_purchase_list’)
);
add_submenu_page(
‘smpos-dashboard’,
‘New Purchase Return’,
‘— New Purchase Return’,
$capability,
‘smpos-purchase-return’,
array($this, ‘render_purchase_return’)
);
// Products Menu Group
add_submenu_page(
‘smpos-dashboard’,
‘Products’,
‘Products’,
$capability,
‘smpos-products-main’,
array($this, ‘render_products_main’)
);
// Products Submenus
add_submenu_page(
‘smpos-dashboard’,
‘New Product’,
‘— New Product’,
$capability,
‘smpos-products-new’,
array($this, ‘render_products_new’)
);
add_submenu_page(
‘smpos-dashboard’,
‘Product List’,
‘— Product List’,
$capability,
‘smpos-products-list’,
array($this, ‘render_products_list’)
);
add_submenu_page(
‘smpos-dashboard’,
‘New Product Category’,
‘— New Product Category’,
$capability,
‘smpos-products-category-new’,
array($this, ‘render_products_category_new’)
);
add_submenu_page(
‘smpos-dashboard’,
‘Product Category List’,
‘— Product Category List’,
$capability,
‘smpos-products-category-list’,
array($this, ‘render_products_category_list’)
);
add_submenu_page(
‘smpos-dashboard’,
‘New Brand’,
‘— New Brand’,
$capability,
‘smpos-brands-new’,
array($this, ‘render_brands_new’)
);
add_submenu_page(
‘smpos-dashboard’,
‘Brand List’,
‘— Brand List’,
$capability,
‘smpos-brands-list’,
array($this, ‘render_brands_list’)
);
add_submenu_page(
‘smpos-dashboard’,
‘Print Labels’,
‘— Print Labels’,
$capability,
‘smpos-print-labels’,
array($this, ‘render_print_labels’)
);
add_submenu_page(
‘smpos-dashboard’,
‘Import Products’,
‘— Import Products’,
$capability,
‘smpos-products-import’,
array($this, ‘render_products_import’)
);
// Sales Menu Group
add_submenu_page(
‘smpos-dashboard’,
‘Sales’,
‘Sales’,
$capability,
‘smpos-sales-main’,
array($this, ‘render_sales_main’)
);
// Sales Submenus
add_submenu_page(
‘smpos-dashboard’,
‘POS’,
‘— POS’,
$capability,
‘smpos-pos’,
array($this, ‘render_pos’)
);
add_submenu_page(
‘smpos-dashboard’,
‘New Sales’,
‘— New Sales’,
$capability,
‘smpos-sales-new’,
array($this, ‘render_sales_new’)
);
add_submenu_page(
‘smpos-dashboard’,
‘Sales List’,
‘— Sales List’,
$capability,
‘smpos-sales-list’,
array($this, ‘render_sales_list’)
);
add_submenu_page(
‘smpos-dashboard’,
‘New Sales Return’,
‘— New Sales Return’,
$capability,
‘smpos-sales-return’,
array($this, ‘render_sales_return’)
);
// Customers Menu Group
add_submenu_page(
‘smpos-dashboard’,
‘Customers’,
‘Customers’,
$capability,
‘smpos-customers-main’,
array($this, ‘render_customers_main’)
);
// Customers Submenus
add_submenu_page(
‘smpos-dashboard’,
‘New Customer’,
‘— New Customer’,
$capability,
‘smpos-customers-new’,
array($this, ‘render_customers_new’)
);
add_submenu_page(
‘smpos-dashboard’,
‘Customers List’,
‘— Customers List’,
$capability,
‘smpos-customers-list’,
array($this, ‘render_customers_list’)
);
add_submenu_page(
‘smpos-dashboard’,
‘Import Customers’,
‘— Import Customers’,
$capability,
‘smpos-customers-import’,
array($this, ‘render_customers_import’)
);
// Staff Salary Menu Group
add_submenu_page(
‘smpos-dashboard’,
‘Staff Salary’,
‘Staff Salary’,
$capability,
‘smpos-staff-salary-main’,
array($this, ‘render_staff_salary_main’)
);
// Staff Salary Submenus
add_submenu_page(
‘smpos-dashboard’,
‘New Staff’,
‘— New Staff’,
$capability,
‘smpos-staff-new’,
array($this, ‘render_staff_new’)
);
add_submenu_page(
‘smpos-dashboard’,
‘Staffs List’,
‘— Staffs List’,
$capability,
‘smpos-staff-list’,
array($this, ‘render_staff_list’)
);
// Expenses Menu Group
add_submenu_page(
‘smpos-dashboard’,
‘Expenses’,
‘Expenses’,
$capability,
‘smpos-expenses-main’,
array($this, ‘render_expenses_main’)
);
// Expenses Submenus
add_submenu_page(
‘smpos-dashboard’,
‘New Expenses’,
‘— New Expenses’,
$capability,
‘smpos-expenses-new’,
array($this, ‘render_expenses_new’)
);
add_submenu_page(
‘smpos-dashboard’,
‘Expenses List’,
‘— Expenses List’,
$capability,
‘smpos-expenses-list’,
array($this, ‘render_expenses_list’)
);
add_submenu_page(
‘smpos-dashboard’,
‘New Category’,
‘— New Category’,
$capability,
‘smpos-expenses-category-new’,
array($this, ‘render_expenses_category_new’)
);
add_submenu_page(
‘smpos-dashboard’,
‘Categories List’,
‘— Categories List’,
$capability,
‘smpos-expenses-category-list’,
array($this, ‘render_expenses_category_list’)
);
// Reports Menu Group
add_submenu_page(
‘smpos-dashboard’,
‘Reports’,
‘Reports’,
$capability,
‘smpos-reports-main’,
array($this, ‘render_reports_main’)
);
// Reports Submenus
$reports_submenus = array(
‘Profit & Loss Reports’ => ‘smpos-reports-profit-loss’,
‘Purchase Report’ => ‘smpos-reports-purchase’,
‘Purchase Return Report’ => ‘smpos-reports-purchase-return’,
‘Purchase Payments Report’ => ‘smpos-reports-purchase-payments’,
‘Product Sales Report’ => ‘smpos-reports-product-sales’,
‘Product Purchase Report’ => ‘smpos-reports-product-purchase’,
‘Sales Report’ => ‘smpos-reports-sales’,
‘Sales Return Report’ => ‘smpos-reports-sales-return’,
‘Sales Payments Report’ => ‘smpos-reports-sales-payments’,
‘Stock Report’ => ‘smpos-reports-stock’,
‘Expense Report’ => ‘smpos-reports-expense’,
‘Expired Product Report’ => ‘smpos-reports-expired-products’
);
foreach($reports_submenus as $label => $page_slug){
add_submenu_page(
‘smpos-dashboard’,
$label,
‘— ‘ . $label,
$capability,
$page_slug,
array($this, ‘render_reports_generic’)
);
}
// User Menu Group
add_submenu_page(
‘smpos-dashboard’,
‘User’,
‘User’,
$capability,
‘smpos-users-main’,
array($this, ‘render_users_main’)
);
// User Submenus
add_submenu_page(
‘smpos-dashboard’,
‘New User’,
‘— New User’,
$capability,
‘smpos-users-new’,
array($this, ‘render_users_new’)
);
add_submenu_page(
‘smpos-dashboard’,
‘Users List’,
‘— Users List’,
$capability,
‘smpos-users-list’,
array($this, ‘render_users_list’)
);
add_submenu_page(
‘smpos-dashboard’,
‘Roles List’,
‘— Roles List’,
$capability,
‘smpos-roles-list’,
array($this, ‘render_roles_list’)
);
// SMS Menu Group
add_submenu_page(
‘smpos-dashboard’,
‘SMS’,
‘SMS’,
$capability,
‘smpos-sms-main’,
array($this, ‘render_sms_main’)
);
// SMS Submenus
add_submenu_page(
‘smpos-dashboard’,
‘Send SMS’,
‘— Send SMS’,
$capability,
‘smpos-sms-send’,
array($this, ‘render_sms_send’)
);
add_submenu_page(
‘smpos-dashboard’,
‘SMS Templates’,
‘— SMS Templates’,
$capability,
‘smpos-sms-templates’,
array($this, ‘render_sms_templates’)
);
add_submenu_page(
‘smpos-dashboard’,
‘SMS API’,
‘— SMS API’,
$capability,
‘smpos-sms-api’,
array($this, ‘render_sms_api’)
);
// Places Menu Group
add_submenu_page(
‘smpos-dashboard’,
‘Places’,
‘Places’,
$capability,
‘smpos-places-main’,
array($this, ‘render_places_main’)
);
// Places Submenus
add_submenu_page(
‘smpos-dashboard’,
‘New Country’,
‘— New Country’,
$capability,
‘smpos-places-country-new’,
array($this, ‘render_places_country_new’)
);
add_submenu_page(
‘smpos-dashboard’,
‘Countries List’,
‘— Countries List’,
$capability,
‘smpos-places-country-list’,
array($this, ‘render_places_country_list’)
);
add_submenu_page(
‘smpos-dashboard’,
‘New State’,
‘— New State’,
$capability,
‘smpos-places-state-new’,
array($this, ‘render_places_state_new’)
);
add_submenu_page(
‘smpos-dashboard’,
‘States List’,
‘— States List’,
$capability,
‘smpos-places-state-list’,
array($this, ‘render_places_state_list’)
);
}
public function highlight_current_menu(){
?>
<style>
#toplevel_page_smpos-dashboard.current > a,
#toplevel_page_smpos-dashboard.wp-has-current-submenu > a {
background: #1e4e7e !important;
color: #fff !important;
}
#toplevel_page_smpos-dashboard .wp-submenu li.current a {
color: #fff !important;
font-weight: 700;
}
#toplevel_page_smpos-dashboard .wp-submenu a {
font-size: 13px;
}
/* Dropdown styles for sidebar */
.smpos-dropdown {
display: none;
margin-left: 15px;
}
.smpos-dropdown.open {
display: block;
}
.smpos-menu-toggle {
background: none;
border: none;
color: #eee;
cursor: pointer;
font-size: 13px;
padding: 5px 12px;
text-align: left;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
}
.smpos-menu-toggle:hover {
color: #00b9eb;
}
.smpos-menu-toggle::after {
content: “▸”;
font-size: 12px;
}
.smpos-menu-toggle.open::after {
content: “▾”;
}
.smpos-dropdown a {
padding-left: 25px !important;
}
.smpos-menu-item {
position: relative;
}
</style>
<?php
}
public function add_dropdown_scripts(){
?>
<script>
jQuery(document).ready(function($) {
// Define main menu items that should have dropdowns
var mainMenuItems = [
‘Suppliers’, ‘Purchase’, ‘Products’, ‘Sales’, ‘Customers’,
‘Staff Salary’, ‘Expenses’, ‘Reports’, ‘User’, ‘SMS’, ‘Places’
];
// Process each main menu item
mainMenuItems.forEach(function(menuText) {
var $mainLink = $(‘#toplevel_page_smpos-dashboard .wp-submenu a’).filter(function() {
return $(this).text().trim() === menuText;
});
if ($mainLink.length > 0) {
var $mainLi = $mainLink.closest(‘li’);
// Find all submenu items that belong to this main menu
var $allSubmenus = $mainLi.nextAll(‘li’);
var $submenuItems = $();
$allSubmenus.each(function() {
var $li = $(this);
var linkText = $li.find(‘a’).text().trim();
// Stop when we hit another main menu item
if (mainMenuItems.includes(linkText)) {
return false;
}
// Add submenu items that start with ‘—’
if (linkText.startsWith(‘—’)) {
$submenuItems = $submenuItems.add($li);
}
});
if ($submenuItems.length > 0) {
// Create dropdown container
var $dropdown = $(‘<div class=”smpos-dropdown”></div>’);
// Move submenu items to dropdown
$submenuItems.each(function() {
$(this).appendTo($dropdown);
});
// Create toggle button
var $toggle = $(‘<button class=”smpos-menu-toggle” type=”button”></button>’);
$toggle.html($mainLink.html() + ‘<span></span>’);
// Replace main link with toggle button and dropdown
$mainLi.html(”).append($toggle).append($dropdown);
// Add click handler for toggle
$toggle.on(‘click’, function(e) {
e.preventDefault();
e.stopPropagation();
var $this = $(this);
var $dropdown = $this.next(‘.smpos-dropdown’);
// Close other dropdowns
$(‘.smpos-dropdown’).not($dropdown).removeClass(‘open’);
$(‘.smpos-menu-toggle’).not($this).removeClass(‘open’);
// Toggle current dropdown
$dropdown.toggleClass(‘open’);
$this.toggleClass(‘open’);
});
}
}
});
// Auto-open dropdown if current page is in submenu
$(‘a.current’).each(function() {
var $current = $(this);
var $dropdown = $current.closest(‘.smpos-dropdown’);
if ($dropdown.length) {
$dropdown.addClass(‘open’);
$dropdown.prev(‘.smpos-menu-toggle’).addClass(‘open’);
}
});
});
</script>
<?php
}
// Main Render Methods
public function render_dashboard(){
$this->enqueue_dashboard_assets();
$file = SMPOS_PATH . ‘views/dashboard.php’;
if(file_exists($file)){
include $file;
} else {
echo ‘<div class=”wrap”><h1>Dashboard</h1><p>Dashboard system coming soon…</p></div>’;
}
}
// Suppliers Render Methods
public function render_suppliers_main(){
echo ‘<div class=”wrap”><h1>Suppliers Management</h1><p>Suppliers main page coming soon…</p></div>’;
}
public function render_suppliers_list(){
$this->enqueue_suppliers_assets();
$file = SMPOS_PATH . ‘views/suppliers-list.php’;
if(file_exists($file)){
include $file;
} else {
echo ‘<div class=”notice notice-error”><p>Missing view: views/suppliers-list.php</p></div>’;
}
}
public function render_suppliers_new(){
$this->enqueue_suppliers_assets();
$file = SMPOS_PATH . ‘views/suppliers-new.php’;
if(file_exists($file)){
include $file;
} else {
echo ‘<div class=”notice notice-error”><p>Missing view: views/suppliers-new.php</p></div>’;
}
}
public function render_suppliers_import(){
$this->enqueue_suppliers_assets();
$file = SMPOS_PATH . ‘views/suppliers-import.php’;
if(file_exists($file)){
include $file;
} else {
echo ‘<div class=”notice notice-error”><p>Missing view: views/suppliers-import.php</p></div>’;
}
}
// Purchase Render Methods
public function render_purchase_main(){
echo ‘<div class=”wrap”><h1>Purchase Management</h1><p>Purchase main page coming soon…</p></div>’;
}
public function render_purchase_new(){
echo ‘<div class=”wrap”><h1>New Purchase</h1><p>New purchase form coming soon…</p></div>’;
}
public function render_purchase_list(){
echo ‘<div class=”wrap”><h1>Purchase List</h1><p>Purchase list page coming soon…</p></div>’;
}
public function render_purchase_return(){
echo ‘<div class=”wrap”><h1>Purchase Return</h1><p>Purchase return page coming soon…</p></div>’;
}
// Products Render Methods
public function render_products_main(){
echo ‘<div class=”wrap”><h1>Products Management</h1><p>Products main page coming soon…</p></div>’;
}
public function render_products_new(){
echo ‘<div class=”wrap”><h1>New Product</h1><p>New product form coming soon…</p></div>’;
}
public function render_products_list(){
echo ‘<div class=”wrap”><h1>Product List</h1><p>Product list page coming soon…</p></div>’;
}
public function render_products_category_new(){
echo ‘<div class=”wrap”><h1>New Product Category</h1><p>New category form coming soon…</p></div>’;
}
public function render_products_category_list(){
echo ‘<div class=”wrap”><h1>Product Category List</h1><p>Category list page coming soon…</p></div>’;
}
public function render_brands_new(){
echo ‘<div class=”wrap”><h1>New Brand</h1><p>New brand form coming soon…</p></div>’;
}
public function render_brands_list(){
echo ‘<div class=”wrap”><h1>Brand List</h1><p>Brand list page coming soon…</p></div>’;
}
public function render_print_labels(){
echo ‘<div class=”wrap”><h1>Print Labels</h1><p>Print labels page coming soon…</p></div>’;
}
public function render_products_import(){
echo ‘<div class=”wrap”><h1>Import Products</h1><p>Import products page coming soon…</p></div>’;
}
// Sales Render Methods
public function render_sales_main(){
echo ‘<div class=”wrap”><h1>Sales Management</h1><p>Sales main page coming soon…</p></div>’;
}
public function render_pos(){
echo ‘<div class=”wrap”><h1>Point of Sale (POS)</h1><p>POS system coming soon…</p></div>’;
}
public function render_sales_new(){
echo ‘<div class=”wrap”><h1>New Sales</h1><p>New sales form coming soon…</p></div>’;
}
public function render_sales_list(){
echo ‘<div class=”wrap”><h1>Sales List</h1><p>Sales list page coming soon…</p></div>’;
}
public function render_sales_return(){
echo ‘<div class=”wrap”><h1>Sales Return</h1><p>Sales return page coming soon…</p></div>’;
}
// Customers Render Methods
public function render_customers_main(){
echo ‘<div class=”wrap”><h1>Customers Management</h1><p>Customers main page coming soon…</p></div>’;
}
public function render_customers_new(){
echo ‘<div class=”wrap”><h1>New Customer</h1><p>New customer form coming soon…</p></div>’;
}
public function render_customers_list(){
echo ‘<div class=”wrap”><h1>Customers List</h1><p>Customers list page coming soon…</p></div>’;
}
public function render_customers_import(){
echo ‘<div class=”wrap”><h1>Import Customers</h1><p>Import customers page coming soon…</p></div>’;
}
// Staff Salary Render Methods
public function render_staff_salary_main(){
echo ‘<div class=”wrap”><h1>Staff Salary Management</h1><p>Staff salary main page coming soon…</p></div>’;
}
public function render_staff_new(){
echo ‘<div class=”wrap”><h1>New Staff</h1><p>New staff form coming soon…</p></div>’;
}
public function render_staff_list(){
echo ‘<div class=”wrap”><h1>Staff List</h1><p>Staff list page coming soon…</p></div>’;
}
// Expenses Render Methods
public function render_expenses_main(){
echo ‘<div class=”wrap”><h1>Expenses Management</h1><p>Expenses main page coming soon…</p></div>’;
}
public function render_expenses_new(){
echo ‘<div class=”wrap”><h1>New Expenses</h1><p>New expenses form coming soon…</p></div>’;
}
public function render_expenses_list(){
echo ‘<div class=”wrap”><h1>Expenses List</h1><p>Expenses list page coming soon…</p></div>’;
}
public function render_expenses_category_new(){
echo ‘<div class=”wrap”><h1>New Expense Category</h1><p>New expense category form coming soon…</p></div>’;
}
public function render_expenses_category_list(){
echo ‘<div class=”wrap”><h1>Expense Categories List</h1><p>Expense categories list page coming soon…</p></div>’;
}
// Reports Render Methods
public function render_reports_main(){
echo ‘<div class=”wrap”><h1>Reports & Analytics</h1><p>Reports main page coming soon…</p></div>’;
}
public function render_reports_generic(){
$current_page = $_GET[‘page’] ?? ”;
$report_name = str_replace(‘smpos-reports-‘, ”, $current_page);
$report_name = str_replace(‘-‘, ‘ ‘, $report_name);
$report_name = ucwords($report_name);
echo ‘<div class=”wrap”><h1>’ . $report_name . ‘</h1><p>’ . $report_name . ‘ report coming soon…</p></div>’;
}
// Users Render Methods
public function render_users_main(){
echo ‘<div class=”wrap”><h1>User Management</h1><p>User management main page coming soon…</p></div>’;
}
public function render_users_new(){
echo ‘<div class=”wrap”><h1>New User</h1><p>New user form coming soon…</p></div>’;
}
public function render_users_list(){
echo ‘<div class=”wrap”><h1>Users List</h1><p>Users list page coming soon…</p></div>’;
}
public function render_roles_list(){
echo ‘<div class=”wrap”><h1>Roles List</h1><p>Roles list page coming soon…</p></div>’;
}
// SMS Render Methods
public function render_sms_main(){
echo ‘<div class=”wrap”><h1>SMS Management</h1><p>SMS main page coming soon…</p></div>’;
}
public function render_sms_send(){
echo ‘<div class=”wrap”><h1>Send SMS</h1><p>Send SMS page coming soon…</p></div>’;
}
public function render_sms_templates(){
echo ‘<div class=”wrap”><h1>SMS Templates</h1><p>SMS templates page coming soon…</p></div>’;
}
public function render_sms_api(){
echo ‘<div class=”wrap”><h1>SMS API</h1><p>SMS API configuration page coming soon…</p></div>’;
}
// Places Render Methods
public function render_places_main(){
echo ‘<div class=”wrap”><h1>Places Management</h1><p>Places main page coming soon…</p></div>’;
}
public function render_places_country_new(){
echo ‘<div class=”wrap”><h1>New Country</h1><p>New country form coming soon…</p></div>’;
}
public function render_places_country_list(){
echo ‘<div class=”wrap”><h1>Countries List</h1><p>Countries list page coming soon…</p></div>’;
}
public function render_places_state_new(){
echo ‘<div class=”wrap”><h1>New State</h1><p>New state form coming soon…</p></div>’;
}
public function render_places_state_list(){
echo ‘<div class=”wrap”><h1>States List</h1><p>States list page coming soon…</p></div>’;
}
// Asset Enqueue Methods
private function enqueue_dashboard_assets(){
wp_enqueue_style(‘smpos-admin’, SMPOS_URL . ‘assets/admin.css’, array(), SMPOS_VER);
wp_enqueue_script(‘smpos-admin’, SMPOS_URL . ‘assets/admin.js’, array(‘wp-api’, ‘jquery’), SMPOS_VER, true);
wp_enqueue_script(‘chartjs’,’https://cdn.jsdelivr.net/npm/chart.js’,array(), ‘4.4.0’, true);
wp_localize_script(‘smpos-admin’,’SMPOS’, array(
‘root’=>esc_url_raw(rest_url(‘smpos/v1/’)),
‘nonce’=>wp_create_nonce(‘wp_rest’),
‘weekStart’=>(int)get_option(‘smpos_week_start’,6),
‘startedAt’=>get_option(‘smpos_started_at’),
‘shopDOB’=>get_option(‘smpos_dob’),
‘shopName’=>get_option(‘smpos_shop_name’)
));
}
private function enqueue_suppliers_assets(){
wp_enqueue_style(‘smpos-suppliers’, SMPOS_URL . ‘assets/suppliers.css’, array(), SMPOS_VER);
wp_enqueue_script(‘smpos-suppliers’, SMPOS_URL . ‘assets/suppliers.js’, array(‘jquery’), SMPOS_VER, true);
wp_localize_script(‘smpos-suppliers’,’SMPOS_SUP’, array(
‘nonce’=> wp_create_nonce(‘wp_rest’),
‘root’=> esc_url_raw( rest_url(‘smpos/v1/’) ),
‘downloadExample’=> admin_url(‘admin-ajax.php?action=smpos_suppliers_example’)
));
}
/**
* CSV example download
*/
public function suppliers_csv_example(){
if (!current_user_can(‘manage_options’)) wp_die(‘Unauthorized’);
$csv = “Supplier Name,Mobile,Email,Phone,GST Number,TAX Number,Country,State,City,Postcode,Address,Opening Balance\n”;
$csv .= “RIDOY TELECOM,01911065826,dronekabir01@gmail.com,,,,\”Bangladesh\”,Dhaka,Dhaka,1200,\”Sample Address\”,0\n”;
header(‘Content-Type: text/csv; charset=utf-8’);
header(‘Content-Disposition: attachment; filename=”suppliers-example.csv”‘);
echo $csv;
exit;
}
}
shop-management-pos/includes/Setup/Rest.php
<?php
namespace SMPOS\Setup;
use WP_REST_Request;
use WP_REST_Response;
class Rest {
public function hooks(){
add_action(‘rest_api_init’, array($this, ‘register_routes’));
}
public function register_routes(){
register_rest_route(‘smpos/v1′,’/kpis’, array(
‘methods’=>’GET’,
‘permission_callback’=>array($this,’can_view’),
‘callback’=>array($this,’kpis’)
));
register_rest_route(‘smpos/v1′,’/recent-items’, array(
‘methods’=>’GET’,’permission_callback’=>array($this,’can_view’),
‘callback’=>array($this,’recent_items’)
));
register_rest_route(‘smpos/v1′,’/stock-alert’, array(
‘methods’=>’GET’,’permission_callback’=>array($this,’can_view’),
‘callback’=>array($this,’stock_alert’)
));
register_rest_route(‘smpos/v1′,’/expired-items’, array(
‘methods’=>’GET’,’permission_callback’=>array($this,’can_view’),
‘callback’=>array($this,’expired_items’)
));
register_rest_route(‘smpos/v1′,’/trending’, array(
‘methods’=>’GET’,’permission_callback’=>array($this,’can_view’),
‘callback’=>array($this,’trending’)
));
register_rest_route(‘smpos/v1′,’/bar-chart’, array(
‘methods’=>’GET’,’permission_callback’=>array($this,’can_view’),
‘callback’=>array($this,’bar_chart’)
));
}
public function can_view(){
return current_user_can(‘manage_options’);
}
public function kpis(WP_REST_Request $req){
$now = new \DateTimeImmutable(‘now’, wp_timezone());
$weekStart = (int) get_option(‘smpos_week_start’,6);
$w = (int)$now->format(‘w’);
$offset = ($w >= $weekStart) ? $w – $weekStart : 7 – ($weekStart – $w);
$weekStartDate = $now->modify(“-{$offset} days”)->setTime(0,0);
$weekEndDate = $weekStartDate->modify(‘+6 days’)->setTime(23,59,59);
// Sample data
$names = array(
‘purchase’,’purchase_due’,’purchase_refund’,’sales’,’sales_due’,’sales_refund’,
‘stock’,’expense’,’cash’,’gross_profit’,’net_profit’,’net_loss’
);
$res = array(
‘totals’=>array(
‘purchase’ => 150000,
‘purchase_due’ => 25000,
‘purchase_refund’ => 5000,
‘sales’ => 200000,
‘sales_due’ => 30000,
‘sales_refund’ => 8000,
‘stock’ => 50000,
‘expense’ => 15000,
‘cash’ => 75000,
‘gross_profit’ => 50000,
‘net_profit’ => 35000,
‘net_loss’ => 0
),
‘today’=>array_fill_keys($names, rand(1000, 5000)),
‘week’=>array_fill_keys($names, rand(5000, 20000)),
‘month’=>array_fill_keys($names, rand(20000, 80000)),
‘year’=>array_fill_keys($names, rand(100000, 300000)),
‘meta’=>array(
‘week_start’=>$weekStartDate->format(‘Y-m-d’),
‘week_end’=>$weekEndDate->format(‘Y-m-d’),
‘shop_age’=>get_option(‘smpos_started_at’)
)
);
return new WP_REST_Response($res, 200);
}
public function recent_items(){
$items = array(
array(‘sl’ => 1, ‘name’ => ‘iPhone 14 Pro’, ‘price’ => ‘125000’),
array(‘sl’ => 2, ‘name’ => ‘Samsung Galaxy S23’, ‘price’ => ‘85000’),
array(‘sl’ => 3, ‘name’ => ‘Xiaomi Redmi Note 12’, ‘price’ => ‘25000’),
);
return new WP_REST_Response(array(‘items’=>$items),200);
}
public function stock_alert(){
$items = array(
array(‘sl’ => 1, ‘name’ => ‘Samsung Charger’, ‘category’ => ‘Accessories’, ‘stock’ => ‘2’),
array(‘sl’ => 2, ‘name’ => ‘iPhone Case’, ‘category’ => ‘Accessories’, ‘stock’ => ‘3’),
);
return new WP_REST_Response(array(‘items’=>$items),200);
}
public function expired_items(){
$items = array(
array(‘code’ => ‘MED001’, ‘name’ => ‘Paracetamol’, ‘category’ => ‘Medicine’, ‘expire_at’ => ‘2024-03-15’),
array(‘code’ => ‘MED002’, ‘name’ => ‘Vitamin C’, ‘category’ => ‘Medicine’, ‘expire_at’ => ‘2024-04-20’),
);
return new WP_REST_Response(array(‘items’=>$items),200);
}
public function trending(){
$items = array(
array(‘name’ => ‘iPhone 14 Pro’, ‘today’ => 5, ‘week’ => 25, ‘month’ => 80, ‘year’ => 450, ‘percent’ => 25),
array(‘name’ => ‘Samsung S23’, ‘today’ => 3, ‘week’ => 18, ‘month’ => 65, ‘year’ => 320, ‘percent’ => 18),
array(‘name’ => ‘Xiaomi Redmi’, ‘today’ => 8, ‘week’ => 35, ‘month’ => 120, ‘year’ => 580, ‘percent’ => 32),
);
return new WP_REST_Response(array(‘items’=>$items),200);
}
public function bar_chart(){
return new WP_REST_Response(array(
‘months’=>array(‘Jan’,’Feb’,’Mar’,’Apr’,’May’,’Jun’,’Jul’,’Aug’,’Sep’,’Oct’,’Nov’,’Dec’),
‘purchase’=>array(1800,3200,2200,3600,2000,4200,3150,3600,2400,3120,3250,2850),
‘sales’=>array(1500,2900,2100,3300,2100,4000,3000,3500,2300,3000,3100,2800)
),200);
}
}
shop-management-pos/includes/Setup/Roles.php
<?php
namespace SMPOS\Setup;
class Roles {
public function register(){
$caps = array(
‘smpos_manage’,’smpos_sales’,’smpos_purchase’,’smpos_products’,’smpos_contacts’,
‘smpos_expense’,’smpos_reports’,’smpos_settings’
);
add_role(‘smpos_manager’,’Shop Manager’, array_fill_keys($caps,true));
add_role(‘smpos_cashier’,’Cashier’, array(
‘smpos_sales’=>true,’smpos_products’=>true,’read’=>true
));
add_role(‘smpos_stockist’,’Stockist’, array(‘smpos_products’=>true,’read’=>true));
add_role(‘smpos_accountant’,’Accountant’, array(
‘smpos_expense’=>true,’smpos_reports’=>true,’read’=>true
));
// Ensure admins have all
$admin = get_role(‘administrator’);
if($admin){ foreach($caps as $c){ $admin->add_cap($c); } }
}
}
shop-management-pos/App.php
<?php
namespace SMPOS;
use SMPOS\Setup\Menus;
use SMPOS\Setup\Rest;
use SMPOS\Setup\Assets;
use SMPOS\Setup\Roles;
class App {
public function boot(){
(new Roles())->register();
(new Assets())->hooks();
(new Menus())->hooks();
(new Rest())->hooks();
}
}
shop-management-pos/hooks-topnav.php
<?php
if (!defined(‘ABSPATH’)) exit;
/**
* Hook top nav into all SMPOS admin pages.
* This will print the shared TopNav just under the WP admin bar for all smpos-* pages.
*/
add_action(‘admin_head’, function(){
// শুধুমাত্র আমাদের প্লাগইনের অ্যাডমিন পেজে দেখান
$screen = get_current_screen();
if (!$screen || $screen->base !== ‘toplevel_page_smpos-dashboard’) {
// tolerate all smpos-* pages
$page = isset($_GET[‘page’]) ? sanitize_text_field($_GET[‘page’]) : ”;
if (strpos($page, ‘smpos-‘) !== 0) return;
}
// Safe include the shared nav
$file = plugin_dir_path(__FILE__) . ‘TopNav.php’;
if (file_exists($file)) {
// admin_head এ echo করলে <head> এর মধ্যে চলে যেত, তাই footer-এ কিউ করুন
add_action(‘in_admin_header’, function() use ($file){
include $file;
});
}
});
shop-management-pos/TopNav.php
<?php
if (!defined(‘ABSPATH’)) exit;
/**
* SMPOS Top Navigation (shared)
* Usage: include this file from any admin page view.
*/
$base = admin_url(‘admin.php’);
/* Main Navigation Structure – All main menus with submenus except SMS and Places */
$main_nav = array(
‘Dashboard’ => array(
‘href’ => $base.’?page=smpos-dashboard’,
‘key’ => ‘smpos-dashboard’
),
‘Suppliers’ => array(
‘href’ => $base.’?page=smpos-suppliers-main’,
‘key’ => ‘smpos-suppliers-main’,
‘submenu’ => array(
‘New Supplier’ => $base.’?page=smpos-suppliers-new’,
‘Suppliers List’ => $base.’?page=smpos-suppliers-list’,
‘Import Suppliers’ => $base.’?page=smpos-suppliers-import’
)
),
‘Purchase’ => array(
‘href’ => $base.’?page=smpos-purchase-main’,
‘key’ => ‘smpos-purchase-main’,
‘submenu’ => array(
‘New Purchase’ => $base.’?page=smpos-purchase-new’,
‘Purchase List’ => $base.’?page=smpos-purchase-list’,
‘New Purchase Return’ => $base.’?page=smpos-purchase-return’
)
),
‘Products’ => array(
‘href’ => $base.’?page=smpos-products-main’,
‘key’ => ‘smpos-products-main’,
‘submenu’ => array(
‘New Product’ => $base.’?page=smpos-products-new’,
‘Product List’ => $base.’?page=smpos-products-list’,
‘New Product Category’ => $base.’?page=smpos-products-category-new’,
‘Product Category List’ => $base.’?page=smpos-products-category-list’,
‘New Brand’ => $base.’?page=smpos-brands-new’,
‘Brand List’ => $base.’?page=smpos-brands-list’,
‘Print Labels’ => $base.’?page=smpos-print-labels’,
‘Import Products’ => $base.’?page=smpos-products-import’
)
),
‘Sales’ => array(
‘href’ => $base.’?page=smpos-sales-main’,
‘key’ => ‘smpos-sales-main’,
‘submenu’ => array(
‘POS’ => $base.’?page=smpos-pos’,
‘New Sales’ => $base.’?page=smpos-sales-new’,
‘Sales List’ => $base.’?page=smpos-sales-list’,
‘New Sales Return’ => $base.’?page=smpos-sales-return’
)
),
‘Customers’ => array(
‘href’ => $base.’?page=smpos-customers-main’,
‘key’ => ‘smpos-customers-main’,
‘submenu’ => array(
‘New Customer’ => $base.’?page=smpos-customers-new’,
‘Customers List’ => $base.’?page=smpos-customers-list’,
‘Import Customers’ => $base.’?page=smpos-customers-import’
)
),
‘Staff Salary’ => array(
‘href’ => $base.’?page=smpos-staff-salary-main’,
‘key’ => ‘smpos-staff-salary-main’,
‘submenu’ => array(
‘New Staff’ => $base.’?page=smpos-staff-new’,
‘Staffs List’ => $base.’?page=smpos-staff-list’
)
),
‘Expenses’ => array(
‘href’ => $base.’?page=smpos-expenses-main’,
‘key’ => ‘smpos-expenses-main’,
‘submenu’ => array(
‘New Expenses’ => $base.’?page=smpos-expenses-new’,
‘Expenses List’ => $base.’?page=smpos-expenses-list’,
‘New Category’ => $base.’?page=smpos-expenses-category-new’,
‘Categories List’ => $base.’?page=smpos-expenses-category-list’
)
),
‘Reports’ => array(
‘href’ => $base.’?page=smpos-reports-main’,
‘key’ => ‘smpos-reports-main’,
‘submenu’ => array(
‘Profit & Loss Reports’ => $base.’?page=smpos-reports-profit-loss’,
‘Purchase Report’ => $base.’?page=smpos-reports-purchase’,
‘Purchase Return Report’ => $base.’?page=smpos-reports-purchase-return’,
‘Purchase Payments Report’ => $base.’?page=smpos-reports-purchase-payments’,
‘Product Sales Report’ => $base.’?page=smpos-reports-product-sales’,
‘Product Purchase Report’ => $base.’?page=smpos-reports-product-purchase’,
‘Sales Report’ => $base.’?page=smpos-reports-sales’,
‘Sales Return Report’ => $base.’?page=smpos-reports-sales-return’,
‘Sales Payments Report’ => $base.’?page=smpos-reports-sales-payments’,
‘Stock Report’ => $base.’?page=smpos-reports-stock’,
‘Expense Report’ => $base.’?page=smpos-reports-expense’,
‘Expired Product Report’ => $base.’?page=smpos-reports-expired-products’
)
),
‘User’ => array(
‘href’ => $base.’?page=smpos-users-main’,
‘key’ => ‘smpos-users-main’,
‘submenu’ => array(
‘New User’ => $base.’?page=smpos-users-new’,
‘Users List’ => $base.’?page=smpos-users-list’,
‘Roles List’ => $base.’?page=smpos-roles-list’
)
)
// SMS and Places are intentionally excluded from top navigation as requested
);
?>
<style>
/* Topbar (responsive styling) */
.smpos-topnav{position:sticky;top:32px;z-index:200000;background:#1e4e7e;color:#fff;border-bottom:1px solid rgba(255,255,255,.15);font-size:14px;line-height:1}
@media (max-width:782px){.smpos-topnav{top:46px}}
.smpos-topnav *{box-sizing:border-box}
.smpos-topnav a{color:#fff;text-decoration:none}
.smpos-topnav-inner{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:6px 10px}
.smpos-topnav .brand{color:#fff;font-weight:800;padding:6px 10px;border-radius:6px;background:rgba(255,255,255,.08);display:inline-block}
.smpos-topnav .nav-toggle{display:none;background:transparent;border:0;color:#fff;font-size:18px;padding:6px 8px;border-radius:6px;cursor:pointer}
.smpos-topnav .nav-items{display:flex;align-items:center;gap:6px;flex-wrap:nowrap;overflow:auto;scrollbar-width:none}
.smpos-topnav .nav-items::-webkit-scrollbar{height:0}
.smpos-topnav .nav-items::-webkit-scrollbar-thumb{background:transparent}
.smpos-topnav .nav-items.open{display:flex}
.smpos-topnav .nav-link{font-weight:700;padding:6px 10px;border-radius:6px;white-space:nowrap;display:inline-block}
.smpos-topnav .nav-link:hover{background:rgba(255,255,255,.15)}
.smpos-topnav .nav-link.has-caret{padding-right:16px}
.smpos-topnav .nav-actions{display:flex;align-items:center;gap:8px}
.smpos-topnav .ic{font-size:14px;background:rgba(255,255,255,.08);padding:6px;border-radius:50%;display:inline-block}
/* Portal dropdown (responsive) */
.smpos-portal-menu{position:fixed;top:0;left:0;transform:translate(-9999px,-9999px);min-width:220px;background:#1c4a77;border:1px solid rgba(255,255,255,.22);box-shadow:0 10px 24px rgba(0,0,0,.25);border-radius:10px;padding:8px;display:none;z-index:300000;max-height:calc(100vh – 80px);overflow:auto}
.smpos-portal-menu.open{display:block;transform:none}
.smpos-portal-menu .dropdown-item{display:block;padding:9px 12px;border-radius:8px;white-space:nowrap;color:#fff;font-weight:600;text-decoration:none}
.smpos-portal-menu .dropdown-item:hover,.smpos-portal-menu .dropdown-item:focus{background:rgba(255,255,255,.18);text-decoration:none}
/* Active tab */
.smpos-topnav .nav-link.is-active{background:rgba(255,255,255,.22)}
/* Responsive */
@media (max-width:1024px){.smpos-topnav .nav-link{padding:6px 8px;font-size:13px}}
@media (max-width:768px){
.smpos-topnav-inner{flex-wrap:wrap}
.smpos-topnav .nav-toggle{display:inline-block}
.smpos-topnav .nav-items{width:100%;order:3;display:none;gap:6px;padding-top:6px;flex-wrap:wrap}
.smpos-topnav .nav-items.open{display:flex}
.smpos-topnav .nav-actions{order:2;margin-left:auto}
.smpos-topnav .brand{order:1}
}
@media (max-width:480px){
.smpos-topnav .nav-link,.smpos-portal-menu .dropdown-item{font-size:12px;padding:6px 8px}
.smpos-topnav .nav-items{gap:4px}
}
</style>
<div class=”smpos-topnav” role=”navigation” aria-label=”Shop POS”>
<div class=”smpos-topnav-inner”>
<a class=”brand” href=”<?php echo esc_url($base.’?page=smpos-dashboard’); ?>”>Shop POS</a>
<button class=”nav-toggle” aria-expanded=”false” aria-controls=”smpos-nav-items” type=”button”>☰</button>
<div id=”smpos-nav-items” class=”nav-items”>
<?php foreach($main_nav as $label => $item): ?>
<?php if(isset($item[‘submenu’])): ?>
<button class=”nav-link has-caret”
type=”button”
aria-haspopup=”true”
aria-expanded=”false”
data-key=”<?php echo esc_attr($item[‘key’]); ?>”
data-menu=”<?php echo esc_attr($item[‘key’]); ?>”>
<?php echo esc_html($label); ?> ▾
</button>
<?php else: ?>
<a class=”nav-link”
data-key=”<?php echo esc_attr($item[‘key’]); ?>”
href=”<?php echo esc_url($item[‘href’]); ?>”>
<?php echo esc_html($label); ?>
</a>
<?php endif; ?>
<?php endforeach; ?>
</div>
<div class=”nav-actions”>
<a class=”ic” href=”<?php echo esc_url(admin_url(‘admin.php?page=smpos-reports-main’)); ?>” title=”Search”>🔎</a>
<a class=”ic” href=”<?php echo esc_url(admin_url(‘admin.php?page=smpos-settings’)); ?>” title=”Settings”>⚙️</a>
<a class=”ic” href=”<?php echo esc_url(admin_url()); ?>” title=”WP Admin”>Ⓦ</a>
</div>
</div>
</div>
<!– Portal menu nodes for each dropdown –>
<?php foreach($main_nav as $label => $item): ?>
<?php if(isset($item[‘submenu’])): ?>
<div id=”smpos-menu-<?php echo esc_attr($item[‘key’]); ?>” class=”smpos-portal-menu” role=”menu” aria-label=”<?php echo esc_attr($label); ?> submenu”>
<?php foreach($item[‘submenu’] as $sub_label => $sub_href): ?>
<a class=”dropdown-item” role=”menuitem” href=”<?php echo esc_url($sub_href); ?>”>
<?php echo esc_html($sub_label); ?>
</a>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php endforeach; ?>
<script>
(function(){
const root = document.querySelector(‘.smpos-topnav’);
if(!root) return;
const btnToggle = root.querySelector(‘.nav-toggle’);
const items = root.querySelector(‘#smpos-nav-items’);
// Mobile toggle
if(btnToggle && items){
btnToggle.addEventListener(‘click’, function(){
const open = items.classList.toggle(‘open’);
btnToggle.setAttribute(‘aria-expanded’, open ? ‘true’ : ‘false’);
});
}
// Dropdown functionality
const dropdownButtons = root.querySelectorAll(‘.nav-link.has-caret’);
function closeAllMenus(){
document.querySelectorAll(‘.smpos-portal-menu’).forEach(menu => {
menu.classList.remove(‘open’);
});
dropdownButtons.forEach(btn => {
btn.setAttribute(‘aria-expanded’, ‘false’);
});
}
function placeMenu(button, menu){
const rect = button.getBoundingClientRect();
const margin = 10;
let left = Math.max(margin, Math.min(rect.left, window.innerWidth – menu.offsetWidth – margin));
let top = rect.bottom + 6;
// If bottom overflows, place above
if(top + menu.offsetHeight > window.innerHeight – margin){
top = Math.max(margin, rect.top – 6 – menu.offsetHeight);
}
menu.style.left = left + “px”;
menu.style.top = top + “px”;
}
dropdownButtons.forEach(button => {
const menuId = button.getAttribute(‘data-menu’);
const menu = document.getElementById(‘smpos-menu-‘ + menuId);
if(menu){
// Ensure portal node is in body
if(menu.parentElement !== document.body){
document.body.appendChild(menu);
}
button.addEventListener(‘click’, function(e){
e.stopPropagation();
const isOpen = menu.classList.contains(‘open’);
closeAllMenus();
if(!isOpen){
menu.classList.add(‘open’);
button.setAttribute(‘aria-expanded’, ‘true’);
placeMenu(button, menu);
}
});
}
});
// Close menus on outside click
document.addEventListener(‘click’, closeAllMenus);
// Close on ESC key
document.addEventListener(‘keydown’, function(e){
if(e.key === ‘Escape’) closeAllMenus();
});
// Reposition on resize/scroll
window.addEventListener(‘resize’, function(){
const openMenu = document.querySelector(‘.smpos-portal-menu.open’);
if(openMenu){
const menuId = openMenu.id.replace(‘smpos-menu-‘, ”);
const button = root.querySelector(`[data-menu=”${menuId}”]`);
if(button) placeMenu(button, openMenu);
}
});
window.addEventListener(‘scroll’, function(){
const openMenu = document.querySelector(‘.smpos-portal-menu.open’);
if(openMenu){
const menuId = openMenu.id.replace(‘smpos-menu-‘, ”);
const button = root.querySelector(`[data-menu=”${menuId}”]`);
if(button) placeMenu(button, openMenu);
}
}, true);
// Active tab highlight
const params = new URLSearchParams(window.location.search);
const currentPage = params.get(‘page’) || ”;
root.querySelectorAll(‘.nav-items a.nav-link, .nav-items button.nav-link’).forEach(element => {
const key = element.getAttribute(‘data-key’) || ”;
if(key && currentPage === key){
element.classList.add(‘is-active’);
}
});
})();
</script>
shop-management-pos/views/partials/top-nav.php
<?php if (!defined(‘ABSPATH’)) exit;
/* Top horizontal nav for Shop POS (adds UI only, no removal of WP sidebar) */
$base = admin_url(‘admin.php’);
$main = [
[‘label’=>’Dashboard’,’href’=> $base.’?page=smpos-dashboard’],
[‘label’=>’Customers’,’href’=> $base.’?page=smpos-customers’],
[‘label’=>’Sales’,’href’=> $base.’?page=smpos-sales’],
[‘label’=>’Purchase’,’href’=> $base.’?page=smpos-purchase’],
[‘label’=>’Products’,’href’=> $base.’?page=smpos-products’],
[‘label’=>’Reports’,’href’=> $base.’?page=smpos-reports’],
];
$suppliers = [
[‘label’=>’New Supplier’,’href’=> $base.’?page=smpos-suppliers-new’],
[‘label’=>’Suppliers List’,’href’=> $base.’?page=smpos-suppliers-list’],
[‘label’=>’Import Suppliers’,’href’=> $base.’?page=smpos-suppliers-import’],
];
?>
<div class=”smpos-topnav” role=”navigation” aria-label=”Shop POS top navigation”>
<div class=”smpos-topnav-inner”>
<a class=”brand” href=”<?php echo esc_url($base.’?page=smpos-dashboard’); ?>”>Shop POS</a>
<button class=”nav-toggle” aria-expanded=”false” aria-controls=”smpos-nav-items” type=”button”>☰</button>
<div id=”smpos-nav-items” class=”nav-items”>
<?php foreach($main as $m): ?>
<a class=”nav-link” href=”<?php echo esc_url($m[‘href’]); ?>”><?php echo esc_html($m[‘label’]); ?></a>
<?php endforeach; ?>
<div class=”nav-dropdown”>
<button class=”nav-link has-caret” aria-haspopup=”true” aria-expanded=”false” type=”button”>Suppliers ▾</button>
<div class=”dropdown-menu” role=”menu”>
<?php foreach($suppliers as $s): ?>
<a class=”dropdown-item” role=”menuitem” href=”<?php echo esc_url($s[‘href’]); ?>”><?php echo esc_html($s[‘label’]); ?></a>
<?php endforeach; ?>
</div>
</div>
</div>
<div class=”nav-actions”>
<a class=”ic” href=”<?php echo esc_url(admin_url(‘admin.php?page=smpos-reports’)); ?>” title=”Search”>🔎</a>
<a class=”ic” href=”<?php echo esc_url(admin_url(‘admin.php?page=smpos-settings’)); ?>” title=”Settings”>⚙️</a>
<a class=”ic” href=”<?php echo esc_url(admin_url()); ?>” title=”WP Admin”>Ⓦ</a>
</div>
</div>
</div>
<script>
(function(){
const root = document.querySelector(‘.smpos-topnav’);
if(!root) return;
const btn = root.querySelector(‘.nav-toggle’);
const items = root.querySelector(‘#smpos-nav-items’);
const dd = root.querySelector(‘.nav-dropdown’);
const ddBtn = dd ? dd.querySelector(‘.nav-link’) : null;
const menu = dd ? dd.querySelector(‘.dropdown-menu’) : null;
if(btn && items){
btn.addEventListener(‘click’, function(){
const open = items.classList.toggle(‘open’);
btn.setAttribute(‘aria-expanded’, open ? ‘true’ : ‘false’);
});
}
if(ddBtn && menu){
ddBtn.addEventListener(‘click’, function(e){
e.stopPropagation();
const open = menu.classList.toggle(‘open’);
ddBtn.setAttribute(‘aria-expanded’, open ? ‘true’ : ‘false’);
});
document.addEventListener(‘click’, function(){
menu.classList.remove(‘open’);
ddBtn.setAttribute(‘aria-expanded’, ‘false’);
});
}
})();
</script>
shop-management-pos/views/dashboard.php
<?php if(!defined(‘ABSPATH’)) exit; ?>
<!– Top Nav: inline CSS + HTML + JS –>
<style>
/* Container */
.smpos-dash-topnav{position:sticky;top:32px;z-index:200000;background:#1e4e7e;color:#fff;border-bottom:1px solid rgba(255,255,255,.15);font-size:14px;line-height:1}
@media (max-width:782px){.smpos-dash-topnav{top:46px}}
.smpos-dash-topnav *{box-sizing:border-box}
.smpos-dash-topnav a{color:#fff;text-decoration:none}
/* Layout */
.smpos-dash-topnav-inner{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:6px 10px}
.smpos-dash-topnav .brand{color:#fff;font-weight:800;padding:6px 10px;border-radius:6px;background:rgba(255,255,255,.08);display:inline-block}
.smpos-dash-topnav .nav-toggle{display:none;background:transparent;border:0;color:#fff;font-size:18px;padding:6px 8px;border-radius:6px;cursor:pointer}
/* Scrollable nav fixes */
.smpos-dash-topnav .nav-items{display:flex;align-items:center;gap:6px;flex-wrap:nowrap;overflow:auto;scrollbar-width:none}
.smpos-dash-topnav .nav-items::-webkit-scrollbar{height:0}
.smpos-dash-topnav .nav-items::-webkit-scrollbar-thumb{background:transparent}
.smpos-dash-topnav .nav-items.open{display:flex}
.smpos-dash-topnav .nav-link{font-weight:700;padding:6px 10px;border-radius:6px;white-space:nowrap;display:inline-block}
.smpos-dash-topnav .nav-link:hover{background:rgba(255,255,255,.15)}
.smpos-dash-topnav .nav-link.has-caret{padding-right:16px}
.smpos-dash-topnav .nav-actions{display:flex;align-items:center;gap:8px}
.smpos-dash-topnav .ic{font-size:14px;background:rgba(255,255,255,.08);padding:6px;border-radius:50%;display:inline-block}
/* Dropdown (portal style) */
.smpos-dash-portal-menu{
position:fixed; top:0; left:0; transform: translate(-9999px, -9999px);
min-width:220px; background:#1c4a77; border:1px solid rgba(255,255,255,.22);
box-shadow:0 10px 24px rgba(0,0,0,.25); border-radius:10px; padding:8px; display:none;
z-index:300000; max-height:calc(100vh – 80px); overflow:auto;
}
.smpos-dash-portal-menu.open{ display:block; transform:none; }
.smpos-dash-portal-menu .dropdown-item{display:block;padding:9px 12px;border-radius:8px;white-space:nowrap;color:#fff;font-weight:600}
.smpos-dash-portal-menu .dropdown-item:hover{background:rgba(255,255,255,.18)}
/* Active tab */
.smpos-dash-topnav .nav-link.is-active{background:rgba(255,255,255,.22)}
/* Responsive */
@media (max-width:1024px){.smpos-dash-topnav .nav-link{padding:6px 8px;font-size:13px}}
@media (max-width:768px){
.smpos-dash-topnav-inner{flex-wrap:wrap}
.smpos-dash-topnav .nav-toggle{display:inline-block}
.smpos-dash-topnav .nav-items{width:100%;order:3;display:none;gap:6px;padding-top:6px;flex-wrap:wrap}
.smpos-dash-topnav .nav-items.open{display:flex}
.smpos-dash-topnav .nav-actions{order:2;margin-left:auto}
.smpos-dash-topnav .brand{order:1}
}
@media (max-width:480px){.smpos-dash-topnav .nav-link,.smpos-dash-portal-menu .dropdown-item{font-size:12px;padding:6px 8px}}
</style>
<?php
$base = admin_url(‘admin.php’);
$main = [
[‘label’=>’Dashboard’,’href’=> $base.’?page=smpos-dashboard’,’key’=>’smpos-dashboard’],
[‘label’=>’Customers’,’href’=> $base.’?page=smpos-customers’,’key’=>’smpos-customers’],
[‘label’=>’Sales’,’href’=> $base.’?page=smpos-sales’,’key’=>’smpos-sales’],
[‘label’=>’Purchase’,’href’=> $base.’?page=smpos-purchase’,’key’=>’smpos-purchase’],
[‘label’=>’Products’,’href’=> $base.’?page=smpos-products’,’key’=>’smpos-products’],
[‘label’=>’Reports’,’href’=> $base.’?page=smpos-reports’,’key’=>’smpos-reports’],
];
$suppliers = [
[‘label’=>’New Supplier’,’href’=> $base.’?page=smpos-suppliers-new’],
[‘label’=>’Suppliers List’,’href’=> $base.’?page=smpos-suppliers-list’],
[‘label’=>’Import Suppliers’,’href’=> $base.’?page=smpos-suppliers-import’],
];
?>
<div class=”smpos-dash-topnav” role=”navigation” aria-label=”Shop POS”>
<div class=”smpos-dash-topnav-inner”>
<a class=”brand” href=”<?php echo esc_url($base.’?page=smpos-dashboard’); ?>”>Shop POS</a>
<button class=”nav-toggle” aria-expanded=”false” aria-controls=”smpos-nav-items” type=”button”>☰</button>
<div id=”smpos-nav-items” class=”nav-items”>
<?php foreach($main as $m): ?>
<a class=”nav-link” data-key=”<?php echo esc_attr($m[‘key’]); ?>” href=”<?php echo esc_url($m[‘href’]); ?>”><?php echo esc_html($m[‘label’]); ?></a>
<?php endforeach; ?>
<button id=”smpos-suppliers-btn” class=”nav-link has-caret” type=”button” aria-haspopup=”true” aria-expanded=”false”>Suppliers ▾</button>
</div>
<div class=”nav-actions”>
<a class=”ic” href=”<?php echo esc_url(admin_url(‘admin.php?page=smpos-reports’)); ?>” title=”Search”>🔎</a>
<a class=”ic” href=”<?php echo esc_url(admin_url(‘admin.php?page=smpos-settings’)); ?>” title=”Settings”>⚙️</a>
<a class=”ic” href=”<?php echo esc_url(admin_url()); ?>” title=”WP Admin”>Ⓦ</a>
</div>
</div>
</div>
<!– Portal menu node appended to body via JS –>
<div id=”smpos-suppliers-menu” class=”smpos-dash-portal-menu” role=”menu” aria-label=”Suppliers submenu”>
<?php foreach($suppliers as $s): ?>
<a class=”dropdown-item” role=”menuitem” href=”<?php echo esc_url($s[‘href’]); ?>”><?php echo esc_html($s[‘label’]); ?></a>
<?php endforeach; ?>
</div>
<script>
(function(){
const root=document.querySelector(‘.smpos-dash-topnav’); if(!root) return;
const btnToggle=root.querySelector(‘.nav-toggle’);
const items=root.querySelector(‘#smpos-nav-items’);
if(btnToggle&&items){
btnToggle.addEventListener(‘click’, function(){
const open=items.classList.toggle(‘open’);
btnToggle.setAttribute(‘aria-expanded’, open?’true’:’false’);
});
}
// Suppliers portal dropdown
const supBtn=document.getElementById(‘smpos-suppliers-btn’);
const menu=document.getElementById(‘smpos-suppliers-menu’);
function placeMenu(){
const r=supBtn.getBoundingClientRect();
const margin=10;
let left=Math.max(margin, Math.min(r.left, window.innerWidth – menu.offsetWidth – margin));
let top=r.bottom + 6;
const overflowBottom = top + menu.offsetHeight > window.innerHeight – margin;
if(overflowBottom){ top = Math.max(margin, r.top – 6 – menu.offsetHeight); }
menu.style.left = left + “px”;
menu.style.top = top + “px”;
}
function openMenu(){
if(!menu || !supBtn) return;
menu.classList.add(‘open’);
supBtn.setAttribute(‘aria-expanded’,’true’);
placeMenu();
}
function closeMenu(){
if(!menu || !supBtn) return;
menu.classList.remove(‘open’);
supBtn.setAttribute(‘aria-expanded’,’false’);
}
if(supBtn && menu){
if(menu.parentElement !== document.body){
document.body.appendChild(menu);
}
supBtn.addEventListener(‘click’, function(e){
e.stopPropagation();
if(menu.classList.contains(‘open’)) closeMenu(); else openMenu();
});
document.addEventListener(‘click’, closeMenu);
document.addEventListener(‘keydown’, function(e){ if(e.key===’Escape’) closeMenu(); });
window.addEventListener(‘resize’, function(){ if(menu.classList.contains(‘open’)) placeMenu(); });
window.addEventListener(‘scroll’, function(){ if(menu.classList.contains(‘open’)) placeMenu(); }, true);
}
// Active tab highlight
const params=new URLSearchParams(window.location.search);
const cur=params.get(‘page’)||”;
root.querySelectorAll(‘.nav-items a.nav-link’).forEach(a=>{
const key=a.getAttribute(‘data-key’)||”;
if(key && cur===key){ a.classList.add(‘is-active’); }
});
})();
</script>
<div class=”smpos-wrap”>
<h1 class=”smpos-title”>Shop Management POS</h1>
<div class=”smpos-cards”>
<div class=”card green card-center”>
<div class=”label”>Customers</div>
<div class=”value value-gap” id=”kpi-customers”>0</div>
<a class=”view-btn” href=”<?php echo admin_url(‘admin.php?page=smpos-customers’); ?>”>View ➜</a>
</div>
<div class=”card teal card-center”>
<div class=”label”>Sales Invoice</div>
<div class=”value value-gap” id=”kpi-sales-invoice”>0</div>
<a class=”view-btn” href=”<?php echo admin_url(‘admin.php?page=smpos-sales’); ?>”>View ➜</a>
</div>
<div class=”card purple card-center”>
<div class=”label”>Suppliers</div>
<div class=”value value-gap” id=”kpi-suppliers”>0</div>
<a class=”view-btn” href=”<?php echo admin_url(‘admin.php?page=smpos-suppliers-list’); ?>”>View ➜</a>
</div>
<div class=”card magenta card-center”>
<div class=”label”>Purchase Invoice</div>
<div class=”value value-gap” id=”kpi-purchase-invoice”>0</div>
<a class=”view-btn” href=”<?php echo admin_url(‘admin.php?page=smpos-purchase’); ?>”>View ➜</a>
</div>
<div class=”card orange card-center”>
<div class=”label”>Shop Age</div>
<div class=”value value-gap”><span id=”shop-age”>0</span></div>
<small class=”sub”>Live Countdown</small>
<small class=”dob”>Shop Date of Birth: <span id=”shop-dob”></span></small>
<a class=”view-btn” href=”<?php echo admin_url(‘admin.php?page=smpos-dashboard’); ?>”>View ➜</a>
</div>
</div>
<?php
$rows=[‘Purchase’,’Purchase Due’,’Purchase Refund’,’Sales’,’Sales Due’,’Sales Refund’,’Expense’,’Stock’,’Cash’,’Gross Profit’,’Net Profit’,’Net Loss’];
$slugs=array_map(function($l){ return strtolower(str_replace(‘ ‘,’_’,$l)); }, $rows);
?>
<div class=”smpos-grid-block”>
<div class=”cell head name”>Name</div>
<div class=”cell head col-total align-right”>Total (Tk)</div>
<div class=”cell head col-today align-right”>Today (Tk)</div>
<div class=”cell head col-week align-right”>This Week (Tk)</div>
<div class=”cell head col-month align-right”>This Month (Tk)</div>
<div class=”cell head col-year align-right”>This Year (Tk)</div>
<?php foreach($rows as $i=>$label): $slug=$slugs[$i]; ?>
<div class=”cell name”><?php echo esc_html($label); ?></div>
<div class=”cell col-total align-right”><span id=”t-<?php echo esc_attr($slug); ?>”>0</span></div>
<div class=”cell col-today align-right”><span id=”d-<?php echo esc_attr($slug); ?>”>0</span></div>
<div class=”cell col-week align-right”><span id=”w-<?php echo esc_attr($slug); ?>”>0</span></div>
<div class=”cell col-month align-right”><span id=”m-<?php echo esc_attr($slug); ?>”>0</span></div>
<div class=”cell col-year align-right”><span id=”y-<?php echo esc_attr($slug); ?>”>0</span></div>
<?php endforeach; ?>
</div>
<div class=”smpos-panels”>
<div class=”panel”>
<h2>Purchase & Sales Bar Chart</h2>
<canvas id=”ps-chart” class=”smpos-canvas”></canvas>
</div>
<div class=”panel”>
<h2>Recently Added Items</h2>
<table class=”smpos-table” id=”recent-items”>
<thead><tr><th>SL</th><th>Item</th><th>Sale Price</th></tr></thead>
<tbody></tbody>
</table>
<div class=”panel-foot”><a class=”panel-view” href=”<?php echo admin_url(‘admin.php?page=smpos-products’); ?>”>View All</a></div>
</div>
<div class=”panel”>
<h2>Expired Items</h2>
<table class=”smpos-table” id=”expired-items”>
<thead><tr><th>Code</th><th>Name</th><th>Category</th><th>Expire Date</th></tr></thead>
<tbody></tbody>
</table>
<div class=”panel-foot”><a class=”panel-view” href=”<?php echo admin_url(‘admin.php?page=smpos-products’); ?>”>View All</a></div>
</div>
<div class=”panel”>
<h2>Stock Alert</h2>
<table class=”smpos-table” id=”stock-alert”>
<thead><tr><th>SL</th><th>Item</th><th>Category</th><th>Stock</th></tr></thead>
<tbody></tbody>
</table>
<div class=”panel-foot”><a class=”panel-view” href=”<?php echo admin_url(‘admin.php?page=smpos-products’); ?>”>View All</a></div>
</div>
<div class=”panel trending”>
<h2>Top 100 Trending Items %</h2>
<div class=”trend-wrap”>
<div class=”trend-pie”><canvas id=”trend-pie” class=”smpos-canvas”></canvas></div>
<div class=”trend-table”>
<div class=”trend-row trend-head”>
<div class=”t sn”>S. N</div>
<div class=”t name”>Product Name</div>
<div class=”t today”>Today (Piece)</div>
<div class=”t week”>This Week (Piece)</div>
<div class=”t month”>This Month (Piece)</div>
<div class=”t year”>This Year (Piece)</div>
</div>
<div id=”trend-body”></div>
</div>
</div>
<div class=”panel-foot”><a id=”trend-toggle” class=”panel-view panel-view–alt” href=”javascript:void(0)”>▾ Show All</a></div>
</div>
</div>
</div>
shop-management-pos/views/suppliers.php
<?php if(!defined(‘ABSPATH’)) exit; ?>
<div class=”wrap”>
<h1>Suppliers</h1>
<p>Next step: New Supplier, Supplier List, Import—exactly per your design.</p>
</div>
shop-management-pos/views/suppliers-import.php
<?php if (!defined(‘ABSPATH’)) exit; ?>
<div class=”smpos-wrap”>
<div class=”smpos-page-head”>
<h1>Import Suppliers <small>Add/Update Suppliers</small></h1>
<div class=”head-actions”>
<a class=”btn” href=”<?php echo admin_url(‘admin.php?page=smpos-suppliers-list’); ?>”>← Back to List</a>
</div>
</div>
<div class=”smpos-card”>
<form id=”sup-import-form” enctype=”multipart/form-data”>
<div class=”import-row”>
<label>Import Suppliers* <input type=”file” name=”file” accept=”.csv” required></label>
<span class=”note”>Note: File must be in CSV format.</span>
</div>
<div class=”form-actions”>
<button type=”submit” class=”btn btn-primary”>Import</button>
<a href=”<?php echo admin_url(‘admin.php?page=smpos-suppliers-list’); ?>” class=”btn”>Close</a>
<a href=”<?php echo admin_url(‘admin-ajax.php?action=smpos_suppliers_example’); ?>” class=”btn”>Download Example Format</a>
</div>
</form>
</div>
<div class=”smpos-card”>
<h3>Import Instructions</h3>
<div class=”table-wrap”>
<table class=”smpos-table”>
<thead><tr><th style=”width:60px;”>#</th><th>Column Name</th><th>Value</th></tr></thead>
<tbody>
<?php
$cols = [
[‘Supplier Name’,’Required’],
[‘Mobile’,’Optional’],
[‘Email’,’Optional’],
[‘Phone’,’Optional’],
[‘GST Number’,’Optional’],
[‘TAX Number’,’Optional’],
[‘Country Name’,’Optional’],
[‘State Name’,’Optional’],
[‘City’,’Optional’],
[‘Postcode’,’Optional’],
[‘Address’,’Optional’],
[‘Opening Balance’,’Optional’],
];
foreach($cols as $i=>$c){
echo ‘<tr><td>’.($i+1).'</td><td>’.$c[0].'</td><td><span class=”‘.($c[1]===’Required’?’badge-success’:’badge-soft’).'”>’.$c[1].'</span></td></tr>’;
}
?>
</tbody>
</table>
</div>
</div>
</div>
shop-management-pos/views/suppliers-list.php
<?php if (!defined(‘ABSPATH’)) exit; ?>
<div class=”smpos-wrap”>
<div class=”smpos-page-head”>
<h1>Suppliers List</h1>
<div class=”head-actions”>
<a class=”btn btn-primary” href=”<?php echo admin_url(‘admin.php?page=smpos-suppliers-new’); ?>”>+ New Supplier</a>
</div>
</div>
<div class=”smpos-card”>
<div class=”toolbar”>
<div class=”left”>
<label>Show
<select id=”sup-show-length”>
<option>10</option><option>25</option><option>50</option><option>100</option>
</select> entries
</label>
</div>
<div class=”right”>
<input type=”search” id=”sup-search” placeholder=”Search…”>
<a class=”btn” href=”#” id=”sup-copy”>Copy</a>
<a class=”btn” href=”#” id=”sup-excel”>Excel</a>
<a class=”btn” href=”#” id=”sup-pdf”>PDF</a>
<a class=”btn” href=”#” id=”sup-print”>Print</a>
<a class=”btn” href=”#” id=”sup-csv”>CSV</a>
</div>
</div>
<div class=”table-wrap”>
<table class=”smpos-table” id=”suppliers-table”>
<thead>
<tr>
<th style=”width:40px;”><input type=”checkbox” id=”sup-select-all”></th>
<th>Supplier ID</th>
<th>Supplier Name</th>
<th>Mobile</th>
<th>Email</th>
<th class=”numeric”>Purchase Due</th>
<th class=”numeric”>Purchase Return Due</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody id=”suppliers-tbody”>
<!– JS fills rows –>
</tbody>
<tfoot>
<tr>
<th colspan=”5″ class=”align-right”>Total</th>
<th class=”numeric” id=”foot-purchase-due”>0.0</th>
<th class=”numeric” id=”foot-return-due”>0.0</th>
<th colspan=”2″></th>
</tr>
</tfoot>
</table>
</div>
<div class=”table-foot”>
<div class=”info” id=”sup-page-info”>Showing 0 to 0 of 0 entries</div>
<div class=”pager”>
<button class=”btn” id=”sup-prev”>Previous</button>
<span id=”sup-page”>1</span>
<button class=”btn” id=”sup-next”>Next</button>
</div>
</div>
</div>
</div>
shop-management-pos/views/suppliers-new.php
<?php if (!defined(‘ABSPATH’)) exit; ?>
<div class=”smpos-wrap”>
<div class=”smpos-page-head”>
<h1>Suppliers <small>Add/Update Suppliers</small></h1>
<div class=”head-actions”>
<a class=”btn” href=”<?php echo admin_url(‘admin.php?page=smpos-suppliers-list’); ?>”>← Back to List</a>
</div>
</div>
<div class=”smpos-card”>
<form id=”supplier-form”>
<div class=”grid two-col”>
<div class=”col”>
<label>Supplier Name<span class=”req”>*</span>
<input type=”text” name=”name” required>
</label>
<label>Mobile
<input type=”text” name=”mobile”>
</label>
<label>Email
<input type=”email” name=”email”>
</label>
<label>Phone
<input type=”text” name=”phone”>
</label>
<label>GST Number
<input type=”text” name=”gst”>
</label>
<label>TAX Number
<input type=”text” name=”tax”>
</label>
</div>
<div class=”col”>
<label>Opening Balance
<input type=”number” name=”opening_balance” step=”0.01″>
</label>
<label>Country
<select name=”country”>
<option value=”Bangladesh” selected>Bangladesh</option>
<option value=”India”>India</option>
<option value=”Nepal”>Nepal</option>
<option value=”Bhutan”>Bhutan</option>
</select>
</label>
<label>State
<select name=”state”>
<option value=””>-Select-</option>
</select>
</label>
<label>City
<input type=”text” name=”city”>
</label>
<label>Postcode
<input type=”text” name=”postcode”>
</label>
<label>Address
<textarea name=”address” rows=”3″></textarea>
</label>
</div>
</div>
<div class=”form-actions”>
<button type=”submit” class=”btn btn-primary”>Save</button>
<a href=”<?php echo admin_url(‘admin.php?page=smpos-suppliers-list’); ?>” class=”btn”>Close</a>
</div>
</form>
</div>
<div class=”smpos-card”>
<h3>Opening Balance Payments</h3>
<div class=”table-wrap”>
<table class=”smpos-table”>
<thead>
<tr>
<th style=”width:60px;”>#</th>
<th>Payment Date</th>
<th>Payment</th>
<th>Payment Type</th>
<th>Payment Note</th>
<th>Action</th>
</tr>
</thead>
<tbody><tr><td colspan=”6″ class=”muted”>No Previous Stock Entry Found!!</td></tr></tbody>
</table>
</div>
</div>
</div>
shop-management-pos/shop-management-pos.php
<?php
/**
* Plugin Name: Shop Management POS
* Description: WordPress POS & Inventory with headless-ready REST API.
* Version: 5.0
* Author: DoorHousting
* Text Domain: smpos
*/
if (!defined(‘ABSPATH’)) exit;
define(‘SMPOS_VER’, ‘5.0’);
define(‘SMPOS_PATH’, plugin_dir_path(__FILE__));
define(‘SMPOS_URL’, plugin_dir_url(__FILE__));
/**
* Core autoloader
*/
require_once SMPOS_PATH . ‘includes/core/autoload.php’;
/**
* Activation/Deactivation
*/
register_activation_hook(__FILE__, [‘SMPOS\Setup\Activator’, ‘activate’]);
register_deactivation_hook(__FILE__, [‘SMPOS\Setup\Deactivator’, ‘deactivate’]);
/**
* Bootstrap
*/
add_action(‘plugins_loaded’, function () {
// Load textdomain
load_plugin_textdomain(‘smpos’, false, dirname(plugin_basename(__FILE__)) . ‘/languages’);
// Initialize the application
if (class_exists(‘SMPOS\App’)) {
(new SMPOS\App())->boot();
} else {
// Fallback: Directly include required files if autoload fails
$required_files = [
‘includes/Setup/Roles.php’,
‘includes/Setup/Assets.php’,
‘includes/Setup/Menus.php’,
‘includes/Setup/Rest.php’
];
foreach ($required_files as $file) {
$file_path = SMPOS_PATH . $file;
if (file_exists($file_path)) {
require_once $file_path;
}
}
// Manually initialize if autoload failed
(new SMPOS\Setup\Roles())->register();
(new SMPOS\Setup\Assets())->hooks();
(new SMPOS\Setup\Menus())->hooks();
(new SMPOS\Setup\Rest())->hooks();
}
});
/**
* Add settings link to plugin page
*/
add_filter(‘plugin_action_links_’ . plugin_basename(__FILE__), function($links) {
$settings_link = ‘<a href=”‘ . admin_url(‘admin.php?page=smpos-dashboard’) . ‘”>Dashboard</a>’;
array_unshift($links, $settings_link);
return $links;
});