Skip to content

Commit f73b3a4

Browse files
Created SELECTS tab page: Admin Settings Page
1 parent df730ad commit f73b3a4

8 files changed

Lines changed: 268 additions & 6 deletions

File tree

backend/src/modules/admin-settings/domain/admin-settings.entity.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,11 @@ export class AdminSettings implements IAdminSettings {
1111
public readonly socialLinks: Record<string, string>,
1212
public readonly workHours: Record<string, string>,
1313
public readonly ownerInfo: IOwnerInfo,
14+
public readonly galleryCategories: string[],
15+
public readonly treatmentCategories: string[],
16+
public readonly veilSilhouettes: string[],
17+
public readonly veilFabrics: string[],
18+
public readonly veilTrainLengths: string[],
19+
public readonly veilNecklines: string[],
1420
) {}
1521
}

backend/src/modules/admin-settings/domain/interfaces/admin-settings.interface.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,10 @@ export interface IAdminSettings {
1515
socialLinks: Record<string, string>;
1616
workHours: Record<string, string>;
1717
ownerInfo: IOwnerInfo;
18+
galleryCategories: string[];
19+
treatmentCategories: string[];
20+
veilSilhouettes: string[];
21+
veilFabrics: string[];
22+
veilTrainLengths: string[];
23+
veilNecklines: string[];
1824
}

backend/src/modules/admin-settings/infrastructure/repositories/admin-settings.repository.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ export class AdminSettingsRepository {
5454
socialLinks,
5555
workHours,
5656
doc.ownerInfo,
57+
doc.galleryCategories || [],
58+
doc.treatmentCategories || [],
59+
doc.veilSilhouettes || [],
60+
doc.veilFabrics || [],
61+
doc.veilTrainLengths || [],
62+
doc.veilNecklines || [],
5763
);
5864
}
5965
}

backend/src/modules/admin-settings/infrastructure/schemas/admin-settings.schema.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,24 @@ export class AdminSettingsSchemaEntity {
2626
name: string;
2727
phoneNumber: string;
2828
};
29+
30+
@Prop({ type: [String], default: [] })
31+
galleryCategories: string[];
32+
33+
@Prop({ type: [String], default: [] })
34+
treatmentCategories: string[];
35+
36+
@Prop({ type: [String], default: [] })
37+
veilSilhouettes: string[];
38+
39+
@Prop({ type: [String], default: [] })
40+
veilFabrics: string[];
41+
42+
@Prop({ type: [String], default: [] })
43+
veilTrainLengths: string[];
44+
45+
@Prop({ type: [String], default: [] })
46+
veilNecklines: string[];
2947
}
3048

3149
export const AdminSettingsSchema = SchemaFactory.createForClass(

backend/src/modules/admin-settings/presentation/dto/create-admin-settings.dto.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
IsOptional,
77
ValidateNested,
88
IsObject,
9+
IsArray,
910
} from 'class-validator';
1011
import {
1112
IAdminLocation,
@@ -52,4 +53,34 @@ export class CreateAdminSettingsDto {
5253
@Type(() => OwnerInfoDto)
5354
@IsNotEmpty()
5455
ownerInfo: OwnerInfoDto;
56+
57+
@IsArray()
58+
@IsString({ each: true })
59+
@IsOptional()
60+
galleryCategories: string[];
61+
62+
@IsArray()
63+
@IsString({ each: true })
64+
@IsOptional()
65+
treatmentCategories: string[];
66+
67+
@IsArray()
68+
@IsString({ each: true })
69+
@IsOptional()
70+
veilSilhouettes: string[];
71+
72+
@IsArray()
73+
@IsString({ each: true })
74+
@IsOptional()
75+
veilFabrics: string[];
76+
77+
@IsArray()
78+
@IsString({ each: true })
79+
@IsOptional()
80+
veilTrainLengths: string[];
81+
82+
@IsArray()
83+
@IsString({ each: true })
84+
@IsOptional()
85+
veilNecklines: string[];
5586
}

frontend/src/entities/admin-settings/admin-settings.model.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,10 @@ export interface AdminSettings {
1515
socialLinks: Record<string, string>;
1616
workHours: Record<string, string>;
1717
ownerInfo: OwnerInfo;
18+
galleryCategories: string[];
19+
treatmentCategories: string[];
20+
veilSilhouettes: string[];
21+
veilFabrics: string[];
22+
veilTrainLengths: string[];
23+
veilNecklines: string[];
1824
}

frontend/src/pages/settings/settings.component.html

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ <h1 class="font-serif text-4xl text-gray-900 mb-2" i18n="@@settingsTitle">Manage
2020
@case('Social Matrix') { <ng-container i18n="@@settingsTabSocial">Social Matrix</ng-container> }
2121
@case('General Info') { <ng-container i18n="@@settingsTabGeneral">General Info</ng-container> }
2222
@case('Additional Links') { <ng-container i18n="@@settingsTabLinks">Additional Links</ng-container> }
23+
@case('SELECTS') { <ng-container i18n="@@settingsTabSelects">SELECTS</ng-container> }
2324
@default { {{ tab }} }
2425
}
2526
</button>
@@ -215,6 +216,120 @@ <h4 class="font-serif text-xl font-semibold text-gray-900" i18n="@@settingsSecti
215216
</section>
216217
}
217218

