Skip to content

Commit 133c79c

Browse files
feat: implement clients management page and add to admin routes
1 parent 6a59184 commit 133c79c

5 files changed

Lines changed: 250 additions & 1 deletion

File tree

frontend/src/app.routes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export const routes: Routes = [
3131
{
3232
path: "clients",
3333
loadComponent: () =>
34-
import("@pages/dashboard").then((m) => m.DashboardComponent),
34+
import("@pages/clients").then((m) => m.ClientsPageComponent),
3535
},
3636
{
3737
path: "gallery",
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
<div class="p-4 sm:p-8 max-w-7xl mx-auto space-y-8 animate-page-enter">
2+
<!-- Header -->
3+
<div class="flex flex-col md:flex-row md:items-center justify-between gap-4">
4+
<div>
5+
<h1 class="font-display text-3xl font-medium text-gray-900" i18n="Clients title@@clientsTitle">Client Relationship Management</h1>
6+
<p class="text-gray-500 mt-1 text-sm" i18n="Clients subtitle@@clientsSubtitle">Manage your prestigious clientele and their history.</p>
7+
</div>
8+
<div>
9+
<button class="flex items-center gap-2 px-6 py-3 bg-[#DEB853] text-white text-sm font-medium rounded-lg hover:bg-yellow-600 transition-colors shadow-sm">
10+
<span class="material-symbols-outlined text-[18px]">person_add</span>
11+
<span i18n="Clients register@@clientsRegister">Register New Client</span>
12+
</button>
13+
</div>
14+
</div>
15+
16+
<!-- Filters & Table Container -->
17+
<div class="bg-white rounded-xl border border-gray-100 shadow-sm overflow-hidden">
18+
<!-- Filters -->
19+
<div class="p-6 border-b border-gray-100 flex flex-col lg:flex-row items-end gap-6 justify-between">
20+
<div class="flex flex-wrap lg:flex-nowrap gap-6 w-full hidden md:flex">
21+
<!-- Membership Level -->
22+
<div class="space-y-1.5 min-w-[200px]">
23+
<label class="text-xs font-semibold text-gray-500 tracking-wider uppercase">Membership Level</label>
24+
<div class="relative">
25+
<select class="w-full appearance-none bg-gray-50 border border-gray-200 text-gray-700 text-sm rounded-lg px-4 py-2.5 pr-8 focus:outline-none focus:ring-2 focus:ring-[#DEB853]/50 focus:border-[#DEB853]">
26+
<option>All Memberships</option>
27+
<option>Platinum VIP</option>
28+
<option>Gold Tier</option>
29+
<option>Silver Tier</option>
30+
</select>
31+
<span class="material-symbols-outlined absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 pointer-events-none text-xl">expand_more</span>
32+
</div>
33+
</div>
34+
35+
<!-- Last Visit -->
36+
<div class="space-y-1.5 min-w-[200px]">
37+
<label class="text-xs font-semibold text-gray-500 tracking-wider uppercase">Last Visit</label>
38+
<div class="relative">
39+
<select class="w-full appearance-none bg-gray-50 border border-gray-200 text-gray-700 text-sm rounded-lg px-4 py-2.5 pr-8 focus:outline-none focus:ring-2 focus:ring-[#DEB853]/50 focus:border-[#DEB853]">
40+
<option>Anytime</option>
41+
<option>Last 7 Days</option>
42+
<option>Last 30 Days</option>
43+
<option>Last 3 Months</option>
44+
</select>
45+
<span class="material-symbols-outlined absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 pointer-events-none text-xl">expand_more</span>
46+
</div>
47+
</div>
48+
49+
<!-- Spend Range -->
50+
<div class="space-y-1.5 min-w-[200px]">
51+
<label class="text-xs font-semibold text-gray-500 tracking-wider uppercase">Spend Range</label>
52+
<div class="relative">
53+
<select class="w-full appearance-none bg-gray-50 border border-gray-200 text-gray-700 text-sm rounded-lg px-4 py-2.5 pr-8 focus:outline-none focus:ring-2 focus:ring-[#DEB853]/50 focus:border-[#DEB853]">
54+
<option>All Amounts</option>
55+
<option>$1,000 - $5,000</option>
56+
<option>$5,000 - $10,000</option>
57+
<option>$10,000+</option>
58+
</select>
59+
<span class="material-symbols-outlined absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 pointer-events-none text-xl">expand_more</span>
60+
</div>
61+
</div>
62+
</div>
63+
64+
<!-- Advanced Filters Button -->
65+
<button class="flex items-center gap-2 px-5 py-2.5 bg-white border border-gray-200 text-gray-700 text-sm font-medium rounded-lg hover:bg-gray-50 transition-colors whitespace-nowrap">
66+
<span class="material-symbols-outlined text-[18px]">filter_list</span>
67+
Advanced Filters
68+
</button>
69+
</div>
70+
71+
<!-- Table -->
72+
<div class="overflow-x-auto">
73+
<table class="w-full text-left border-collapse min-w-[900px]">
74+
<thead>
75+
<tr class="bg-gray-50/50">
76+
<th class="py-4 px-6 text-xs font-semibold text-gray-500 tracking-wider uppercase">Client Profile</th>
77+
<th class="py-4 px-6 text-xs font-semibold text-gray-500 tracking-wider uppercase">Contact Details</th>
78+
<th class="py-4 px-6 text-xs font-semibold text-gray-500 tracking-wider uppercase">Membership</th>
79+
<th class="py-4 px-6 text-xs font-semibold text-gray-500 tracking-wider uppercase">Last Visit</th>
80+
<th class="py-4 px-6 text-xs font-semibold text-gray-500 tracking-wider uppercase">Total Spend</th>
81+
<th class="py-4 px-6 text-xs font-semibold text-gray-500 tracking-wider uppercase text-right">Actions</th>
82+
</tr>
83+
</thead>
84+
<tbody class="divide-y divide-gray-100 bg-white">
85+
@for (client of clients(); track client.id; let i = $index) {
86+
<tr class="hover:bg-gray-50/50 transition-colors group reveal-item" [style.animation-delay.ms]="i * 50">
87+
<!-- Client Profile -->
88+
<td class="py-4 px-6">
89+
<div class="flex items-center gap-3">
90+
<!-- Avatar -->
91+
@if (client.avatarType === 'image') {
92+
<img [src]="client.avatarUrl" alt="" class="w-10 h-10 rounded-full object-cover shadow-sm ring-1 ring-gray-100">
93+
} @else if (client.avatarType === 'icon') {
94+
<div class="w-10 h-10 rounded-full bg-gray-200 text-gray-500 flex items-center justify-center">
95+
<span class="material-symbols-outlined text-[20px]">person</span>
96+
</div>
97+
} @else {
98+
<div class="w-10 h-10 rounded-full bg-yellow-100 text-yellow-700 font-bold flex items-center justify-center text-sm shadow-sm">
99+
{{ client.initials }}
100+
</div>
101+
}
102+
103+
<div>
104+
<div class="text-sm font-medium text-gray-900">{{ client.name }}</div>
105+
<div class="text-xs text-gray-500 mt-0.5">ID: {{ client.id }}</div>
106+
</div>
107+
</div>
108+
</td>
109+
110+
<!-- Contact Details -->
111+
<td class="py-4 px-6">
112+
<div class="text-sm text-gray-900">{{ client.phone }}</div>
113+
<div class="text-xs text-gray-500 mt-0.5">{{ client.email }}</div>
114+
</td>
115+
116+
<!-- Membership -->
117+
<td class="py-4 px-6">
118+
<span class="inline-flex items-center px-2.5 py-1 text-[10px] font-bold uppercase tracking-wider rounded-full border"
119+
[ngClass]="{
120+
'bg-yellow-50 text-yellow-700 border-yellow-200': client.membership === 'Platinum VIP',
121+
'bg-gray-100 text-gray-600 border-gray-200': client.membership === 'Gold Tier' || client.membership === 'Silver Tier'
122+
}">
123+
{{ client.membership }}
124+
</span>
125+
</td>
126+
127+
<!-- Last Visit -->
128+
<td class="py-4 px-6 text-sm text-gray-700">
129+
{{ client.lastVisit }}
130+
</td>
131+
132+
<!-- Total Spend -->
133+
<td class="py-4 px-6 text-sm font-medium text-gray-900">
134+
{{ client.totalSpend | currency:'USD':'symbol':'1.2-2' }}
135+
</td>
136+
137+
<!-- Actions -->
138+
<td class="py-4 px-6 text-right">
139+
<div class="flex items-center justify-end gap-3 text-gray-400">
140+
<button class="hover:text-gray-900 transition-colors"><span class="material-symbols-outlined text-[18px]">visibility</span></button>
141+
<button class="hover:text-blue-600 transition-colors"><span class="material-symbols-outlined text-[18px]">edit</span></button>
142+
<button class="hover:text-red-600 transition-colors"><span class="material-symbols-outlined text-[18px]">delete</span></button>
143+
</div>
144+
</td>
145+
</tr>
146+
}
147+
</tbody>
148+
</table>
149+
</div>
150+
151+
<!-- Pagination -->
152+
<div class="p-4 px-6 border-t border-gray-100 flex flex-col sm:flex-row items-center justify-between gap-4">
153+
<div class="text-sm text-gray-500">
154+
Showing <span class="font-medium text-gray-900">1</span> to <span class="font-medium text-gray-900">4</span> of <span class="font-medium text-gray-900">{{ totalClients() }}</span> registered clients
155+
</div>
156+
<div class="flex items-center gap-1">
157+
<button class="w-8 h-8 flex items-center justify-center text-gray-400 hover:text-gray-700 hover:bg-gray-50 rounded transition-colors" disabled>
158+
<span class="material-symbols-outlined text-[18px]">chevron_left</span>
159+
</button>
160+
<button class="w-8 h-8 flex items-center justify-center bg-[#DEB853] text-white font-medium rounded hover:bg-yellow-600 transition-colors text-sm">1</button>
161+
<button class="w-8 h-8 flex items-center justify-center text-gray-600 font-medium hover:bg-gray-50 rounded transition-colors text-sm">2</button>
162+
<button class="w-8 h-8 flex items-center justify-center text-gray-600 font-medium hover:bg-gray-50 rounded transition-colors text-sm">3</button>
163+
<span class="w-8 h-8 flex items-center justify-center text-gray-400 text-sm">...</span>
164+
<button class="w-8 h-8 flex items-center justify-center text-gray-600 font-medium hover:bg-gray-50 rounded transition-colors text-sm">38</button>
165+
<button class="w-8 h-8 flex items-center justify-center text-gray-600 hover:text-gray-900 hover:bg-gray-50 rounded transition-colors">
166+
<span class="material-symbols-outlined text-[18px]">chevron_right</span>
167+
</button>
168+
</div>
169+
</div>
170+
</div>
171+
</div>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:host {
2+
display: block;
3+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { Component, ChangeDetectionStrategy, signal, computed } from '@angular/core';
2+
import { CommonModule } from '@angular/common';
3+
4+
export interface Client {
5+
id: string;
6+
name: string;
7+
phone: string;
8+
email: string;
9+
membership: 'Platinum VIP' | 'Gold Tier' | 'Silver Tier';
10+
lastVisit: string;
11+
totalSpend: number;
12+
avatarType: 'image' | 'icon' | 'initials';
13+
avatarUrl?: string; // If 'image'
14+
initials?: string; // If 'initials'
15+
}
16+
17+
@Component({
18+
selector: 'app-admin-clients',
19+
standalone: true,
20+
imports: [CommonModule],
21+
templateUrl: './clients.component.html',
22+
styleUrls: ['./clients.component.scss'],
23+
changeDetection: ChangeDetectionStrategy.OnPush,
24+
})
25+
export class ClientsPageComponent {
26+
clients = signal<Client[]>([
27+
{
28+
id: '#CL-8902',
29+
name: 'Elena Kuznetsova',
30+
phone: '+7 (900) 123-45-67',
31+
email: 'elena.k@example.com',
32+
membership: 'Platinum VIP',
33+
lastVisit: 'Oct 24, 2024',
34+
totalSpend: 12450.00,
35+
avatarType: 'image',
36+
avatarUrl: 'https://i.pravatar.cc/150?img=1'
37+
},
38+
{
39+
id: '#CL-8905',
40+
name: 'Alina Ivanova',
41+
phone: '+7 (901) 987-65-43',
42+
email: 'ivanova.alina@test.ru',
43+
membership: 'Gold Tier',
44+
lastVisit: 'Oct 20, 2024',
45+
totalSpend: 4200.00,
46+
avatarType: 'icon'
47+
},
48+
{
49+
id: '#CL-8911',
50+
name: 'Sarah Jenkins',
51+
phone: '+44 20 7946 0958',
52+
email: 'sarah.j@outlook.com',
53+
membership: 'Silver Tier',
54+
lastVisit: 'Sep 30, 2024',
55+
totalSpend: 1850.00,
56+
avatarType: 'image',
57+
avatarUrl: 'https://i.pravatar.cc/150?img=5'
58+
},
59+
{
60+
id: '#CL-8940',
61+
name: 'Olga Kareva',
62+
phone: '+7 (905) 555-44-33',
63+
email: 'okareva@mail.ru',
64+
membership: 'Platinum VIP',
65+
lastVisit: 'Oct 22, 2024',
66+
totalSpend: 15200.00,
67+
avatarType: 'initials',
68+
initials: 'OK'
69+
}
70+
]);
71+
72+
totalClients = signal(152);
73+
74+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './clients.component';

0 commit comments

Comments
 (0)