@duyanhdev/mvp-ifs-ui-kit 21.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.
Files changed (170) hide show
  1. package/.editorconfig +16 -0
  2. package/.gitmodules +3 -0
  3. package/.postcssrc.json +5 -0
  4. package/.prettierignore +14 -0
  5. package/.prettierrc.json +29 -0
  6. package/LICENSE.md +21 -0
  7. package/README.md +59 -0
  8. package/angular.json +98 -0
  9. package/eslint.config.js +89 -0
  10. package/package.json +59 -0
  11. package/public/demo/images/flag/flag_placeholder.png +0 -0
  12. package/public/demo/images/footer-image.gif +0 -0
  13. package/public/demo/images/galleria/galleria1.jpg +0 -0
  14. package/public/demo/images/galleria/galleria10.jpg +0 -0
  15. package/public/demo/images/galleria/galleria10s.jpg +0 -0
  16. package/public/demo/images/galleria/galleria11.jpg +0 -0
  17. package/public/demo/images/galleria/galleria11s.jpg +0 -0
  18. package/public/demo/images/galleria/galleria12.jpg +0 -0
  19. package/public/demo/images/galleria/galleria12s.jpg +0 -0
  20. package/public/demo/images/galleria/galleria13.jpg +0 -0
  21. package/public/demo/images/galleria/galleria13s.jpg +0 -0
  22. package/public/demo/images/galleria/galleria14.jpg +0 -0
  23. package/public/demo/images/galleria/galleria14s.jpg +0 -0
  24. package/public/demo/images/galleria/galleria15.jpg +0 -0
  25. package/public/demo/images/galleria/galleria15s.jpg +0 -0
  26. package/public/demo/images/galleria/galleria1s.jpg +0 -0
  27. package/public/demo/images/galleria/galleria2.jpg +0 -0
  28. package/public/demo/images/galleria/galleria2s.jpg +0 -0
  29. package/public/demo/images/galleria/galleria3.jpg +0 -0
  30. package/public/demo/images/galleria/galleria3s.jpg +0 -0
  31. package/public/demo/images/galleria/galleria4.jpg +0 -0
  32. package/public/demo/images/galleria/galleria4s.jpg +0 -0
  33. package/public/demo/images/galleria/galleria5.jpg +0 -0
  34. package/public/demo/images/galleria/galleria5s.jpg +0 -0
  35. package/public/demo/images/galleria/galleria6.jpg +0 -0
  36. package/public/demo/images/galleria/galleria6s.jpg +0 -0
  37. package/public/demo/images/galleria/galleria7.jpg +0 -0
  38. package/public/demo/images/galleria/galleria7s.jpg +0 -0
  39. package/public/demo/images/galleria/galleria8.jpg +0 -0
  40. package/public/demo/images/galleria/galleria8s.jpg +0 -0
  41. package/public/demo/images/galleria/galleria9.jpg +0 -0
  42. package/public/demo/images/galleria/galleria9s.jpg +0 -0
  43. package/public/demo/images/product/bamboo-watch.jpg +0 -0
  44. package/public/demo/images/product/black-watch.jpg +0 -0
  45. package/public/demo/images/product/blue-band.jpg +0 -0
  46. package/public/demo/images/product/blue-t-shirt.jpg +0 -0
  47. package/public/demo/images/product/bracelet.jpg +0 -0
  48. package/public/demo/images/product/brown-purse.jpg +0 -0
  49. package/public/demo/images/product/chakra-bracelet.jpg +0 -0
  50. package/public/demo/images/product/galaxy-earrings.jpg +0 -0
  51. package/public/demo/images/product/game-controller.jpg +0 -0
  52. package/public/demo/images/product/gaming-set.jpg +0 -0
  53. package/public/demo/images/product/gold-phone-case.jpg +0 -0
  54. package/public/demo/images/product/green-earbuds.jpg +0 -0
  55. package/public/demo/images/product/green-t-shirt.jpg +0 -0
  56. package/public/demo/images/product/grey-t-shirt.jpg +0 -0
  57. package/public/demo/images/product/headphones.jpg +0 -0
  58. package/public/demo/images/product/light-green-t-shirt.jpg +0 -0
  59. package/public/demo/images/product/lime-band.jpg +0 -0
  60. package/public/demo/images/product/mini-speakers.jpg +0 -0
  61. package/public/demo/images/product/painted-phone-case.jpg +0 -0
  62. package/public/demo/images/product/pink-band.jpg +0 -0
  63. package/public/demo/images/product/pink-purse.jpg +0 -0
  64. package/public/demo/images/product/product-placeholder.svg +10 -0
  65. package/public/demo/images/product/purple-band.jpg +0 -0
  66. package/public/demo/images/product/purple-gemstone-necklace.jpg +0 -0
  67. package/public/demo/images/product/purple-t-shirt.jpg +0 -0
  68. package/public/demo/images/product/shoes.jpg +0 -0
  69. package/public/demo/images/product/sneakers.jpg +0 -0
  70. package/public/demo/images/product/teal-t-shirt.jpg +0 -0
  71. package/public/demo/images/product/yellow-earbuds.jpg +0 -0
  72. package/public/demo/images/product/yoga-mat.jpg +0 -0
  73. package/public/demo/images/product/yoga-set.jpg +0 -0
  74. package/src/app/layout/component/configurator/app.configurator.html +48 -0
  75. package/src/app/layout/component/configurator/app.configurator.ts +396 -0
  76. package/src/app/layout/component/floatingconfigurator/app.floatingconfigurator.ts +31 -0
  77. package/src/app/layout/component/footer/app.footer.scss +52 -0
  78. package/src/app/layout/component/footer/app.footer.ts +26 -0
  79. package/src/app/layout/component/layout/app.layout.ts +50 -0
  80. package/src/app/layout/component/menu/app.menu.html +7 -0
  81. package/src/app/layout/component/menu/app.menu.scss +13 -0
  82. package/src/app/layout/component/menu/app.menu.ts +90 -0
  83. package/src/app/layout/component/menuitem/app.menuitem.html +56 -0
  84. package/src/app/layout/component/menuitem/app.menuitem.scss +218 -0
  85. package/src/app/layout/component/menuitem/app.menuitem.ts +126 -0
  86. package/src/app/layout/component/sidebar/app.sidebar.html +3 -0
  87. package/src/app/layout/component/sidebar/app.sidebar.scss +0 -0
  88. package/src/app/layout/component/sidebar/app.sidebar.ts +106 -0
  89. package/src/app/layout/component/topbar/app.topbar.html +190 -0
  90. package/src/app/layout/component/topbar/app.topbar.scss +8 -0
  91. package/src/app/layout/component/topbar/app.topbar.ts +68 -0
  92. package/src/app/layout/service/layout.service.ts +117 -0
  93. package/src/app/pages/auth/access.ts +32 -0
  94. package/src/app/pages/auth/auth.routes.ts +10 -0
  95. package/src/app/pages/auth/error.ts +32 -0
  96. package/src/app/pages/auth/login.ts +71 -0
  97. package/src/app/pages/crud/crud.ts +387 -0
  98. package/src/app/pages/dashboard/dashboard.css +778 -0
  99. package/src/app/pages/dashboard/dashboard.html +191 -0
  100. package/src/app/pages/dashboard/dashboard.ts +348 -0
  101. package/src/app/pages/documentation/documentation.ts +73 -0
  102. package/src/app/pages/empty/empty.ts +11 -0
  103. package/src/app/pages/landing/components/featureswidget.ts +139 -0
  104. package/src/app/pages/landing/components/footerwidget.ts +73 -0
  105. package/src/app/pages/landing/components/herowidget.ts +25 -0
  106. package/src/app/pages/landing/components/highlightswidget.ts +46 -0
  107. package/src/app/pages/landing/components/pricingwidget.ts +119 -0
  108. package/src/app/pages/landing/components/topbarwidget.component.ts +68 -0
  109. package/src/app/pages/landing/landing.ts +31 -0
  110. package/src/app/pages/notfound/notfound.ts +68 -0
  111. package/src/app/pages/pages.routes.ts +17 -0
  112. package/src/app/pages/profile/profile.html +57 -0
  113. package/src/app/pages/profile/profile.scss +145 -0
  114. package/src/app/pages/profile/profile.ts +19 -0
  115. package/src/app/pages/service/country.service.ts +255 -0
  116. package/src/app/pages/service/customer.service.ts +9057 -0
  117. package/src/app/pages/service/icon.service.ts +23 -0
  118. package/src/app/pages/service/node.service.ts +816 -0
  119. package/src/app/pages/service/photo.service.ts +103 -0
  120. package/src/app/pages/service/product.service.ts +1322 -0
  121. package/src/app/pages/tickets/tickets-create/tickets-create.html +140 -0
  122. package/src/app/pages/tickets/tickets-create/tickets-create.scss +617 -0
  123. package/src/app/pages/tickets/tickets-create/tickets-create.ts +104 -0
  124. package/src/app/pages/tickets/tickets-list/ticket-list.html +150 -0
  125. package/src/app/pages/tickets/tickets-list/ticket-list.scss +392 -0
  126. package/src/app/pages/tickets/tickets-list/ticket-list.ts +178 -0
  127. package/src/app/pages/uikit/buttondemo.ts +254 -0
  128. package/src/app/pages/uikit/chartdemo.ts +290 -0
  129. package/src/app/pages/uikit/filedemo.ts +52 -0
  130. package/src/app/pages/uikit/formlayoutdemo.ts +129 -0
  131. package/src/app/pages/uikit/inputdemo.ts +339 -0
  132. package/src/app/pages/uikit/listdemo.ts +217 -0
  133. package/src/app/pages/uikit/mediademo.ts +1021 -0
  134. package/src/app/pages/uikit/menudemo.ts +540 -0
  135. package/src/app/pages/uikit/messagesdemo.ts +101 -0
  136. package/src/app/pages/uikit/miscdemo.ts +192 -0
  137. package/src/app/pages/uikit/overlaydemo.ts +235 -0
  138. package/src/app/pages/uikit/panelsdemo.ts +235 -0
  139. package/src/app/pages/uikit/tabledemo.ts +568 -0
  140. package/src/app/pages/uikit/timelinedemo.ts +141 -0
  141. package/src/app/pages/uikit/treedemo.ts +75 -0
  142. package/src/app/pages/uikit/uikit.routes.ts +35 -0
  143. package/src/app.component.ts +22 -0
  144. package/src/app.config.ts +23 -0
  145. package/src/app.routes.ts +23 -0
  146. package/src/assets/demo/code.scss +17 -0
  147. package/src/assets/demo/demo.scss +2 -0
  148. package/src/assets/demo/flags/flags.css +984 -0
  149. package/src/assets/layout/_core.scss +24 -0
  150. package/src/assets/layout/_footer.scss +8 -0
  151. package/src/assets/layout/_main.scss +21 -0
  152. package/src/assets/layout/_menu.scss +159 -0
  153. package/src/assets/layout/_mixins.scss +15 -0
  154. package/src/assets/layout/_preloading.scss +47 -0
  155. package/src/assets/layout/_responsive.scss +111 -0
  156. package/src/assets/layout/_topbar.scss +201 -0
  157. package/src/assets/layout/_typography.scss +68 -0
  158. package/src/assets/layout/_utils.scss +25 -0
  159. package/src/assets/layout/layout.scss +13 -0
  160. package/src/assets/layout/variables/_common.scss +21 -0
  161. package/src/assets/layout/variables/_dark.scss +5 -0
  162. package/src/assets/layout/variables/_light.scss +5 -0
  163. package/src/assets/styles.scss +4 -0
  164. package/src/assets/tailwind.css +32 -0
  165. package/src/index.html +15 -0
  166. package/src/main.ts +5 -0
  167. package/tsconfig.app.json +15 -0
  168. package/tsconfig.json +33 -0
  169. package/tsconfig.spec.json +15 -0
  170. package/vercel.json +9 -0
