GuestOps/web/src/AutomationPage.tsx

24 lines
6.5 KiB
TypeScript

import {useEffect,useState} from 'react';
import {api,type Hotel,type Knowledge} from './api';
type Rule={id:string;question:string;knowledgeId:string;knowledgeVersion:number;enabled:boolean;version:number};
type Status={liveConfigured:boolean;preview:boolean;questions:string[];dailyLimit:number};
type Decision={matches:boolean;reason:string;body:string};
type History={id:string;subject:string;from:string;autoReplyCheckedAt:string;autoReplyMatched:boolean;autoReplyDetail:string;delivery:string|null};
type Props={hotel:Hotel;owner:boolean;busy:boolean;run:(f:()=>Promise<void>)=>Promise<void>;onHotel:(h:Hotel)=>void};
export function AutomationPage({hotel,owner,busy,run,onHotel}:Props){
const [status,setStatus]=useState<Status|null>(null),[rules,setRules]=useState<Rule[]>([]),[knowledge,setKnowledge]=useState<Knowledge[]>([]),[history,setHistory]=useState<History[]>([]);
const [question,setQuestion]=useState(0),[answer,setAnswer]=useState(''),[enabled,setEnabled]=useState(false),[subject,setSubject]=useState('Parking question'),[body,setBody]=useState('Is parking available?'),[result,setResult]=useState<Decision|null>(null);
async function refresh(){const [s,r,k,h]=await Promise.all([api<Status>('/auto-replies/status'),api<Rule[]>('/auto-replies/rules'),api<Knowledge[]>('/knowledge'),api<History[]>('/auto-replies/history')]);setStatus(s);setRules(r);setKnowledge(k);setHistory(h);}
useEffect(()=>{run(refresh);},[hotel.id]);
const current=rules.find(r=>r.question===status?.questions[question]);
useEffect(()=>{setAnswer(current?.knowledgeId||'');setEnabled(current?.enabled||false);},[question,current?.id,current?.version]);
async function mode(value:string){await run(async()=>{if(value==='Live'&&!window.confirm('Enable automatic FAQ sending for new incoming messages? Confirm that you reviewed test-mode results and accepted Gmail delivery with a sandbox mailbox. Up to 20 replies per hotel per UTC day may be sent.'))return;onHotel(await api<Hotel>('/auto-replies/mode','PUT',{mode:value,version:hotel.version,acceptanceConfirmed:value==='Live'}));});}
async function save(e:React.FormEvent){e.preventDefault();await run(async()=>{const item=await api<Rule>(`/auto-replies/rules/${question}`,'PUT',{question:status!.questions[question],knowledgeId:answer,enabled,version:current?.version||0});setRules(old=>[item,...old.filter(r=>r.id!==item.id)]);setResult(null);});}
return <div className="page"><div className="heading-row"><div className="page-heading"><span className="eyebrow">Simple questions, thoughtful answers</span><h1>FAQ automation</h1><p>Start in test mode. Let approved answers handle a small set of straightforward questions.</p></div><button className="button secondary" disabled={busy} onClick={()=>run(refresh)}>Refresh results</button></div>
<section className="settings-card"><h2>Automation mode: {hotel.autoReplyMode||'Off'}</h2><p>Only exact, complete FAQ questions qualify. Extra requests, attachments and conversations already in progress stay with your team.</p><div className="form-actions"><button className="button secondary" disabled={busy||!owner} onClick={()=>mode('Off')}>Turn off</button><button className="button secondary" disabled={busy||!owner} onClick={()=>mode('Test')}>Use test mode</button><button className="button primary" disabled={busy||!owner||!status?.liveConfigured||!hotel.staffSendingEnabled} onClick={()=>mode('Live')}>Enable live replies</button></div><p className="small muted">Test mode sends nothing. Live mode requires administrator enablement and Gmail sending. Each mode change starts with new incoming messages; existing inbox messages are not sent automatically.</p><p className="small muted">Maximum 20 automatic replies per hotel per UTC day, one per sender per UTC day, and one per Gmail thread. Turning off stops queued replies when the worker next checks them; a request already submitted to Gmail cannot be recalled.</p>{status?.preview&&<p className="staff-note">Sample workspace: the question tester works here. Live sending is disabled.</p>}</section>
<div className="pms-columns"><section className="settings-card"><h2>Review a FAQ rule</h2><form onSubmit={save}><fieldset disabled={busy||!owner}><label>Complete guest question<select value={question} onChange={e=>setQuestion(Number(e.target.value))}>{status?.questions.map((q,i)=><option value={i} key={q}>{q}</option>)}</select></label><label>Approved hotel answer<select value={answer} required onChange={e=>setAnswer(e.target.value)}><option value="">Choose an answer</option>{knowledge.filter(k=>k.approved).map(k=><option value={k.id} key={k.id}>{k.title}</option>)}</select></label>{answer&&<p className="staff-note">{knowledge.find(k=>k.id===answer)?.answer}</p>}<label className="checkbox-label"><input type="checkbox" checked={enabled} onChange={e=>setEnabled(e.target.checked)}/>Enable this exact question and reviewed answer</label><button className="button secondary">Save reviewed rule</button></fieldset></form><p className="small muted">The approved answer is sent exactly as saved, with the hotel signature. Editing the answer pauses matching until this rule is reviewed and saved again.</p></section>
<section className="settings-card"><h2>Try a question</h2><form onSubmit={e=>{e.preventDefault();run(async()=>setResult(await api<Decision>('/auto-replies/test','POST',{subject,body})));}}><fieldset disabled={busy||!owner}><label>Subject<input value={subject} maxLength={200} onChange={e=>setSubject(e.target.value)}/></label><label>Complete message<textarea rows={4} value={body} maxLength={2000} required onChange={e=>setBody(e.target.value)}/></label><button className="button secondary">Check match without sending</button></fieldset></form>{result&&<div role="status" className="staff-note"><strong>{result.matches?'Content matches a reviewed rule':'Keep with staff'}</strong><p>{result.reason}</p>{result.body&&<p>{result.body}</p>}</div>}<p className="small muted">This tester checks content only. Real messages must also pass sender, recipient, age, thread and delivery-limit checks.</p></section></div>
<section className="settings-card"><h2>Recent incoming-message results</h2>{history.length===0?<p>No incoming messages have been evaluated yet. Enable test mode after connecting your mailbox.</p>:<div className="pms-history">{history.map(h=><div className="pms-history-item" key={h.id}><strong>{h.subject}</strong><span>{h.autoReplyDetail}</span><span>{h.delivery?`Delivery: ${h.delivery} · `:''}{new Date(h.autoReplyCheckedAt).toLocaleString()}</span></div>)}</div>}</section>
</div>;
}