Deploy stack su Proxmox / deploy (push) Failing after 3s
Monorepo npm con apps/public (Angular SSR), apps/private (Angular SPA), apps/api (Fastify), packages/shared e packages/styles; Medusa 2.21 in medusa/; infrastruttura Docker (Postgres PostGIS+pgvector, Redis, Caddy, backup) e workflow Gitea Actions per il deploy via SSH a ogni push su master. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jQyT6dR9czzGwqb7FkG6u
89 lines
3.6 KiB
TypeScript
89 lines
3.6 KiB
TypeScript
import type { ContactRequestDto, ContactRequestStatus, Paginated } from '@iti/shared'
|
|
import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core'
|
|
import { rxResource } from '@angular/core/rxjs-interop'
|
|
import { RouterLink } from '@angular/router'
|
|
import { ApiService } from '../../core/api.service'
|
|
import { formatDateTime } from '../../core/format'
|
|
import { CONTACT_STATUS } from '../../core/labels'
|
|
import { Badge } from '../../shared/badge'
|
|
import { type FilterOption, FilterPills, SearchBox } from '../../shared/list-filters'
|
|
import { Pager, pageFromQuery } from '../../shared/pager'
|
|
import { Empty, Loading, LoadError } from '../../shared/state'
|
|
import { ADMIN_PAGE_SIZE, adminTitle, pageParams } from './admin-common'
|
|
|
|
const STATUSES = Object.keys(CONTACT_STATUS) as ContactRequestStatus[]
|
|
const FILTERS: FilterOption[] = [{ value: null, label: 'Tutte' }, ...STATUSES.map((s) => ({ value: s, label: CONTACT_STATUS[s].label }))]
|
|
|
|
/** Messaggi dal form contatti del sito. */
|
|
@Component({
|
|
selector: 'app-admin-contacts',
|
|
imports: [RouterLink, Badge, FilterPills, SearchBox, Pager, Loading, Empty, LoadError],
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
template: `
|
|
<h1>Contatti</h1>
|
|
<div class="toolbar">
|
|
<app-filter-pills param="stato" [options]="filters" [current]="status()" label="Filtra per stato" />
|
|
<app-search-box [value]="q()" label="Cerca per nome, email o oggetto" />
|
|
</div>
|
|
|
|
@if (res.isLoading()) {
|
|
<app-loading label="Caricamento dei messaggi…" />
|
|
} @else if (res.error()) {
|
|
<app-load-error [error]="res.error()" (retry)="res.reload()" />
|
|
} @else if (res.value(); as data) {
|
|
@if (data.items.length) {
|
|
<ul class="item-list">
|
|
@for (c of data.items; track c.id) {
|
|
<li>
|
|
<a class="item" [routerLink]="['/admin/contatti', c.id]">
|
|
<span class="item__main">
|
|
<span class="item__title">{{ c.subject || 'Senza oggetto' }}</span>
|
|
<span class="meta">{{ c.name }} · {{ c.email }} · {{ dateTime(c.createdAt) }}</span>
|
|
<span class="excerpt">{{ excerpt(c.message) }}</span>
|
|
</span>
|
|
<app-badge [status]="labels[c.status]" />
|
|
</a>
|
|
</li>
|
|
}
|
|
</ul>
|
|
<app-pager [page]="page()" [total]="data.total" [pageSize]="pageSize" />
|
|
} @else {
|
|
<app-empty message="Nessun messaggio trovato." />
|
|
}
|
|
}
|
|
`,
|
|
styles: `
|
|
.excerpt {
|
|
color: var(--color-ink-muted);
|
|
overflow-wrap: anywhere;
|
|
}
|
|
`,
|
|
})
|
|
export default class AdminContacts {
|
|
private readonly api = inject(ApiService)
|
|
readonly pagina = input<string>()
|
|
readonly stato = input<string>()
|
|
readonly q = input<string>()
|
|
|
|
protected readonly filters = FILTERS
|
|
protected readonly pageSize = ADMIN_PAGE_SIZE
|
|
protected readonly page = computed(() => pageFromQuery(this.pagina()))
|
|
protected readonly status = computed(() => ((STATUSES as string[]).includes(this.stato() ?? '') ? (this.stato() as ContactRequestStatus) : null))
|
|
protected readonly res = rxResource({
|
|
params: () => ({ page: this.page(), status: this.status(), q: this.q() }),
|
|
stream: ({ params }) =>
|
|
this.api.get<Paginated<ContactRequestDto>>('/admin/contact-requests', { query: { ...pageParams(params.page), status: params.status, q: params.q } }),
|
|
})
|
|
|
|
protected readonly labels = CONTACT_STATUS
|
|
protected readonly dateTime = formatDateTime
|
|
protected excerpt(text: string): string {
|
|
const flat = text.replace(/\s+/g, ' ').trim()
|
|
return flat.length > 140 ? `${flat.slice(0, 139)}…` : flat
|
|
}
|
|
|
|
constructor() {
|
|
adminTitle()('Contatti')
|
|
}
|
|
}
|