|
|
|
@ -1,7 +1,7 @@
|
|
|
|
import { useEffect, useState } from 'react'
|
|
|
|
import { useEffect, useState } from 'react'
|
|
|
|
import { useNavigate, useParams } from 'react-router-dom'
|
|
|
|
import { Link, useNavigate, useParams } from 'react-router-dom'
|
|
|
|
import { formatINR } from '@sims/domain'
|
|
|
|
import { formatINR } from '@sims/domain'
|
|
|
|
import { Badge, Button, DataTable, EmptyState, Notice, PageHeader, Toolbar } from '@sims/ui'
|
|
|
|
import { Badge, Button, DataTable, EmptyState, ErrorState, Notice, RecordHeader, Skeleton, Toolbar } from '@sims/ui'
|
|
|
|
import {
|
|
|
|
import {
|
|
|
|
createShare, documentAction, downloadDocumentPdf, fetchPdfBlob, getClient, getDocumentFull,
|
|
|
|
createShare, documentAction, downloadDocumentPdf, fetchPdfBlob, getClient, getDocumentFull,
|
|
|
|
revokeShare, type Client, type Doc, type Share,
|
|
|
|
revokeShare, type Client, type Doc, type Share,
|
|
|
|
@ -56,8 +56,8 @@ export function DocumentView() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, [id, doc?.docNo, doc?.status])
|
|
|
|
}, [id, doc?.docNo, doc?.status])
|
|
|
|
|
|
|
|
|
|
|
|
if (full.error !== undefined) return <Notice tone="err">{full.error}</Notice>
|
|
|
|
if (full.error !== undefined) return <div className="wf-page"><ErrorState message={full.error} onRetry={full.reload} /></div>
|
|
|
|
if (doc === undefined || full.data === undefined) return <EmptyState>Loading…</EmptyState>
|
|
|
|
if (doc === undefined || full.data === undefined) return <div className="wf-page"><Skeleton rows={6} /></div>
|
|
|
|
|
|
|
|
|
|
|
|
/** POST an action; convert/credit-note navigate to the new document. */
|
|
|
|
/** POST an action; convert/credit-note navigate to the new document. */
|
|
|
|
const act = (action: string, body?: Record<string, unknown>, navigateToResult = false) => {
|
|
|
|
const act = (action: string, body?: Record<string, unknown>, navigateToResult = false) => {
|
|
|
|
@ -87,11 +87,23 @@ export function DocumentView() {
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
<div className="wf-page">
|
|
|
|
<div className="wf-page">
|
|
|
|
<PageHeader
|
|
|
|
<div className="wf-crumbs">
|
|
|
|
title={`${TITLE[doc.docType]} ${doc.docNo ?? '(draft)'}`}
|
|
|
|
<Link to="/clients">Clients</Link><span>/</span>
|
|
|
|
badge={doc.fy}
|
|
|
|
{client.data !== undefined ? <Link to={`/clients/${doc.clientId}`}>{client.data.name}</Link> : <span>…</span>}
|
|
|
|
desc={`${client.data?.name ?? '…'} · ${doc.docDate} · Payable ${formatINR(doc.payablePaise)}`}
|
|
|
|
<span>/</span><span className="mono">{doc.docNo ?? 'draft'}</span>
|
|
|
|
actions={<Badge tone={DOC_TONE[doc.status]}>{doc.status}</Badge>}
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<RecordHeader
|
|
|
|
|
|
|
|
name={TITLE[doc.docType]}
|
|
|
|
|
|
|
|
avatarSeed={client.data?.name ?? doc.clientId}
|
|
|
|
|
|
|
|
status={<Badge tone={DOC_TONE[doc.status]}>{doc.status}</Badge>}
|
|
|
|
|
|
|
|
meta={
|
|
|
|
|
|
|
|
<>
|
|
|
|
|
|
|
|
<span className="mono">{doc.docNo ?? '(draft)'}</span>
|
|
|
|
|
|
|
|
<span>· {doc.fy}</span>
|
|
|
|
|
|
|
|
<span>· {doc.docDate}</span>
|
|
|
|
|
|
|
|
<span>· Payable <strong className="num">{formatINR(doc.payablePaise)}</strong></span>
|
|
|
|
|
|
|
|
</>
|
|
|
|
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<Toolbar>
|
|
|
|
<Toolbar>
|
|
|
|
@ -160,36 +172,42 @@ export function DocumentView() {
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
<h3 style={{ marginTop: 20 }}>Timeline</h3>
|
|
|
|
<div className="dash-grid">
|
|
|
|
{full.data.events.length === 0 ? <EmptyState>No events.</EmptyState> : (
|
|
|
|
<section>
|
|
|
|
<DataTable
|
|
|
|
<h3 style={{ marginTop: 20 }}>Timeline</h3>
|
|
|
|
columns={[
|
|
|
|
{full.data.events.length === 0 ? <EmptyState>No events.</EmptyState> : (
|
|
|
|
{ key: 'at', label: 'When' }, { key: 'kind', label: 'Event' }, { key: 'meta', label: 'Detail' },
|
|
|
|
<DataTable
|
|
|
|
]}
|
|
|
|
columns={[
|
|
|
|
rows={full.data.events.map((ev) => ({
|
|
|
|
{ key: 'at', label: 'When' }, { key: 'kind', label: 'Event' }, { key: 'meta', label: 'Detail' },
|
|
|
|
at: new Date(ev.atWall).toLocaleString(),
|
|
|
|
]}
|
|
|
|
kind: <Badge tone="accent">{ev.kind}</Badge>,
|
|
|
|
rows={full.data.events.map((ev) => ({
|
|
|
|
meta: Object.keys(ev.meta).length > 0 ? JSON.stringify(ev.meta) : '—',
|
|
|
|
at: new Date(ev.atWall).toLocaleString(),
|
|
|
|
}))}
|
|
|
|
kind: <Badge tone="accent">{ev.kind}</Badge>,
|
|
|
|
/>
|
|
|
|
meta: Object.keys(ev.meta).length > 0 ? JSON.stringify(ev.meta) : '—',
|
|
|
|
)}
|
|
|
|
}))}
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<h3 style={{ marginTop: 20 }}>Email log</h3>
|
|
|
|
<section>
|
|
|
|
{full.data.emails.length === 0 ? <EmptyState>Not emailed yet.</EmptyState> : (
|
|
|
|
<h3 style={{ marginTop: 20 }}>Email log</h3>
|
|
|
|
<DataTable
|
|
|
|
{full.data.emails.length === 0 ? <EmptyState>Not emailed yet.</EmptyState> : (
|
|
|
|
columns={[
|
|
|
|
<DataTable
|
|
|
|
{ key: 'at', label: 'When' }, { key: 'to', label: 'To' },
|
|
|
|
columns={[
|
|
|
|
{ key: 'subject', label: 'Subject' }, { key: 'status', label: 'Status' },
|
|
|
|
{ key: 'at', label: 'When' }, { key: 'to', label: 'To' },
|
|
|
|
{ key: 'error', label: 'Error' },
|
|
|
|
{ key: 'subject', label: 'Subject' }, { key: 'status', label: 'Status' },
|
|
|
|
]}
|
|
|
|
{ key: 'error', label: 'Error' },
|
|
|
|
rows={full.data.emails.map((e) => ({
|
|
|
|
]}
|
|
|
|
at: new Date(e.at_wall).toLocaleString(),
|
|
|
|
rows={full.data.emails.map((e) => ({
|
|
|
|
to: e.to_addr, subject: e.subject,
|
|
|
|
at: new Date(e.at_wall).toLocaleString(),
|
|
|
|
status: <Badge tone={e.status === 'sent' ? 'ok' : 'err'}>{e.status}</Badge>,
|
|
|
|
to: e.to_addr, subject: e.subject,
|
|
|
|
error: e.error ?? '—',
|
|
|
|
status: <Badge tone={e.status === 'sent' ? 'ok' : 'err'}>{e.status}</Badge>,
|
|
|
|
}))}
|
|
|
|
error: e.error ?? '—',
|
|
|
|
/>
|
|
|
|
}))}
|
|
|
|
)}
|
|
|
|
/>
|
|
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|