219+
<!-- SELECTS Section -->
220+
@if (activeTab() === 'SELECTS') {
221+
<section class="bg-white rounded-2xl shadow-card border border-gray-100 overflow-hidden animate-page-enter">
222+
<div class="p-6 border-b border-gray-100 flex justify-between items-center bg-gray-50/50">
223+
<div class="flex items-center">
224+
<span class="material-symbols-outlined text-primary mr-3">list_alt</span>
225+
<h4 class="font-serif text-xl font-semibold text-gray-900" i18n="@@settingsSectionSelects">Selection Lists</h4>
226+
</div>
227+
<button (click)="saveSelectionLists()" class="flex items-center px-4 py-2 bg-primary hover:bg-primary-hover text-black rounded-lg text-sm font-medium transition-all shadow-md btn-primary-shimmer active:scale-[0.98]" i18n="@@settingsBtnSave">
228+
Save Lists
229+
</button>
230+
</div>
231+
<div class="p-8 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10">
232+
233+
<!-- Gallery Categories -->
234+
<div class="space-y-4">
235+
<div class="flex justify-between items-center border-b border-gray-100 pb-2">
236+
<label class="text-sm font-semibold text-gray-700 uppercase tracking-wider" i18n="@@settingsLabelGalleryCats">Gallery Categories</label>
237+
<button (click)="addItem('gallery')" class="text-primary hover:text-primary-hover"><span class="material-symbols-outlined">add_circle</span></button>
238+
</div>
239+
<div class="space-y-2 max-h-[300px] overflow-y-auto pr-2 custom-scrollbar">
240+
@for(item of galleryCategories(); track $index) {
241+
<div class="flex items-center space-x-2 group">
242+
<input #input (blur)="updateItem('gallery', $index, input.value)" [value]="item" class="flex-1 px-3 py-2 bg-gray-50 border border-gray-200 rounded-lg text-sm focus:bg-white transition-all"/>
243+
<button (click)="removeItem('gallery', $index)" class="text-gray-400 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-all"><span class="material-symbols-outlined text-lg">delete</span></button>
244+
</div>
245+
}
246+
</div>
247+
</div>
248+
249+
<!-- Treatment Categories -->
250+
<div class="space-y-4">
251+
<div class="flex justify-between items-center border-b border-gray-100 pb-2">
252+
<label class="text-sm font-semibold text-gray-700 uppercase tracking-wider" i18n="@@settingsLabelTreatmentCats">Treatment Categories</label>
253+
<button (click)="addItem('treatment')" class="text-primary hover:text-primary-hover"><span class="material-symbols-outlined">add_circle</span></button>
254+
</div>
255+
<div class="space-y-2 max-h-[300px] overflow-y-auto pr-2 custom-scrollbar">
256+
@for(item of treatmentCategories(); track $index) {
257+
<div class="flex items-center space-x-2 group">
258+
<input #input (blur)="updateItem('treatment', $index, input.value)" [value]="item" class="flex-1 px-3 py-2 bg-gray-50 border border-gray-200 rounded-lg text-sm focus:bg-white transition-all"/>
259+
<button (click)="removeItem('treatment', $index)" class="text-gray-400 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-all"><span class="material-symbols-outlined text-lg">delete</span></button>
260+
</div>
261+
}
262+
</div>
263+
</div>
264+
265+
<!-- Veil Silhouettes -->
266+
<div class="space-y-4">
267+
<div class="flex justify-between items-center border-b border-gray-100 pb-2">
268+
<label class="text-sm font-semibold text-gray-700 uppercase tracking-wider" i18n="@@settingsLabelSilhouettes">Silhouettes</label>
269+
<button (click)="addItem('silhouette')" class="text-primary hover:text-primary-hover"><span class="material-symbols-outlined">add_circle</span></button>
270+
</div>
271+
<div class="space-y-2 max-h-[300px] overflow-y-auto pr-2 custom-scrollbar">
272+
@for(item of veilSilhouettes(); track $index) {
273+
<div class="flex items-center space-x-2 group">
274+
<input #input (blur)="updateItem('silhouette', $index, input.value)" [value]="item" class="flex-1 px-3 py-2 bg-gray-50 border border-gray-200 rounded-lg text-sm focus:bg-white transition-all"/>
275+
<button (click)="removeItem('silhouette', $index)" class="text-gray-400 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-all"><span class="material-symbols-outlined text-lg">delete</span></button>
276+
</div>
277+
}
278+
</div>
279+
</div>
280+
281+
<!-- Veil Fabrics -->
282+
<div class="space-y-4">
283+
<div class="flex justify-between items-center border-b border-gray-100 pb-2">
284+
<label class="text-sm font-semibold text-gray-700 uppercase tracking-wider" i18n="@@settingsLabelFabrics">Fabrics</label>
285+
<button (click)="addItem('fabric')" class="text-primary hover:text-primary-hover"><span class="material-symbols-outlined">add_circle</span></button>
286+
</div>
287+
<div class="space-y-2 max-h-[300px] overflow-y-auto pr-2 custom-scrollbar">
288+
@for(item of veilFabrics(); track $index) {
289+
<div class="flex items-center space-x-2 group">
290+
<input #input (blur)="updateItem('fabric', $index, input.value)" [value]="item" class="flex-1 px-3 py-2 bg-gray-50 border border-gray-200 rounded-lg text-sm focus:bg-white transition-all"/>
291+
<button (click)="removeItem('fabric', $index)" class="text-gray-400 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-all"><span class="material-symbols-outlined text-lg">delete</span></button>
292+
</div>
293+
}
294+
</div>
295+
</div>
296+
297+
<!-- Veil Train Lengths -->
298+
<div class="space-y-4">
299+
<div class="flex justify-between items-center border-b border-gray-100 pb-2">
300+
<label class="text-sm font-semibold text-gray-700 uppercase tracking-wider" i18n="@@settingsLabelTrains">Train Lengths</label>
301+
<button (click)="addItem('train')" class="text-primary hover:text-primary-hover"><span class="material-symbols-outlined">add_circle</span></button>
302+
</div>
303+
<div class="space-y-2 max-h-[300px] overflow-y-auto pr-2 custom-scrollbar">
304+
@for(item of veilTrainLengths(); track $index) {
305+
<div class="flex items-center space-x-2 group">
306+
<input #input (blur)="updateItem('train', $index, input.value)" [value]="item" class="flex-1 px-3 py-2 bg-gray-50 border border-gray-200 rounded-lg text-sm focus:bg-white transition-all"/>
307+
<button (click)="removeItem('train', $index)" class="text-gray-400 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-all"><span class="material-symbols-outlined text-lg">delete</span></button>
308+
</div>
309+
}
310+
</div>
311+
</div>
312+
313+
<!-- Veil Necklines -->
314+
<div class="space-y-4">
315+
<div class="flex justify-between items-center border-b border-gray-100 pb-2">
316+
<label class="text-sm font-semibold text-gray-700 uppercase tracking-wider" i18n="@@settingsLabelNecklines">Necklines</label>
317+
<button (click)="addItem('neckline')" class="text-primary hover:text-primary-hover"><span class="material-symbols-outlined">add_circle</span></button>
318+
</div>
319+
<div class="space-y-2 max-h-[300px] overflow-y-auto pr-2 custom-scrollbar">
320+
@for(item of veilNecklines(); track $index) {
321+
<div class="flex items-center space-x-2 group">
322+
<input #input (blur)="updateItem('neckline', $index, input.value)" [value]="item" class="flex-1 px-3 py-2 bg-gray-50 border border-gray-200 rounded-lg text-sm focus:bg-white transition-all"/>
323+
<button (click)="removeItem('neckline', $index)" class="text-gray-400 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-all"><span class="material-symbols-outlined text-lg">delete</span></button>
324+
</div>
325+
}
326+
</div>
327+
</div>
328+
329+
</div>
330+
</section>
331+
}
332+
218333
</div>
219334
<!-- Final Actions -->
220335
<div class="mt-12 pt-8 border-t border-gray-200 flex justify-between items-center">

