feat(representation): update representation deactivation logic and enhance API documentation

This commit is contained in:
hamed
2026-06-19 00:46:34 +03:30
parent 0f3bf68fdd
commit 2943e29663
3 changed files with 41 additions and 19 deletions
+16 -13
View File
@@ -1,7 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { EyeIcon, TrashIcon, PlusIcon, MagnifyingGlassIcon } from '@heroicons/react/24/outline'; import { EyeIcon, NoSymbolIcon, PlusIcon, MagnifyingGlassIcon } from '@heroicons/react/24/outline';
import { useForm, Controller } from 'react-hook-form'; import { useForm, Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod'; import { z } from 'zod';
@@ -75,10 +75,11 @@ export default function RepresentationsPage() {
onError: (err: Error) => toast.error(err.message), onError: (err: Error) => toast.error(err.message),
}); });
const deleteMutation = useMutation({ const deactivateMutation = useMutation({
mutationFn: (r: Representation) => api.delete<ApiResponse<null>>(`/api/v1/representation/${r.uuid}`), mutationFn: (r: Representation) =>
api.patch<ApiResponse<Representation>>(`/api/v1/representation/${r.uuid}`, { active: false }),
onSuccess: () => { onSuccess: () => {
toast.success('نماینده حذف شد'); toast.success('نماینده غیرفعال شد');
setDeleteTarget(null); setDeleteTarget(null);
qc.invalidateQueries({ queryKey: ['representations'] }); qc.invalidateQueries({ queryKey: ['representations'] });
}, },
@@ -146,10 +147,12 @@ export default function RepresentationsPage() {
className="mini-btn" title="مشاهده"> className="mini-btn" title="مشاهده">
<EyeIcon style={{ width: 15, height: 15 }} /> <EyeIcon style={{ width: 15, height: 15 }} />
</button> </button>
<button onClick={() => setDeleteTarget(rep)} {(rep.is_active ?? rep.active) && (
className="mini-btn danger" title="حذف"> <button onClick={() => setDeleteTarget(rep)}
<TrashIcon style={{ width: 15, height: 15 }} /> className="mini-btn danger" title="غیرفعال‌سازی">
</button> <NoSymbolIcon style={{ width: 15, height: 15 }} />
</button>
)}
</> </>
)} )}
/> />
@@ -206,12 +209,12 @@ export default function RepresentationsPage() {
<ConfirmDialog <ConfirmDialog
open={!!deleteTarget} open={!!deleteTarget}
title="حذف نماینده" title="غیرفعال‌سازی نماینده"
message={`آیا از حذف نماینده "${deleteTarget?.full_name}" اطمینان دارید؟`} message={`آیا از غیرفعال‌سازی نماینده "${deleteTarget?.full_name}" اطمینان دارید؟ این نماینده دیگر فعال نخواهد بود.`}
confirmLabel="حذف" confirmLabel="غیرفعال‌سازی"
danger danger
loading={deleteMutation.isPending} loading={deactivateMutation.isPending}
onConfirm={() => deleteTarget && deleteMutation.mutate(deleteTarget)} onConfirm={() => deleteTarget && deactivateMutation.mutate(deleteTarget)}
onCancel={() => setDeleteTarget(null)} onCancel={() => setDeleteTarget(null)}
/> />
</div> </div>
+21 -3
View File
@@ -621,12 +621,30 @@ List all representations.
| Param | Type | Required | Description | | Param | Type | Required | Description |
|-------|------|----------|-------------| |-------|------|----------|-------------|
| `page` | integer | ❌ | Default: 1 | | `page` | integer | ❌ | Default: 1 |
| `limit` | integer | ❌ | Default: 20 | | `limit` | integer | ❌ | Default: 15 |
| `search` | string | ❌ | Search by name | | `search` | string | ❌ | Search by name or mobile (representation's or linked user's) |
| `city_id` | integer | ❌ | Filter by city | | `city_id` | integer | ❌ | Filter by city |
### Response `200` ### Response `200`
Paginated representation list. Paginated representation list. Each item:
```json
{
"id": 3,
"uuid": "...",
"full_name": "حامد حسینی",
"mobile_number": "09120671756",
"city_id": 132,
"city": "یزد",
"commission_percent": 10.0,
"wallet_balance": 0,
"is_active": true,
"created_at": "2026-06-18T..."
}
```
> `mobile_number` falls back to the linked user's mobile when the representation's own `mobile_number` column is empty.
> **Deactivation, not deletion:** the admin panel deactivates a representation via `PATCH /api/v1/representation/{uuid}` with `{ "active": false }` rather than calling `DELETE`.
--- ---
+4 -3
View File
@@ -921,13 +921,14 @@ class AdminApiController extends BaseController
$cityId = $request->query->get('city_id'); $cityId = $request->query->get('city_id');
$qb = $this->em->createQueryBuilder() $qb = $this->em->createQueryBuilder()
->select('r.id, r.uuid, r.fullName, r.mobileNumber, r.cityId, r.commissionPercent, r.active, r.createdAt, c.name as city_name') ->select('r.id, r.uuid, r.fullName, r.mobileNumber, u.mobileNumber as user_mobile, r.cityId, r.commissionPercent, r.active, r.createdAt, c.name as city_name')
->from(Representation::class, 'r') ->from(Representation::class, 'r')
->join('r.user', 'u')
->leftJoin(City::class, 'c', 'WITH', 'c.id = r.cityId') ->leftJoin(City::class, 'c', 'WITH', 'c.id = r.cityId')
->orderBy('r.createdAt', 'DESC'); ->orderBy('r.createdAt', 'DESC');
if ($search !== '') { if ($search !== '') {
$qb->andWhere('r.fullName LIKE :s OR r.mobileNumber LIKE :s') $qb->andWhere('r.fullName LIKE :s OR r.mobileNumber LIKE :s OR u.mobileNumber LIKE :s')
->setParameter('s', '%' . $search . '%'); ->setParameter('s', '%' . $search . '%');
} }
@@ -946,7 +947,7 @@ class AdminApiController extends BaseController
'uuid' => $r['uuid'], 'uuid' => $r['uuid'],
'domain' => $r['fullName'], 'domain' => $r['fullName'],
'full_name' => $r['fullName'], 'full_name' => $r['fullName'],
'mobile_number' => $r['mobileNumber'], 'mobile_number' => $r['mobileNumber'] ?: ($r['user_mobile'] ?? null),
'city_id' => $r['cityId'], 'city_id' => $r['cityId'],
'city' => $r['city_name'] ?? null, 'city' => $r['city_name'] ?? null,
'commission_percent' => (float) $r['commissionPercent'], 'commission_percent' => (float) $r['commissionPercent'],