feat: implement template editing functionality and revert approved templates to draft status

This commit is contained in:
hamed
2026-06-19 20:48:44 +03:30
parent fa332f7fa1
commit b686cd1e7d
3 changed files with 67 additions and 6 deletions
+58 -3
View File
@@ -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={
+7 -3
View File
@@ -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']);
+2
View File
@@ -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;