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 [tagFilter, setTagFilter] = useState('');
|
||||||
const [editMsg, setEditMsg] = useState<SmsMessageText | null>(null);
|
const [editMsg, setEditMsg] = useState<SmsMessageText | null>(null);
|
||||||
const [editBody, setEditBody] = useState('');
|
const [editBody, setEditBody] = useState('');
|
||||||
|
const [editTemplate, setEditTemplate] = useState<SmsTemplate | null>(null);
|
||||||
|
const [editTplName, setEditTplName] = useState('');
|
||||||
|
const [editTplBody, setEditTplBody] = useState('');
|
||||||
const limit = 15;
|
const limit = 15;
|
||||||
|
|
||||||
const sampleTemplatesQuery = useQuery({
|
const sampleTemplatesQuery = useQuery({
|
||||||
@@ -119,6 +122,17 @@ export default function SmsPage() {
|
|||||||
onError: (err: Error) => toast.error(err.message),
|
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({
|
const approveMutation = useMutation({
|
||||||
mutationFn: (t: SmsTemplate) =>
|
mutationFn: (t: SmsTemplate) =>
|
||||||
api.post<ApiResponse<null>>(`/api/v1/admin/sms/template/${t.uuid}/approve`, {}),
|
api.post<ApiResponse<null>>(`/api/v1/admin/sms/template/${t.uuid}/approve`, {}),
|
||||||
@@ -292,9 +306,17 @@ export default function SmsPage() {
|
|||||||
loading={sampleTemplatesQuery.isLoading}
|
loading={sampleTemplatesQuery.isLoading}
|
||||||
emptyMessage="هیچ قالبی یافت نشد"
|
emptyMessage="هیچ قالبی یافت نشد"
|
||||||
actions={(t) => (
|
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
|
<Pagination
|
||||||
@@ -482,6 +504,39 @@ export default function SmsPage() {
|
|||||||
</form>
|
</form>
|
||||||
</Modal>
|
</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="رد قالب پیامک"
|
<Modal open={!!rejectTarget} title="رد قالب پیامک"
|
||||||
onClose={() => { setRejectTarget(null); setRejectReason(''); }}
|
onClose={() => { setRejectTarget(null); setRejectReason(''); }}
|
||||||
footer={
|
footer={
|
||||||
|
|||||||
@@ -204,11 +204,15 @@ class SmsController extends BaseController
|
|||||||
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'تمپلیت یافت نشد', 404);
|
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'تمپلیت یافت نشد', 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($template->getStatus() === SmsTemplate::STATUS_APPROVED) {
|
$data = json_decode($request->getContent(), true) ?? [];
|
||||||
return $this->error(ErrorCodes::ERR_SMS_003, ErrorCodes::message(ErrorCodes::ERR_SMS_003), 422);
|
|
||||||
|
// ویرایش محتوای یک قالبِ تأییدشده، آن را به پیشنویس برمیگرداند تا متن جدید
|
||||||
|
// دوباره تأیید/ارسال شود (متن قبلی با سرویسدهنده هماهنگ بوده).
|
||||||
|
$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('name', $data)) $template->setName($data['name']);
|
||||||
if (array_key_exists('body', $data)) $template->setBody($data['body']);
|
if (array_key_exists('body', $data)) $template->setBody($data['body']);
|
||||||
if (array_key_exists('variables', $data)) $template->setVariables($data['variables']);
|
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 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
|
public function approve(?string $note = null): self
|
||||||
{
|
{
|
||||||
$this->status = self::STATUS_APPROVED;
|
$this->status = self::STATUS_APPROVED;
|
||||||
|
|||||||
Reference in New Issue
Block a user