Skip to content

Commit 30b49e6

Browse files
alfredangclaude
andcommitted
Phase 1: Learner My Classes dashboard + role-specific sidebars
Learner dashboard: - KPI cards (current/upcoming/past class counts) - Course cards in 3-column grid with image, title, code, dates, status - Filter tabs (All/Current/Upcoming/Past) + search - Data from sales orders linked to logged-in user's email Sidebars: - Learner: My Classes, My Profile, Billing History - Trainer: My Classes, Ed Tools - Admin/Marketing/Training Provider: standard Magento sidebar Role routing: - Sidebar reads active role from server session (not localStorage) - Dashboard panels routed: Learner/Trainer → My Classes, Admin → charts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 28ba97c commit 30b49e6

3 files changed

Lines changed: 218 additions & 63 deletions

File tree

app/design/adminhtml/default/default/template/dashboard/index.phtml

Lines changed: 202 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,83 @@ foreach ($coc as $row) {
273273
$code = $row->getData('country_code');
274274
$ordersByCountry[] = array('country' => isset($cnames[$code]) ? $cnames[$code] : $code, 'count' => (int)$row->getData('order_count'));
275275
}
276+
277+
// =====================================================================
278+
// DATA — Learner panel (My Classes)
279+
// =====================================================================
280+
$_learnerCourses = array();
281+
$_learnerCurrent = 0; $_learnerUpcoming = 0; $_learnerPast = 0;
282+
$_loggedInEmail = '';
283+
try {
284+
$_loggedInUser = Mage::getSingleton('admin/session')->getUser();
285+
$_loggedInEmail = $_loggedInUser ? strtolower($_loggedInUser->getEmail()) : '';
286+
} catch (Exception $e) {}
287+
288+
if ($_loggedInEmail) {
289+
$_now = date('Y-m-d');
290+
$_resource = Mage::getSingleton('core/resource');
291+
$_read = $_resource->getConnection('core_read');
292+
$_orderTable = $_resource->getTableName('sales/order');
293+
$_itemTable = $_resource->getTableName('sales/order_item');
294+
$_prodTable = $_resource->getTableName('catalog/product');
295+
$_eav_dt = $_resource->getTableName('catalog_product_entity_datetime');
296+
$_eav_vc = $_resource->getTableName('catalog_product_entity_varchar');
297+
298+
// Get attribute IDs
299+
$_nameAttrId = (int) $_read->fetchOne("SELECT attribute_id FROM eav_attribute WHERE attribute_code='name' AND entity_type_id=4");
300+
$_startAttrId = (int) $_read->fetchOne("SELECT attribute_id FROM eav_attribute WHERE attribute_code='news_from_date' AND entity_type_id=4");
301+
$_endAttrId = (int) $_read->fetchOne("SELECT attribute_id FROM eav_attribute WHERE attribute_code='news_to_date' AND entity_type_id=4");
302+
303+
$_sql = "SELECT DISTINCT oi.product_id, oi.sku, oi.name as item_name,
304+
o.state as order_state,
305+
pn.value as product_name,
306+
sd.value as start_date,
307+
ed.value as end_date
308+
FROM {$_orderTable} o
309+
JOIN {$_itemTable} oi ON o.entity_id = oi.order_id
310+
LEFT JOIN {$_eav_vc} pn ON oi.product_id = pn.entity_id AND pn.attribute_id = {$_nameAttrId} AND pn.store_id = 0
311+
LEFT JOIN {$_eav_dt} sd ON oi.product_id = sd.entity_id AND sd.attribute_id = {$_startAttrId} AND sd.store_id = 0
312+
LEFT JOIN {$_eav_dt} ed ON oi.product_id = ed.entity_id AND ed.attribute_id = {$_endAttrId} AND ed.store_id = 0
313+
WHERE LOWER(o.customer_email) = ?
314+
AND o.state NOT IN ('canceled','closed')
315+
ORDER BY sd.value DESC";
316+
317+
$_rows = $_read->fetchAll($_sql, array($_loggedInEmail));
318+
foreach ($_rows as $_r) {
319+
$_start = $_r['start_date'] ? date('Y-m-d', strtotime($_r['start_date'])) : null;
320+
$_end = $_r['end_date'] ? date('Y-m-d', strtotime($_r['end_date'])) : null;
321+
322+
$_status = 'Confirmed';
323+
if (in_array($_r['order_state'], array('pending','holded','new'))) $_status = 'Pending';
324+
325+
$_timeStatus = 'past';
326+
if ($_start && $_end) {
327+
if ($_now >= $_start && $_now <= $_end) { $_timeStatus = 'current'; $_learnerCurrent++; }
328+
elseif ($_now < $_start) { $_timeStatus = 'upcoming'; $_learnerUpcoming++; }
329+
else { $_learnerPast++; }
330+
} else { $_learnerPast++; }
331+
332+
// Get product image
333+
$_imgUrl = '';
334+
try {
335+
$_prod = Mage::getModel('catalog/product')->load($_r['product_id']);
336+
if ($_prod->getImage() && $_prod->getImage() !== 'no_selection') {
337+
$_imgUrl = (string) Mage::helper('catalog/image')->init($_prod, 'image')->resize(400, 250);
338+
}
339+
} catch (Exception $e) {}
340+
341+
$_learnerCourses[] = array(
342+
'name' => $_r['product_name'] ?: $_r['item_name'],
343+
'sku' => $_r['sku'],
344+
'start_date' => $_start ? date('j M Y', strtotime($_start)) : '',
345+
'end_date' => $_end ? date('j M Y', strtotime($_end)) : '',
346+
'status' => $_status,
347+
'time_status' => $_timeStatus,
348+
'image' => $_imgUrl,
349+
'order_state' => $_r['order_state'],
350+
);
351+
}
352+
}
276353
?>
277354

