Skip to content

Commit 6a59184

Browse files
feat: implement settings page with tabbed configuration for business profile, social matrix, general info, and selection lists
1 parent 5f3a052 commit 6a59184

17 files changed

Lines changed: 227 additions & 243 deletions

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

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,7 @@ <h1 class="font-serif text-4xl text-gray-900 mb-2" i18n="@@settingsTitle">Manage
3232

3333
@if (activeTab() === 'Business Profile') {
3434
<app-business-profile
35-
[location]="location()"
36-
[ownerInfo]="ownerInfo()"
37-
(updateLocation)="location.set($event)"
38-
(updateOwnerInfo)="ownerInfo.set($event)"
35+
[settingsForm]="settingsForm"
3936
(save)="saveBusinessProfile()"
4037
/>
4138
}
@@ -52,10 +49,7 @@ <h1 class="font-serif text-4xl text-gray-900 mb-2" i18n="@@settingsTitle">Manage
5249

5350
@if (activeTab() === 'General Info') {
5451
<app-general-info
55-
[biography]="biography()"
56-
[philosophy]="philosophy()"
57-
(updateBiography)="biography.set($event)"
58-
(updatePhilosophy)="philosophy.set($event)"
52+
[settingsForm]="settingsForm"
5953
(save)="saveGeneralInfo()"
6054
/>
6155
}
@@ -71,12 +65,12 @@ <h1 class="font-serif text-4xl text-gray-900 mb-2" i18n="@@settingsTitle">Manage
7165

