feat: implement template editing functionality and revert approved templates to draft status
This commit is contained in:
@@ -58,6 +58,9 @@ export default function SmsPage() {
|
||||
const [tagFilter, setTagFilter] = useState('');
|
||||
const [editMsg, setEditMsg] = useState<SmsMessageText | null>(null);
|
||||
const [editBody, setEditBody] = useState('');
|
||||
const [editTemplate, setEditTemplate] = useState<SmsTemplate | null>(null);
|
||||
const [editTplName, setEditTplName] = useState('');
|
||||
const [editTplBody, setEditTplBody] = useState('');
|
||||
const limit = 15;
|
||||
|
||||
const sampleTemplatesQuery = useQuery({
|
||||
@@ -119,6 +122,17 @@ export default function SmsPage() {
|
||||
onError: (err: Error) => toast.error(err.message),
|
||||
});
|
||||
|
||||
const updateTemplateMut = useMutation({
|
||||
mutationFn: ({ uuid, name, body }: { uuid: string; name: string; body: string }) =>
|
||||
api.patch<ApiResponse<SmsTemplate>>(`/api/v1/sms/template/${uuid}`, { name, body }),
|
||||
onSuccess: () => {
|
||||
toast.success('قالب ویرایش شد');
|
||||
setEditTemplate(null);
|
||||
qc.invalidateQueries({ queryKey: ['sms-samples'] });
|
||||
},
|
||||
onError: (err: Error) => toast.error(err.message),
|
||||
});
|
||||
|
||||
const approveMutation = useMutation({
|
||||
mutationFn: (t: SmsTemplate) =>
|
||||
api.post<ApiResponse<null>>(`/api/v1/admin/sms/template/${t.uuid}/approve`, {}),
|
||||
@@ -292,9 +306,17 @@ export default function SmsPage() {
|
||||
loading={sampleTemplatesQuery.isLoading}
|
||||
emptyMessage="هیچ قالبی یافت نشد"
|
||||
actions={(t) => (
|
||||
<button onClick={() => setDeleteTarget(t)} className="mini-btn danger" title="حذف">
|
||||
<TrashIcon style={{ width: 15, height: 15 }} />
|
||||
</button>
|
||||
<>
|
||||
<button
|
||||
onClick={() => { setEditTemplate(t); setEditTplName(t.name); setEditTplBody(t.body); }}
|
||||
className="mini-btn" title="ویرایش"
|
||||
>
|
||||
<PencilIcon style={{ width: 15, height: 15 }} />
|
||||
</button>
|
||||
<button onClick={() => setDeleteTarget(t)} className="mini-btn danger" title="حذف">
|
||||
<TrashIcon style={{ width: 15, height: 15 }} />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<Pagination
|
||||
@@ -482,6 +504,39 @@ export default function SmsPage() {
|
||||
</form>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
open={editTemplate !== null}
|
||||
title="ویرایش قالب نمونه"
|
||||
onClose={() => setEditTemplate(null)}
|
||||
footer={
|
||||
<>
|
||||
<button onClick={() => setEditTemplate(null)} className="btn ghost sm">لغو</button>
|
||||
<button
|
||||
className="btn primary sm"
|
||||
disabled={updateTemplateMut.isPending || !editTplName.trim() || !editTplBody.trim()}
|
||||
onClick={() => editTemplate && updateTemplateMut.mutate({ uuid: editTemplate.uuid, name: editTplName, body: editTplBody })}
|
||||
>
|
||||
{updateTemplateMut.isPending ? 'در حال ذخیره...' : 'ذخیره'}
|
||||
</button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className="form-row">
|
||||
<label>نام قالب</label>
|
||||
<input className="input" value={editTplName} onChange={(e) => setEditTplName(e.target.value)} />
|
||||
</div>
|
||||
<div className="form-row" style={{ marginTop: 12 }}>
|
||||
<label>متن قالب</label>
|
||||
<textarea className="input" rows={4} dir="rtl" value={editTplBody}
|
||||
onChange={(e) => setEditTplBody(e.target.value)} style={{ resize: 'none', height: 'auto' }} />
|
||||
</div>
|
||||
{editTemplate?.status === 'approved' && (
|
||||
<p className="muted" style={{ fontSize: 12, marginTop: 8 }}>
|
||||
توجه: با ویرایش، این قالبِ تأییدشده به «پیشنویس» برمیگردد و باید دوباره برای تأیید ارسال شود.
|
||||
</p>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
<Modal open={!!rejectTarget} title="رد قالب پیامک"
|
||||
onClose={() => { setRejectTarget(null); setRejectReason(''); }}
|
||||
footer={
|
||||
|
||||
@@ -204,11 +204,15 @@ class SmsController extends BaseController
|
||||
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'تمپلیت یافت نشد', 404);
|
||||
}
|
||||
|
||||
if ($template->getStatus() === SmsTemplate::STATUS_APPROVED) {
|
||||
return $this->error(ErrorCodes::ERR_SMS_003, ErrorCodes::message(ErrorCodes::ERR_SMS_003), 422);
|
||||
$data = json_decode($request->getContent(), true) ?? [];
|
||||
|
||||
// ویرایش محتوای یک قالبِ تأییدشده، آن را به پیشنویس برمیگرداند تا متن جدید
|
||||
// دوباره تأیید/ارسال شود (متن قبلی با سرویسدهنده هماهنگ بوده).
|
||||
$contentChanged = array_key_exists('name', $data) || array_key_exists('body', $data) || array_key_exists('variables', $data);
|
||||
if ($template->getStatus() === SmsTemplate::STATUS_APPROVED && $contentChanged) {
|
||||
$template->revertToDraft();
|
||||
}
|
||||
|
||||
$data = json_decode($request->getContent(), true) ?? [];
|
||||
if (array_key_exists('name', $data)) $template->setName($data['name']);
|
||||
if (array_key_exists('body', $data)) $template->setBody($data['body']);
|
||||
if (array_key_exists('variables', $data)) $template->setVariables($data['variables']);
|
||||
|
||||
@@ -72,6 +72,8 @@ class SmsTemplate
|
||||
|
||||
public function submitForReview(): self { $this->status = self::STATUS_PENDING; $this->touch(); return $this; }
|
||||
|
||||
public function revertToDraft(): self { $this->status = self::STATUS_DRAFT; $this->touch(); return $this; }
|
||||
|
||||
public function approve(?string $note = null): self
|
||||
{
|
||||
$this->status = self::STATUS_APPROVED;
|
||||
|
||||
Reference in New Issue
Block a user