users added
This commit is contained in:
parent
6c9a6d60a2
commit
f8b6de86f2
@ -21,10 +21,11 @@ export function clientFor(slug: WorkflowSlug): ZinoClient {
|
|||||||
export const orderBookingClient = clientFor('orderBooking');
|
export const orderBookingClient = clientFor('orderBooking');
|
||||||
export const storeClient = clientFor('store');
|
export const storeClient = clientFor('store');
|
||||||
export const dailyReportsClient = clientFor('dailyReports');
|
export const dailyReportsClient = clientFor('dailyReports');
|
||||||
|
export const userClient = clientFor('user');
|
||||||
|
|
||||||
// The user JWT (from /usr/login) is valid across every workflow, but each
|
// The user JWT (from /usr/login) is valid across every workflow, but each
|
||||||
// client holds its own copy — so auth helpers fan out to all of them.
|
// client holds its own copy — so auth helpers fan out to all of them.
|
||||||
const ALL = [orderBookingClient, storeClient, dailyReportsClient];
|
const ALL = [orderBookingClient, storeClient, dailyReportsClient, userClient];
|
||||||
|
|
||||||
/** Log in once and share the JWT with every workflow client. */
|
/** Log in once and share the JWT with every workflow client. */
|
||||||
export async function loginAll(email: string, password: string, orgId?: string) {
|
export async function loginAll(email: string, password: string, orgId?: string) {
|
||||||
|
|||||||
@ -170,11 +170,38 @@ export const DAILY_REPORTS = {
|
|||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
// --- User Management (src/docs/api/user.md) ---
|
||||||
|
export const USER = {
|
||||||
|
workflowUuid: '9874af12-78b7-4730-834a-19a32221f3aa',
|
||||||
|
versionUuid: '', // No version UUID provided, server will use latest
|
||||||
|
activities: {
|
||||||
|
ADD_USER: {
|
||||||
|
uid: '3a8f132e-8c63-4d4d-b466-67595d345a99', // init
|
||||||
|
fields: {
|
||||||
|
name: 'name', // text
|
||||||
|
email: 'email', // email
|
||||||
|
password: 'password', // password
|
||||||
|
},
|
||||||
|
},
|
||||||
|
EDIT_USER: {
|
||||||
|
uid: '3091530c-4315-42b9-b53b-a148f2c54a81',
|
||||||
|
fields: {
|
||||||
|
name: 'name_3', // text
|
||||||
|
email: 'email_3', // email
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
recordViews: {
|
||||||
|
SALES_OFFICERS: '406f89d7-09f6-42ba-ba35-84334ccb6204',
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
// All workflows keyed by slug, for generic lookup.
|
// All workflows keyed by slug, for generic lookup.
|
||||||
export const WORKFLOWS = {
|
export const WORKFLOWS = {
|
||||||
orderBooking: ORDER_BOOKING,
|
orderBooking: ORDER_BOOKING,
|
||||||
store: STORE,
|
store: STORE,
|
||||||
dailyReports: DAILY_REPORTS,
|
dailyReports: DAILY_REPORTS,
|
||||||
|
user: USER,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// --- Daily Sales Report Constants ---
|
// --- Daily Sales Report Constants ---
|
||||||
|
|||||||
48
src/components/cards/UserCard.tsx
Normal file
48
src/components/cards/UserCard.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { User, Edit2 } from 'lucide-react';
|
||||||
|
|
||||||
|
export function UserCard({ row, onEdit }: { row: any; onEdit?: (row: any) => void }) {
|
||||||
|
const name = String(row.name_2 || row.name || 'Unnamed User');
|
||||||
|
const email = row.email_2 || row.email || '';
|
||||||
|
|
||||||
|
// extract initials for a sleek avatar
|
||||||
|
const initials = name
|
||||||
|
.split(' ')
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((n: string) => n[0])
|
||||||
|
.join('')
|
||||||
|
.substring(0, 2)
|
||||||
|
.toUpperCase();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="bg-app p-4 mb-3 flex items-center justify-between hover:bg-slate-50 transition-colors cursor-pointer border border-border-subtle rounded-xl shadow-[0_2px_8px_rgba(11,27,59,0.04)]"
|
||||||
|
onClick={() => onEdit?.(row)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-4 min-w-0">
|
||||||
|
{/* Avatar */}
|
||||||
|
<div className="w-11 h-11 rounded-full bg-primary/10 flex items-center justify-center text-primary shrink-0 font-bold tracking-tight">
|
||||||
|
{initials || <User size={20} />}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Info */}
|
||||||
|
<div className="flex flex-col min-w-0 pr-4">
|
||||||
|
<h3 className="m-0 text-base font-semibold text-strong truncate">{name}</h3>
|
||||||
|
{email && <span className="text-sm text-faint truncate">{email}</span>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Edit Action */}
|
||||||
|
{onEdit && (
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onEdit(row);
|
||||||
|
}}
|
||||||
|
className="p-2 text-faint hover:text-primary hover:bg-primary/10 rounded-full transition-colors shrink-0"
|
||||||
|
>
|
||||||
|
<Edit2 size={18} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
5
src/components/cards/index.ts
Normal file
5
src/components/cards/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export * from './OrderCard';
|
||||||
|
export * from './CallCard';
|
||||||
|
export * from './StoreCard';
|
||||||
|
export * from './DailyLogCard';
|
||||||
|
export * from './UserCard';
|
||||||
24
src/components/rv/UsersView.tsx
Normal file
24
src/components/rv/UsersView.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { userClient } from '../../api/clients';
|
||||||
|
import { USER } from '../../api/config';
|
||||||
|
import { RecordView } from './RecordView';
|
||||||
|
import type { WiredRecordViewProps } from './OrdersView';
|
||||||
|
import { UserCard } from '../cards/UserCard';
|
||||||
|
|
||||||
|
/** Users / Sales Officers record view (User workflow). */
|
||||||
|
export function UsersView({ onRowClick, onEditRow, pageSize, headerActions, refreshKey, initialFilters }: WiredRecordViewProps & { headerActions?: React.ReactNode; onEditRow?: (row: any) => void }) {
|
||||||
|
return (
|
||||||
|
<RecordView
|
||||||
|
client={userClient}
|
||||||
|
rvUid={USER.recordViews.SALES_OFFICERS}
|
||||||
|
title="Sales Officers"
|
||||||
|
onRowClick={onRowClick}
|
||||||
|
pageSize={pageSize}
|
||||||
|
headerActions={headerActions}
|
||||||
|
refreshKey={refreshKey}
|
||||||
|
initialFilters={initialFilters}
|
||||||
|
sortBy="instance_id"
|
||||||
|
sortDir="desc"
|
||||||
|
renderItem={(row) => <UserCard row={row} onEdit={onEditRow} />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -5,3 +5,4 @@ export type { WiredRecordViewProps } from './OrdersView';
|
|||||||
export { CallsView } from './CallsView';
|
export { CallsView } from './CallsView';
|
||||||
export { StoresView } from './StoresView';
|
export { StoresView } from './StoresView';
|
||||||
export { DailyLogsView } from './DailyLogsView';
|
export { DailyLogsView } from './DailyLogsView';
|
||||||
|
export { UsersView } from './UsersView';
|
||||||
247
src/docs/api/user.md
Normal file
247
src/docs/api/user.md
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
# Data Collection API
|
||||||
|
|
||||||
|
- core: `https://dev.getzino.in`
|
||||||
|
- view: `https://dev.getzino.in`
|
||||||
|
|
||||||
|
## Activity submission
|
||||||
|
|
||||||
|
Calls that create an instance or submit an activity's form. Init = /start; subsequent = /activity. Upload/OCR fire during fill for file-bearing fields.
|
||||||
|
|
||||||
|
#### Add User
|
||||||
|
|
||||||
|
`POST /app/434/start`
|
||||||
|
|
||||||
|
> Create a new workflow instance and run the init activity.
|
||||||
|
|
||||||
|
**Headers**
|
||||||
|
|
||||||
|
- `Authorization: Bearer <JWT_TOKEN>` (required) — User session JWT from login.
|
||||||
|
- `Content-Type: application/json` (required)
|
||||||
|
|
||||||
|
**Request body**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"activity_id": "3a8f132e-8c63-4d4d-b466-67595d345a99",
|
||||||
|
"data": {
|
||||||
|
"email": "user@example.com",
|
||||||
|
"name": "string_value",
|
||||||
|
"password": "string_value"
|
||||||
|
},
|
||||||
|
"version": 1,
|
||||||
|
"workflow_uuid": "9874af12-78b7-4730-834a-19a32221f3aa"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Request fields**
|
||||||
|
|
||||||
|
- `workflow_uuid` (string) *required* — Clone-portable workflow UID.
|
||||||
|
- `activity_id` (string) *required* — The init activity UID.
|
||||||
|
- `version` (int) — Deployed workflow version.
|
||||||
|
- `data` (object) — Field values keyed by field id.
|
||||||
|
- `name` (text) *required* — "Name" (text)
|
||||||
|
- `email` (email) *required* — "Email" (email)
|
||||||
|
- `password` (password) *required* — "Password" (password)
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {},
|
||||||
|
"instance_id": 1024,
|
||||||
|
"message": "Activity completed successfully",
|
||||||
|
"status_code": 200,
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response fields**
|
||||||
|
|
||||||
|
- `success` (bool) — Whether the activity ran.
|
||||||
|
- `status_code` (int) — HTTP status; mirrors a Response node if the trigger graph has one.
|
||||||
|
- `message` (string) — Human-readable result; from a Response node when present.
|
||||||
|
- `data` (object) — Response-node payload; empty object by default.
|
||||||
|
- `instance_id` (int64) — The workflow instance affected/created.
|
||||||
|
|
||||||
|
**curl**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "https://dev.getzino.in/app/434/start" \
|
||||||
|
-H "Authorization: Bearer <JWT_TOKEN>" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"activity_id": "3a8f132e-8c63-4d4d-b466-67595d345a99",
|
||||||
|
"data": {
|
||||||
|
"email": "user@example.com",
|
||||||
|
"name": "string_value",
|
||||||
|
"password": "string_value"
|
||||||
|
},
|
||||||
|
"version": 1,
|
||||||
|
"workflow_uuid": "9874af12-78b7-4730-834a-19a32221f3aa"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Edit User
|
||||||
|
|
||||||
|
`POST /app/434/activity`
|
||||||
|
|
||||||
|
> Advance an existing instance through this activity (normal submission).
|
||||||
|
|
||||||
|
**Headers**
|
||||||
|
|
||||||
|
- `Authorization: Bearer <JWT_TOKEN>` (required) — User session JWT from login.
|
||||||
|
- `Content-Type: application/json` (required)
|
||||||
|
|
||||||
|
**Request body**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"activity_id": "3091530c-4315-42b9-b53b-a148f2c54a81",
|
||||||
|
"data": {
|
||||||
|
"email_3": "user@example.com",
|
||||||
|
"name_3": "string_value"
|
||||||
|
},
|
||||||
|
"instance_id": "<INSTANCE_ID>",
|
||||||
|
"workflow_uuid": "9874af12-78b7-4730-834a-19a32221f3aa"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Request fields**
|
||||||
|
|
||||||
|
- `workflow_uuid` (string) *required* — Clone-portable workflow UID.
|
||||||
|
- `activity_id` (string) *required* — This activity's UID.
|
||||||
|
- `instance_id` (int64) *required* — Target instance (runtime value).
|
||||||
|
- `data` (object) — Field values keyed by field id.
|
||||||
|
- `name_3` (text) — "Name" (text)
|
||||||
|
- `email_3` (email) — "Email" (email)
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {},
|
||||||
|
"instance_id": 1024,
|
||||||
|
"message": "Activity completed successfully",
|
||||||
|
"status_code": 200,
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response fields**
|
||||||
|
|
||||||
|
- `success` (bool) — Whether the activity ran.
|
||||||
|
- `status_code` (int) — HTTP status; mirrors a Response node if the trigger graph has one.
|
||||||
|
- `message` (string) — Human-readable result; from a Response node when present.
|
||||||
|
- `data` (object) — Response-node payload; empty object by default.
|
||||||
|
- `instance_id` (int64) — The workflow instance affected/created.
|
||||||
|
|
||||||
|
**curl**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "https://dev.getzino.in/app/434/activity" \
|
||||||
|
-H "Authorization: Bearer <JWT_TOKEN>" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"activity_id": "3091530c-4315-42b9-b53b-a148f2c54a81",
|
||||||
|
"data": {
|
||||||
|
"email_3": "user@example.com",
|
||||||
|
"name_3": "string_value"
|
||||||
|
},
|
||||||
|
"instance_id": "<INSTANCE_ID>",
|
||||||
|
"workflow_uuid": "9874af12-78b7-4730-834a-19a32221f3aa"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## View APIs
|
||||||
|
|
||||||
|
Read endpoints that power record/detail/activity/chart views, segregated by source type with each view's UI name.
|
||||||
|
|
||||||
|
### Record View
|
||||||
|
|
||||||
|
#### Sales Officers
|
||||||
|
|
||||||
|
`POST /app/434/view/recordview`
|
||||||
|
|
||||||
|
> Paginated records + tiles + charts for this record view.
|
||||||
|
|
||||||
|
**Headers**
|
||||||
|
|
||||||
|
- `Authorization: Bearer <JWT_TOKEN>` (required) — User session JWT from login.
|
||||||
|
- `Content-Type: application/json` (required)
|
||||||
|
|
||||||
|
**Request body**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"params": {},
|
||||||
|
"preset_alias": "",
|
||||||
|
"rv_template_uid": "406f89d7-09f6-42ba-ba35-84334ccb6204",
|
||||||
|
"search_query": {
|
||||||
|
"filters": [],
|
||||||
|
"limit": 25,
|
||||||
|
"page": 1,
|
||||||
|
"search": "",
|
||||||
|
"sort_by": "",
|
||||||
|
"sort_dir": "desc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Request fields**
|
||||||
|
|
||||||
|
- `rv_template_uid` (string) *required* — This view's template UID.
|
||||||
|
- `preset_alias` (string) — Optional; selects a named prefilter preset declared on this view.
|
||||||
|
- `params` (object) — Optional; values for the view's declared input params (key→value), consumed by ${input.<key>} prefilter refs.
|
||||||
|
- `search_query.page` (int) — 1-based page.
|
||||||
|
- `search_query.limit` (int) — Default 25, max 200.
|
||||||
|
- `search_query.sort_by` (string) — field_key | created_at | updated_at | instance_id.
|
||||||
|
- `search_query.sort_dir` (string) — asc | desc.
|
||||||
|
- `search_query.search` (string) — ILIKE term across searchable fields.
|
||||||
|
- `search_query.filters` (array) — [{field_key, value, value2?, data_type}].
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"chart_data": [],
|
||||||
|
"data": [],
|
||||||
|
"pagination": {
|
||||||
|
"limit": 25,
|
||||||
|
"page": 1,
|
||||||
|
"total_records": 0
|
||||||
|
},
|
||||||
|
"tile_values": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response fields**
|
||||||
|
|
||||||
|
- `data` (array) — Companion-table rows.
|
||||||
|
- `tile_values` (object)
|
||||||
|
- `chart_data` (array)
|
||||||
|
- `pagination` (object) — {page, limit, total_records}.
|
||||||
|
|
||||||
|
**curl**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "https://dev.getzino.in/app/434/view/recordview" \
|
||||||
|
-H "Authorization: Bearer <JWT_TOKEN>" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"params": {},
|
||||||
|
"preset_alias": "",
|
||||||
|
"rv_template_uid": "406f89d7-09f6-42ba-ba35-84334ccb6204",
|
||||||
|
"search_query": {
|
||||||
|
"filters": [],
|
||||||
|
"limit": 25,
|
||||||
|
"page": 1,
|
||||||
|
"search": "",
|
||||||
|
"sort_by": "",
|
||||||
|
"sort_dir": "desc"
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## In-form activity helper APIs
|
||||||
|
|
||||||
|
Calls a form makes while being filled, segregated per activity: lookup, workflow-lookup, app-user, dataset options, field actions, and peer-instance prefetch.
|
||||||
@ -6,6 +6,7 @@ import { AllDailyLogsPage } from './screens/AllDailyLogsPage'
|
|||||||
import { AnalyticsPage } from './screens/AnalyticsPage'
|
import { AnalyticsPage } from './screens/AnalyticsPage'
|
||||||
import { DailySalesReportPage } from './screens/DailySalesReportPage'
|
import { DailySalesReportPage } from './screens/DailySalesReportPage'
|
||||||
import { DatasetsPage } from './screens/admin/DatasetsPage'
|
import { DatasetsPage } from './screens/admin/DatasetsPage'
|
||||||
|
import { UsersPage } from './screens/admin/UsersPage'
|
||||||
|
|
||||||
export const routeConfig = [
|
export const routeConfig = [
|
||||||
// Manager & Admin (and maybe Sales Officer for daily logs, adapting from krishna_sales where my-daily was Sales Officer and daily was Manager/Admin. In mobile we have daily and all-daily)
|
// Manager & Admin (and maybe Sales Officer for daily logs, adapting from krishna_sales where my-daily was Sales Officer and daily was Manager/Admin. In mobile we have daily and all-daily)
|
||||||
@ -74,7 +75,12 @@ export const routeConfig = [
|
|||||||
{
|
{
|
||||||
path: "/sales-report",
|
path: "/sales-report",
|
||||||
element: <DailySalesReportPage />,
|
element: <DailySalesReportPage />,
|
||||||
roles: ["Manager", "Admin", "Sales Officer"],
|
},
|
||||||
|
{
|
||||||
|
path: "/admin/users",
|
||||||
|
element: <UsersPage />,
|
||||||
|
roles: ["Manager", "Admin"],
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Admin Panel
|
// Admin Panel
|
||||||
@ -83,4 +89,5 @@ export const routeConfig = [
|
|||||||
element: <DatasetsPage />,
|
element: <DatasetsPage />,
|
||||||
adminOnly: true,
|
adminOnly: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { NavLink, Navigate, Outlet, useNavigate } from 'react-router-dom';
|
import { NavLink, Navigate, Outlet, useNavigate } from 'react-router-dom';
|
||||||
import { LogOut, Menu, X, Database } from 'lucide-react';
|
import { LogOut, Menu, X, Database, Users } from 'lucide-react';
|
||||||
import { cn } from '../lib/cn';
|
import { cn } from '../lib/cn';
|
||||||
import { useAuth } from '../auth/context';
|
import { useAuth } from '../auth/context';
|
||||||
import { onAuthErrorAll, orderBookingClient } from '../api/clients';
|
import { onAuthErrorAll, orderBookingClient } from '../api/clients';
|
||||||
@ -144,17 +144,31 @@ export function ConsoleLayout() {
|
|||||||
<div className="my-2 border-t border-border-subtle" />
|
<div className="my-2 border-t border-border-subtle" />
|
||||||
|
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<NavLink
|
<>
|
||||||
to="/admin/datasets"
|
<NavLink
|
||||||
onClick={() => setIsMenuOpen(false)}
|
to="/admin/users"
|
||||||
className={({ isActive }) => cn(
|
onClick={() => setIsMenuOpen(false)}
|
||||||
"flex items-center gap-3 px-4 py-3 mx-2 rounded-lg no-underline transition-colors duration-150 sidebar-item",
|
className={({ isActive }) => cn(
|
||||||
isActive ? "active" : ""
|
"flex items-center gap-3 px-4 py-3 mx-2 rounded-lg no-underline transition-colors duration-150 sidebar-item",
|
||||||
)}
|
isActive ? "active" : ""
|
||||||
>
|
)}
|
||||||
<Database size={20} className="shrink-0" />
|
>
|
||||||
<span>Manage Datasets</span>
|
<Users size={20} className="shrink-0" />
|
||||||
</NavLink>
|
<span>Sales Officers</span>
|
||||||
|
</NavLink>
|
||||||
|
|
||||||
|
<NavLink
|
||||||
|
to="/admin/datasets"
|
||||||
|
onClick={() => setIsMenuOpen(false)}
|
||||||
|
className={({ isActive }) => cn(
|
||||||
|
"flex items-center gap-3 px-4 py-3 mx-2 rounded-lg no-underline transition-colors duration-150 sidebar-item",
|
||||||
|
isActive ? "active" : ""
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Database size={20} className="shrink-0" />
|
||||||
|
<span>Manage Datasets</span>
|
||||||
|
</NavLink>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
62
src/screens/admin/UsersPage.tsx
Normal file
62
src/screens/admin/UsersPage.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { UserPlus } from 'lucide-react';
|
||||||
|
import { Modal } from '../../components/reusable';
|
||||||
|
import { UsersView } from '../../components/rv';
|
||||||
|
import { DynamicForm } from '../../components/forms/DynamicForm';
|
||||||
|
import { USER } from '../../api/config';
|
||||||
|
import { userClient } from '../../api/clients';
|
||||||
|
|
||||||
|
export function UsersPage() {
|
||||||
|
const [isCreating, setIsCreating] = useState(false);
|
||||||
|
const [refreshKey, setRefreshKey] = useState(0);
|
||||||
|
const [activeActivity, setActiveActivity] = useState<{ id: string; name: string, instanceId?: number } | null>(null);
|
||||||
|
|
||||||
|
const handleEditRow = (row: any) => {
|
||||||
|
setActiveActivity({
|
||||||
|
id: USER.activities.EDIT_USER.uid,
|
||||||
|
name: "Edit User",
|
||||||
|
instanceId: row.instance_id
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<UsersView
|
||||||
|
refreshKey={refreshKey}
|
||||||
|
onEditRow={handleEditRow}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => setIsCreating(true)}
|
||||||
|
className="fixed bottom-24 right-6 w-[60px] h-[60px] bg-primary rounded-3xl flex items-center justify-center shadow-lg text-white z-40 hover:opacity-90 transition-transform hover:scale-105 active:scale-95 cursor-pointer"
|
||||||
|
title="Add Sales Officer"
|
||||||
|
>
|
||||||
|
<UserPlus size={24} className="text-white" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
open={isCreating || activeActivity !== null}
|
||||||
|
onClose={() => {
|
||||||
|
setIsCreating(false);
|
||||||
|
setActiveActivity(null);
|
||||||
|
}}
|
||||||
|
title={activeActivity?.name || "Add User"}
|
||||||
|
>
|
||||||
|
<DynamicForm
|
||||||
|
client={userClient}
|
||||||
|
activityId={activeActivity?.id || USER.activities.ADD_USER.uid}
|
||||||
|
instanceId={activeActivity?.instanceId}
|
||||||
|
onSuccess={() => {
|
||||||
|
setIsCreating(false);
|
||||||
|
setActiveActivity(null);
|
||||||
|
setRefreshKey(k => k + 1);
|
||||||
|
}}
|
||||||
|
onCancel={() => {
|
||||||
|
setIsCreating(false);
|
||||||
|
setActiveActivity(null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user