ng-tablekit 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,75 @@
1
+ <div class="template-code-actions"><app-copy-template-button [sourcePaths]="[{ name: 'Report Page', path: 'report-page/report-page' }]" />
2
+ </div>
3
+ <section class="template-page">
4
+ <div class="table-toolbar">
5
+ <div class="table-toolbar__left"><button class="back-btn" title="Back" aria-label="Go back" type="button" (click)="goBack()"><span nz-icon
6
+ nzType="arrow-left"></span></button>
7
+ <h1 class="page-heading">{{ reportTitle }}</h1>
8
+ </div>
9
+ <div class="table-toolbar__right">
10
+ @if(showFilter) { <button nz-button class="compact-btn" type="button" (click)="toggleFilter()"><span nz-icon
11
+ nzType="filter"></span> Filter</button> }
12
+ @if(showExcel) { <button nz-button nzType="primary" class="compact-btn" type="button"
13
+ (click)="downloadExcel()"><span nz-icon nzType="file-excel"></span>
14
+ Excel</button> }
15
+ @if(showPdf) { <button nz-button nzType="primary" class="compact-btn" type="button" (click)="downloadPdf()"><span
16
+ nz-icon nzType="file-pdf"></span> PDF</button> }
17
+ <nz-form-item class="search-field"><nz-input-group nzSearch [nzAddOnAfter]="searchButton"><input type="text"
18
+ nz-input (keydown.enter)="onSearch()" placeholder="Search Records"
19
+ [(ngModel)]="searchText" /></nz-input-group><ng-template #searchButton><button nz-button
20
+ class="compact-btn icon-only" nzType="primary" type="button" (click)="onSearch()"
21
+ aria-label="Search records"><span nz-icon nzType="search"></span></button></ng-template></nz-form-item>
22
+ </div>
23
+ </div>
24
+ @if(filterVisible){
25
+ <form class="filter-panel" [formGroup]="filterForm">
26
+ <div class="filter-panel__grid">
27
+ @if (showCategoryFilter) {
28
+ <div class="filter-panel__field"><label>Category</label><nz-select formControlName="category" nzAllowClear
29
+ nzPlaceHolder="Select category">@for(option of categories;track
30
+ option){<nz-option [nzLabel]="option" [nzValue]="option" />}</nz-select></div>
31
+ }
32
+ @if (showMultiselectFilter) {
33
+ <div class="filter-panel__field"><label>Categories (Multi)</label><nz-select formControlName="categories" nzMode="multiple" nzAllowClear
34
+ nzPlaceHolder="Select categories">@for(option of categories;track
35
+ option){<nz-option [nzLabel]="option" [nzValue]="option" />}</nz-select></div>
36
+ }
37
+ @if (showDateFilter) {
38
+ <div class="filter-panel__field"><label>Created Date</label><nz-date-picker formControlName="date" nzPlaceHolder="Select date"></nz-date-picker></div>
39
+ }
40
+ @if (showDateRangeFilter) {
41
+ <div class="filter-panel__field"><label>Date Range</label><nz-range-picker formControlName="dateRange"></nz-range-picker></div>
42
+ }
43
+ @if (showRadioFilter) {
44
+ <div class="filter-panel__field"><label>Status</label><nz-radio-group formControlName="status">
45
+ @for(option of statuses;track option){<label nz-radio [nzValue]="option">{{option}}</label>}
46
+ </nz-radio-group></div>
47
+ }
48
+ </div>
49
+ <div class="filter-panel__actions"><button nz-button class="compact-btn" type="button"
50
+ (click)="clearFilters()">Clear</button><button nz-button nzType="primary" class="compact-btn" type="button"
51
+ (click)="applyFilters()">Apply Filters</button></div>
52
+ </form>
53
+ }
54
+ <nz-table #table nzBordered nzSize="small" [nzData]="records" [(nzPageIndex)]="pageIndex" [nzPageSize]="10"
55
+ [nzShowSizeChanger]="true" [nzPageSizeOptions]="pageSizeOptions" [nzScroll]="{ y: '55vh' }"
56
+ (nzQueryParams)="onQueryParamsChange($event)">
57
+ <thead>
58
+ <tr>@for(column of columns; track column.key){
59
+ @if(column.sortable === true){
60
+ <th [nzWidth]="column.width ?? null" [nzSortFn]="sortBy(column.key)">{{column.title}}</th>
61
+ } @else {
62
+ <th [nzWidth]="column.width ?? null">{{column.title}}</th>
63
+ }
64
+ }
65
+ </tr>
66
+ </thead>
67
+ <tbody>@for(record of table.data; track record.id){<tr>@for(column of
68
+ columns; track column.key){<td [nzAlign]="column.align || 'left'">
69
+ @switch(column.type){@case('date'){ {{
70
+ formatDateValue(record[column.key]) }} }@case('status'){<nz-tag class="status-tag"
71
+ [nzColor]="getStatusColor(record.status)">{{record.status}}</nz-tag>}@case('currency'){ {{ getCurrencyValue(record[column.key]) | currency }} }@default{
72
+ {{record[column.key]}} }}
73
+ </td>}</tr>}</tbody>
74
+ </nz-table>
75
+ </section>
@@ -0,0 +1,174 @@
1
+ import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
2
+ import { CurrencyPipe, Location } from '@angular/common';
3
+ import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms';
4
+ import { ArrowLeftOutline, FileExcelOutline, FilePdfOutline, FilterOutline, SearchOutline } from '@ant-design/icons-angular/icons';
5
+ import { jsPDF } from 'jspdf';
6
+ import * as XLSX from 'xlsx';
7
+ import { NzButtonModule } from 'ng-zorro-antd/button';
8
+ import { NzFormModule } from 'ng-zorro-antd/form';
9
+ import { NzIconModule, provideNzIconsPatch } from 'ng-zorro-antd/icon';
10
+ import { NzInputModule } from 'ng-zorro-antd/input';
11
+ import { NzSelectModule } from 'ng-zorro-antd/select';
12
+ import { NzTableModule, NzTableQueryParams } from 'ng-zorro-antd/table';
13
+ import { NzTagModule } from 'ng-zorro-antd/tag';
14
+ import { NzDatePickerModule } from 'ng-zorro-antd/date-picker';
15
+ import { NzRadioModule } from 'ng-zorro-antd/radio';
16
+ import { PAGE_SIZE_OPTIONS, STANDARD_COLUMNS } from '@core/config/table.config';
17
+ import { TABLE_RECORDS } from '@core/data/record.data';
18
+ import { TableRecord } from '@core/models/table.models';
19
+ import { createColumnSortFn, formatDateValue, getStatusColor, globalRecordSearch } from '@core/utils/table.utils';
20
+ import { CopyTemplateButtonComponent } from '@shared/copy-template-button/copy-template-button.component';
21
+
22
+ @Component({
23
+ selector: 'app-report-page',
24
+ standalone: true,
25
+ imports: [CurrencyPipe, FormsModule, ReactiveFormsModule, NzButtonModule, NzFormModule, NzIconModule, NzInputModule, NzSelectModule, NzTableModule, NzTagModule, NzDatePickerModule, NzRadioModule, CopyTemplateButtonComponent],
26
+ providers: [...provideNzIconsPatch([ArrowLeftOutline, FileExcelOutline, FilePdfOutline, FilterOutline, SearchOutline])],
27
+ templateUrl: './report-page.component.html',
28
+ styleUrl: './report-page.component.css',
29
+ changeDetection: ChangeDetectionStrategy.OnPush
30
+ })
31
+ export class ReportPageComponent {
32
+ private readonly fb = inject(FormBuilder);
33
+ private readonly location = inject(Location);
34
+ protected readonly columns = STANDARD_COLUMNS;
35
+ protected readonly pageSizeOptions = PAGE_SIZE_OPTIONS;
36
+ protected readonly getStatusColor = getStatusColor;
37
+ protected readonly formatDateValue = formatDateValue;
38
+ protected readonly sortBy = createColumnSortFn<TableRecord>;
39
+ protected readonly categories = ['Category A', 'Category B', 'Category C', 'Category D'];
40
+ protected readonly statuses = ['Active', 'Inactive', 'Pending', 'Approved', 'Rejected'];
41
+
42
+ protected reportTitle = 'Report Page';
43
+ protected showFilter = true;
44
+ protected showExcel = true;
45
+ protected showPdf = true;
46
+ protected showCategoryFilter = true;
47
+ protected showMultiselectFilter = true;
48
+ protected showDateFilter = true;
49
+ protected showDateRangeFilter = true;
50
+ protected showRadioFilter = true;
51
+
52
+ protected readonly filterForm = this.fb.nonNullable.group({
53
+ category: '',
54
+ categories: [[] as string[]],
55
+ date: [null as Date | null],
56
+ dateRange: [[] as Date[]],
57
+ status: ''
58
+ });
59
+ protected filterVisible = false;
60
+ protected searchText = '';
61
+ protected pageIndex = 1;
62
+ protected records: TableRecord[] = TABLE_RECORDS;
63
+ protected currentSort: { key: string | null; value: string | null } = { key: null, value: null };
64
+
65
+ protected toggleFilter(): void { this.filterVisible = !this.filterVisible; }
66
+ protected goBack(): void { this.location.back(); }
67
+ protected getCurrencyValue(value: unknown): string | number {
68
+ return typeof value === 'string' || typeof value === 'number' ? value : 0;
69
+ }
70
+ protected onSearch(): void { this.applyQuery(); }
71
+ protected applyFilters(): void {
72
+ this.applyQuery();
73
+ this.filterVisible = false;
74
+ }
75
+ protected clearFilters(): void {
76
+ this.filterForm.reset({ category: '', categories: [], date: null, dateRange: [], status: '' });
77
+ this.applyQuery();
78
+ this.filterVisible = false;
79
+ }
80
+
81
+ protected onQueryParamsChange(params: NzTableQueryParams): void {
82
+ const sort = params.sort.find((item: any) => item.value !== null);
83
+ this.currentSort = { key: sort?.key ?? null, value: sort?.value ?? null };
84
+ }
85
+
86
+ private getSortedRecords(): TableRecord[] {
87
+ if (!this.currentSort.key || !this.currentSort.value) return this.records;
88
+ return [...this.records].sort((a: any, b: any) => {
89
+ const valA = a[this.currentSort.key!];
90
+ const valB = b[this.currentSort.key!];
91
+ if (valA === valB) return 0;
92
+ const isAsc = this.currentSort.value === 'ascend';
93
+ return valA < valB ? (isAsc ? -1 : 1) : (isAsc ? 1 : -1);
94
+ });
95
+ }
96
+
97
+ protected downloadExcel(): void {
98
+ const rows = this.getSortedRecords().map(record => ({
99
+ Name: record.name,
100
+ Code: record.code,
101
+ Category: record.category,
102
+ 'Created Date': formatDateValue(record.createdDate),
103
+ Status: record.status
104
+ }));
105
+ const sheet = XLSX.utils.json_to_sheet(rows);
106
+ const workbook = XLSX.utils.book_new();
107
+ XLSX.utils.book_append_sheet(workbook, sheet, 'Report');
108
+ XLSX.writeFile(workbook, 'report-page.xlsx');
109
+ }
110
+
111
+ protected downloadPdf(): void {
112
+ const document = new jsPDF({ orientation: 'landscape', unit: 'mm', format: 'a4' });
113
+ const columns = [
114
+ { label: 'Name', x: 14, width: 58 },
115
+ { label: 'Code', x: 75, width: 34 },
116
+ { label: 'Category', x: 112, width: 42 },
117
+ { label: 'Created Date', x: 157, width: 38 },
118
+ { label: 'Status', x: 198, width: 34 }
119
+ ];
120
+ const drawHeader = (): number => {
121
+ document.setFont('helvetica', 'bold');
122
+ document.setFontSize(16);
123
+ document.text(this.reportTitle, 14, 16);
124
+ document.setFontSize(10);
125
+ columns.forEach(column => document.text(column.label, column.x, 26));
126
+ document.line(14, 29, 232, 29);
127
+ document.setFont('helvetica', 'normal');
128
+ return 35;
129
+ };
130
+ let y = drawHeader();
131
+ for (const record of this.getSortedRecords()) {
132
+ if (y > 195) {
133
+ document.addPage();
134
+ y = drawHeader();
135
+ }
136
+ const values = [record.name, record.code, record.category, formatDateValue(record.createdDate), record.status];
137
+ values.forEach((value, index) => {
138
+ const column = columns[index];
139
+ const text = document.splitTextToSize(String(value), column.width)[0] ?? '';
140
+ document.text(text, column.x, y);
141
+ });
142
+ y += 7;
143
+ }
144
+ document.save('report-page.pdf');
145
+ }
146
+
147
+ private applyQuery(): void {
148
+ this.pageIndex = 1;
149
+ const search = this.searchText.trim();
150
+ const { category, categories, date, dateRange, status } = this.filterForm.getRawValue();
151
+ this.records = TABLE_RECORDS.filter(record => {
152
+ let pass = true;
153
+ if (!globalRecordSearch(record, search)) pass = false;
154
+ if (category && record.category !== category) pass = false;
155
+ if (categories && categories.length > 0 && !categories.includes(record.category)) pass = false;
156
+ if (status && record.status !== status) pass = false;
157
+
158
+ if (date) {
159
+ const recordDate = new Date(record.createdDate).setHours(0, 0, 0, 0);
160
+ const filterDate = new Date(date).setHours(0, 0, 0, 0);
161
+ if (recordDate !== filterDate) pass = false;
162
+ }
163
+
164
+ if (dateRange && dateRange.length === 2) {
165
+ const recordDate = new Date(record.createdDate).setHours(0, 0, 0, 0);
166
+ const start = new Date(dateRange[0]).setHours(0, 0, 0, 0);
167
+ const end = new Date(dateRange[1]).setHours(0, 0, 0, 0);
168
+ if (recordDate < start || recordDate > end) pass = false;
169
+ }
170
+
171
+ return pass;
172
+ });
173
+ }
174
+ }