forked from ttc/micro-frontend
Merge branch 'main' of http://10.9.0.0/ttc/micro-frontend
This commit is contained in:
9
.gitea/workflows/build-image.yml
Normal file
9
.gitea/workflows/build-image.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
name: Build Docker Image
|
||||
run-name: Build Docker Image
|
||||
on: [push]
|
||||
jobs:
|
||||
Preparing Dependecies:
|
||||
steps:
|
||||
- run: |
|
||||
ls
|
||||
|
||||
@@ -22,6 +22,13 @@ const routes: Routes = [
|
||||
(m) => m.MainControlModule
|
||||
),
|
||||
},
|
||||
// {
|
||||
// path: 'report',
|
||||
// loadChildren: () =>
|
||||
// import('./controls/report-control/report-control.module').then(
|
||||
// (m) => m.ReportControlModule
|
||||
// ),
|
||||
// },
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
@@ -203,7 +203,7 @@
|
||||
<div class="pie-chart" [style.background]="ActSumDataGradient">
|
||||
<div class="pie-chart__center">
|
||||
<p>รวมเดือนนี้</p>
|
||||
<strong>฿732K</strong>
|
||||
<strong>{{myActSumData.summary.totalExpense}}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="pie-legend">
|
||||
|
||||
@@ -0,0 +1,441 @@
|
||||
:host {
|
||||
display: block;
|
||||
padding: 2rem clamp(1rem, 4vw, 3rem);
|
||||
background: #f8fafc;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.report {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.report__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
font-size: 0.8rem;
|
||||
color: #94a3b8;
|
||||
margin: 0 0 0.25rem;
|
||||
}
|
||||
|
||||
.report__header h1 {
|
||||
margin: 0 0 0.25rem;
|
||||
font-size: clamp(1.8rem, 4vw, 2.4rem);
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.muted {
|
||||
margin: 0;
|
||||
color: #94a3b8;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.report__actions {
|
||||
display: inline-flex;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn {
|
||||
border: none;
|
||||
border-radius: 999px;
|
||||
padding: 0.65rem 1.4rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.btn--primary {
|
||||
background: linear-gradient(135deg, #22d3ee, #0ea5e9);
|
||||
color: #0f172a;
|
||||
box-shadow: 0 15px 30px rgba(14, 165, 233, 0.25);
|
||||
}
|
||||
|
||||
.btn--ghost {
|
||||
background: #fff;
|
||||
color: #0f172a;
|
||||
border: 1px solid #cbd5f5;
|
||||
}
|
||||
|
||||
.btn--compact {
|
||||
padding: 0.45rem 1.1rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
position: relative;
|
||||
background: #fff;
|
||||
border-radius: 20px;
|
||||
padding: 1.25rem;
|
||||
box-shadow: 0 12px 30px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.summary-card__label {
|
||||
margin: 0;
|
||||
color: #64748b;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.summary-card h2 {
|
||||
margin: 0.4rem 0;
|
||||
font-size: 1.6rem;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.summary-card__detail {
|
||||
margin: 0;
|
||||
color: #94a3b8;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.summary-card__tone {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 20px;
|
||||
pointer-events: none;
|
||||
opacity: 0.15;
|
||||
}
|
||||
|
||||
.tone-mint { background: linear-gradient(135deg, #a7f3d0, #34d399); }
|
||||
.tone-amber { background: linear-gradient(135deg, #fde68a, #fbbf24); }
|
||||
.tone-indigo { background: linear-gradient(135deg, #c4b5fd, #818cf8); }
|
||||
.tone-slate { background: linear-gradient(135deg, #cbd5f5, #94a3b8); }
|
||||
|
||||
.report__content {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr);
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.panel {
|
||||
background: #fff;
|
||||
border-radius: 24px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 15px 45px rgba(15, 23, 42, 0.08);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.panel__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.panel__header h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.panel__header p {
|
||||
margin: 0;
|
||||
color: #94a3b8;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.table {
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 18px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table__head,
|
||||
.table__row {
|
||||
display: grid;
|
||||
grid-template-columns: 1.2fr 1fr 1.6fr 1fr 0.8fr;
|
||||
padding: 0.85rem 1rem;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.table__head {
|
||||
background: #f1f5f9;
|
||||
font-size: 0.85rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.table__row:nth-child(even) {
|
||||
background: rgba(15, 23, 42, 0.015);
|
||||
}
|
||||
|
||||
.table__row strong {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.table__row small {
|
||||
display: block;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.mono {
|
||||
font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.amount-col {
|
||||
text-align: right;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.income {
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.expense {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.pie-panel__content {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.pie-chart {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
box-shadow: inset 0 0 20px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.pie-chart__center {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.1);
|
||||
}
|
||||
|
||||
.pie-chart__center p {
|
||||
margin: 0;
|
||||
color: #94a3b8;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.pie-chart__center strong {
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.pie-legend {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.8rem;
|
||||
}
|
||||
|
||||
.pie-legend li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
.swatch {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.legend-label {
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.legend-value {
|
||||
margin: 0;
|
||||
color: #94a3b8;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.preview-modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 120;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.preview-modal__backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(15, 23, 42, 0.55);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.preview-modal__content {
|
||||
position: relative;
|
||||
background: #fff;
|
||||
border-radius: 24px;
|
||||
padding: 1.5rem;
|
||||
width: min(1100px, 100%);
|
||||
max-height: 90vh;
|
||||
overflow: auto;
|
||||
box-shadow: 0 25px 60px rgba(15, 23, 42, 0.35);
|
||||
}
|
||||
|
||||
.preview-modal__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.preview-modal__actions {
|
||||
display: inline-flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.preview-sheet {
|
||||
margin-top: 1.5rem;
|
||||
background: #fff;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 16px;
|
||||
padding: 1.5rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.preview-sheet__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.preview-totals {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.preview-totals p {
|
||||
margin: 0;
|
||||
color: #94a3b8;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.preview-totals strong {
|
||||
display: block;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.preview-pie {
|
||||
margin: 1.5rem 0;
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mini-pie {
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
border-radius: 50%;
|
||||
box-shadow: inset 0 0 20px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.preview-pie ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.preview-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.preview-table th,
|
||||
.preview-table td {
|
||||
padding: 0.65rem 0.75rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.preview-table th {
|
||||
background: #f1f5f9;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.08em;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.preview-table td:last-child {
|
||||
text-align: right;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.report__content {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.table__head,
|
||||
.table__row {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.amount-col {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.pie-panel__content {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
:host {
|
||||
padding: 1.5rem 1rem 2rem;
|
||||
}
|
||||
|
||||
.report__actions {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.preview-modal {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
<section class="report">
|
||||
<header class="report__header">
|
||||
<div>
|
||||
<p class="eyebrow">สรุปรายงาน</p>
|
||||
<h1>รายงานรายรับรายจ่าย</h1>
|
||||
<p class="muted">ช่วงวันที่ {{ reportRange.start }} - {{ reportRange.end }}</p>
|
||||
</div>
|
||||
<div class="report__actions">
|
||||
<button class="btn btn--ghost">ส่งออกเป็น Excel</button>
|
||||
<button class="btn btn--primary" (click)="openPreview()">ปริ้นรายงาน</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="summary-grid">
|
||||
<article class="summary-card" *ngFor="let card of summaryCards">
|
||||
<p class="summary-card__label">{{ card.label }}</p>
|
||||
<h2>{{ card.value }}</h2>
|
||||
<p class="summary-card__detail">{{ card.detail }}</p>
|
||||
<span class="summary-card__tone" [ngClass]="'tone-' + card.tone"></span>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="report__content">
|
||||
<article class="panel">
|
||||
<div class="panel__header">
|
||||
<div>
|
||||
<h2>สมุดรายวัน</h2>
|
||||
<p>บันทึกรายรับรายจ่ายทั้งหมดในช่วงเวลา</p>
|
||||
</div>
|
||||
<button class="btn btn--compact btn--ghost">กรองข้อมูล</button>
|
||||
</div>
|
||||
<div class="table">
|
||||
<div class="table__head">
|
||||
<span>วันที่</span>
|
||||
<span>เลขที่เอกสาร</span>
|
||||
<span>หัวข้อ</span>
|
||||
<span>หมวดหมู่</span>
|
||||
<span class="amount-col">ยอดเงิน</span>
|
||||
</div>
|
||||
<div class="table__row" *ngFor="let record of formattedRecords">
|
||||
<span>{{ record.date }}</span>
|
||||
<span class="mono">{{ record.doc }}</span>
|
||||
<span>
|
||||
<strong>{{ record.topic }}</strong>
|
||||
<small class="muted">{{ record.type === 'income' ? 'รายรับ' : 'รายจ่าย' }}</small>
|
||||
</span>
|
||||
<span>{{ record.category }}</span>
|
||||
<span class="amount-col" [ngClass]="record.tone">{{ record.displayAmount }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="panel pie-panel">
|
||||
<div class="panel__header">
|
||||
<div>
|
||||
<h2>สัดส่วนค่าใช้จ่าย</h2>
|
||||
<p>เปรียบเทียบหมวดหลักของรายจ่ายเดือนนี้</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pie-panel__content">
|
||||
<div class="pie-chart" [style.background]="expenseGradient">
|
||||
<div class="pie-chart__center">
|
||||
<p>รวมรายจ่าย</p>
|
||||
<strong>฿732K</strong>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="pie-legend">
|
||||
<li *ngFor="let part of expenseBreakdown">
|
||||
<span class="swatch" [style.background]="part.color"></span>
|
||||
<div>
|
||||
<p class="legend-label">{{ part.label }}</p>
|
||||
<p class="legend-value">{{ part.value }}%</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section class="preview-modal" *ngIf="printPreviewOpen">
|
||||
<div class="preview-modal__backdrop" (click)="closePreview()"></div>
|
||||
<div class="preview-modal__content">
|
||||
<header class="preview-modal__header">
|
||||
<div>
|
||||
<p class="eyebrow">Print Preview</p>
|
||||
<h2>รายงานรายรับรายจ่าย</h2>
|
||||
<p class="muted">ช่วงวันที่ {{ reportRange.start }} - {{ reportRange.end }}</p>
|
||||
</div>
|
||||
<div class="preview-modal__actions">
|
||||
<button class="btn btn--ghost" (click)="closePreview()">ปิด</button>
|
||||
<button class="btn btn--primary">พิมพ์</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="preview-sheet">
|
||||
<div class="preview-sheet__header">
|
||||
<div>
|
||||
<h3>Accounting Summary</h3>
|
||||
<p>Prepared on {{ reportRange.end }}</p>
|
||||
</div>
|
||||
<div class="preview-totals">
|
||||
<div *ngFor="let total of previewTotals">
|
||||
<p>{{ total.label }}</p>
|
||||
<strong>{{ total.value }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="preview-pie">
|
||||
<div class="mini-pie" [style.background]="expenseGradient"></div>
|
||||
<ul>
|
||||
<li *ngFor="let part of expenseBreakdown">
|
||||
<span class="swatch" [style.background]="part.color"></span>
|
||||
<span>{{ part.label }} · {{ part.value }}%</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<table class="preview-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>วันที่</th>
|
||||
<th>เลขที่</th>
|
||||
<th>หัวข้อ</th>
|
||||
<th>หมวดหมู่</th>
|
||||
<th>ยอดเงิน</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let record of formattedRecords">
|
||||
<td>{{ record.date }}</td>
|
||||
<td>{{ record.doc }}</td>
|
||||
<td>{{ record.topic }}</td>
|
||||
<td>{{ record.category }}</td>
|
||||
<td [ngClass]="record.tone">{{ record.displayAmount }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,138 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-main-report',
|
||||
templateUrl: './main-report.component.html',
|
||||
standalone: false,
|
||||
styleUrls: ['./main-report.component.css']
|
||||
})
|
||||
export class MainReportComponent {
|
||||
readonly reportRange = {
|
||||
start: '1 มิถุนายน 2567',
|
||||
end: '30 มิถุนายน 2567'
|
||||
};
|
||||
|
||||
readonly summaryCards = [
|
||||
{ label: 'รายรับรวม', value: '฿1,284,500', detail: '+12.4% MoM', tone: 'mint' },
|
||||
{ label: 'รายจ่ายรวม', value: '฿732,800', detail: '-4.1% MoM', tone: 'amber' },
|
||||
{ label: 'กำไรสุทธิ', value: '฿551,700', detail: 'Margin 42.9%', tone: 'indigo' },
|
||||
{ label: 'บันทึกรายการ', value: '86 รายการ', detail: '32 รายรับ · 54 รายจ่าย', tone: 'slate' }
|
||||
];
|
||||
|
||||
readonly ledgerRecords = [
|
||||
{
|
||||
date: '01 มิ.ย. 2567',
|
||||
doc: 'RCPT-9101',
|
||||
type: 'income',
|
||||
topic: 'ค่าบริการที่ปรึกษา',
|
||||
category: 'บริการ',
|
||||
amount: 145000
|
||||
},
|
||||
{
|
||||
date: '02 มิ.ย. 2567',
|
||||
doc: 'EXP-4407',
|
||||
type: 'expense',
|
||||
topic: 'ค่าวัสดุโครงการ A',
|
||||
category: 'ต้นทุนโครงการ',
|
||||
amount: -38900
|
||||
},
|
||||
{
|
||||
date: '06 มิ.ย. 2567',
|
||||
doc: 'RCPT-9110',
|
||||
type: 'income',
|
||||
topic: 'รับเงินมัดจำโครงการ',
|
||||
category: 'สัญญาใหม่',
|
||||
amount: 220000
|
||||
},
|
||||
{
|
||||
date: '09 มิ.ย. 2567',
|
||||
doc: 'EXP-4412',
|
||||
type: 'expense',
|
||||
topic: 'เงินเดือนพนักงาน',
|
||||
category: 'บุคลากร',
|
||||
amount: -180000
|
||||
},
|
||||
{
|
||||
date: '12 มิ.ย. 2567',
|
||||
doc: 'EXP-4416',
|
||||
type: 'expense',
|
||||
topic: 'ค่าเช่าออฟฟิศ',
|
||||
category: 'ค่าใช้จ่ายคงที่',
|
||||
amount: -48000
|
||||
},
|
||||
{
|
||||
date: '19 มิ.ย. 2567',
|
||||
doc: 'RCPT-9122',
|
||||
type: 'income',
|
||||
topic: 'ค่าสัญญาบริการรายปี',
|
||||
category: 'บริการ',
|
||||
amount: 325000
|
||||
},
|
||||
{
|
||||
date: '23 มิ.ย. 2567',
|
||||
doc: 'EXP-4425',
|
||||
type: 'expense',
|
||||
topic: 'ค่าโฆษณาออนไลน์',
|
||||
category: 'การตลาด',
|
||||
amount: -72000
|
||||
},
|
||||
{
|
||||
date: '28 มิ.ย. 2567',
|
||||
doc: 'RCPT-9133',
|
||||
type: 'income',
|
||||
topic: 'รายรับจากคู่ค้าใหม่',
|
||||
category: 'พันธมิตร',
|
||||
amount: 210500
|
||||
}
|
||||
];
|
||||
|
||||
readonly expenseBreakdown = [
|
||||
{ label: 'ต้นทุนโครงการ', value: 34, color: '#10b981' },
|
||||
{ label: 'บุคลากร', value: 26, color: '#6366f1' },
|
||||
{ label: 'การตลาด', value: 18, color: '#f97316' },
|
||||
{ label: 'ค่าใช้จ่ายคงที่', value: 14, color: '#0ea5e9' },
|
||||
{ label: 'อื่นๆ', value: 8, color: '#e11d48' }
|
||||
];
|
||||
|
||||
readonly previewTotals = [
|
||||
{ label: 'รายรับรวม', value: '฿1,284,500' },
|
||||
{ label: 'รายจ่ายรวม', value: '฿732,800' },
|
||||
{ label: 'กำไรสุทธิ', value: '฿551,700' }
|
||||
];
|
||||
|
||||
printPreviewOpen = false;
|
||||
|
||||
get expenseGradient(): string {
|
||||
let current = 0;
|
||||
const segments = this.expenseBreakdown
|
||||
.map(slice => {
|
||||
const start = current;
|
||||
const end = current + slice.value;
|
||||
current = end;
|
||||
return `${slice.color} ${start}% ${end}%`;
|
||||
})
|
||||
.join(', ');
|
||||
return `conic-gradient(${segments})`;
|
||||
}
|
||||
|
||||
get formattedRecords() {
|
||||
return this.ledgerRecords.map(record => ({
|
||||
...record,
|
||||
displayAmount: this.formatCurrency(record.amount),
|
||||
tone: record.type === 'income' ? 'income' : 'expense'
|
||||
}));
|
||||
}
|
||||
|
||||
openPreview(): void {
|
||||
this.printPreviewOpen = true;
|
||||
}
|
||||
|
||||
closePreview(): void {
|
||||
this.printPreviewOpen = false;
|
||||
}
|
||||
|
||||
private formatCurrency(amount: number): string {
|
||||
const formatter = new Intl.NumberFormat('th-TH', { style: 'currency', currency: 'THB', maximumFractionDigits: 0 });
|
||||
return formatter.format(amount);
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ export class LoginContentComponent implements OnInit {
|
||||
if (this.loginPageComponent) {
|
||||
this.loginPageComponent.message = errorMessage;
|
||||
}
|
||||
this.generalService.trowApi(error.error || { message_th: 'เกิดข้อผิดพลาดไม่ทราบสาเหตุ' });
|
||||
this.generalService.trowApi(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -54,10 +54,12 @@ export class MainDashboardContentComponent implements OnInit {
|
||||
this.generalService.trowApi(result);
|
||||
this.myActData = result.data;
|
||||
this.dashboardStateService.setStateAccountResult(this.myActData);
|
||||
}else{
|
||||
this.generalService.trowApi(result);
|
||||
}
|
||||
},
|
||||
error: (error: any) => {
|
||||
|
||||
this.generalService.trowApi(error);
|
||||
},
|
||||
complete: () => {
|
||||
|
||||
@@ -78,10 +80,12 @@ export class MainDashboardContentComponent implements OnInit {
|
||||
this.generalService.trowApi(result);
|
||||
this.myDropAct = result.data
|
||||
this.dashboardStateService.setStateResult(this.myDropAct)
|
||||
}else{
|
||||
this.generalService.trowApi(result);
|
||||
}
|
||||
},
|
||||
error: (error: any) => {
|
||||
|
||||
this.generalService.trowApi(error);
|
||||
},
|
||||
complete: () => {
|
||||
|
||||
@@ -100,10 +104,12 @@ export class MainDashboardContentComponent implements OnInit {
|
||||
this.generalService.trowApi(result);
|
||||
this.myActSumData = result.data
|
||||
this.dashboardStateService.setStateSumResult(this.myActSumData);
|
||||
}else{
|
||||
this.generalService.trowApi(result);
|
||||
}
|
||||
},
|
||||
error: (error: any) => {
|
||||
|
||||
this.generalService.trowApi(error);
|
||||
},
|
||||
complete: () => {
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { MainDashboardContentComponent } from '../../content/main-dashboard-content/main-dashboard-content.component';
|
||||
import { MainReportComponent } from '../../component/main-report/main-report.component';
|
||||
// import { MainReportComponent } from '../../component/main-report/main-report.component';
|
||||
|
||||
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: 'dashboard', component: MainDashboardContentComponent },
|
||||
{ path: 'report', component: MainReportComponent },
|
||||
// children: [
|
||||
// {
|
||||
// path: 'dashboard',
|
||||
|
||||
@@ -9,6 +9,7 @@ import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { MainDashboardComponent } from '../../component/main-dashboard/main-dashboard.component';
|
||||
import { MainDashboardContentComponent } from '../../content/main-dashboard-content/main-dashboard-content.component';
|
||||
import { AccDateFormatPipe } from '../../pipe/dtmtodatetime.pipe';
|
||||
import { MainReportComponent } from '../../component/main-report/main-report.component';
|
||||
|
||||
// import { MainReportComponent } from '../../component/main-report/main-report.component';
|
||||
|
||||
@@ -18,6 +19,7 @@ import { AccDateFormatPipe } from '../../pipe/dtmtodatetime.pipe';
|
||||
declarations: [
|
||||
MainDashboardComponent,
|
||||
MainDashboardContentComponent,
|
||||
MainReportComponent,
|
||||
AccDateFormatPipe
|
||||
// MainReportComponent
|
||||
],
|
||||
|
||||
@@ -54,8 +54,14 @@ export class GeneralService {
|
||||
return this.http.post(fullUrl, payload, this.getHttpOptions()).pipe(
|
||||
map((res: any) => res),
|
||||
catchError((error: any) => {
|
||||
const response = error?.error;
|
||||
console.error('❌ [POST Request Error]:', error);
|
||||
return throwError(() => error);
|
||||
return throwError(() => ({
|
||||
status: error.status,
|
||||
code: response?.code ?? '500',
|
||||
message: response?.message ?? 'Internal Server Error',
|
||||
message_th: response?.message_th ?? 'เกิดข้อผิดพลาดภายในเซิร์ฟเวอร์'
|
||||
}));
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -66,9 +72,14 @@ export class GeneralService {
|
||||
return this.http.get(fullUrl, this.getHttpOptions()).pipe(
|
||||
map((res: any) => res),
|
||||
catchError((error: any) => {
|
||||
const response = error?.error;
|
||||
console.error('❌ [GET Request Error]:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
return throwError(() => ({
|
||||
status: error.status,
|
||||
code: response?.code ?? '500',
|
||||
message: response?.message ?? 'Internal Server Error',
|
||||
message_th: response?.message_th ?? 'เกิดข้อผิดพลาดภายในเซิร์ฟเวอร์'
|
||||
})); })
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user