Commit 17cb7f81 by Ooh-Ao

dashboard

parent abb883fb
<app-page-header
[title]="'Dashboard Appraisal'"
activeitem="Home">
<app-page-header [title]="'Dashboard Appraisal'" activeitem="Home">
</app-page-header>
<div class="container mx-auto px-4 py-8 space-y-8">
<div class="dashboard">
<!-- ░░ Row #1 – KPI Cards ░░ -->
<div class="grid gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5">
<ng-container *ngFor="let card of kpiCards; trackBy: trackByLabel">
<div
class="bg-white rounded-2xl shadow transition
motion-safe:hover:-translate-y-1 hover:shadow-xl p-6
flex items-center gap-4">
<!-- icon -->
<div class="w-14 h-14 flex items-center justify-center rounded-full" [ngClass]="card.color">
<i class="text-sky-700 text-xl" [ngClass]="card.icon"></i>
<!-- ░ Row 1 – KPI ░ -->
<div class="kpi-grid">
<ng-container *ngFor="let card of kpiCards">
<div class="kpi-card">
<div class="kpi-icon" [ngStyle]="{'background': card.bg}">
<i [ngClass]="card.icon"></i>
</div>
<!-- value -->
<div class="flex-1 text-right">
<p class="text-sm text-gray-500 whitespace-nowrap">{{ card.label }}</p>
<p class="text-2xl font-bold text-gray-800">{{ card.value | number }}</p>
<span class="text-xs font-semibold"
[ngClass]="card.change >= 0 ? 'text-green-600' : 'text-red-600'">
<div class="kpi-text">
<p class="kpi-label">{{ card.label }}</p>
<p class="kpi-number">{{ card.value | number }}</p>
<span class="kpi-rate" [ngClass]="card.change >= 0 ? 'text-up' : 'text-down'">
{{ card.change | number:'1.0-2' }}%
</span>
</div>
......@@ -29,21 +21,15 @@
</ng-container>
</div>
<!-- ░░ Row #2 – Bar per Department ░░ -->
<div
class="bg-white rounded-2xl shadow transition
motion-safe:hover:-translate-y-1 hover:shadow-xl p-6">
<h2 class="font-semibold text-slate-600 mb-4">
{{ 'dashboard.deptChart' }}
</h2>
<div echarts class="h-[350px] w-full" [options]="deptScoreOption" [autoResize]="true"></div>
<!-- ░ Row 2 – Bar Chart ░ -->
<div class="glass-card">
<h2 class="section-head">การประเมินตามแผนก</h2>
<div echarts class="chart-xl" [options]="deptScoreOption"></div>
</div>
<!-- ░░ Row #3 – Mini-Charts (3 cols on md+) ░░ -->
<div class="grid gap-6 md:grid-cols-3">
<!-- Donut -->
<div
class="bg-white rounded-2xl shadow transition
<!-- ░ Row 3 – Mini Charts ░ -->
<div class="mini-grid">
<div class="glass-card bg-white rounded-2xl shadow transition
motion-safe:hover:-translate-y-1 hover:shadow-xl p-6 flex flex-col items-center">
<div echarts class="h-[220px] w-full" [options]="summaryDonutOption" [autoResize]="true"></div>
<p class="mt-3 text-sm text-gray-500">
......@@ -51,8 +37,7 @@
</p>
</div>
<!-- Year compare -->
<div
class="bg-white rounded-2xl shadow transition
<div class=" glass-card bg-white rounded-2xl shadow transition
motion-safe:hover:-translate-y-1 hover:shadow-xl p-6">
<h3 class="text-sm font-medium text-gray-600 mb-2">
{{ 'dashboard.yearCompare' }}
......@@ -60,8 +45,7 @@
<div echarts class="h-[220px] w-full" [options]="yearlyCompareOption" [autoResize]="true"></div>
</div>
<!-- Level distribution -->
<div
class="bg-white rounded-2xl shadow transition
<div class="glass-card bg-white rounded-2xl shadow transition
motion-safe:hover:-translate-y-1 hover:shadow-xl p-6">
<h3 class="text-sm font-medium text-gray-600 mb-2">
{{ 'dashboard.levelDist' }}
......@@ -70,11 +54,9 @@
</div>
</div>
<!-- ░░ Row #4 – Tables (stack → 2-cols on lg) ░░ -->
<div class="grid gap-6 lg:grid-cols-2">
<!-- pending table -->
<div
class="bg-white rounded-2xl shadow overflow-hidden transition
<!-- ░ Row 4 – Tables ░ -->
<div class="tbl-grid">
<div class="glass-card bg-white rounded-2xl shadow overflow-hidden transition
motion-safe:hover:-translate-y-1 hover:shadow-xl">
<h2 class="px-6 py-3 bg-slate-50 border-b font-semibold text-slate-600">
{{ 'dashboard.todoList' }}
......@@ -90,8 +72,7 @@
</tr>
</thead>
<tbody>
<tr *ngFor="let row of pendingList"
class="odd:bg-white even:bg-slate-50 hover:bg-sky-50">
<tr *ngFor="let row of pendingList" class="odd:bg-white even:bg-slate-50 hover:bg-sky-50">
<td class="px-6 py-3">{{ row.employee }}</td>
<td class="px-6 py-3">{{ row.evaluator }}</td>
<td class="px-6 py-3">{{ row.round }}</td>
......@@ -103,8 +84,7 @@
</div>
<!-- top-score table -->
<div
class="bg-white rounded-2xl shadow overflow-hidden transition
<div class="glass-card bg-white rounded-2xl shadow overflow-hidden transition
motion-safe:hover:-translate-y-1 hover:shadow-xl">
<h2 class="px-6 py-3 bg-slate-50 border-b font-semibold text-slate-600">
{{ 'dashboard.topScore' }}
......@@ -121,13 +101,11 @@
</tr>
</thead>
<tbody>
<tr *ngFor="let row of topScoreList"
class="odd:bg-white even:bg-slate-50 hover:bg-sky-50">
<tr *ngFor="let row of topScoreList" class="odd:bg-white even:bg-slate-50 hover:bg-sky-50">
<td class="px-6 py-3">{{ row.employee }}</td>
<td class="px-6 py-3 font-semibold text-slate-700">{{ row.score }}</td>
<td class="px-6 py-3">
<span class="px-2 py-0.5 rounded text-white"
[ngClass]="{
<span class="px-2 py-0.5 rounded text-white" [ngClass]="{
'bg-emerald-600': row.grade === 'S',
'bg-sky-600' : row.grade === 'A',
'bg-amber-600' : row.grade === 'B'
......
/* ───── Tailwind-like tokens ───── */
$gray-50 : #f8fafc;
$gray-100: #f1f5f9;
$gray-500: #64748b;
$gray-600: #475569;
$gray-700: #334155;
$blue-50 : #eff6ff;
$blue-100: #dbeafe;
$blue-600: #2563eb;
$radius-sm: .5rem; //≈ rounded-lg
$radius-lg: 1rem; //≈ rounded-2xl
$shadow : 0 12px 28px -14px rgba(0,0,0,.18);
$shadow-h : 0 18px 42px -16px rgba(0,0,0,.24);
$dur : .35s;
$ease : cubic-bezier(.4,0,.2,1);
/* ───── Wrapper ───── */
.dashboard{
max-width: 1500px;
margin-inline: auto;
padding: 2rem 1rem;
display: flex;
flex-direction: column;
gap: 2rem;
}
/* ───── KPI grid ───── */
.kpi-grid{
display: grid;
gap: 1rem;
@media (min-width:640px){ grid-template-columns: repeat(2,1fr); }
@media (min-width:768px){ grid-template-columns: repeat(3,1fr); }
@media (min-width:1024px){ grid-template-columns: repeat(5,1fr); }
}
/* KPI card */
.kpi-card{
display:flex; gap:1rem; align-items:center;
padding:1.5rem;
background:#fff; border-radius:$radius-lg;
box-shadow:$shadow;
transition:transform $dur $ease, box-shadow $dur $ease;
&:hover{ transform:translateY(-4px); box-shadow:$shadow-h; }
}
.kpi-icon{
width:3.5rem; height:3.5rem;
display:flex; align-items:center; justify-content:center;
border-radius:9999px; color:#fff;
}
.kpi-text{ text-align:right; flex:1; }
.kpi-label { font-size:.8rem; color:$gray-500; white-space:nowrap; }
.kpi-number{ font:700 1.5rem/1 'Inter',sans-serif; color:$gray-700; }
.kpi-rate { font-size:.65rem; font-weight:600; }
.text-up { color:#059669; } /* emerald-600 */
.text-down { color:#b91c1c; } /* red-700 */
/* ───── universal glass card ───── */
.glass-card{
position:relative;
padding:2rem;
border-radius:$radius-lg;
background:rgba(255,255,255,.55);
backdrop-filter:blur(9px) saturate(160%);
box-shadow:$shadow;
transition:transform $dur $ease, box-shadow $dur $ease, background $dur;
/* glow border */
&::before{
content:'';
position:absolute; inset:0;
border-radius:inherit;
padding:1px;
background:linear-gradient(140deg,#38bdf8,#818cf8 50%,#a855f7);
mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);
mask-composite:exclude;
opacity:.45; pointer-events:none;
transition:opacity $dur;
}
&:hover{
transform:translateY(-6px);
background:#fff; box-shadow:$shadow-h;
&::before{ opacity:.9; }
}
}
/* headings */
.section-head{ font-weight:600; color:$gray-600; margin-bottom:1rem; }
.mini-head { font-weight:500; color:$gray-600; margin-bottom:.6rem; font-size:.9rem; }
.mini-cap { margin-top:.75rem; font-size:.85rem; color:$gray-500; }
/* charts */
.chart-xl{ height:350px; width:100%; }
.mini-grid{
display:grid; gap:1.5rem;
@media (min-width:768px){ grid-template-columns:repeat(3,1fr); }
}
.mini-card{ @extend .glass-card; display:flex; flex-direction:column; align-items:center; }
/* ───── tables ───── */
.tbl-grid{
display:grid; gap:1.5rem;
@media (min-width:1024px){ grid-template-columns:repeat(2,1fr); }
}
.table-card{ @extend .glass-card; overflow-x:auto; }
.table-head{
background:$gray-50;
padding:.9rem 1.5rem;
font-weight:600; color:$gray-600;
border-bottom:1px solid #e2e8f0;
}
/* zebra & hover row */
table{ width:100%; font-size:.875rem;
thead{ background:$gray-50; color:$gray-500; }
tbody tr:nth-child(odd){ background:#fff; }
tbody tr:nth-child(even){ background:$gray-100; }
tbody tr:hover{ background:$blue-50; }
}
......@@ -32,12 +32,31 @@ interface TopScore {
export class DashboardEvaluationComponent implements OnInit {
/* ========== KPI Cards (mock) ========== */
kpiCards: KpiCard[] = [
{ label: 'ประเมินเสร็จสิ้น', value: 1_293, change: 1.35, icon: 'heroicons-outline:check-circle', color: 'bg-emerald-100' },
{ label: 'กำลังประเมิน', value: 853, change: 0.82, icon: 'heroicons-outline:pencil-square', color: 'bg-amber-100' },
{ label: 'ยังไม่ประเมิน', value: 625, change: -2.11, icon: 'heroicons-outline:clipboard', color: 'bg-rose-100' },
{ label: 'ผลประเมินต่ำสุด', value: 4, change: 2.78, icon: 'heroicons-outline:trending-down', color: 'bg-sky-100' },
{ label: 'ผลประเมินสูงสุด', value: 12, change: 12.05, icon: 'heroicons-outline:trending-up', color: 'bg-purple-100' }
kpiCards = [
{
label: 'ประเมินเสร็จสิ้น', value: 1293, change: 1.35,
icon: 'ti ti-layout-grid', bg: 'linear-gradient(135deg,#2563eb,#1d4ed8)'
},
{
label: 'กำลังประเมิน', value: 853, change: 0.82,
icon: 'ti ti-clipboard-check', bg: 'linear-gradient(135deg,#0ea5e9,#0284c7)'
},
{
label: 'ยังไม่ประเมิน', value: 625, change: -1.12,
icon: 'ti ti-clock', bg: 'linear-gradient(135deg,#9333ea,#7e22ce)'
},
{
label: 'ผลต่ำสุด', value: 4, change: 2.78,
icon: 'ti ti-arrow-big-down-line', bg: 'linear-gradient(135deg,#ef4444,#b91c1c)'
},
{
label: 'ผลสูงสุด', value: 12, change: 12.05,
icon: 'ti ti-arrow-big-up-line', bg: 'linear-gradient(135deg,#22c55e,#15803d)'
}
];
/* ========== Chart Options (mock) ========== */
......@@ -98,7 +117,7 @@ export class DashboardEvaluationComponent implements OnInit {
type: 'category',
data: [
'ทรัพยากรบุคคล', 'การเงิน/บัญชี', 'การตลาด',
'ฝ่ายขาย', 'ไอที', 'ฝ่ายผลิต', 'ลูกค้าสัมพันธ์'
'ฝ่ายขาย', 'ไอที', 'ฝ่ายผลิต', 'ลูกค้าสัมพันธ์'
],
axisLabel: { rotate: 25, color: '#6B7280' }
},
......@@ -110,7 +129,7 @@ export class DashboardEvaluationComponent implements OnInit {
label: { show: true, position: 'top' },
data: [
{
value: 88,
value: 88,
itemStyle: {
color: '#00CFE8', // สีของ "ทรัพยากรบุคคล"
borderRadius: [6, 6, 0, 0],
......@@ -203,7 +222,7 @@ export class DashboardEvaluationComponent implements OnInit {
{ name: 'A', type: 'bar', stack: 'total', data: [40], itemStyle: { color: '#9af0f5' } },
{ name: 'B', type: 'bar', stack: 'total', data: [30], itemStyle: { color: '#34bdeb' } },
{ name: 'C', type: 'bar', stack: 'total', data: [10], itemStyle: { color: '#3090C7' } },
{ name: 'D', type: 'bar', stack: 'total', data: [5], itemStyle: { color: '#659EC7' } }
{ name: 'D', type: 'bar', stack: 'total', data: [5], itemStyle: { color: '#659EC7' } }
]
};
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment