Compare commits
14 Commits
1.0.0beta
...
5ce012b558
| Author | SHA1 | Date | |
|---|---|---|---|
| 5ce012b558 | |||
| 4b3e52ff43 | |||
| 8f9159a330 | |||
|
|
ccab40852c | ||
| 60662d88d4 | |||
| ee72ef6676 | |||
| 7b441c3600 | |||
| 139167be8a | |||
| 80edb10361 | |||
| 3cc4a4a632 | |||
| f27389da29 | |||
| 1664be0c8b | |||
| b3fa94f904 | |||
| 37ca45701b |
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
|
||||
|
||||
@@ -8,7 +8,7 @@ const routes: Routes = [
|
||||
|
||||
{ path: 'login', loadChildren: () => import('./controls/login-control/login-control.module').then(m => m.LoginControlModule) },
|
||||
|
||||
{ path: 'c', component: LicensePrivacyTermsComponent},
|
||||
{ path: 'license', component: LicensePrivacyTermsComponent},
|
||||
|
||||
{
|
||||
path: 'main',
|
||||
@@ -22,9 +22,18 @@ const routes: Routes = [
|
||||
(m) => m.MainControlModule
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'report',
|
||||
loadChildren: () =>
|
||||
import('./controls/report-control/report-control.module').then(
|
||||
(m) => m.ReportControlModule
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// {path: 'license' , component: LicensePrivacyTermsComponent}
|
||||
|
||||
{ path: '', redirectTo: 'login', pathMatch: 'full' },
|
||||
|
||||
{ path: '**', redirectTo: 'login' }
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
@@ -21,7 +21,10 @@ import { LicensePrivacyTermsComponent } from './component/license-privacy-terms/
|
||||
// import { LoginPageComponent } from './component/login-page/login-page.component';
|
||||
// import { LoginContentComponent } from './content/login-content/login-content.component';
|
||||
|
||||
|
||||
import { provideCharts, withDefaultRegisterables } from 'ng2-charts';
|
||||
// import { AccDateFormatPipe } from './pipe/dtmtodatetime.pipe';
|
||||
// import { DtmtodatetimePipe } from './dtmtodatetime.pipe';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -30,6 +33,8 @@ import { provideCharts, withDefaultRegisterables } from 'ng2-charts';
|
||||
SidebarContentComponent,
|
||||
SidebarComponent,
|
||||
LicensePrivacyTermsComponent,
|
||||
// AccDateFormatPipe
|
||||
// DtmtodatetimePipe,
|
||||
// MainDashboardContentComponent,
|
||||
// MainDashboardComponent,
|
||||
// LoginForgotComponent,
|
||||
@@ -51,6 +56,9 @@ import { provideCharts, withDefaultRegisterables } from 'ng2-charts';
|
||||
HttpClientModule,
|
||||
FontAwesomeModule
|
||||
],
|
||||
exports: [
|
||||
// AccDateFormatPipe
|
||||
],
|
||||
providers: [provideCharts(withDefaultRegisterables())],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
|
||||
@@ -627,3 +627,20 @@
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.quick-log__form select {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3E%3C/svg%3E");
|
||||
background-position: right 0.5rem center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 1.5em 1.5em;
|
||||
padding-right: 2.5rem;
|
||||
}
|
||||
|
||||
.ledger-table.is-scrollable {
|
||||
max-height: 25rem;
|
||||
overflow-y: auto;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
@@ -77,7 +77,25 @@
|
||||
<div class="quick-log__grid">
|
||||
<label>
|
||||
<span>หมวดหมู่</span>
|
||||
<input type="text" placeholder="เลือกหมวดหมู่" />
|
||||
@if(mode == 'i'){
|
||||
<select class="w-full h-full px-4 py-2 border rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-100 focus:border-blue-50 transition-all bg-white">
|
||||
<option value="">ไม่เลือก</option>
|
||||
@for (item of myDropAct.income; track item.dtlcod) {
|
||||
<option [value]="item.dtlcod">
|
||||
{{ item.dtlnam }}
|
||||
</option>
|
||||
}
|
||||
</select>
|
||||
}@else if(mode == 'e'){
|
||||
<select class="w-full h-full px-4 py-2 border rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-100 focus:border-blue-50 transition-all bg-white">
|
||||
<option value="">ไม่เลือก</option>
|
||||
@for (item of myDropAct.expense; track item.dtlcod) {
|
||||
<option [value]="item.dtlcod">
|
||||
{{ item.dtlnam }}
|
||||
</option>
|
||||
}
|
||||
</select>
|
||||
}
|
||||
</label>
|
||||
<label>
|
||||
<span>ยอดเงิน (฿)</span>
|
||||
@@ -100,29 +118,58 @@
|
||||
</div>
|
||||
<button class="btn btn--ghost btn--compact">ดูทั้งหมด</button>
|
||||
</div>
|
||||
<div class="ledger-table">
|
||||
<div class="ledger-table" [class.is-scrollable]="myActData.length > 5">
|
||||
<div class="ledger-row ledger-head">
|
||||
<span>รายการ</span>
|
||||
<span>หมวดหมู่</span>
|
||||
<span>ยอดเงิน</span>
|
||||
<span>บันทึก</span>
|
||||
</div>
|
||||
<div class="ledger-row" *ngFor="let idx of ledgerEntries; let i = index;">
|
||||
<div class="ledger-main">
|
||||
<span class="pill" [ngClass]="idx.type == 'i' ? 'pill--income' : 'pill--expense'">
|
||||
{{ idx.type == 'i' ? 'รับ' : 'จ่าย' }}
|
||||
</span>
|
||||
<div>
|
||||
<p class="ledger-title">{{ idx.title }}</p>
|
||||
<p class="ledger-date">{{ idx.date }}</p>
|
||||
<!-- @for (idx of myActData; track i; let i = $index) {
|
||||
<div class="ledger-row">
|
||||
<div class="ledger-main">
|
||||
<span class="pill" [ngClass]="idx.acttyp === 'i' ? 'pill--income' : 'pill--expense'">
|
||||
{{ idx.type === 'i' ? 'รับ' : 'จ่าย' }}
|
||||
</span>
|
||||
|
||||
<div>
|
||||
<p class="ledger-title">{{ idx.title }}</p>
|
||||
<p class="ledger-date">{{ idx.date }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span class="ledger-category">{{ idx.category }}</span>
|
||||
|
||||
<span class="ledger-amount" [ngClass]="idx.type === 'i' ? 'is-credit' : 'is-debit'">
|
||||
{{ idx.amount }}
|
||||
</span>
|
||||
|
||||
<span class="ledger-note">{{ idx.note }}</span>
|
||||
</div>
|
||||
<span class="ledger-category">{{ idx.category }}</span>
|
||||
<span class="ledger-amount" [ngClass]="idx.type == 'i' ? 'is-credit' : 'is-debit'">
|
||||
{{ idx.amount }}
|
||||
</span>
|
||||
<span class="ledger-note">{{ idx.note }}</span>
|
||||
</div>
|
||||
} -->
|
||||
@for (idx of myActData; track idx.actseq; let i = $index) {
|
||||
<div class="ledger-row">
|
||||
|
||||
<div class="ledger-main">
|
||||
<span class="pill" [ngClass]="idx.acttyp === 'i' ? 'pill--income' : 'pill--expense'">
|
||||
{{ idx.acttyp === 'i' ? 'รับ' : 'จ่าย' }}
|
||||
</span>
|
||||
|
||||
<div>
|
||||
<p class="ledger-title">{{ idx.acttypnam }}</p>
|
||||
<p class="ledger-date">{{ idx.actacpdtm ?? '' | dtmtodatetime}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span class="ledger-category">{{ idx.actcatnam }}</span>
|
||||
|
||||
<span class="ledger-amount" [ngClass]="idx.acttyp === 'i' ? 'is-credit' : 'is-debit'">
|
||||
{{ idx.actqty }}
|
||||
</span>
|
||||
|
||||
<span class="ledger-note">{{ idx.actcmt }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
@@ -153,18 +200,19 @@
|
||||
<button class="btn btn--ghost btn--compact">จัดการหมวดหมู่</button>
|
||||
</div>
|
||||
<div class="pie-panel__content">
|
||||
<div class="pie-chart" [style.background]="expenseGradient">
|
||||
<div class="pie-chart" [style.background]="ActSumDataGradient">
|
||||
<div class="pie-chart__center">
|
||||
<p>รวมเดือนนี้</p>
|
||||
<strong>฿732K</strong>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="pie-legend">
|
||||
<li class="pie-legend__item" *ngFor="let part of expenseBreakdown">
|
||||
<span class="swatch" [style.background]="part.color"></span>
|
||||
<li class="pie-legend__item" *ngFor="let idx of myActSumData.pie.expense">
|
||||
<span class="swatch" [style.background]="idx.color"></span>
|
||||
<div>
|
||||
<p class="pie-legend__label">{{ part.label }}</p>
|
||||
<p class="pie-legend__value">{{ part.value }}%</p>
|
||||
<p class="pie-legend__label">{{ idx.label }}</p>
|
||||
<p class="pie-legend__value">{{ idx.percent }}%</p>
|
||||
<p class="pie-legend__value">{{ idx.value }} บาท</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms';
|
||||
import { GeneralService } from '../../services/generalservice';
|
||||
import { IDropAct, IStateDrop, IStateResultResponse, IActData, IActSumData } from '../../interfaces/dashboard.interface'
|
||||
import { DashboardStateService } from '../../services/state/dashboard-state.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-main-dashboard',
|
||||
@@ -15,10 +17,32 @@ export class MainDashboardComponent implements OnInit {
|
||||
isSubmitting: boolean = false;
|
||||
arrearsForm!: FormGroup;
|
||||
saveFrm!: FormGroup;
|
||||
myActData: IActData[] = [];
|
||||
// myDropAct: IStateDrop[] = [];
|
||||
myDropAct: IStateDrop = { income: [], expense: [] };
|
||||
myActSumData: IActSumData = {
|
||||
summary: {
|
||||
totalIncome: '',
|
||||
totalExpense: '',
|
||||
netProfit: '',
|
||||
profitRate: '',
|
||||
adjustedProfitRate: '',
|
||||
period: ''
|
||||
},
|
||||
pie: {
|
||||
income: [],
|
||||
expense: []
|
||||
}
|
||||
};
|
||||
ActSumDataGradient: any
|
||||
|
||||
|
||||
readonly ownerName = 'Nuttakit';
|
||||
|
||||
constructor(
|
||||
private dashboardStateService: DashboardStateService
|
||||
){}
|
||||
|
||||
readonly kpiCards = [
|
||||
{
|
||||
label: 'รายรับรวม',
|
||||
@@ -50,14 +74,14 @@ export class MainDashboardComponent implements OnInit {
|
||||
}
|
||||
];
|
||||
|
||||
readonly revenueTrend = [
|
||||
{ label: 'ม.ค.', value: 52 },
|
||||
{ label: 'ก.พ.', value: 61 },
|
||||
{ label: 'มี.ค.', value: 73 },
|
||||
{ label: 'เม.ย.', value: 68 },
|
||||
{ label: 'พ.ค.', value: 82 },
|
||||
{ label: 'มิ.ย.', value: 77 }
|
||||
];
|
||||
// readonly revenueTrend = [
|
||||
// { label: 'ม.ค.', value: 52 },
|
||||
// { label: 'ก.พ.', value: 61 },
|
||||
// { label: 'มี.ค.', value: 73 },
|
||||
// { label: 'เม.ย.', value: 68 },
|
||||
// { label: 'พ.ค.', value: 82 },
|
||||
// { label: 'มิ.ย.', value: 77 }
|
||||
// ];
|
||||
|
||||
readonly quickRatios = [
|
||||
{ label: 'กระแสเงินสด', value: '+฿312K', status: 'positive' },
|
||||
@@ -131,40 +155,40 @@ export class MainDashboardComponent implements OnInit {
|
||||
}
|
||||
];
|
||||
|
||||
readonly ledgerEntries = [
|
||||
{
|
||||
type: 'i',
|
||||
title: 'ค่าบริการที่ปรึกษา',
|
||||
category: 'บริการ',
|
||||
amount: '+฿85,000',
|
||||
date: 'วันนี้ · 10:15',
|
||||
note: 'โครงการ Warehouse Automation'
|
||||
},
|
||||
{
|
||||
type: 'e',
|
||||
title: 'ค่าเช่าออฟฟิศ',
|
||||
category: 'ค่าใช้จ่ายคงที่',
|
||||
amount: '-฿48,000',
|
||||
date: 'วันนี้ · 09:00',
|
||||
note: 'สำนักงานพระราม 9'
|
||||
},
|
||||
{
|
||||
type: 'i',
|
||||
title: 'รับเงินมัดจำ',
|
||||
category: 'สัญญาใหม่',
|
||||
amount: '+฿120,000',
|
||||
date: 'เมื่อวาน',
|
||||
note: 'ลูกค้า Urbane CoWorking'
|
||||
},
|
||||
{
|
||||
type: 'e',
|
||||
title: 'ค่าวัตถุดิบ',
|
||||
category: 'ต้นทุนโครงการ',
|
||||
amount: '-฿27,500',
|
||||
date: '12 มิ.ย.',
|
||||
note: 'สั่งผ่าน Blue Supply'
|
||||
}
|
||||
];
|
||||
// readonly ledgerEntries = [
|
||||
// {
|
||||
// type: 'i',
|
||||
// title: 'ค่าบริการที่ปรึกษา',
|
||||
// category: 'บริการ',
|
||||
// amount: '+฿85,000',
|
||||
// date: 'วันนี้ · 10:15',
|
||||
// note: 'โครงการ Warehouse Automation'
|
||||
// },
|
||||
// {
|
||||
// type: 'e',
|
||||
// title: 'ค่าเช่าออฟฟิศ',
|
||||
// category: 'ค่าใช้จ่ายคงที่',
|
||||
// amount: '-฿48,000',
|
||||
// date: 'วันนี้ · 09:00',
|
||||
// note: 'สำนักงานพระราม 9'
|
||||
// },
|
||||
// {
|
||||
// type: 'i',
|
||||
// title: 'รับเงินมัดจำ',
|
||||
// category: 'สัญญาใหม่',
|
||||
// amount: '+฿120,000',
|
||||
// date: 'เมื่อวาน',
|
||||
// note: 'ลูกค้า Urbane CoWorking'
|
||||
// },
|
||||
// {
|
||||
// type: 'e',
|
||||
// title: 'ค่าวัตถุดิบ',
|
||||
// category: 'ต้นทุนโครงการ',
|
||||
// amount: '-฿27,500',
|
||||
// date: '12 มิ.ย.',
|
||||
// note: 'สั่งผ่าน Blue Supply'
|
||||
// }
|
||||
// ];
|
||||
|
||||
readonly expenseBreakdown = [
|
||||
{ label: 'ฝ่ายบริหาร', value: 32, color: '#0ea5e9' },
|
||||
@@ -174,12 +198,28 @@ export class MainDashboardComponent implements OnInit {
|
||||
{ label: 'อื่นๆ', value: 8, color: '#e11d48' }
|
||||
];
|
||||
|
||||
readonly expenseGradient = this.buildExpenseGradient();
|
||||
|
||||
ngOnInit(): void {
|
||||
this.setupFormControl();
|
||||
this.dashboardStateService.getStateResult().subscribe(data => {
|
||||
if (data) {
|
||||
this.myDropAct = data;
|
||||
}
|
||||
});
|
||||
// ผลลับท์ ของ รายการ
|
||||
this.dashboardStateService.getStateAccountResult().subscribe(data => {
|
||||
if (data) {
|
||||
this.myActData = data;
|
||||
}
|
||||
});
|
||||
// ผลลัพการ คำนวณ ของ ปัญชี ต่างๆ
|
||||
this.dashboardStateService.getStateSumResult().subscribe(data => {
|
||||
if (data) {
|
||||
this.myActSumData = data;
|
||||
this.ActSumDataGradient = this.buildExpenseGradient()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setupFormControl(){
|
||||
this.arrearsForm = new FormGroup({
|
||||
// email: new FormControl('',[Validators.required, Validators.email, Validators.maxLength(100)]),
|
||||
@@ -206,15 +246,19 @@ export class MainDashboardComponent implements OnInit {
|
||||
}
|
||||
|
||||
private buildExpenseGradient(): string {
|
||||
if (!this.myActSumData?.pie?.expense?.length) return '';
|
||||
|
||||
let current = 0;
|
||||
const segments = this.expenseBreakdown
|
||||
const segments = this.myActSumData.pie.expense
|
||||
.map(part => {
|
||||
const start = current;
|
||||
const end = current + part.value;
|
||||
const percent = parseFloat(part.percent); // แปลงจาก string → number
|
||||
const end = current + percent;
|
||||
current = end;
|
||||
return `${part.color} ${start}% ${end}%`;
|
||||
})
|
||||
.join(', ');
|
||||
|
||||
return `conic-gradient(${segments})`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,8 @@ export const CACHEABLE_URLS = {
|
||||
// e.g., '/api/data'
|
||||
],
|
||||
POST: [
|
||||
'/api/web/accountingSearch'
|
||||
'/api/web/accountingSetup',
|
||||
'/api/nigga'
|
||||
// Add POST URIs here that you want to cache
|
||||
// e.g., '/api/search'
|
||||
]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { DashboardStateService } from './../../services/state/dashboard-state.service';
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { ChartConfiguration, ChartOptions } from 'chart.js';
|
||||
import { BaseChartDirective } from 'ng2-charts';
|
||||
import { GeneralService } from '../../services/generalservice';
|
||||
import { IDropAct, IStateDrop, IActData, IActSumData } from '../../interfaces/dashboard.interface';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-main-dashboard-content',
|
||||
@@ -11,44 +14,33 @@ import { GeneralService } from '../../services/generalservice';
|
||||
})
|
||||
export class MainDashboardContentComponent implements OnInit {
|
||||
@ViewChild(BaseChartDirective) chart?: BaseChartDirective;
|
||||
|
||||
public lineChartData: ChartConfiguration<'line'>['data'] = {
|
||||
labels: [],
|
||||
datasets: [
|
||||
{
|
||||
data: [],
|
||||
label: 'Revenue',
|
||||
fill: true,
|
||||
tension: 0.5,
|
||||
borderColor: 'rgba(75,192,192,1)',
|
||||
backgroundColor: 'rgba(75,192,192,0.2)'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
public lineChartOptions: ChartOptions<'line'> = {
|
||||
responsive: true,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true
|
||||
}
|
||||
myDropAct!: IStateDrop;
|
||||
myActData: IActData[] = [];
|
||||
myActSumData: IActSumData = {
|
||||
summary: {
|
||||
totalIncome: '',
|
||||
totalExpense: '',
|
||||
netProfit: '',
|
||||
profitRate: '',
|
||||
adjustedProfitRate: '',
|
||||
period: ''
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: true,
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Revenue Summary - Last 6 Months'
|
||||
}
|
||||
pie: {
|
||||
income: [],
|
||||
expense: []
|
||||
}
|
||||
};
|
||||
|
||||
constructor(private generalService: GeneralService) {}
|
||||
constructor(
|
||||
private generalService: GeneralService,
|
||||
private dashboardStateService: DashboardStateService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
let token = localStorage.getItem('access_token')
|
||||
this.OnSearchAct(token, true);
|
||||
this.OnSetupDashboard(token, true);
|
||||
this.OnSearchSum(token, true);
|
||||
}
|
||||
|
||||
OnSearchAct(value: any, setupFirst: boolean): void {
|
||||
@@ -60,11 +52,64 @@ export class MainDashboardContentComponent implements OnInit {
|
||||
next: (result: any) => {
|
||||
if (result.code === '200') {
|
||||
this.generalService.trowApi(result);
|
||||
console.log(`✅ OTP ส่งไปที่ ${value.email}`);
|
||||
this.myActData = result.data;
|
||||
this.dashboardStateService.setStateAccountResult(this.myActData);
|
||||
}else{
|
||||
this.generalService.trowApi(result);
|
||||
}
|
||||
},
|
||||
error: (error: any) => {
|
||||
this.generalService.trowApi(error);
|
||||
},
|
||||
complete: () => {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
OnSetupDashboard(value: any, setupFirst: boolean): void {
|
||||
const uri = '/api/web/accountingSetup';
|
||||
let request = {
|
||||
token: value
|
||||
}
|
||||
this.generalService.postRequest(uri, request).subscribe({
|
||||
next: (result: any) => {
|
||||
if (result.code === '200') {
|
||||
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: () => {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
OnSearchSum(value: any, setupFirst: boolean): void {
|
||||
const uri = '/api/web/accountingSum';
|
||||
let request = {
|
||||
token: value
|
||||
}
|
||||
this.generalService.postRequest(uri, request).subscribe({
|
||||
next: (result: any) => {
|
||||
if (result.code === '200') {
|
||||
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: () => {
|
||||
|
||||
@@ -94,24 +139,24 @@ export class MainDashboardContentComponent implements OnInit {
|
||||
// });
|
||||
// }
|
||||
|
||||
processChartData(data: any[]): void {
|
||||
const labels = data.map(item => item.month);
|
||||
const revenues = data.map(item => item.revenue);
|
||||
// processChartData(data: any[]): void {
|
||||
// const labels = data.map(item => item.month);
|
||||
// const revenues = data.map(item => item.revenue);
|
||||
|
||||
this.lineChartData.labels = labels;
|
||||
this.lineChartData.datasets[0].data = revenues;
|
||||
// this.lineChartData.labels = labels;
|
||||
// this.lineChartData.datasets[0].data = revenues;
|
||||
|
||||
this.chart?.update();
|
||||
}
|
||||
// this.chart?.update();
|
||||
// }
|
||||
|
||||
setupPlaceholderData(): void {
|
||||
// This function is called if the API fails, to show a sample graph.
|
||||
const labels = ['January', 'February', 'March', 'April', 'May', 'June'];
|
||||
const revenues = [1200, 1900, 3000, 5000, 2300, 3200]; // Sample data
|
||||
// setupPlaceholderData(): void {
|
||||
// // This function is called if the API fails, to show a sample graph.
|
||||
// const labels = ['January', 'February', 'March', 'April', 'May', 'June'];
|
||||
// const revenues = [1200, 1900, 3000, 5000, 2300, 3200]; // Sample data
|
||||
|
||||
this.lineChartData.labels = labels;
|
||||
this.lineChartData.datasets[0].data = revenues;
|
||||
// this.lineChartData.labels = labels;
|
||||
// this.lineChartData.datasets[0].data = revenues;
|
||||
|
||||
this.chart?.update();
|
||||
}
|
||||
// this.chart?.update();
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -8,6 +8,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';
|
||||
|
||||
@@ -16,7 +17,8 @@ import { MainDashboardContentComponent } from '../../content/main-dashboard-cont
|
||||
@NgModule({
|
||||
declarations: [
|
||||
MainDashboardComponent,
|
||||
MainDashboardContentComponent
|
||||
MainDashboardContentComponent,
|
||||
AccDateFormatPipe
|
||||
// MainReportComponent
|
||||
],
|
||||
imports: [
|
||||
@@ -24,6 +26,9 @@ import { MainDashboardContentComponent } from '../../content/main-dashboard-cont
|
||||
MainControlRoutingModule,
|
||||
ReactiveFormsModule
|
||||
// BrowserAnimationsModule
|
||||
],
|
||||
exports: [
|
||||
AccDateFormatPipe
|
||||
]
|
||||
})
|
||||
export class MainControlModule { }
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { MainReportComponent } from '../../component/main-report/main-report.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: MainReportComponent
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class ReportControlRoutingModule { }
|
||||
@@ -0,0 +1,18 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MainReportComponent } from '../../component/main-report/main-report.component';
|
||||
import { ReportControlRoutingModule } from './report-control-routing.module';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
MainReportComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
ReportControlRoutingModule,
|
||||
ReactiveFormsModule
|
||||
]
|
||||
})
|
||||
export class ReportControlModule { }
|
||||
@@ -0,0 +1,94 @@
|
||||
export interface IStateDrop {
|
||||
income: IDropAct[];
|
||||
expense: IDropAct[];
|
||||
}
|
||||
|
||||
export interface IDropAct {
|
||||
dtlnam?: string,
|
||||
dtlcod?: string
|
||||
}
|
||||
|
||||
export interface IActData {
|
||||
actseq?: number,
|
||||
actnum?: number,
|
||||
acttyp?: string,
|
||||
acttypnam?: string,
|
||||
actcatnam?: string,
|
||||
actqty?: number,
|
||||
actcmt?: string,
|
||||
actacpdtm?: string
|
||||
}
|
||||
export interface IStateResultResponse {
|
||||
data: IStateDrop;
|
||||
}
|
||||
|
||||
export interface IStateResultResponse {
|
||||
data: IStateDrop;
|
||||
}
|
||||
|
||||
|
||||
export interface IActSumData {
|
||||
summary: IActSummary;
|
||||
pie: IActPie;
|
||||
}
|
||||
|
||||
export interface IActSummary {
|
||||
totalIncome: string;
|
||||
totalExpense: string;
|
||||
netProfit: string;
|
||||
profitRate: string;
|
||||
adjustedProfitRate: string;
|
||||
period: string;
|
||||
}
|
||||
|
||||
export interface IActPie {
|
||||
expense: IActCategory[];
|
||||
income: IActCategory[];
|
||||
}
|
||||
|
||||
export interface IActCategory {
|
||||
label: string;
|
||||
value: number;
|
||||
percent: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
// ข้อมูลสินค้าหลัก
|
||||
// export interface IProduct {
|
||||
// id: string;
|
||||
// name: string;
|
||||
// price: number;
|
||||
// category: string;
|
||||
// inStock: boolean;
|
||||
// description?: string; // optional
|
||||
// imageUrl?: string;
|
||||
// tags: string[];
|
||||
// createdAt: Date;
|
||||
// updatedAt: Date;
|
||||
// }
|
||||
|
||||
// // ข้อมูลสินค้าแบบย่อ (ใช้ในรายการ)
|
||||
// export interface IProductSummary {
|
||||
// id: string;
|
||||
// name: string;
|
||||
// price: number;
|
||||
// imageUrl?: string;
|
||||
// inStock: boolean;
|
||||
// }
|
||||
|
||||
// // ข้อมูลสำหรับฟอร์ม
|
||||
// export interface IProductForm {
|
||||
// name: string;
|
||||
// price: number;
|
||||
// category: string;
|
||||
// description?: string;
|
||||
// inStock: boolean;
|
||||
// }
|
||||
|
||||
// // ข้อมูลการจัดหมวดหมู่
|
||||
// export interface IProductCategory {
|
||||
// id: string;
|
||||
// name: string;
|
||||
// parentId?: string;
|
||||
// productCount: number;
|
||||
// }
|
||||
24
accounting-ng-nuttakit/src/app/pipe/dtmtodatetime.pipe.ts
Normal file
24
accounting-ng-nuttakit/src/app/pipe/dtmtodatetime.pipe.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'dtmtodatetime',
|
||||
standalone: false
|
||||
})
|
||||
export class AccDateFormatPipe implements PipeTransform {
|
||||
|
||||
transform(value: string | number): string {
|
||||
if (value === null || value === undefined) return '';
|
||||
|
||||
const str = value.toString();
|
||||
if (str.length !== 12) return str;
|
||||
|
||||
const dd = str.slice(0, 2);
|
||||
const mm = str.slice(2, 4);
|
||||
const yyyy = str.slice(4, 8);
|
||||
const hh = str.slice(8, 10);
|
||||
const min = str.slice(10, 12);
|
||||
|
||||
return `${dd}/${mm}/${yyyy} ${hh}:${min}`;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 ?? 'เกิดข้อผิดพลาดภายในเซิร์ฟเวอร์'
|
||||
})); })
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { IDropAct, IStateDrop, IActData, IActSumData } from '../../interfaces/dashboard.interface';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class DashboardStateService {
|
||||
// ประกาศ BehaviorSubject ด้วย Interface
|
||||
private dashboardState = new BehaviorSubject<IStateDrop | null>(null);
|
||||
private accounttingState = new BehaviorSubject<IActData[] | null>(null);
|
||||
private actsumState = new BehaviorSubject<IActSumData | null>(null);
|
||||
|
||||
// ส่ง Observable ไปให้ components subscribe
|
||||
getStateResult(): Observable<IStateDrop | null> {
|
||||
return this.dashboardState.asObservable();
|
||||
}
|
||||
|
||||
// เซ็ท state
|
||||
setStateResult(dashboard: IStateDrop): void {
|
||||
this.dashboardState.next(dashboard);
|
||||
}
|
||||
|
||||
|
||||
setStateAccountResult(dashboard: IActData[]): void {
|
||||
this.accounttingState.next(dashboard);
|
||||
}
|
||||
|
||||
|
||||
setStateSumResult(sumResult: IActSumData): void {
|
||||
this.actsumState.next(sumResult);
|
||||
}
|
||||
|
||||
// เคลียร์ state
|
||||
clearState(): void {
|
||||
this.dashboardState.next(null);
|
||||
}
|
||||
|
||||
getStateAccountResult(): Observable<IActData[] | null> {
|
||||
return this.accounttingState.asObservable();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
getStateSumResult(): Observable<IActSumData | null> {
|
||||
return this.actsumState.asObservable();
|
||||
}
|
||||
// ดึงค่า current state (ไม่ใช่ observable)
|
||||
// getCurrentState(): IDropAct | null {
|
||||
// return this.dashboardState.value;
|
||||
// }
|
||||
}
|
||||
Reference in New Issue
Block a user