diff --git a/.gitea/workflows/build-image.yml b/.gitea/workflows/build-image.yml
new file mode 100644
index 0000000..aa6eff0
--- /dev/null
+++ b/.gitea/workflows/build-image.yml
@@ -0,0 +1,9 @@
+name: Build Docker Image
+run-name: Build Docker Image
+on: [push]
+jobs:
+ Preparing Dependecies:
+ steps:
+ - run: |
+ ls
+
diff --git a/accounting-ng-nuttakit/src/app/app-routing.module.ts b/accounting-ng-nuttakit/src/app/app-routing.module.ts
index dd8c2a1..61ac5f6 100644
--- a/accounting-ng-nuttakit/src/app/app-routing.module.ts
+++ b/accounting-ng-nuttakit/src/app/app-routing.module.ts
@@ -22,6 +22,13 @@ const routes: Routes = [
(m) => m.MainControlModule
),
},
+ // {
+ // path: 'report',
+ // loadChildren: () =>
+ // import('./controls/report-control/report-control.module').then(
+ // (m) => m.ReportControlModule
+ // ),
+ // },
],
},
diff --git a/accounting-ng-nuttakit/src/app/component/main-dashboard/main-dashboard.component.html b/accounting-ng-nuttakit/src/app/component/main-dashboard/main-dashboard.component.html
index 061614f..0044269 100644
--- a/accounting-ng-nuttakit/src/app/component/main-dashboard/main-dashboard.component.html
+++ b/accounting-ng-nuttakit/src/app/component/main-dashboard/main-dashboard.component.html
@@ -203,7 +203,7 @@
รวมเดือนนี้
-
฿732K
+
{{myActSumData.summary.totalExpense}}
diff --git a/accounting-ng-nuttakit/src/app/component/main-report/main-report.component.css b/accounting-ng-nuttakit/src/app/component/main-report/main-report.component.css
new file mode 100644
index 0000000..9a96ffa
--- /dev/null
+++ b/accounting-ng-nuttakit/src/app/component/main-report/main-report.component.css
@@ -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;
+ }
+}
diff --git a/accounting-ng-nuttakit/src/app/component/main-report/main-report.component.html b/accounting-ng-nuttakit/src/app/component/main-report/main-report.component.html
new file mode 100644
index 0000000..99bb809
--- /dev/null
+++ b/accounting-ng-nuttakit/src/app/component/main-report/main-report.component.html
@@ -0,0 +1,142 @@
+
+
+
+
+
+ {{ card.label }}
+ {{ card.value }}
+ {{ card.detail }}
+
+
+
+
+
+
+
+
+
+ วันที่
+ เลขที่เอกสาร
+ หัวข้อ
+ หมวดหมู่
+ ยอดเงิน
+
+
+ {{ record.date }}
+ {{ record.doc }}
+
+ {{ record.topic }}
+ {{ record.type === 'income' ? 'รายรับ' : 'รายจ่าย' }}
+
+ {{ record.category }}
+ {{ record.displayAmount }}
+
+
+
+
+
+
+
+
+
+ -
+
+
+
{{ part.label }}
+
{{ part.value }}%
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+ {{ part.label }} · {{ part.value }}%
+
+
+
+
+
+
+
+ | วันที่ |
+ เลขที่ |
+ หัวข้อ |
+ หมวดหมู่ |
+ ยอดเงิน |
+
+
+
+
+ | {{ record.date }} |
+ {{ record.doc }} |
+ {{ record.topic }} |
+ {{ record.category }} |
+ {{ record.displayAmount }} |
+
+
+
+
+
+
diff --git a/accounting-ng-nuttakit/src/app/component/main-report/main-report.component.ts b/accounting-ng-nuttakit/src/app/component/main-report/main-report.component.ts
new file mode 100644
index 0000000..1ca16fc
--- /dev/null
+++ b/accounting-ng-nuttakit/src/app/component/main-report/main-report.component.ts
@@ -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);
+ }
+}
diff --git a/accounting-ng-nuttakit/src/app/content/login-content/login-content.component.ts b/accounting-ng-nuttakit/src/app/content/login-content/login-content.component.ts
index 5918822..9bc734e 100644
--- a/accounting-ng-nuttakit/src/app/content/login-content/login-content.component.ts
+++ b/accounting-ng-nuttakit/src/app/content/login-content/login-content.component.ts
@@ -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);
}
});
}
diff --git a/accounting-ng-nuttakit/src/app/content/main-dashboard-content/main-dashboard-content.component.ts b/accounting-ng-nuttakit/src/app/content/main-dashboard-content/main-dashboard-content.component.ts
index 3a049ed..7ac22b0 100644
--- a/accounting-ng-nuttakit/src/app/content/main-dashboard-content/main-dashboard-content.component.ts
+++ b/accounting-ng-nuttakit/src/app/content/main-dashboard-content/main-dashboard-content.component.ts
@@ -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: () => {
diff --git a/accounting-ng-nuttakit/src/app/controls/main-control/main-control-routing.module.ts b/accounting-ng-nuttakit/src/app/controls/main-control/main-control-routing.module.ts
index 632626a..285e146 100644
--- a/accounting-ng-nuttakit/src/app/controls/main-control/main-control-routing.module.ts
+++ b/accounting-ng-nuttakit/src/app/controls/main-control/main-control-routing.module.ts
@@ -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',
diff --git a/accounting-ng-nuttakit/src/app/controls/main-control/main-control.module.ts b/accounting-ng-nuttakit/src/app/controls/main-control/main-control.module.ts
index 6bf2214..ce71e45 100644
--- a/accounting-ng-nuttakit/src/app/controls/main-control/main-control.module.ts
+++ b/accounting-ng-nuttakit/src/app/controls/main-control/main-control.module.ts
@@ -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
],
diff --git a/accounting-ng-nuttakit/src/app/services/generalservice.ts b/accounting-ng-nuttakit/src/app/services/generalservice.ts
index 0d5376c..3be2af2 100644
--- a/accounting-ng-nuttakit/src/app/services/generalservice.ts
+++ b/accounting-ng-nuttakit/src/app/services/generalservice.ts
@@ -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 ?? 'เกิดข้อผิดพลาดภายในเซิร์ฟเวอร์'
+ })); })
);
}