@@ -0,0 +1,178 @@
1
+ import { Component, OnInit } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { FormsModule } from '@angular/forms';
4
+
5
+ export type TicketStatus = 'open' | 'progress' | 'done';
6
+ export type TicketPriority = 'high' | 'medium' | 'low';
7
+
8
+ export interface Ticket {
9
+ id: string;
10
+ title: string;
11
+ sub?: string;
12
+ type: string;
13
+ priority: TicketPriority;
14
+ requester: string;
15
+ dept: string;
16
+ status: TicketStatus;
17
+ created: string;
18
+ }
19
+
20
+ interface FilterOption {
21
+ label: string;
22
+ value: string;
23
+ dotColor?: string;
24
+ }
25
+
26
+ @Component({
27
+ selector: 'app-ticket-list',
28
+ standalone: true,
29
+ imports: [CommonModule, FormsModule],
30
+ templateUrl: './ticket-list.html',
31
+ styleUrls: ['./ticket-list.scss'],
32
+ })
33
+ export class TicketListComponent implements OnInit {
34
+
35
+ // ── State ────────────────────────────────────────────────────
36
+ searchQuery = '';
37
+ activeFilter = 'all';
38
+ currentPage = 1;
39
+ perPage = 10;
40
+
41
+ filters: FilterOption[] = [
42
+ { label: 'Tất cả', value: 'all' },
43
+ { label: 'Mở', value: 'open', dotColor: '#ef4444' },
44
+ { label: 'Xử lý', value: 'progress', dotColor: '#f59e0b' },
45
+ { label: 'Xong', value: 'done', dotColor: '#10b981' },
46
+ ];
47
+
48
+ // ── Data (thay bằng service thực tế) ────────────────────────
49
+ data: Ticket[] = [
50
+ { id:'TK-001', title:'Màn hình không hiển thị', sub:'Dell P2419H', type:'Hardware', priority:'high', requester:'Nguyễn Văn An', dept:'Kế toán', status:'open', created:'2025-07-01' },
51
+ { id:'TK-002', title:'Không kết nối được VPN', sub:'HP EliteBook', type:'Mạng', priority:'high', requester:'Trần Thị Bình', dept:'Kinh doanh', status:'progress', created:'2025-07-02' },
52
+ { id:'TK-003', title:'Cài đặt phần mềm MISA', sub:'PC-KT-03', type:'Software', priority:'medium', requester:'Lê Minh Cường', dept:'Kế toán', status:'done', created:'2025-07-02' },
53
+ { id:'TK-004', title:'Quên mật khẩu Windows', sub:'Lenovo ThinkPad', type:'Tài khoản', priority:'medium', requester:'Phạm Thu Dung', dept:'Nhân sự', status:'done', created:'2025-07-03' },
54
+ { id:'TK-005', title:'Máy in offline', sub:'HP LaserJet M404', type:'Máy in', priority:'low', requester:'Hoàng Văn Em', dept:'Vận hành', status:'open', created:'2025-07-03' },
55
+ { id:'TK-006', title:'Email gửi đi bị spam', sub:'—', type:'Email', priority:'medium', requester:'Vũ Thị Phương', dept:'Marketing', status:'progress', created:'2025-07-04' },
56
+ { id:'TK-007', title:'CPU quá nóng 95°C', sub:'PC-IT-07', type:'Hardware', priority:'high', requester:'Đỗ Quang Hưng', dept:'IT', status:'open', created:'2025-07-05' },
57
+ { id:'TK-008', title:'Mất file sau khi format', sub:'Dell XPS 15', type:'Software', priority:'high', requester:'Ngô Thanh Lan', dept:'Ban giám đốc', status:'progress', created:'2025-07-05' },
58
+ { id:'TK-009', title:'WiFi yếu tầng 3', sub:'—', type:'Mạng', priority:'low', requester:'Bùi Văn Minh', dept:'Vận hành', status:'done', created:'2025-07-06' },
59
+ { id:'TK-010', title:'CRM không mở được', sub:'PC-KD-10', type:'Software', priority:'medium', requester:'Lý Ngọc Nam', dept:'Kinh doanh', status:'open', created:'2025-07-07' },
60
+ { id:'TK-011', title:'Tạo tài khoản nhân viên mới', sub:'—', type:'Tài khoản', priority:'low', requester:'Trần Văn Oai', dept:'Nhân sự', status:'done', created:'2025-07-07' },
61
+ { id:'TK-012', title:'BSOD khi chạy Excel', sub:'PC-KT-12', type:'Hardware', priority:'high', requester:'Đinh Thị Phúc', dept:'Kế toán', status:'progress', created:'2025-07-08' },
62
+ { id:'TK-013', title:'Không vào được portal nội bộ', sub:'—', type:'Mạng', priority:'high', requester:'Cao Minh Quân', dept:'IT', status:'open', created:'2025-07-08' },
63
+ { id:'TK-014', title:'Outlook không đồng bộ lịch', sub:'PC-HR-05', type:'Email', priority:'medium', requester:'Phan Thị Ry', dept:'Nhân sự', status:'progress', created:'2025-07-09' },
64
+ { id:'TK-015', title:'Màn hình thứ 2 không nhận', sub:'Dell U2722', type:'Hardware', priority:'low', requester:'Lê Anh Sơn', dept:'Thiết kế', status:'open', created:'2025-07-09' },
65
+ { id:'TK-016', title:'Không in được file PDF', sub:'HP Color LaserJet', type:'Máy in', priority:'medium', requester:'Nguyễn Thu Thảo', dept:'Marketing', status:'done', created:'2025-07-10' },
66
+ { id:'TK-017', title:'VPN ngắt kết nối liên tục', sub:'—', type:'Mạng', priority:'high', requester:'Trịnh Văn Uy', dept:'Kinh doanh', status:'progress', created:'2025-07-10' },
67
+ { id:'TK-018', title:'Quên mã PIN máy tính bảng', sub:'iPad Pro', type:'Tài khoản', priority:'low', requester:'Bùi Thị Vân', dept:'Ban giám đốc', status:'done', created:'2025-07-11' },
68
+ { id:'TK-019', title:'Phần mềm kế toán bị lỗi', sub:'MISA SME', type:'Software', priority:'high', requester:'Đặng Văn Xuân', dept:'Kế toán', status:'open', created:'2025-07-11' },
69
+ { id:'TK-020', title:'Máy scan không nhận driver', sub:'Canon DR-C240', type:'Máy in', priority:'medium', requester:'Hoàng Thị Yến', dept:'Hành chính', status:'progress', created:'2025-07-12' },
70
+ ];
71
+
72
+ filteredData: Ticket[] = [];
73
+ pagedData: Ticket[] = [];
74
+
75
+ // ── Lifecycle ────────────────────────────────────────────────
76
+ ngOnInit(): void { this.applyFilter(); }
77
+
78
+ // ── Filter / Search ──────────────────────────────────────────
79
+ onSearch(): void { this.currentPage = 1; this.applyFilter(); }
80
+
81
+ setFilter(value: string): void {
82
+ this.activeFilter = value;
83
+ this.currentPage = 1;
84
+ this.applyFilter();
85
+ }
86
+
87
+ private applyFilter(): void {
88
+ const q = this.searchQuery.toLowerCase().trim();
89
+ this.filteredData = this.data.filter(t => {
90
+ const mf = this.activeFilter === 'all' || t.status === this.activeFilter;
91
+ const ms = !q || [t.id, t.title, t.requester, t.dept].some(v => v.toLowerCase().includes(q));
92
+ return mf && ms;
93
+ });
94
+ this.buildPage();
95
+ }
96
+
97
+ // ── Pagination ───────────────────────────────────────────────
98
+ get totalPages(): number {
99
+ return Math.max(1, Math.ceil(this.filteredData.length / this.perPage));
100
+ }
101
+
102
+ get rangeFrom(): number {
103
+ return this.filteredData.length === 0 ? 0 : (this.currentPage - 1) * this.perPage + 1;
104
+ }
105
+
106
+ get rangeTo(): number {
107
+ return Math.min(this.currentPage * this.perPage, this.filteredData.length);
108
+ }
109
+
110
+ get pageRange(): number[] {
111
+ const total = this.totalPages;
112
+ const cur = this.currentPage;
113
+ const set = new Set<number>([1, total, cur]);
114
+ if (cur > 1) set.add(cur - 1);
115
+ if (cur < total) set.add(cur + 1);
116
+ return [...set].sort((a, b) => a - b);
117
+ }
118
+
119
+ goPage(p: number): void {
120
+ if (p < 1 || p > this.totalPages) return;
121
+ this.currentPage = p;
122
+ this.buildPage();
123
+ }
124
+
125
+ onPerPageChange(): void {
126
+ this.currentPage = 1;
127
+ this.buildPage();
128
+ }
129
+
130
+ private buildPage(): void {
131
+ const start = (this.currentPage - 1) * this.perPage;
132
+ this.pagedData = this.filteredData.slice(start, start + this.perPage);
133
+ }
134
+
135
+ // ── Status update ────────────────────────────────────────────
136
+ onStatusChange(ticket: Ticket, event: Event): void {
137
+ const select = event.target as HTMLSelectElement;
138
+ ticket.status = select.value as TicketStatus;
139
+ this.applyFilter();
140
+ }
141
+
142
+ // ── Helpers ──────────────────────────────────────────────────
143
+ private readonly typeClassMap: Record<string, string> = {
144
+ 'Hardware': 'tb-hw',
145
+ 'Software': 'tb-sw',
146
+ 'Mạng': 'tb-net',
147
+ 'Tài khoản': 'tb-acc',
148
+ 'Máy in': 'tb-prt',
149
+ 'Email': 'tb-eml',
150
+ };
151
+
152
+ private readonly statusLabels: Record<TicketStatus, string> = {
153
+ open: 'Mở',
154
+ progress: 'Đang xử lý',
155
+ done: 'Hoàn thành',
156
+ };
157
+
158
+ private readonly priorityLabels: Record<TicketPriority, string> = {
159
+ high: 'Cao',
160
+ medium: 'Trung bình',
161
+ low: 'Thấp',
162
+ };
163
+
164
+ getTypeClass(type: string): string { return this.typeClassMap[type] ?? 'tb-def'; }
165
+ getStatusClass(s: TicketStatus): string { return `st-${s}`; }
166
+ getStatusLabel(s: TicketStatus): string { return this.statusLabels[s] ?? s; }
167
+ getPriorityClass(p: TicketPriority): string { return `pri-${p}`; }
168
+ getPriorityLabel(p: TicketPriority): string { return this.priorityLabels[p] ?? p; }
169
+
170
+ countByStatus(status: TicketStatus): number {
171
+ return this.data.filter(t => t.status === status).length;
172
+ }
173
+
174
+ onView(ticket: Ticket): void {
175
+ console.log('View ticket:', ticket);
176
+ // TODO: navigate hoặc mở dialog chi tiết
177
+ }
178
+ }
@@ -0,0 +1,254 @@
1
+ import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { FormsModule } from '@angular/forms';
4
+ import { MessageService, ConfirmationService } from 'primeng/api';
5
+ import { ButtonModule } from 'primeng/button';
6
+ import { TableModule } from 'primeng/table';
7
+ import { DialogModule } from 'primeng/dialog';
8
+ import { InputTextModule } from 'primeng/inputtext';
9
+ import { InputNumberModule } from 'primeng/inputnumber';
10
+ import { SelectModule } from 'primeng/select';
11
+ import { ToastModule } from 'primeng/toast';
12
+ import { ConfirmDialogModule } from 'primeng/confirmdialog';
13
+ import { TagModule } from 'primeng/tag';
14
+ import { TooltipModule } from 'primeng/tooltip';
15
+ import { IconFieldModule } from 'primeng/iconfield';
16
+ import { InputIconModule } from 'primeng/inputicon';
17
+
18
+ export interface VatTu {
19
+ id: number;
20
+ maVatTu: string;
21
+ tenVatTu: string;
22
+ donViTinh: string;
23
+ soLuong: number;
24
+ donGia: number;
25
+ nhaCungCap: string;
26
+ trangThai: 'Con hang' | 'Het hang' | 'Sap het';
27
+ ngayNhap: Date;
28
+ }
29
+
30
+ @Component({
31
+ selector: 'app-button-demo',
32
+ standalone: true,
33
+ imports: [CommonModule, FormsModule, ButtonModule, TableModule, DialogModule, InputTextModule, InputNumberModule, SelectModule, ToastModule, ConfirmDialogModule, TagModule, TooltipModule, IconFieldModule, InputIconModule],
34
+ providers: [MessageService, ConfirmationService],
35
+ styles: [
36
+ `
37
+ :host ::ng-deep .p-paginator {
38
+ justify-content: flex-end !important;
39
+ }
40
+ `
41
+ ],
42
+ template: ``
43
+ })
44
+ export class ButtonDemo implements OnInit {
45
+ @ViewChild('fileInput') fileInput!: ElementRef;
46
+
47
+ vatTuList: VatTu[] = [];
48
+ selectedVatTu: VatTu[] = [];
49
+ vatTu: VatTu | null = null;
50
+ importPreview: VatTu[] = [];
51
+
52
+ first = 0;
53
+ rows = 10;
54
+ searchValue = '';
55
+
56
+ showDialog = false;
57
+ showImportDialog = false;
58
+ isEditMode = false;
59
+ isViewMode = false;
60
+ dialogTitle = '';
61
+
62
+ donViTinhOptions = ['Cái', 'Hộp', 'Thùng', 'Kg', 'Lít', 'Mét', 'Tấm', 'Cuộn', 'Bộ'];
63
+ nhaCungCapOptions = ['Công ty TNHH Thiên Phú', 'Cty CP Minh Long', 'Đại lý Hoàng Gia', 'Nhà máy Đông Nam', 'Tập đoàn Việt Tiến'];
64
+ trangThaiOptions = [
65
+ { label: 'Còn hàng', value: 'Con hang' },
66
+ { label: 'Hết hàng', value: 'Het hang' },
67
+ { label: 'Sắp hết', value: 'Sap het' }
68
+ ];
69
+
70
+ constructor(
71
+ private messageService: MessageService,
72
+ private confirmationService: ConfirmationService
73
+ ) {}
74
+
75
+ ngOnInit() {
76
+ this.vatTuList = this.getFakeData();
77
+ }
78
+
79
+ // ═══════════════════════════════════════════════════════
80
+ // EXPORT EXCEL
81
+ // ═══════════════════════════════════════════════════════
82
+ exportExcel() {}
83
+
84
+ // ═══════════════════════════════════════════════════════
85
+ // EXPORT PDF
86
+ // ═══════════════════════════════════════════════════════
87
+ exportPDF() {}
88
+
89
+ // ═══════════════════════════════════════════════════════
90
+ // IMPORT EXCEL
91
+ // ═══════════════════════════════════════════════════════
92
+ onFileImport(event: any) {}
93
+
94
+ // ═══════════════════════════════════════════════════════
95
+ // CRUD
96
+ // ═══════════════════════════════════════════════════════
97
+ openNew() {
98
+ this.vatTu = { id: 0, maVatTu: '', tenVatTu: '', donViTinh: 'Cái', soLuong: 0, donGia: 0, nhaCungCap: '', trangThai: 'Con hang', ngayNhap: new Date() };
99
+ this.isEditMode = false;
100
+ this.isViewMode = false;
101
+ this.dialogTitle = 'Thêm vật tư mới';
102
+ this.showDialog = true;
103
+ }
104
+
105
+ viewVatTu(vt: VatTu) {
106
+ this.vatTu = { ...vt };
107
+ this.isViewMode = true;
108
+ this.isEditMode = false;
109
+ this.dialogTitle = 'Chi tiết vật tư';
110
+ this.showDialog = true;
111
+ }
112
+
113
+ editVatTu(vt: VatTu) {
114
+ this.vatTu = { ...vt };
115
+ this.isEditMode = true;
116
+ this.isViewMode = false;
117
+ this.dialogTitle = 'Cập nhật vật tư';
118
+ this.showDialog = true;
119
+ }
120
+
121
+ save() {
122
+ if (!this.vatTu) return;
123
+ if (!this.vatTu.maVatTu || !this.vatTu.tenVatTu || !this.vatTu.nhaCungCap) {
124
+ this.messageService.add({ severity: 'warn', summary: 'Thiếu thông tin', detail: 'Vui lòng điền đầy đủ các trường bắt buộc!' });
125
+ return;
126
+ }
127
+ if (this.isEditMode) {
128
+ const idx = this.vatTuList.findIndex((v) => v.id === this.vatTu!.id);
129
+ if (idx !== -1) this.vatTuList[idx] = { ...this.vatTu };
130
+ this.vatTuList = [...this.vatTuList];
131
+ this.messageService.add({ severity: 'success', summary: 'Cập nhật thành công', detail: this.vatTu.tenVatTu });
132
+ } else {
133
+ this.vatTu.id = Math.max(0, ...this.vatTuList.map((v) => v.id)) + 1;
134
+ this.vatTu.ngayNhap = new Date();
135
+ this.vatTuList = [...this.vatTuList, { ...this.vatTu }];
136
+ this.messageService.add({ severity: 'success', summary: 'Thêm thành công', detail: this.vatTu.tenVatTu });
137
+ }
138
+ this.hideDialog();
139
+ }
140
+
141
+ deleteVatTu(vt: VatTu) {
142
+ this.confirmationService.confirm({
143
+ message: `Xóa vật tư <b>${vt.tenVatTu}</b>?`,
144
+ header: 'Xác nhận xóa',
145
+ icon: 'pi pi-exclamation-triangle',
146
+ acceptLabel: 'Xóa',
147
+ rejectLabel: 'Hủy',
148
+ acceptButtonStyleClass: 'p-button-danger',
149
+ accept: () => {
150
+ this.vatTuList = this.vatTuList.filter((v) => v.id !== vt.id);
151
+ this.messageService.add({ severity: 'success', summary: 'Đã xóa', detail: vt.tenVatTu });
152
+ }
153
+ });
154
+ }
155
+
156
+ deleteSelected() {
157
+ this.confirmationService.confirm({
158
+ message: `Xóa <b>${this.selectedVatTu.length}</b> vật tư đã chọn?`,
159
+ header: 'Xác nhận xóa nhiều',
160
+ icon: 'pi pi-exclamation-triangle',
161
+ acceptLabel: 'Xóa tất cả',
162
+ rejectLabel: 'Hủy',
163
+ acceptButtonStyleClass: 'p-button-danger',
164
+ accept: () => {
165
+ const ids = new Set(this.selectedVatTu.map((v) => v.id));
166
+ this.vatTuList = this.vatTuList.filter((v) => !ids.has(v.id));
167
+ this.selectedVatTu = [];
168
+ this.messageService.add({ severity: 'success', summary: 'Đã xóa', detail: 'Xóa các vật tư đã chọn thành công.' });
169
+ }
170
+ });
171
+ }
172
+
173
+ hideDialog() {
174
+ this.showDialog = false;
175
+ this.vatTu = null;
176
+ this.isViewMode = false;
177
+ this.isEditMode = false;
178
+ }
179
+
180
+ // ═══════════════════════════════════════════════════════
181
+ // HELPERS
182
+ // ═══════════════════════════════════════════════════════
183
+ private getDateStr() {
184
+ return new Date().toISOString().slice(0, 10).replace(/-/g, '');
185
+ }
186
+
187
+ private parseTrangThai(val: string): 'Con hang' | 'Het hang' | 'Sap het' {
188
+ if (!val) return 'Con hang';
189
+ const v = val.toLowerCase();
190
+ if (v.includes('hết hang') || v.includes('het hang')) return 'Het hang';
191
+ if (v.includes('sắp') || v.includes('sap')) return 'Sap het';
192
+ return 'Con hang';
193
+ }
194
+
195
+ getSeverity(tt: string): 'success' | 'warn' | 'danger' {
196
+ return tt === 'Con hang' ? 'success' : tt === 'Sap het' ? 'warn' : 'danger';
197
+ }
198
+
199
+ getLabelTrangThai(tt: string): string {
200
+ return tt === 'Con hang' ? 'Còn hàng' : tt === 'Sap het' ? 'Sắp hết' : 'Hết hàng';
201
+ }
202
+
203
+ getSoLuongClass(sl: number): string {
204
+ if (sl === 0) return 'font-bold text-red-600';
205
+ if (sl <= 10) return 'font-bold text-orange-500';
206
+ return 'font-bold text-green-600';
207
+ }
208
+
209
+ // ═══════════════════════════════════════════════════════
210
+ // PAGINATION
211
+ // ═══════════════════════════════════════════════════════
212
+ next() {
213
+ this.first = this.first + this.rows;
214
+ }
215
+ prev() {
216
+ this.first = this.first - this.rows;
217
+ }
218
+ reset() {
219
+ this.first = 0;
220
+ }
221
+ pageChange(e: any) {
222
+ this.first = e.first;
223
+ this.rows = e.rows;
224
+ }
225
+ isLastPage(): boolean {
226
+ return this.vatTuList ? this.first + this.rows >= this.vatTuList.length : true;
227
+ }
228
+ isFirstPage(): boolean {
229
+ return this.vatTuList ? this.first === 0 : true;
230
+ }
231
+
232
+ confirmImport() {}
233
+
234
+ downloadTemplate() {}
235
+ // ═══════════════════════════════════════════════════════
236
+ // FAKE DATA
237
+ // ═══════════════════════════════════════════════════════
238
+ getFakeData(): VatTu[] {
239
+ return [
240
+ { id: 1, maVatTu: 'VT001', tenVatTu: 'Ống thép DN50', donViTinh: 'Mét', soLuong: 250, donGia: 85000, nhaCungCap: 'Công ty TNHH Thiên Phú', trangThai: 'Con hang', ngayNhap: new Date('2024-01-15') },
241
+ { id: 2, maVatTu: 'VT002', tenVatTu: 'Van cầu inox PN16', donViTinh: 'Cái', soLuong: 8, donGia: 320000, nhaCungCap: 'Cty CP Minh Long', trangThai: 'Sap het', ngayNhap: new Date('2024-02-10') },
242
+ { id: 3, maVatTu: 'VT003', tenVatTu: 'Dây cáp điện 3x4mm', donViTinh: 'Mét', soLuong: 500, donGia: 42000, nhaCungCap: 'Tập đoàn Việt Tiến', trangThai: 'Con hang', ngayNhap: new Date('2024-01-20') },
243
+ { id: 4, maVatTu: 'VT004', tenVatTu: 'Bulong M16 x 80', donViTinh: 'Hộp', soLuong: 0, donGia: 155000, nhaCungCap: 'Đại lý Hoàng Gia', trangThai: 'Het hang', ngayNhap: new Date('2023-12-05') },
244
+ { id: 5, maVatTu: 'VT005', tenVatTu: 'Sơn chống rỉ Jotun', donViTinh: 'Thùng', soLuong: 15, donGia: 1250000, nhaCungCap: 'Nhà máy Đông Nam', trangThai: 'Con hang', ngayNhap: new Date('2024-03-01') },
245
+ { id: 6, maVatTu: 'VT006', tenVatTu: 'Gioăng cao su DN100', donViTinh: 'Cái', soLuong: 6, donGia: 45000, nhaCungCap: 'Công ty TNHH Thiên Phú', trangThai: 'Sap het', ngayNhap: new Date('2024-02-28') },
246
+ { id: 7, maVatTu: 'VT007', tenVatTu: 'Tấm thép tấm 5mm', donViTinh: 'Tấm', soLuong: 40, donGia: 780000, nhaCungCap: 'Tập đoàn Việt Tiến', trangThai: 'Con hang', ngayNhap: new Date('2024-01-08') },
247
+ { id: 8, maVatTu: 'VT008', tenVatTu: 'CB Schneider 3P 100A', donViTinh: 'Cái', soLuong: 12, donGia: 2350000, nhaCungCap: 'Cty CP Minh Long', trangThai: 'Con hang', ngayNhap: new Date('2024-03-10') },
248
+ { id: 9, maVatTu: 'VT009', tenVatTu: 'Xăng dầu DO 0.05S', donViTinh: 'Lít', soLuong: 0, donGia: 21500, nhaCungCap: 'Đại lý Hoàng Gia', trangThai: 'Het hang', ngayNhap: new Date('2024-02-01') },
249
+ { id: 10, maVatTu: 'VT010', tenVatTu: 'Bảo hộ lao động bộ', donViTinh: 'Bộ', soLuong: 30, donGia: 185000, nhaCungCap: 'Nhà máy Đông Nam', trangThai: 'Con hang', ngayNhap: new Date('2024-03-05') },
250
+ { id: 11, maVatTu: 'VT011', tenVatTu: 'Bơm ly tâm 22kW', donViTinh: 'Cái', soLuong: 2, donGia: 18500000, nhaCungCap: 'Tập đoàn Việt Tiến', trangThai: 'Con hang', ngayNhap: new Date('2024-01-25') },
251
+ { id: 12, maVatTu: 'VT012', tenVatTu: 'Vòng bi SKF 6205', donViTinh: 'Cái', soLuong: 7, donGia: 95000, nhaCungCap: 'Công ty TNHH Thiên Phú', trangThai: 'Sap het', ngayNhap: new Date('2024-02-14') }
252
+ ];
253
+ }
254
+ }