@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,617 @@
1
+ // ═══════════════════════════════════════════════════════════════
2
+ // IT Helpdesk — Ticket Form | PrimeNG Dark-Mode Enhanced
3
+ // ═══════════════════════════════════════════════════════════════
4
+
5
+ :host {
6
+ display: block;
7
+ }
8
+
9
+ // ── Wrapper & Container ─────────────────────────────────────────
10
+ .wrapper {
11
+ display: flex;
12
+ justify-content: center;
13
+ align-items: flex-start;
14
+ padding: 2.5rem 1.25rem 5rem;
15
+ min-height: 100%;
16
+ background: var(--surface-ground);
17
+ }
18
+
19
+ .container {
20
+ width: 100%;
21
+ max-width: 820px;
22
+ }
23
+
24
+ // ── Hero ────────────────────────────────────────────────────────
25
+ .hero {
26
+ display: flex;
27
+ align-items: flex-end;
28
+ justify-content: space-between;
29
+ flex-wrap: wrap;
30
+ gap: 16px;
31
+ margin-bottom: 32px;
32
+ }
33
+
34
+ .badge-live {
35
+ display: inline-flex;
36
+ align-items: center;
37
+ gap: 8px;
38
+ background: color-mix(in srgb, var(--primary-color) 12%, transparent);
39
+ border: 1.5px solid color-mix(in srgb, var(--primary-color) 40%, transparent);
40
+ color: var(--primary-color);
41
+ font-size: 11px;
42
+ font-weight: 700;
43
+ padding: 5px 14px;
44
+ border-radius: 99px;
45
+ margin-bottom: 12px;
46
+ letter-spacing: 0.5px;
47
+ text-transform: uppercase;
48
+ }
49
+
50
+ .live-dot {
51
+ width: 8px;
52
+ height: 8px;
53
+ border-radius: 50%;
54
+ background: var(--primary-color);
55
+ animation: pulse-dot 1.8s ease-in-out infinite;
56
+ flex-shrink: 0;
57
+ }
58
+
59
+ @keyframes pulse-dot {
60
+
61
+ 0%,
62
+ 100% {
63
+ opacity: 1;
64
+ transform: scale(1);
65
+ }
66
+
67
+ 50% {
68
+ opacity: 0.4;
69
+ transform: scale(1.4);
70
+ }
71
+ }
72
+
73
+ .page-title {
74
+ font-size: 24px;
75
+ font-weight: 700;
76
+ color: var(--text-color);
77
+ margin-bottom: 6px;
78
+ letter-spacing: -0.3px;
79
+ }
80
+
81
+ .page-sub {
82
+ font-size: 13px;
83
+ color: var(--text-color-secondary);
84
+ line-height: 1.5;
85
+ }
86
+
87
+ // ── Stat pills ──────────────────────────────────────────────────
88
+ .stats-row {
89
+ display: flex;
90
+ gap: 10px;
91
+ flex-shrink: 0;
92
+ }
93
+
94
+ .stat-pill {
95
+ background: var(--surface-card);
96
+ border: 1.5px solid var(--surface-border);
97
+ border-radius: 16px;
98
+ padding: 12px 22px;
99
+ text-align: center;
100
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
101
+ transition: border-color 0.2s;
102
+
103
+ &:hover {
104
+ border-color: var(--primary-color);
105
+ }
106
+ }
107
+
108
+ .stat-num {
109
+ display: block;
110
+ font-size: 22px;
111
+ font-weight: 800;
112
+ color: var(--primary-color);
113
+ line-height: 1.2;
114
+ }
115
+
116
+ .stat-lbl {
117
+ display: block;
118
+ font-size: 11px;
119
+ font-weight: 600;
120
+ color: var(--text-color-secondary);
121
+ margin-top: 3px;
122
+ text-transform: uppercase;
123
+ letter-spacing: 0.4px;
124
+ }
125
+
126
+ // ── Card shell ──────────────────────────────────────────────────
127
+ .card {
128
+ background: var(--surface-card);
129
+ border: 1.5px solid var(--surface-border);
130
+ border-radius: 20px;
131
+ overflow: hidden;
132
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.07);
133
+ }
134
+
135
+ // Gradient accent bar at top
136
+ .card-progress-bar {
137
+ height: 4px;
138
+ background: linear-gradient(90deg,
139
+ var(--primary-color) 0%,
140
+ var(--primary-400, #60a5fa) 55%,
141
+ color-mix(in srgb, var(--primary-color) 30%, transparent) 100%);
142
+ }
143
+
144
+ // ── Card Header ─────────────────────────────────────────────────
145
+ .card-header {
146
+ padding: 18px 28px;
147
+ background: var(--primary-color);
148
+ display: flex;
149
+ align-items: center;
150
+ justify-content: space-between;
151
+ flex-wrap: wrap;
152
+ gap: 10px;
153
+ }
154
+
155
+ .card-header-left {
156
+ display: flex;
157
+ align-items: center;
158
+ gap: 12px;
159
+ }
160
+
161
+ .header-icon {
162
+ width: 36px;
163
+ height: 36px;
164
+ background: rgba(255, 255, 255, 0.18);
165
+ border-radius: 10px;
166
+ border: 1.5px solid rgba(255, 255, 255, 0.3);
167
+ display: flex;
168
+ align-items: center;
169
+ justify-content: center;
170
+
171
+ i {
172
+ color: #fff;
173
+ font-size: 16px;
174
+ }
175
+ }
176
+
177
+ .card-title {
178
+ font-size: 15px;
179
+ font-weight: 700;
180
+ color: #fff;
181
+ letter-spacing: -0.1px;
182
+ }
183
+
184
+ .req-badge {
185
+ background: rgba(255, 255, 255, 0.2);
186
+ color: rgba(255, 255, 255, 0.95);
187
+ font-size: 11px;
188
+ font-weight: 600;
189
+ padding: 5px 14px;
190
+ border-radius: 99px;
191
+ border: 1px solid rgba(255, 255, 255, 0.3);
192
+ }
193
+
194
+ // ── Card Body ───────────────────────────────────────────────────
195
+ .card-body {
196
+ padding: 32px 28px 28px;
197
+ }
198
+
199
+ // ── Section label ───────────────────────────────────────────────
200
+ .section-label {
201
+ display: flex;
202
+ align-items: center;
203
+ gap: 10px;
204
+ font-size: 10px;
205
+ font-weight: 700;
206
+ color: var(--text-color-secondary);
207
+ text-transform: uppercase;
208
+ letter-spacing: 1.2px;
209
+ margin-bottom: 22px;
210
+ padding-bottom: 12px;
211
+ border-bottom: 1.5px dashed var(--surface-border);
212
+
213
+ &::before {
214
+ content: '';
215
+ display: block;
216
+ width: 4px;
217
+ height: 16px;
218
+ background: var(--primary-color);
219
+ border-radius: 3px;
220
+ flex-shrink: 0;
221
+ }
222
+ }
223
+
224
+ // ── Form Grid ───────────────────────────────────────────────────
225
+ .form-grid {
226
+ display: grid;
227
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
228
+ gap: 20px;
229
+
230
+ @media (max-width: 580px) {
231
+ grid-template-columns: minmax(0, 1fr);
232
+ }
233
+ }
234
+
235
+ .form-group {
236
+ display: flex;
237
+ flex-direction: column;
238
+ gap: 8px;
239
+
240
+ &.full {
241
+ grid-column: 1 / -1;
242
+
243
+ @media (max-width: 580px) {
244
+ grid-column: 1;
245
+ }
246
+ }
247
+ }
248
+
249
+ // ── Labels ──────────────────────────────────────────────────────
250
+ .form-label {
251
+ font-size: 11.5px;
252
+ font-weight: 700;
253
+ color: var(--text-color);
254
+ text-transform: uppercase;
255
+ letter-spacing: 0.6px;
256
+ }
257
+
258
+ .req {
259
+ color: var(--red-500);
260
+ }
261
+
262
+ // ── Inputs (clear dark mode borders) ────────────────────────────
263
+ .form-input {
264
+ padding: 10px 14px;
265
+ background: var(--surface-section, var(--surface-ground));
266
+ border: 1.5px solid var(--surface-border);
267
+ border-radius: 10px;
268
+ color: var(--text-color);
269
+ font-size: 13.5px;
270
+ font-family: var(--font-family);
271
+ outline: none;
272
+ transition: border-color 0.18s, background 0.18s, box-shadow 0.18s;
273
+ width: 100%;
274
+
275
+ // Force higher contrast border in dark mode
276
+ @media (prefers-color-scheme: dark) {
277
+ border-color: var(--surface-500, #52525b);
278
+ }
279
+
280
+ &::placeholder {
281
+ color: var(--text-color-secondary);
282
+ opacity: 0.55;
283
+ }
284
+
285
+ &:hover {
286
+ border-color: var(--primary-400, #60a5fa);
287
+ background: var(--surface-hover);
288
+ }
289
+
290
+ &:focus {
291
+ border-color: var(--primary-color);
292
+ background: var(--surface-card);
293
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary-color) 22%, transparent);
294
+ }
295
+
296
+ option {
297
+ background: var(--surface-overlay);
298
+ color: var(--text-color);
299
+ }
300
+ }
301
+
302
+ select.form-input {
303
+ cursor: pointer;
304
+ appearance: none;
305
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%236b7280' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
306
+ background-repeat: no-repeat;
307
+ background-position: right 14px center;
308
+ padding-right: 38px;
309
+ }
310
+
311
+ textarea.form-input {
312
+ resize: vertical;
313
+ min-height: 120px;
314
+ line-height: 1.75;
315
+ }
316
+
317
+ // ── Priority Buttons ─────────────────────────────────────────────
318
+ .pri-row {
319
+ display: flex;
320
+ gap: 8px;
321
+ }
322
+
323
+ .pri-btn {
324
+ flex: 1;
325
+ padding: 9px 6px;
326
+ border-radius: 10px;
327
+ border: 1.5px solid var(--surface-border);
328
+ background: var(--surface-section, var(--surface-ground));
329
+ color: var(--text-color-secondary);
330
+ font-size: 12px;
331
+ font-weight: 600;
332
+ cursor: pointer;
333
+ transition: all 0.18s;
334
+ font-family: var(--font-family);
335
+ text-align: center;
336
+ letter-spacing: 0.2px;
337
+
338
+ &:hover {
339
+ border-color: var(--surface-400, #a1a1aa);
340
+ color: var(--text-color);
341
+ transform: translateY(-1px);
342
+ }
343
+
344
+ &.active-high {
345
+ background: color-mix(in srgb, var(--red-500) 14%, transparent);
346
+ border-color: var(--red-400, #f87171);
347
+ color: var(--red-500);
348
+ font-weight: 700;
349
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--red-500) 12%, transparent);
350
+ }
351
+
352
+ &.active-medium {
353
+ background: color-mix(in srgb, var(--yellow-500) 14%, transparent);
354
+ border-color: var(--yellow-400, #facc15);
355
+ color: var(--yellow-600, #ca8a04);
356
+ font-weight: 700;
357
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--yellow-500) 12%, transparent);
358
+ }
359
+
360
+ &.active-low {
361
+ background: color-mix(in srgb, var(--green-500) 14%, transparent);
362
+ border-color: var(--green-400, #4ade80);
363
+ color: var(--green-600, #16a34a);
364
+ font-weight: 700;
365
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--green-500) 12%, transparent);
366
+ }
367
+ }
368
+
369
+ // ── Upload Zone ──────────────────────────────────────────────────
370
+ .upload-zone {
371
+ border: 2px dashed color-mix(in srgb, var(--primary-color) 55%, transparent);
372
+ border-radius: 14px;
373
+ padding: 32px 20px;
374
+ display: flex;
375
+ flex-direction: column;
376
+ align-items: center;
377
+ gap: 10px;
378
+ cursor: pointer;
379
+ background: color-mix(in srgb, var(--primary-color) 6%, transparent);
380
+ transition: all 0.22s;
381
+
382
+ &:hover,
383
+ &.drag-over {
384
+ border-color: var(--primary-color);
385
+ background: color-mix(in srgb, var(--primary-color) 12%, transparent);
386
+ transform: scale(1.005);
387
+ }
388
+ }
389
+
390
+ .upload-icon-wrap {
391
+ width: 54px;
392
+ height: 54px;
393
+ background: var(--primary-color);
394
+ border-radius: 16px;
395
+ display: flex;
396
+ align-items: center;
397
+ justify-content: center;
398
+ margin-bottom: 4px;
399
+ box-shadow: 0 4px 14px color-mix(in srgb, var(--primary-color) 40%, transparent);
400
+ }
401
+
402
+ .upload-label {
403
+ font-size: 13.5px;
404
+ font-weight: 700;
405
+ color: var(--primary-color);
406
+ }
407
+
408
+ .upload-hint {
409
+ font-size: 12px;
410
+ color: var(--text-color-secondary);
411
+ font-weight: 500;
412
+ }
413
+
414
+ // ── Image Previews ───────────────────────────────────────────────
415
+ .preview-grid {
416
+ display: grid;
417
+ grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
418
+ gap: 10px;
419
+ margin-top: 14px;
420
+ }
421
+
422
+ .preview-item {
423
+ position: relative;
424
+ border-radius: 12px;
425
+ overflow: hidden;
426
+ border: 2px solid color-mix(in srgb, var(--primary-color) 45%, transparent);
427
+ aspect-ratio: 1;
428
+ background: var(--surface-ground);
429
+ transition: transform 0.18s, border-color 0.18s;
430
+
431
+ &:hover {
432
+ transform: scale(1.05);
433
+ border-color: var(--primary-color);
434
+ }
435
+
436
+ img {
437
+ width: 100%;
438
+ height: 100%;
439
+ object-fit: cover;
440
+ display: block;
441
+ }
442
+ }
443
+
444
+ .preview-remove {
445
+ position: absolute;
446
+ top: 5px;
447
+ right: 5px;
448
+ width: 22px;
449
+ height: 22px;
450
+ border-radius: 50%;
451
+ background: rgba(0, 0, 0, 0.65);
452
+ border: none;
453
+ cursor: pointer;
454
+ display: flex;
455
+ align-items: center;
456
+ justify-content: center;
457
+ color: #fff;
458
+ font-size: 10px;
459
+ padding: 0;
460
+ opacity: 0.8;
461
+ transition: opacity 0.15s, background 0.15s;
462
+
463
+ &:hover {
464
+ opacity: 1;
465
+ background: var(--red-500);
466
+ }
467
+ }
468
+
469
+ // ── Divider ──────────────────────────────────────────────────────
470
+ .section-divider {
471
+ height: 1.5px;
472
+ background: var(--surface-border);
473
+ margin: 28px 0 24px;
474
+ border-radius: 2px;
475
+ }
476
+
477
+ // ── Actions ──────────────────────────────────────────────────────
478
+ .actions {
479
+ display: flex;
480
+ gap: 12px;
481
+ align-items: center;
482
+ margin-top: 8px;
483
+ padding-top: 22px;
484
+ border-top: 1.5px solid var(--surface-border);
485
+ flex-wrap: wrap;
486
+ }
487
+
488
+ .btn-primary {
489
+ display: inline-flex;
490
+ align-items: center;
491
+ gap: 8px;
492
+ padding: 12px 28px;
493
+ background: var(--primary-color);
494
+ border: 1.5px solid var(--primary-color);
495
+ border-radius: 12px;
496
+ color: var(--primary-color-text);
497
+ font-size: 13.5px;
498
+ font-weight: 700;
499
+ cursor: pointer;
500
+ font-family: var(--font-family);
501
+ transition: all 0.2s;
502
+ letter-spacing: 0.2px;
503
+
504
+ i {
505
+ font-size: 15px;
506
+ }
507
+
508
+ &:hover {
509
+ background: var(--primary-600, #2563eb);
510
+ border-color: var(--primary-600, #2563eb);
511
+ transform: translateY(-1px);
512
+ box-shadow: 0 4px 16px color-mix(in srgb, var(--primary-color) 40%, transparent);
513
+ }
514
+
515
+ &:active {
516
+ transform: translateY(0);
517
+ box-shadow: none;
518
+ }
519
+ }
520
+
521
+ .btn-ghost {
522
+ display: inline-flex;
523
+ align-items: center;
524
+ gap: 8px;
525
+ padding: 12px 22px;
526
+ background: transparent;
527
+ border: 1.5px solid var(--surface-border);
528
+ border-radius: 12px;
529
+ color: var(--text-color-secondary);
530
+ font-size: 13.5px;
531
+ font-weight: 600;
532
+ cursor: pointer;
533
+ font-family: var(--font-family);
534
+ transition: all 0.18s;
535
+
536
+ i {
537
+ font-size: 14px;
538
+ }
539
+
540
+ &:hover {
541
+ background: var(--surface-hover);
542
+ border-color: var(--surface-400, #a1a1aa);
543
+ color: var(--text-color);
544
+ }
545
+ }
546
+
547
+ // ── Toast ────────────────────────────────────────────────────────
548
+ .toast {
549
+ display: flex;
550
+ align-items: center;
551
+ gap: 10px;
552
+ margin-top: 18px;
553
+ padding: 14px 18px;
554
+ border-radius: 12px;
555
+ font-size: 13.5px;
556
+ font-weight: 600;
557
+ animation: slide-in 0.28s cubic-bezier(0.34, 1.56, 0.64, 1);
558
+ border: 1.5px solid;
559
+
560
+ i {
561
+ font-size: 16px;
562
+ flex-shrink: 0;
563
+ }
564
+
565
+ &.toast-success {
566
+ background: color-mix(in srgb, var(--green-500) 12%, var(--surface-card));
567
+ border-color: var(--green-400, #4ade80);
568
+ color: var(--green-600, #16a34a);
569
+ }
570
+
571
+ &.toast-error {
572
+ background: color-mix(in srgb, var(--red-500) 12%, var(--surface-card));
573
+ border-color: var(--red-400, #f87171);
574
+ color: var(--red-600, #dc2626);
575
+ }
576
+ }
577
+
578
+ @keyframes slide-in {
579
+ from {
580
+ opacity: 0;
581
+ transform: translateY(10px) scale(0.97);
582
+ }
583
+
584
+ to {
585
+ opacity: 1;
586
+ transform: translateY(0) scale(1);
587
+ }
588
+ }
589
+
590
+ // ── Responsive ──────────────────────────────────────────────────
591
+ @media (max-width: 480px) {
592
+ .card-body {
593
+ padding: 20px 16px;
594
+ }
595
+
596
+ .card-header {
597
+ padding: 14px 18px;
598
+ }
599
+
600
+ .hero {
601
+ margin-bottom: 20px;
602
+ }
603
+
604
+ .page-title {
605
+ font-size: 20px;
606
+ }
607
+
608
+ .actions {
609
+ gap: 8px;
610
+ }
611
+
612
+ .btn-primary,
613
+ .btn-ghost {
614
+ width: 100%;
615
+ justify-content: center;
616
+ }
617
+ }
@@ -0,0 +1,104 @@
1
+ import { Component } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { FormsModule } from '@angular/forms';
4
+
5
+ interface TicketForm {
6
+ title: string;
7
+ type: string;
8
+ priority: 'high' | 'medium' | 'low';
9
+ requester: string;
10
+ department: string;
11
+ device: string;
12
+ description: string;
13
+ }
14
+
15
+ interface Preview {
16
+ url: string;
17
+ name: string;
18
+ }
19
+
20
+ @Component({
21
+ selector: 'app-ticket-create',
22
+ standalone: true,
23
+ imports: [CommonModule, FormsModule],
24
+ templateUrl: './tickets-create.html',
25
+ styleUrl: './tickets-create.scss'
26
+ })
27
+ export class TicketCreateComponent {
28
+ form: TicketForm = this.defaultForm();
29
+ previews: Preview[] = [];
30
+ isDragging = false;
31
+ toast = { show: false, message: '', type: '' };
32
+
33
+ setPri(p: 'high' | 'medium' | 'low') {
34
+ this.form.priority = p;
35
+ }
36
+
37
+ handleFiles(event: Event) {
38
+ const input = event.target as HTMLInputElement;
39
+ if (input.files) this.addFiles(input.files);
40
+ }
41
+
42
+ onDragOver(event: DragEvent) {
43
+ event.preventDefault();
44
+ this.isDragging = true;
45
+ }
46
+
47
+ onDragLeave() {
48
+ this.isDragging = false;
49
+ }
50
+
51
+ onDrop(event: DragEvent) {
52
+ event.preventDefault();
53
+ this.isDragging = false;
54
+ if (event.dataTransfer?.files) this.addFiles(event.dataTransfer.files);
55
+ }
56
+
57
+ addFiles(files: FileList) {
58
+ const remaining = 5 - this.previews.length;
59
+ Array.from(files)
60
+ .filter((f) => f.type.startsWith('image/'))
61
+ .slice(0, remaining)
62
+ .forEach((file) => {
63
+ const reader = new FileReader();
64
+ reader.onload = (e) => this.previews.push({ url: e.target!.result as string, name: file.name });
65
+ reader.readAsDataURL(file);
66
+ });
67
+ }
68
+
69
+ removeFile(index: number) {
70
+ this.previews.splice(index, 1);
71
+ }
72
+
73
+ submitTicket() {
74
+ if (!this.form.title || !this.form.type || !this.form.requester) {
75
+ this.showToast('Vui lòng điền đầy đủ các trường bắt buộc (*).', 'error');
76
+ return;
77
+ }
78
+ this.showToast('Ticket đã được tạo thành công!', 'success');
79
+ // TODO: gọi API tạo ticket ở đây
80
+ }
81
+
82
+ clearForm() {
83
+ this.form = this.defaultForm();
84
+ this.previews = [];
85
+ this.toast.show = false;
86
+ }
87
+
88
+ private defaultForm(): TicketForm {
89
+ return {
90
+ title: '',
91
+ type: '',
92
+ priority: 'high',
93
+ requester: '',
94
+ department: '',
95
+ device: '',
96
+ description: ''
97
+ };
98
+ }
99
+
100
+ private showToast(message: string, type: string) {
101
+ this.toast = { show: true, message, type };
102
+ setTimeout(() => (this.toast.show = false), 4000);
103
+ }
104
+ }