7266
@if (activeTab() === 'SELECTS') {
7367
<app-selects-settings
74-
[galleryCategories]="galleryCategories()"
75-
[treatmentCategories]="treatmentCategories()"
76-
[veilSilhouettes]="veilSilhouettes()"
77-
[veilFabrics]="veilFabrics()"
78-
[veilTrainLengths]="veilTrainLengths()"
79-
[veilNecklines]="veilNecklines()"
68+
[galleryCategories]="settingsModel().galleryCategories"
69+
[treatmentCategories]="settingsModel().treatmentCategories"
70+
[veilSilhouettes]="settingsModel().veilSilhouettes"
71+
[veilFabrics]="settingsModel().veilFabrics"
72+
[veilTrainLengths]="settingsModel().veilTrainLengths"
73+
[veilNecklines]="settingsModel().veilNecklines"
8074
(addItem)="addItem($event)"
8175
(updateItem)="updateItem($event.type, $event.index, $event.value)"
8276
(removeItem)="removeItem($event.type, $event.index)"

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

Lines changed: 76 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { Component, ChangeDetectionStrategy, signal, inject, OnInit } from '@angular/core';
22
import { CommonModule } from '@angular/common';
33
import { FormsModule } from '@angular/forms';
4+
import { form } from '@angular/forms/signals';
45
import { AdminSettingsService } from '@entities/admin-settings';
5-
import { AdminLocation, OwnerInfo } from '@shared/models/admin-settings.model';
6-
import { BusinessProfileComponent } from './ui/business-profile.component';
7-
import { SocialMatrixComponent, SocialPlatform } from './ui/social-matrix.component';
8-
import { GeneralInfoComponent } from './ui/general-info.component';
9-
import { AdditionalLinksComponent, AdditionalLink } from './ui/additional-links.component';
10-
import { SelectsSettingsComponent, SelectListType } from './ui/selects-settings.component';
6+
import { AdminSettings } from '@shared/models/admin-settings.model';
7+
import { BusinessProfileComponent } from './ui/business-profile/business-profile.component';
8+
import { SocialMatrixComponent, SocialPlatform } from './ui/social-matrix/social-matrix.component';
9+
import { GeneralInfoComponent } from './ui/general-info/general-info.component';
10+
import { AdditionalLinksComponent, AdditionalLink } from './ui/additional-links/additional-links.component';
11+
import { SelectsSettingsComponent, SelectListType } from './ui/selects-settings/selects-settings.component';
1112

1213
type SettingsTab = 'Business Profile' | 'Social Matrix' | 'General Info' | 'Additional Links' | 'SELECTS';
1314

@@ -34,18 +35,23 @@ export class SettingsComponent implements OnInit {
3435
activeTab = signal<SettingsTab>('Business Profile');
3536

3637
// Admin Settings State (Signals)
37-
location = signal<AdminLocation>({ address: '', latitude: 0, longitude: 0 });
38-
ownerInfo = signal<OwnerInfo>({ name: '', phoneNumber: '' });
39-
biography = signal<string>('');
40-
philosophy = signal<string>('');
41-
42-
// Selection Lists (Signals)
43-
galleryCategories = signal<string[]>([]);
44-
treatmentCategories = signal<string[]>([]);
45-
veilSilhouettes = signal<string[]>([]);
46-
veilFabrics = signal<string[]>([]);
47-
veilTrainLengths = signal<string[]>([]);
48-
veilNecklines = signal<string[]>([]);
38+
settingsModel = signal<AdminSettings>({
39+
id: '',
40+
location: { address: '', latitude: 0, longitude: 0 },
41+
ownerInfo: { name: '', phoneNumber: '' },
42+
biography: '',
43+
philosophy: '',
44+
galleryCategories: [],
45+
treatmentCategories: [],
46+
veilSilhouettes: [],
47+
veilFabrics: [],
48+
veilTrainLengths: [],
49+
veilNecklines: [],
50+
socialLinks: {},
51+
workHours: {}
52+
});
53+
54+
settingsForm = form(this.settingsModel);
4955

5056
// UI-only for now or needs conversion
5157
socialPlatforms = signal<SocialPlatform[]>([
@@ -62,16 +68,11 @@ export class SettingsComponent implements OnInit {
6268
ngOnInit() {
6369
this.adminSettingsService.getSettings().subscribe(settings => {
6470
if (settings) {
65-
this.location.set(settings.location || { address: '', latitude: 0, longitude: 0 });
66-
this.ownerInfo.set(settings.ownerInfo || { name: '', phoneNumber: '' });
67-
this.biography.set(settings.biography || '');
68-
this.philosophy.set(settings.philosophy || '');
69-
this.galleryCategories.set(settings.galleryCategories || []);
70-
this.treatmentCategories.set(settings.treatmentCategories || []);
71-
this.veilSilhouettes.set(settings.veilSilhouettes || []);
72-
this.veilFabrics.set(settings.veilFabrics || []);
73-
this.veilTrainLengths.set(settings.veilTrainLengths || []);
74-
this.veilNecklines.set(settings.veilNecklines || []);
71+
const current = this.settingsModel();
72+
this.settingsModel.set({
73+
...current,
74+
...settings
75+
});
7576
if (settings.socialLinks && Object.keys(settings.socialLinks).length > 0) {
7677
const platforms = Object.entries(settings.socialLinks).map(([name, url], i) => ({
7778
id: Date.now() + i,
@@ -90,8 +91,8 @@ export class SettingsComponent implements OnInit {
9091

9192
saveBusinessProfile() {
9293
this.adminSettingsService.updateSettings({
93-
location: this.location(),
94-
ownerInfo: this.ownerInfo()
94+
location: this.settingsModel().location,
95+
ownerInfo: this.settingsModel().ownerInfo
9596
}).subscribe();
9697
}
9798

@@ -107,56 +108,68 @@ export class SettingsComponent implements OnInit {
107108

108109
saveGeneralInfo() {
109110
this.adminSettingsService.updateSettings({
110-
biography: this.biography(),
111-
philosophy: this.philosophy()
111+
biography: this.settingsModel().biography,
112+
philosophy: this.settingsModel().philosophy
112113
}).subscribe();
113114
}
114115

115116
saveSelectionLists() {
116117
this.adminSettingsService.updateSettings({
117-
galleryCategories: this.galleryCategories(),
118-
treatmentCategories: this.treatmentCategories(),
119-
veilSilhouettes: this.veilSilhouettes(),
120-
veilFabrics: this.veilFabrics(),
121-
veilTrainLengths: this.veilTrainLengths(),
122-
veilNecklines: this.veilNecklines()
118+
galleryCategories: this.settingsModel().galleryCategories,
119+
treatmentCategories: this.settingsModel().treatmentCategories,
120+
veilSilhouettes: this.settingsModel().veilSilhouettes,
121+
veilFabrics: this.settingsModel().veilFabrics,
122+
veilTrainLengths: this.settingsModel().veilTrainLengths,
123+
veilNecklines: this.settingsModel().veilNecklines
123124
}).subscribe();
124125
}
125126

126127
// --- CRUD for Selection Lists ---
127-
128+
// (Note: To simplify the implementation, dynamic arrays are updated directly via the signal model)
128129
addItem(type: SelectListType) {
129130
const newItem = 'New Item';
130-
switch(type) {
131-
case 'gallery': this.galleryCategories.update(items => [...items, newItem]); break;
132-
case 'treatment': this.treatmentCategories.update(items => [...items, newItem]); break;
133-
case 'silhouette': this.veilSilhouettes.update(items => [...items, newItem]); break;
134-
case 'fabric': this.veilFabrics.update(items => [...items, newItem]); break;
135-
case 'train': this.veilTrainLengths.update(items => [...items, newItem]); break;
136-
case 'neckline': this.veilNecklines.update(items => [...items, newItem]); break;
137-
}
131+
this.settingsModel.update(m => {
132+
const lists = {...m};
133+
switch(type) {
134+
case 'gallery': lists.galleryCategories = [...m.galleryCategories, newItem]; break;
135+
case 'treatment': lists.treatmentCategories = [...m.treatmentCategories, newItem]; break;
136+
case 'silhouette': lists.veilSilhouettes = [...m.veilSilhouettes, newItem]; break;
137+
case 'fabric': lists.veilFabrics = [...m.veilFabrics, newItem]; break;
138+
case 'train': lists.veilTrainLengths = [...m.veilTrainLengths, newItem]; break;
139+
case 'neckline': lists.veilNecklines = [...m.veilNecklines, newItem]; break;
140+
}
141+
return lists;
142+
});
138143
}
139144

140145
updateItem(type: SelectListType, index: number, value: string) {
141-
switch(type) {
142-
case 'gallery': this.galleryCategories.update(items => { items[index] = value; return [...items]; }); break;
143-
case 'treatment': this.treatmentCategories.update(items => { items[index] = value; return [...items]; }); break;
144-
case 'silhouette': this.veilSilhouettes.update(items => { items[index] = value; return [...items]; }); break;
145-
case 'fabric': this.veilFabrics.update(items => { items[index] = value; return [...items]; }); break;
146-
case 'train': this.veilTrainLengths.update(items => { items[index] = value; return [...items]; }); break;
147-
case 'neckline': this.veilNecklines.update(items => { items[index] = value; return [...items]; }); break;
148-
}
146+
this.settingsModel.update(m => {
147+
const lists = {...m};
148+
switch(type) {
149+
case 'gallery': lists.galleryCategories = lists.galleryCategories.map((v, i) => i === index ? value : v); break;
150+
case 'treatment': lists.treatmentCategories = lists.treatmentCategories.map((v, i) => i === index ? value : v); break;
151+
case 'silhouette': lists.veilSilhouettes = lists.veilSilhouettes.map((v, i) => i === index ? value : v); break;
152+
case 'fabric': lists.veilFabrics = lists.veilFabrics.map((v, i) => i === index ? value : v); break;
153+
case 'train': lists.veilTrainLengths = lists.veilTrainLengths.map((v, i) => i === index ? value : v); break;
154+
case 'neckline': lists.veilNecklines = lists.veilNecklines.map((v, i) => i === index ? value : v); break;
155+
}
156+
return lists;
157+
});
149158
}
150159

151160
removeItem(type: SelectListType, index: number) {
152-
switch(type) {
153-
case 'gallery': this.galleryCategories.update(items => items.filter((_, i) => i !== index)); break;
154-
case 'treatment': this.treatmentCategories.update(items => items.filter((_, i) => i !== index)); break;
155-
case 'silhouette': this.veilSilhouettes.update(items => items.filter((_, i) => i !== index)); break;
156-
case 'fabric': this.veilFabrics.update(items => items.filter((_, i) => i !== index)); break;
157-
case 'train': this.veilTrainLengths.update(items => items.filter((_, i) => i !== index)); break;
158-
case 'neckline': this.veilNecklines.update(items => items.filter((_, i) => i !== index)); break;
159-
}
161+
this.settingsModel.update(m => {
162+
const lists = {...m};
163+
switch(type) {
164+
case 'gallery': lists.galleryCategories = lists.galleryCategories.filter((_, i) => i !== index); break;
165+
case 'treatment': lists.treatmentCategories = lists.treatmentCategories.filter((_, i) => i !== index); break;
166+
case 'silhouette': lists.veilSilhouettes = lists.veilSilhouettes.filter((_, i) => i !== index); break;
167+
case 'fabric': lists.veilFabrics = lists.veilFabrics.filter((_, i) => i !== index); break;
168+
case 'train': lists.veilTrainLengths = lists.veilTrainLengths.filter((_, i) => i !== index); break;
169+
case 'neckline': lists.veilNecklines = lists.veilNecklines.filter((_, i) => i !== index); break;
170+
}
171+
return lists;
172+
});
160173
}
161174

162175
// --- Social Platform methods ---

frontend/src/pages/settings/ui/additional-links.component.ts renamed to frontend/src/pages/settings/ui/additional-links/additional-links.component.html

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,4 @@
1-
import { Component, ChangeDetectionStrategy, input, output } from '@angular/core';
2-
import { CommonModule } from '@angular/common';
3-
import { FormsModule } from '@angular/forms';
4-
5-
export interface AdditionalLink {
6-
id: number;
7-
label: string;
8-
targetUrl: string;
9-
category: string;
10-
categoryColor: 'blue' | 'green';
11-
}
12-
13-
@Component({
14-
selector: 'app-additional-links',
15-
standalone: true,
16-
imports: [CommonModule, FormsModule],
17-
changeDetection: ChangeDetectionStrategy.OnPush,
18-
template: `
19-
<section class="bg-white rounded-2xl shadow-card border border-gray-100 overflow-hidden animate-page-enter">
1+
<section class="bg-white rounded-2xl shadow-card border border-gray-100 overflow-hidden animate-page-enter">
202
<div class="p-6 border-b border-gray-100 flex justify-between items-center bg-gray-50/50">
213
<div class="flex items-center">
224
<span class="material-symbols-outlined text-primary mr-3">link</span>
@@ -68,19 +50,3 @@ <h4 class="font-serif text-xl font-semibold text-gray-900" i18n="@@settingsSecti
6850
</div>
6951
</div>
7052
</section>
71-
`
72-
})
73-
export class AdditionalLinksComponent {
74-
links = input.required<AdditionalLink[]>();
75-
76-
addLink = output<void>();
77-
removeLink = output<number>();
78-
updateLink = output<AdditionalLink>();
79-
80-
onUpdate(id: number, field: keyof AdditionalLink, value: any) {
81-
const link = this.links().find(l => l.id === id);
82-
if (link) {
83-
this.updateLink.emit({ ...link, [field]: value });
84-
}
85-
}
86-
}

frontend/src/pages/settings/ui/additional-links/additional-links.component.scss

Whitespace-only changes.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Component, ChangeDetectionStrategy, input, output } from '@angular/core';
2+
import { CommonModule } from '@angular/common';
3+
import { FormsModule } from '@angular/forms';
4+
5+
export interface AdditionalLink {
6+
id: number;
7+
label: string;
8+
targetUrl: string;
9+
category: string;
10+
categoryColor: 'blue' | 'green';
11+
}
12+
13+
@Component({
14+
selector: 'app-additional-links',
15+
standalone: true,
16+
imports: [CommonModule, FormsModule],
17+
changeDetection: ChangeDetectionStrategy.OnPush,
18+
templateUrl: './additional-links.component.html',
19+
styleUrl: './additional-links.component.scss'
20+
})
21+
export class AdditionalLinksComponent {
22+
links = input.required<AdditionalLink[]>();
23+
24+
addLink = output<void>();
25+
removeLink = output<number>();
26+
updateLink = output<AdditionalLink>();
27+
28+
onUpdate(id: number, field: keyof AdditionalLink, value: any) {
29+
const link = this.links().find(l => l.id === id);
30+
if (link) {
31+
this.updateLink.emit({ ...link, [field]: value });
32+
}
33+
}
34+
}

frontend/src/pages/settings/ui/business-profile.component.ts renamed to frontend/src/pages/settings/ui/business-profile/business-profile.component.html

Lines changed: 5 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,4 @@
1-
import { Component, ChangeDetectionStrategy, input, output } from '@angular/core';
2-
import { CommonModule } from '@angular/common';
3-
import { FormsModule } from '@angular/forms';
4-
import { AdminLocation, OwnerInfo } from '@shared/models';
5-
6-
@Component({
7-
selector: 'app-business-profile',
8-
standalone: true,
9-
imports: [CommonModule, FormsModule],
10-
changeDetection: ChangeDetectionStrategy.OnPush,
11-
template: `
12-
<section class="bg-white rounded-2xl shadow-card border border-gray-100 overflow-hidden animate-page-enter">
1+
<section class="bg-white rounded-2xl shadow-card border border-gray-100 overflow-hidden animate-page-enter">
132
<div class="p-6 border-b border-gray-100 flex justify-between items-center bg-gray-50/50">
143
<div class="flex items-center">
154
<span class="material-symbols-outlined text-primary mr-3">business_center</span>
@@ -28,8 +17,7 @@ <h4 class="font-serif text-xl font-semibold text-gray-900" i18n="@@settingsSecti
2817
<input
2918
class="w-full pl-11 pr-4 py-3 bg-white border border-gray-200 rounded-xl focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all text-sm text-gray-900"
3019
type="text"
31-
[ngModel]="ownerInfo().phoneNumber"
32-
(ngModelChange)="onOwnerInfoChange('phoneNumber', $event)"
20+
[formField]="settingsForm().ownerInfo.phoneNumber"
3321
/>
3422
</div>
3523
</div>
@@ -40,8 +28,7 @@ <h4 class="font-serif text-xl font-semibold text-gray-900" i18n="@@settingsSecti
4028
<input
4129
class="w-full pl-11 pr-4 py-3 bg-white border border-gray-200 rounded-xl focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all text-sm text-gray-900"
4230
type="text"
43-
[ngModel]="location().address"
44-
(ngModelChange)="onLocationChange('address', $event)"
31+
[formField]="settingsForm().location.address"
4532
/>
4633
</div>
4734
</div>
@@ -51,17 +38,15 @@ <h4 class="font-serif text-xl font-semibold text-gray-900" i18n="@@settingsSecti
5138
<input
5239
class="w-full px-4 py-3 bg-white border border-gray-200 rounded-xl focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all text-sm font-mono text-gray-900"
5340
type="number"
54-
[ngModel]="location().latitude"
55-
(ngModelChange)="onLocationChange('latitude', $event)"
41+
[formField]="settingsForm().location.latitude"
5642
/>
5743
</div>
5844
<div>
5945
<label class="block text-sm font-semibold text-gray-700 mb-2 uppercase tracking-wider text-xs" i18n="@@settingsLabelLong">Longitude</label>
6046
<input
6147
class="w-full px-4 py-3 bg-white border border-gray-200 rounded-xl focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all text-sm font-mono text-gray-900"
6248
type="number"
63-
[ngModel]="location().longitude"
64-
(ngModelChange)="onLocationChange('longitude', $event)"
49+
[formField]="settingsForm().location.longitude"
6550
/>
6651
</div>
6752
</div>
@@ -80,21 +65,3 @@ <h4 class="font-serif text-xl font-semibold text-gray-900" i18n="@@settingsSecti
8065
</div>
8166
</div>
8267
</section>
83-
`
84-
})
85-
export class BusinessProfileComponent {
86-
location = input.required<AdminLocation>();
87-
ownerInfo = input.required<OwnerInfo>();
88-
89-
updateLocation = output<AdminLocation>();
90-
updateOwnerInfo = output<OwnerInfo>();
91-
save = output<void>();
92-
93-
onLocationChange(field: keyof AdminLocation, value: any) {
94-
this.updateLocation.emit({ ...this.location(), [field]: value });
95-
}
96-
97-
onOwnerInfoChange(field: keyof OwnerInfo, value: any) {
98-
this.updateOwnerInfo.emit({ ...this.ownerInfo(), [field]: value });
99-
}
100-
}

frontend/src/pages/settings/ui/business-profile/business-profile.component.scss

Whitespace-only changes.

0 commit comments

Comments
 (0)