This commit is contained in:
2026-03-19 15:47:42 +01:00
parent 4e19090f0f
commit 7fc9108d2a
9 changed files with 194 additions and 30 deletions
+10
View File
@@ -12,6 +12,7 @@ import type {
VirtualBoxUpdate,
} from '@/types/api.types'
export const virtualBoxesApi = {
/** Crea una nuova Virtual Box. */
create: (data: VirtualBoxCreate) =>
@@ -27,6 +28,15 @@ export const virtualBoxesApi = {
myVirtualBoxes: () =>
apiClient.get<VirtualBoxResponse[]>('/virtual-boxes/my').then((r) => r.data),
/**
* Caselle PEC attive da cui l'utente può inviare tramite le sue Virtual Box.
* Usato nella pagina di composizione per mostrare le caselle mittente disponibili.
*/
getMyMailboxes: () =>
apiClient
.get<MailboxBriefResponse[]>('/virtual-boxes/my/mailboxes')
.then((r) => r.data),
/** Dettaglio Virtual Box. */
get: (id: string) =>
apiClient.get<VirtualBoxResponse>(`/virtual-boxes/${id}`).then((r) => r.data),
+72 -19
View File
@@ -1,7 +1,7 @@
import { useState, useRef } from 'react'
import { useState, useRef, useMemo } from 'react'
import { useNavigate, useLocation } from 'react-router-dom'
import { useForm, useFieldArray } from 'react-hook-form'
import { Send, X, Plus, ArrowLeft, AlertCircle, Paperclip, Upload } from 'lucide-react'
import { Send, X, Plus, ArrowLeft, AlertCircle, Paperclip, Upload, Filter } from 'lucide-react'
import { useQuery, useMutation } from '@tanstack/react-query'
import toast from 'react-hot-toast'
import { Button } from '@/components/ui/Button'
@@ -10,9 +10,19 @@ import { Label } from '@/components/ui/Label'
import { RichTextEditor } from '@/components/RichTextEditor/RichTextEditor'
import { sendApi } from '@/api/send.api'
import { mailboxesApi } from '@/api/mailboxes.api'
import { virtualBoxesApi } from '@/api/virtual_boxes.api'
import { getErrorMessage } from '@/api/client'
import type { MessageResponse } from '@/types/api.types'
/** Tipo unificato casella mittente (sia da permessi diretti che da Virtual Box) */
interface MailboxSelectItem {
id: string
email_address: string
display_name: string | null
status: string
fromVbox?: boolean
}
interface ComposeFormValues {
mailbox_id: string
to_addresses: { value: string }[]
@@ -91,12 +101,18 @@ export function ComposePage() {
remove: removeCc,
} = useFieldArray({ control, name: 'cc_addresses' })
// Carica caselle disponibili per l'invio
// Carica caselle disponibili per l'invio (permessi diretti)
const { data: mailboxesData, isLoading: mailboxesLoading } = useQuery({
queryKey: ['mailboxes'],
queryFn: () => mailboxesApi.list(),
})
// Carica caselle disponibili tramite Virtual Box (operatori senza permessi diretti)
const { data: vboxMailboxes = [], isLoading: vboxMailboxesLoading } = useQuery({
queryKey: ['virtual-boxes', 'my-mailboxes'],
queryFn: () => virtualBoxesApi.getMyMailboxes(),
})
const sendMutation = useMutation({
mutationFn: (args: {
data: Parameters<typeof sendApi.sendMultipart>[0]
@@ -158,7 +174,36 @@ export function ComposePage() {
})
}
const activeCaselle = mailboxesData?.items.filter((m) => m.status === 'active') || []
// ── Lista unificata caselle mittente ──────────────────────────────────────
// Combina caselle con permesso diretto + caselle accessibili via VBox
const activeCaselle = useMemo((): MailboxSelectItem[] => {
const regularActive: MailboxSelectItem[] = (
mailboxesData?.items.filter((m) => m.status === 'active') || []
).map((m) => ({
id: m.id,
email_address: m.email_address,
display_name: m.display_name,
status: m.status,
fromVbox: false,
}))
const regularIds = new Set(regularActive.map((m) => m.id))
// Aggiungi caselle VBox non già presenti nella lista diretta
const vboxActive: MailboxSelectItem[] = vboxMailboxes
.filter((m) => m.status === 'active' && !regularIds.has(m.id))
.map((m) => ({
id: m.id,
email_address: m.email_address,
display_name: m.display_name,
status: m.status,
fromVbox: true,
}))
return [...regularActive, ...vboxActive]
}, [mailboxesData, vboxMailboxes])
const isLoadingMailboxes = mailboxesLoading || vboxMailboxesLoading
return (
<div className="flex flex-col h-full">
@@ -197,27 +242,35 @@ export function ComposePage() {
{/* Casella mittente */}
<div className="space-y-1.5">
<Label htmlFor="mailbox_id">Casella mittente *</Label>
{mailboxesLoading ? (
{isLoadingMailboxes ? (
<div className="h-10 rounded-md border bg-muted animate-pulse" />
) : activeCaselle.length === 0 ? (
<div className="rounded-md border border-destructive/50 bg-destructive/10 p-3 text-sm text-destructive">
Nessuna casella PEC attiva disponibile. Contatta l'amministratore.
</div>
) : (
<select
id="mailbox_id"
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
{...register('mailbox_id', {
required: 'Seleziona una casella mittente',
})}
>
<option value="">Seleziona casella...</option>
{activeCaselle.map((mb) => (
<option key={mb.id} value={mb.id}>
{mb.display_name || mb.email_address} ({mb.email_address})
</option>
))}
</select>
<>
<select
id="mailbox_id"
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
{...register('mailbox_id', {
required: 'Seleziona una casella mittente',
})}
>
<option value="">Seleziona casella...</option>
{activeCaselle.map((mb) => (
<option key={mb.id} value={mb.id}>
{mb.fromVbox ? '📥 ' : ''}{mb.display_name || mb.email_address} ({mb.email_address})
</option>
))}
</select>
{activeCaselle.some((m) => m.fromVbox) && (
<p className="text-xs text-purple-600 flex items-center gap-1">
<Filter className="h-3 w-3" />
Le caselle con 📥 sono accessibili tramite Virtual Box
</p>
)}
</>
)}
{errors.mailbox_id && (
<p className="text-xs text-destructive">{errors.mailbox_id.message}</p>
+1
View File
@@ -353,6 +353,7 @@ export interface MailboxBriefResponse {
id: string
email_address: string
display_name: string | null
status: string
}
export interface VirtualBoxCreate {