feat(hq-web): module quote-content live print preview

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat/client-detail-redesign
Thomas Joise 1 week ago
parent b65e03e1c4
commit 0b340fd85f

@ -1,10 +1,11 @@
import { useState } from 'react' import { useMemo, useState } from 'react'
import { formatINR, fromRupees } from '@sims/domain' import { formatINR, fromRupees } from '@sims/domain'
import { Badge, Button, DataTable, EmptyState, Field, Notice, PageHeader, Toolbar } from '@sims/ui' import { Badge, Button, DataTable, EmptyState, Field, Notice, PageHeader, Toolbar } from '@sims/ui'
import { import {
addPrice, createModule, getModules, getPrices, patchModule, role, addPrice, createModule, getModules, getPrices, patchModule, previewSample, role,
KIND_LABEL, type Kind, type Module, KIND_LABEL, type Kind, type Module,
} from '../api' } from '../api'
import { LivePreview } from '../components/LivePreview'
import { useData } from './Clients' import { useData } from './Clients'
const ALL_KINDS: Kind[] = ['one_time', 'monthly', 'yearly', 'usage'] const ALL_KINDS: Kind[] = ['one_time', 'monthly', 'yearly', 'usage']
@ -65,6 +66,8 @@ function QuoteContent(props: { module: Module; isOwner: boolean; onSaved: () =>
const [text, setText] = useState(props.module.quoteContent.join('\n')) const [text, setText] = useState(props.module.quoteContent.join('\n'))
const [saved, setSaved] = useState(false) const [saved, setSaved] = useState(false)
const [error, setError] = useState<string | undefined>() const [error, setError] = useState<string | undefined>()
const [commitNonce, setCommitNonce] = useState(0)
const previewBody = useMemo(() => JSON.stringify({ contentLines: splitLines(text) }), [text])
const save = () => { const save = () => {
setError(undefined) setError(undefined)
@ -77,17 +80,26 @@ function QuoteContent(props: { module: Module; isOwner: boolean; onSaved: () =>
<> <>
<h3 style={{ marginTop: 20 }}>Quote content {props.module.name}</h3> <h3 style={{ marginTop: 20 }}>Quote content {props.module.name}</h3>
{props.isOwner ? ( {props.isOwner ? (
<div style={{ maxWidth: 560 }}> <div style={{ display: 'flex', gap: 16, alignItems: 'flex-start', flexWrap: 'wrap' }}>
<textarea <div style={{ flex: '0 0 560px', minWidth: 0 }}>
className="wf" rows={4} placeholder="One included item per line…" <textarea className="wf" rows={4} placeholder="One included item per line…"
value={text} onChange={(e) => { setText(e.target.value); setSaved(false) }} value={text} onChange={(e) => { setText(e.target.value); setSaved(false) }} onBlur={() => setCommitNonce((n) => n + 1)} />
/>
<Toolbar> <Toolbar>
<Button tone="primary" onClick={save}>Save quote content</Button> <Button tone="primary" onClick={save}>Save quote content</Button>
{saved && <Badge tone="ok">saved</Badge>} {saved && <Badge tone="ok">saved</Badge>}
</Toolbar> </Toolbar>
{error !== undefined && <Notice tone="err">{error}</Notice>} {error !== undefined && <Notice tone="err">{error}</Notice>}
</div> </div>
<div style={{ flex: 1, minWidth: 320, height: 420, background: 'var(--bg-sunken, #e9e9ee)', borderRadius: 8, padding: 12 }}>
<div style={{ width: '100%', height: '100%', background: '#fff', borderRadius: 4, boxShadow: '0 1px 6px rgba(0,0,0,.15)', overflow: 'hidden' }}>
<LivePreview
body={previewBody}
commitNonce={commitNonce}
fetchPreview={(b, signal) => previewSample(JSON.parse(b), signal).then((r) => ({ html: r.html }))}
/>
</div>
</div>
</div>
) : props.module.quoteContent.length === 0 ? ( ) : props.module.quoteContent.length === 0 ? (
<EmptyState>No quote content.</EmptyState> <EmptyState>No quote content.</EmptyState>
) : ( ) : (

Loading…
Cancel
Save