feat(representation): update representation deactivation logic and enhance API documentation
This commit is contained in:
@@ -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
@@ -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`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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'],
|
||||||
|
|||||||
Reference in New Issue
Block a user