278355
<style>
@@ -615,42 +692,143 @@ foreach ($coc as $row) {
615692
</div>
616693
</div>
617694

618-
<?php /* ===== PANEL 4: Learner / Trainer ===== */ ?>
695+
<?php /* ===== PANEL 4: Learner My Classes ===== */ ?>
619696
<div id="dash-panel-learner" style="display:none;">
620-
<div class="lms-kpi-cards">
621-
<div class="lms-kpi-card lms-current"><div class="lms-kpi-value">0</div><div class="lms-kpi-label">Current Classes</div></div>
622-
<div class="lms-kpi-card lms-upcoming"><div class="lms-kpi-value">0</div><div class="lms-kpi-label">Upcoming Classes</div></div>
623-
<div class="lms-kpi-card lms-past"><div class="lms-kpi-value">0</div><div class="lms-kpi-label">Past Classes</div></div>
624-
</div>
625-
<div class="lms-tabs">
626-
<button class="lms-tab active">All Classes</button>
627-
<button class="lms-tab">Current Classes</button>
628-
<button class="lms-tab">Upcoming Classes</button>
629-
<button class="lms-tab">Past Classes</button>
630-
</div>
631-
<div class="lms-search">
632-
<svg class="lms-search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
633-
<input type="text" class="lms-search-input" placeholder="Search by course code, title, or run ID..." />
634-
</div>
635-
<div class="lms-empty">
636-
<div class="lms-empty-icon"><svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg></div>
637-
<div class="lms-empty-title">No enrolled courses found</div>
638-
<div class="lms-empty-sub">You haven't enrolled in any courses yet</div>
697+
<style>
698+
.lmc-wrap{padding:0}
699+
.lmc-title{font-size:24px;font-weight:700;color:var(--t1);margin:0 0 20px}
700+
.lmc-kpi{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-bottom:24px}
701+
.lmc-kpi-card{background:#131d2e;border:1px solid var(--b1);border-radius:12px;padding:24px;text-align:center}
702+
.lmc-kpi-num{font-size:36px;font-weight:700;line-height:1.1;margin-bottom:4px}
703+
.lmc-kpi-num.green{color:#22c55e}.lmc-kpi-num.blue{color:#3b82f6}.lmc-kpi-num.gray{color:#94a3b8}
704+
.lmc-kpi-lbl{color:var(--t4);font-size:13px}
705+
.lmc-tabs{display:flex;gap:8px;margin-bottom:16px}
706+
.lmc-tab{padding:8px 18px;border-radius:20px;border:none;font-size:13px;font-weight:500;cursor:pointer;background:#1e293b;color:#94a3b8}
707+
.lmc-tab.active{background:#3b82f6;color:#fff}
708+
.lmc-search{position:relative;margin-bottom:24px}
709+
.lmc-search input{width:100%;padding:10px 16px 10px 40px;background:#1e293b;border:1px solid #334155;border-radius:10px;color:#f1f5f9;font-size:14px;outline:none;box-sizing:border-box}
710+
.lmc-search input:focus{border-color:#60a5fa}
711+
.lmc-search svg{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:#64748b}
712+
.lmc-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:20px}
713+
.lmc-card{background:#131d2e;border:1px solid #1e293b;border-radius:14px;overflow:hidden;transition:transform .15s,box-shadow .15s}
714+
.lmc-card:hover{transform:translateY(-2px);box-shadow:0 8px 24px rgba(0,0,0,.3)}
715+
.lmc-card-img{width:100%;aspect-ratio:16/9;object-fit:cover;background:#0f172a;display:block}
716+
.lmc-card-img-placeholder{width:100%;aspect-ratio:16/9;background:linear-gradient(135deg,#1e3a5f,#0f172a);display:flex;align-items:center;justify-content:center;color:#334155}
717+
.lmc-card-body{padding:16px}
718+
.lmc-card-title{font-size:15px;font-weight:600;color:#f1f5f9;margin:0 0 10px;line-height:1.4;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
719+
.lmc-card-row{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px}
720+
.lmc-card-label{color:#64748b;font-size:12px}
721+
.lmc-card-value{color:#cbd5e1;font-size:12px;font-weight:500}
722+
.lmc-badge{padding:2px 10px;border-radius:12px;font-size:11px;font-weight:600}
723+
.lmc-badge-wsq{background:rgba(59,130,246,.15);color:#60a5fa;border:1px solid rgba(59,130,246,.3)}
724+
.lmc-badge-confirmed{background:rgba(34,197,94,.12);color:#22c55e;border:1px solid rgba(34,197,94,.3)}
725+
.lmc-badge-pending{background:rgba(245,158,11,.12);color:#f59e0b;border:1px solid rgba(245,158,11,.3)}
726+
.lmc-badge-cancelled{background:rgba(239,68,68,.12);color:#ef4444;border:1px solid rgba(239,68,68,.3)}
727+
.lmc-card-footer{padding:12px 16px;border-top:1px solid #1e293b;display:flex;justify-content:space-between;align-items:center}
728+
.lmc-card-footer a{color:#06b6d4;font-size:13px;font-weight:600;text-decoration:none}
729+
.lmc-card-footer a:hover{color:#22d3ee}
730+
.lmc-empty{text-align:center;padding:60px 20px;color:#64748b}
731+
.lmc-empty svg{margin-bottom:12px;opacity:.5}
732+
@media(max-width:1024px){.lmc-grid{grid-template-columns:repeat(2,1fr)}}
733+
@media(max-width:640px){.lmc-grid{grid-template-columns:1fr}}
734+
</style>
735+
<div class="lmc-wrap">
736+
<h2 class="lmc-title">My Classes</h2>
737+
<div class="lmc-kpi">
738+
<div class="lmc-kpi-card"><div class="lmc-kpi-num green"><?php echo $_learnerCurrent ?></div><div class="lmc-kpi-lbl">Current Classes</div></div>
739+
<div class="lmc-kpi-card"><div class="lmc-kpi-num blue"><?php echo $_learnerUpcoming ?></div><div class="lmc-kpi-lbl">Upcoming Classes</div></div>
740+
<div class="lmc-kpi-card"><div class="lmc-kpi-num gray"><?php echo $_learnerPast ?></div><div class="lmc-kpi-lbl">Past Classes</div></div>
741+
</div>
742+
<div class="lmc-tabs">
743+
<button class="lmc-tab active" onclick="lmcFilter('all',this)">All Classes</button>
744+
<button class="lmc-tab" onclick="lmcFilter('current',this)">Current Classes</button>
745+
<button class="lmc-tab" onclick="lmcFilter('upcoming',this)">Upcoming Classes</button>
746+
<button class="lmc-tab" onclick="lmcFilter('past',this)">Past Classes</button>
747+
</div>
748+
<div class="lmc-search">
749+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
750+
<input type="text" id="lmc-search" placeholder="Search by course code, title, or run ID..." oninput="lmcSearch(this.value)" />
751+
</div>
752+
753+
<?php if (empty($_learnerCourses)): ?>
754+
<div class="lmc-empty">
755+
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>
756+
<div style="font-size:16px;font-weight:600;color:#94a3b8;margin-bottom:4px;">No enrolled courses found</div>
757+
<div>You haven't enrolled in any courses yet</div>
758+
</div>
759+
<?php else: ?>
760+
<div class="lmc-grid" id="lmc-grid">
761+
<?php foreach ($_learnerCourses as $_c): ?>
762+
<div class="lmc-card" data-time="<?php echo $_c['time_status'] ?>" data-search="<?php echo strtolower($_c['name'] . ' ' . $_c['sku']) ?>">
763+
<?php if ($_c['image']): ?>
764+
<img class="lmc-card-img" src="<?php echo $_c['image'] ?>" alt="" />
765+
<?php else: ?>
766+
<div class="lmc-card-img-placeholder">
767+
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>
768+
</div>
769+
<?php endif; ?>
770+
<div class="lmc-card-body">
771+
<div class="lmc-card-title"><?php echo htmlspecialchars($_c['name']) ?></div>
772+
<div class="lmc-card-row">
773+
<span class="lmc-card-label">Course Code</span>
774+
<span class="lmc-card-value"><?php echo htmlspecialchars($_c['sku']) ?></span>
775+
</div>
776+
<div class="lmc-card-row">
777+
<span class="lmc-card-label">Course Type</span>
778+
<span class="lmc-badge lmc-badge-wsq">WSQ</span>
779+
</div>
780+
<div class="lmc-card-row">
781+
<span class="lmc-card-label">Class Status</span>
782+
<span class="lmc-badge lmc-badge-<?php echo strtolower($_c['status']) ?>"><?php echo $_c['status'] ?></span>
783+
</div>
784+
<div class="lmc-card-row">
785+
<span class="lmc-card-label">Start Date</span>
786+
<span class="lmc-card-value"><?php echo $_c['start_date'] ?></span>
787+
</div>
788+
<div class="lmc-card-row">
789+
<span class="lmc-card-label">End Date</span>
790+
<span class="lmc-card-value"><?php echo $_c['end_date'] ?></span>
791+
</div>
792+
</div>
793+
<div class="lmc-card-footer">
794+
<a href="#">View Course</a>
795+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#06b6d4" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="m10 8 6 4-6 4z" fill="#06b6d4"/></svg>
796+
</div>
797+
</div>
798+
<?php endforeach; ?>
799+
</div>
800+
<?php endif; ?>
639801
</div>
802+
<script type="text/javascript">
803+
function lmcFilter(type, btn) {
804+
var tabs = document.querySelectorAll('.lmc-tab');
805+
for (var i = 0; i < tabs.length; i++) tabs[i].classList.remove('active');
806+
btn.classList.add('active');
807+
var cards = document.querySelectorAll('.lmc-card');
808+
for (var i = 0; i < cards.length; i++) {
809+
cards[i].style.display = (type === 'all' || cards[i].getAttribute('data-time') === type) ? '' : 'none';
810+
}
811+
}
812+
function lmcSearch(q) {
813+
q = q.toLowerCase();
814+
var cards = document.querySelectorAll('.lmc-card');
815+
for (var i = 0; i < cards.length; i++) {
816+
cards[i].style.display = (!q || cards[i].getAttribute('data-search').indexOf(q) >= 0) ? '' : 'none';
817+
}
818+
}
819+
</script>
640820
</div>
641821

642822
<?php /* ===== Role switcher JS ===== */ ?>
643823
<script type="text/javascript">
644824
(function(){
645825
function showPanel(role) {
646-
// Temporarily show admin dashboard (charts) for all roles
647-
// TODO: restore per-role dashboards once role UIs are configured
648826
var panels = {
649827
'Admin': 'dash-panel-superadmin',
650828
'Marketing': 'dash-panel-superadmin',
651829
'Training Provider': 'dash-panel-superadmin',
652-
'Learner': 'dash-panel-superadmin',
653-
'Trainer': 'dash-panel-superadmin'
830+
'Learner': 'dash-panel-learner',
831+
'Trainer': 'dash-panel-learner'
654832
};
655833
var all = ['dash-panel-admin','dash-panel-superadmin','dash-panel-trainingprovider','dash-panel-learner'];
656834
for (var i=0; i<all.length; i++) {

0 commit comments

Comments
 (0)