More webapp changes+fixes
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
<div class="page-content">
|
||||
@if (loading()) {
|
||||
<div class="loading-container">
|
||||
<nz-spin nzSimple nzSize="large"></nz-spin>
|
||||
</div>
|
||||
} @else if (client()) {
|
||||
<div class="detail-header">
|
||||
<button nz-button (click)="goBack()">
|
||||
<span nz-icon nzType="arrow-left" nzTheme="outline"></span>
|
||||
Back to Clients
|
||||
</button>
|
||||
<div class="header-actions">
|
||||
<button
|
||||
nz-button
|
||||
nzDanger
|
||||
nz-popconfirm
|
||||
nzPopconfirmTitle="Are you sure you want to delete this client?"
|
||||
(nzOnConfirm)="deleteClient()"
|
||||
>
|
||||
<span nz-icon nzType="delete"></span>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nz-card>
|
||||
<div class="client-header">
|
||||
<span
|
||||
nz-icon
|
||||
[nzType]="getClientIcon(client()!.type)"
|
||||
nzTheme="outline"
|
||||
class="client-type-icon"
|
||||
></span>
|
||||
<h2 class="client-title">{{ client()!.name || 'Unnamed Client' }}</h2>
|
||||
<nz-tag>{{ getClientTypeLabel(client()!.type) }}</nz-tag>
|
||||
</div>
|
||||
|
||||
<scn-metadata-grid>
|
||||
<scn-metadata-value label="Client ID">
|
||||
<span class="mono">{{ client()!.client_id }}</span>
|
||||
</scn-metadata-value>
|
||||
<scn-metadata-value label="Type">
|
||||
<nz-tag>{{ getClientTypeLabel(client()!.type) }}</nz-tag>
|
||||
</scn-metadata-value>
|
||||
<scn-metadata-value label="Agent">
|
||||
<div class="agent-info">
|
||||
<span>{{ client()!.agent_model }}</span>
|
||||
<span class="agent-version">v{{ client()!.agent_version }}</span>
|
||||
</div>
|
||||
</scn-metadata-value>
|
||||
<scn-metadata-value label="Created">
|
||||
<div class="timestamp-absolute">{{ client()!.timestamp_created | date:'yyyy-MM-dd HH:mm:ss' }}</div>
|
||||
<div class="timestamp-relative">{{ client()!.timestamp_created | relativeTime }}</div>
|
||||
</scn-metadata-value>
|
||||
<scn-metadata-value label="FCM Token">
|
||||
<span class="mono fcm-token" nz-tooltip [nzTooltipTitle]="client()!.fcm_token">
|
||||
{{ client()!.fcm_token }}
|
||||
</span>
|
||||
</scn-metadata-value>
|
||||
</scn-metadata-grid>
|
||||
</nz-card>
|
||||
} @else {
|
||||
<nz-card>
|
||||
<div class="not-found">
|
||||
<p>Client not found</p>
|
||||
<button nz-button nzType="primary" (click)="goBack()">
|
||||
Back to Clients
|
||||
</button>
|
||||
</div>
|
||||
</nz-card>
|
||||
}
|
||||
</div>
|
||||
@@ -0,0 +1,67 @@
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.client-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.client-type-icon {
|
||||
font-size: 24px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.client-title {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.agent-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.agent-version {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.fcm-token {
|
||||
display: block;
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.not-found {
|
||||
text-align: center;
|
||||
padding: 48px;
|
||||
|
||||
p {
|
||||
color: #999;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.timestamp-absolute {
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.timestamp-relative {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { Component, inject, signal, OnInit } from '@angular/core';
|
||||
import { CommonModule, DatePipe } from '@angular/common';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { NzCardModule } from 'ng-zorro-antd/card';
|
||||
import { NzButtonModule } from 'ng-zorro-antd/button';
|
||||
import { NzIconModule } from 'ng-zorro-antd/icon';
|
||||
import { NzTagModule } from 'ng-zorro-antd/tag';
|
||||
import { NzSpinModule } from 'ng-zorro-antd/spin';
|
||||
import { NzPopconfirmModule } from 'ng-zorro-antd/popconfirm';
|
||||
import { NzToolTipModule } from 'ng-zorro-antd/tooltip';
|
||||
import { ApiService } from '../../../core/services/api.service';
|
||||
import { AuthService } from '../../../core/services/auth.service';
|
||||
import { NotificationService } from '../../../core/services/notification.service';
|
||||
import { Client, ClientType, getClientTypeIcon } from '../../../core/models';
|
||||
import { RelativeTimePipe } from '../../../shared/pipes/relative-time.pipe';
|
||||
import { MetadataGridComponent, MetadataValueComponent } from '../../../shared/components/metadata-grid';
|
||||
|
||||
@Component({
|
||||
selector: 'app-client-detail',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
DatePipe,
|
||||
NzCardModule,
|
||||
NzButtonModule,
|
||||
NzIconModule,
|
||||
NzTagModule,
|
||||
NzSpinModule,
|
||||
NzPopconfirmModule,
|
||||
NzToolTipModule,
|
||||
RelativeTimePipe,
|
||||
MetadataGridComponent,
|
||||
MetadataValueComponent,
|
||||
],
|
||||
templateUrl: './client-detail.component.html',
|
||||
styleUrl: './client-detail.component.scss'
|
||||
})
|
||||
export class ClientDetailComponent implements OnInit {
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
private apiService = inject(ApiService);
|
||||
private authService = inject(AuthService);
|
||||
private notification = inject(NotificationService);
|
||||
|
||||
client = signal<Client | null>(null);
|
||||
loading = signal(true);
|
||||
|
||||
ngOnInit(): void {
|
||||
const clientId = this.route.snapshot.paramMap.get('id');
|
||||
if (clientId) {
|
||||
this.loadClient(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
loadClient(clientId: string): void {
|
||||
const userId = this.authService.getUserId();
|
||||
if (!userId) return;
|
||||
|
||||
this.loading.set(true);
|
||||
this.apiService.getClient(userId, clientId).subscribe({
|
||||
next: (client) => {
|
||||
this.client.set(client);
|
||||
this.loading.set(false);
|
||||
},
|
||||
error: () => {
|
||||
this.loading.set(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
goBack(): void {
|
||||
this.router.navigate(['/clients']);
|
||||
}
|
||||
|
||||
getClientIcon(type: ClientType): string {
|
||||
return getClientTypeIcon(type);
|
||||
}
|
||||
|
||||
getClientTypeLabel(type: ClientType): string {
|
||||
switch (type) {
|
||||
case 'ANDROID': return 'Android';
|
||||
case 'IOS': return 'iOS';
|
||||
case 'MACOS': return 'macOS';
|
||||
case 'WINDOWS': return 'Windows';
|
||||
case 'LINUX': return 'Linux';
|
||||
default: return type;
|
||||
}
|
||||
}
|
||||
|
||||
deleteClient(): void {
|
||||
const client = this.client();
|
||||
const userId = this.authService.getUserId();
|
||||
if (!client || !userId) return;
|
||||
|
||||
this.apiService.deleteClient(userId, client.client_id).subscribe({
|
||||
next: () => {
|
||||
this.notification.success('Client deleted');
|
||||
this.router.navigate(['/clients']);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user