frontend/src/pages/settings/settings.component.ts

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
2-
import { Component, ChangeDetectionStrategy, signal } from '@angular/core';
1+
import { Component, ChangeDetectionStrategy, signal, inject, OnInit } from '@angular/core';
32
import { CommonModule } from '@angular/common';
43
import { FormsModule } from '@angular/forms';
4+
import { AdminSettingsService } from '@entities/admin-settings';
55

6-
type SettingsTab = 'Business Profile' | 'Social Matrix' | 'General Info' | 'Additional Links';
6+
type SettingsTab = 'Business Profile' | 'Social Matrix' | 'General Info' | 'Additional Links' | 'SELECTS';
77

88
interface SocialPlatform {
99
id: number;
@@ -21,7 +21,6 @@ interface AdditionalLink {
2121
categoryColor: 'blue' | 'green';
2222
}
2323

24-
2524
@Component({
2625
selector: 'app-settings',
2726
standalone: true,
@@ -30,10 +29,20 @@ interface AdditionalLink {
3029
templateUrl: './settings.component.html',
3130
styleUrls: ['./settings.component.scss']
3231
})
33-
export class SettingsComponent {
34-
tabs: SettingsTab[] = ['Business Profile', 'Social Matrix', 'General Info', 'Additional Links'];
32+
export class SettingsComponent implements OnInit {
33+
private adminSettingsService = inject(AdminSettingsService);
34+
35+
tabs: SettingsTab[] = ['Business Profile', 'Social Matrix', 'General Info', 'Additional Links', 'SELECTS'];
3536
activeTab = signal<SettingsTab>('Business Profile');
3637

38+
// Selection Lists (Signals)
39+
galleryCategories = signal<string[]>([]);
40+
treatmentCategories = signal<string[]>([]);
41+
veilSilhouettes = signal<string[]>([]);
42+
veilFabrics = signal<string[]>([]);
43+
veilTrainLengths = signal<string[]>([]);
44+
veilNecklines = signal<string[]>([]);
45+
3746
socialPlatforms = signal<SocialPlatform[]>([
3847
{ id: 1, name: 'Instagram', url: 'https://instagram.com/mavluda_azizova', iconUrl: 'https://upload.wikimedia.org/wikipedia/commons/a/a5/Instagram_icon.png', alt: 'Instagram' },
3948
{ id: 2, name: 'Telegram', url: 'https://t.me/mavluda_beauty', iconUrl: 'https://upload.wikimedia.org/wikipedia/commons/8/82/Telegram_logo.svg', alt: 'Telegram' },
@@ -45,6 +54,71 @@ export class SettingsComponent {
4554
{ id: 2, label: 'Medical Certification', targetUrl: 'https://docs.mavluda.uz/certs/iso-9001', category: 'Compliance', categoryColor: 'green' },
4655
]);
4756

57+
ngOnInit() {
58+
this.adminSettingsService.getSettings().subscribe(settings => {
59+
if (settings) {
60+
this.galleryCategories.set(settings.galleryCategories || []);
61+
this.treatmentCategories.set(settings.treatmentCategories || []);
62+
this.veilSilhouettes.set(settings.veilSilhouettes || []);
63+
this.veilFabrics.set(settings.veilFabrics || []);
64+
this.veilTrainLengths.set(settings.veilTrainLengths || []);
65+
this.veilNecklines.set(settings.veilNecklines || []);
66+
}
67+
});
68+
}
69+
70+
// --- CRUD for Selection Lists ---
71+
72+
addItem(list: 'gallery' | 'treatment' | 'silhouette' | 'fabric' | 'train' | 'neckline') {
73+
const newItem = 'New Item';
74+
switch(list) {
75+
case 'gallery': this.galleryCategories.update(items => [...items, newItem]); break;
76+
case 'treatment': this.treatmentCategories.update(items => [...items, newItem]); break;
77+
case 'silhouette': this.veilSilhouettes.update(items => [...items, newItem]); break;
78+
case 'fabric': this.veilFabrics.update(items => [...items, newItem]); break;
79+
case 'train': this.veilTrainLengths.update(items => [...items, newItem]); break;
80+
case 'neckline': this.veilNecklines.update(items => [...items, newItem]); break;
81+
}
82+
}
83+
84+
updateItem(list: 'gallery' | 'treatment' | 'silhouette' | 'fabric' | 'train' | 'neckline', index: number, value: string) {
85+
switch(list) {
86+
case 'gallery': this.galleryCategories.update(items => { items[index] = value; return [...items]; }); break;
87+
case 'treatment': this.treatmentCategories.update(items => { items[index] = value; return [...items]; }); break;
88+
case 'silhouette': this.veilSilhouettes.update(items => { items[index] = value; return [...items]; }); break;
89+
case 'fabric': this.veilFabrics.update(items => { items[index] = value; return [...items]; }); break;
90+
case 'train': this.veilTrainLengths.update(items => { items[index] = value; return [...items]; }); break;
91+
case 'neckline': this.veilNecklines.update(items => { items[index] = value; return [...items]; }); break;
92+
}
93+
}
94+
95+
removeItem(list: 'gallery' | 'treatment' | 'silhouette' | 'fabric' | 'train' | 'neckline', index: number) {
96+
switch(list) {
97+
case 'gallery': this.galleryCategories.update(items => items.filter((_, i) => i !== index)); break;
98+
case 'treatment': this.treatmentCategories.update(items => items.filter((_, i) => i !== index)); break;
99+
case 'silhouette': this.veilSilhouettes.update(items => items.filter((_, i) => i !== index)); break;
100+
case 'fabric': this.veilFabrics.update(items => items.filter((_, i) => i !== index)); break;
101+
case 'train': this.veilTrainLengths.update(items => items.filter((_, i) => i !== index)); break;
102+
case 'neckline': this.veilNecklines.update(items => items.filter((_, i) => i !== index)); break;
103+
}
104+
}
105+
106+
saveSelectionLists() {
107+
const settings = this.adminSettingsService.settings();
108+
if (settings) {
109+
this.adminSettingsService.updateSettings({
110+
galleryCategories: this.galleryCategories(),
111+
treatmentCategories: this.treatmentCategories(),
112+
veilSilhouettes: this.veilSilhouettes(),
113+
veilFabrics: this.veilFabrics(),
114+
veilTrainLengths: this.veilTrainLengths(),
115+
veilNecklines: this.veilNecklines()
116+
}).subscribe();
117+
}
118+
}
119+
120+
// --- Existing Methods ---
121+
48122
addSocialPlatform() {
49123
const newPlatform: SocialPlatform = {
50124
id: Date.now(),

0 commit comments

Comments
 (0)