import { useState } from 'react' import { Link } from 'react-router-dom' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { client, errorMessage } from '../../api/client' import type { Page } from '../../api/types' import { Icon } from '../../components/Icon' import { PAGE_SIZE, Pagination } from '../../components/Pagination' import { useToasts } from '../../components/Toasts' import { Badge, Button, EmptyState, ErrorState, Select, Spinner, Textarea } from '../../components/ui' import { formatLocal } from '../../lib/dates' import { panelPath } from '../../lib/routes' /** * Lista zapytan ofertowych wraz z ich obsluga. * * Jeden komponent obsluguje dwa miejsca: zakladke konkretnego webinaru * (`webinarId` podany) i ekran zbiorczy ze wszystkich webinarow (`webinarId` * puste). Dzieki temu handlowiec widzi zapytanie tak samo niezaleznie od tego, * ktora droga do niego trafil, a poprawka w obsludze nie wymaga zmiany * w dwoch plikach. * * Gdy komponent jest osadzony przy webinarze, znika filtr po webinarze * i nazwa webinaru przy kazdej pozycji: jedno i drugie powtarzaloby to, * co uzytkownik ma w naglowku strony. */ export type OfferView = { id: number webinarId: number webinarTitle: string content: string topic: string | null status: 'NEW' | 'HANDLED' | 'REJECTED' | 'APPROVED' | 'ANSWERED' handlerNote: string | null firstName: string | null lastName: string | null email: string | null phone: string | null company: string | null consentVersion: string | null consentAt: string | null createdAt: string } const STATUS_LABEL: Record = { NEW: 'Nowe', HANDLED: 'Załatwione', } export function OfferSection({ webinarId }: { webinarId?: number }) { const [status, setStatus] = useState('') const [page, setPage] = useState(0) const [exporting, setExporting] = useState(false) const toasts = useToasts() const offers = useQuery({ queryKey: ['offers', status, webinarId ?? '', page], queryFn: async () => { const response = await client.get>('/admin/offers', { params: { status: status || undefined, webinarId: webinarId ?? undefined, page, size: PAGE_SIZE, }, }) return response.data }, }) async function handleExport() { setExporting(true) try { const response = await client.get('/admin/offers/export', { params: { status: status || undefined, webinarId: webinarId ?? undefined }, responseType: 'blob', }) const url = URL.createObjectURL(response.data as Blob) const link = document.createElement('a') link.href = url link.download = 'zapytania.csv' document.body.appendChild(link) link.click() link.remove() URL.revokeObjectURL(url) toasts.success('Plik CSV został pobrany') } catch (error) { toasts.error(errorMessage(error, 'Nie udało się pobrać pliku CSV')) } finally { setExporting(false) } } return (
{offers.isPending && } {offers.isError && (
)} {offers.data && offers.data.content.length === 0 && ( )} {offers.data && offers.data.content.length > 0 && (
    {offers.data.content.map((offer) => ( ))}
)} {offers.data && ( )}
) } /** * Jedno zapytanie wraz z obsluga. * * `showWebinar` odpowiada za nazwe webinaru: przy zakladce konkretnego * webinaru jest zbedna, na ekranie zbiorczym niezbedna. */ function OfferRow({ offer, showWebinar }: { offer: OfferView; showWebinar: boolean }) { const queryClient = useQueryClient() const toasts = useToasts() const [noting, setNoting] = useState(false) const [note, setNote] = useState(offer.handlerNote ?? '') const handle = useMutation({ mutationFn: () => client.post(`/admin/offers/${offer.id}/handled`, { note }), onSuccess: () => { toasts.success('Zapytanie oznaczone jako załatwione') queryClient.invalidateQueries({ queryKey: ['offers'] }) // licznik zapytan przy webinarze pochodzi z listy webinarow queryClient.invalidateQueries({ queryKey: ['webinars'] }) setNoting(false) }, onError: (error) => toasts.error(errorMessage(error, 'Nie udało się zapisać')), }) return (
  • {offer.firstName} {offer.lastName} {offer.company && - {offer.company}}

    {offer.email} {offer.phone && ( {offer.phone} )}
    {STATUS_LABEL[offer.status] ?? offer.status}

    {offer.content}

    {showWebinar && ( {offer.webinarTitle} )} {offer.topic && temat: {offer.topic}} {formatLocal(offer.createdAt)} {offer.consentVersion && ( zgoda {offer.consentVersion} {offer.consentAt && z {formatLocal(offer.consentAt)}} )}
    {offer.handlerNote && !noting && (

    Notatka: {offer.handlerNote}

    )} {noting ? (