Compare commits
No commits in common. "dev" and "main" have entirely different histories.
19
API.md
19
API.md
@ -48,7 +48,7 @@ Output
|
|||||||
{"ok":true}
|
{"ok":true}
|
||||||
```
|
```
|
||||||
|
|
||||||
### - Post a ticket
|
### - Post the complaint request
|
||||||
|
|
||||||
POST http://localhost:3000/api/ticket
|
POST http://localhost:3000/api/ticket
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ Output :
|
|||||||
"message": "Ticket No:2 created successfully"
|
"message": "Ticket No:2 created successfully"
|
||||||
```
|
```
|
||||||
|
|
||||||
### - Get the list of ticket raised by the user
|
### - Get the request By the user
|
||||||
|
|
||||||
GET http://localhost:3000/api/ticket
|
GET http://localhost:3000/api/ticket
|
||||||
|
|
||||||
@ -107,19 +107,4 @@ Output :
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
### - Get the details ticket for particular ticket raised by the user
|
|
||||||
|
|
||||||
GET http://localhost:3000/api/ticket/[ticket_id]
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"ticket_id": 1,
|
|
||||||
"category_of_request": "ATM Related",
|
|
||||||
"nature_of_request": "Transaction Issue",
|
|
||||||
"additional_info": "ATM ID: PNB23567WE456, Debit Card: 34567895434567, Transaction Amount: 15000, Transaction Reference Number : 345678987654323, Transaction Date: 2025-02-11",
|
|
||||||
"message": "Money got debited but money not receive. ",
|
|
||||||
"created_date": "12/2/2025, 12:31:13 pm"
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
160
Docs/CRM.postman_collection.json
Normal file
160
Docs/CRM.postman_collection.json
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
{
|
||||||
|
"info": {
|
||||||
|
"_postman_id": "c0971b0e-8469-4ffd-a95e-090b1ba552da",
|
||||||
|
"name": "CRM",
|
||||||
|
"schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json",
|
||||||
|
"_exporter_id": "39926215"
|
||||||
|
},
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "Login",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\r\n \"AccNo\" :\"30022497138\"\r\n}",
|
||||||
|
"options": {
|
||||||
|
"raw": {
|
||||||
|
"language": "json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": "http://localhost:3000/api/auth/login/account_no"
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "get account details",
|
||||||
|
"request": {
|
||||||
|
"auth": {
|
||||||
|
"type": "inherit",
|
||||||
|
"inherit": {}
|
||||||
|
},
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": "http://localhost:3000/api/user"
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Validate OTP",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\"OTP\": 77208}",
|
||||||
|
"options": {
|
||||||
|
"raw": {
|
||||||
|
"language": "text"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": "http://localhost:3000/api/otp"
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "logout",
|
||||||
|
"request": {
|
||||||
|
"auth": {
|
||||||
|
"type": "inherit",
|
||||||
|
"inherit": {}
|
||||||
|
},
|
||||||
|
"method": "POST",
|
||||||
|
"header": [],
|
||||||
|
"url": "http://localhost:3000/api/auth/logout"
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "generate OTP",
|
||||||
|
"protocolProfileBehavior": {
|
||||||
|
"disableBodyPruning": true
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "",
|
||||||
|
"options": {
|
||||||
|
"raw": {
|
||||||
|
"language": "json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": "http://localhost:3000/api/otp"
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Fetch Mobile Number",
|
||||||
|
"request": {
|
||||||
|
"auth": {
|
||||||
|
"type": "inherit",
|
||||||
|
"inherit": {}
|
||||||
|
},
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": "http://localhost:3000/api/mobile_no/by/30022497139"
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Get User",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": "http://localhost:3000/api/user"
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Create Ticket",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\r\n \"summary\":\" Login issue Related\", \r\n \"description\":\"Login related\", \r\n \r\n\t\"steps_to_reproduce\" :\"Money got debited from account\",\r\n\t\"category\": {\r\n \"name\": \"General\"\r\n\t},\r\n\t\"project\": {\r\n \"id\": 1\r\n }\r\n}",
|
||||||
|
"options": {
|
||||||
|
"raw": {
|
||||||
|
"language": "json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": "http://localhost:3000/api/ticket"
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Add column",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": "http://localhost:3000/api/addColumn"
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Dev populate",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": "http://localhost:3000/api/dev/populate"
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "View Ticket",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": "http://localhost:3000/api/ticket"
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
172
Docs/MantisBT.postman_collection.json
Normal file
172
Docs/MantisBT.postman_collection.json
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
{
|
||||||
|
"info": {
|
||||||
|
"_postman_id": "62121509-c7ee-4d92-9684-337a1d96a6ac",
|
||||||
|
"name": "MantisBT",
|
||||||
|
"schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json",
|
||||||
|
"_exporter_id": "39926215"
|
||||||
|
},
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "Create Token",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "beHEFeDe-eE0YdTIK098naY0-iirvISx",
|
||||||
|
"type": "text",
|
||||||
|
"disabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "YdTsu-wuBfc4ekVDZpfk0K4dvjmoH6Z-",
|
||||||
|
"type": "text",
|
||||||
|
"disabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "DBSC0FHDatcHiScox5vm2GPuNnNrezqM",
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"url": {
|
||||||
|
"raw": "http://localhost/mantisBT/api/rest/users/:user_id/token",
|
||||||
|
"protocol": "http",
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"mantisBT",
|
||||||
|
"api",
|
||||||
|
"rest",
|
||||||
|
"users",
|
||||||
|
":user_id",
|
||||||
|
"token"
|
||||||
|
],
|
||||||
|
"variable": [
|
||||||
|
{
|
||||||
|
"key": "user_id",
|
||||||
|
"value": "5",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Create issue",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "beHEFeDe-eE0YdTIK098naY0-iirvISx",
|
||||||
|
"type": "text",
|
||||||
|
"disabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "YdTsu-wuBfc4ekVDZpfk0K4dvjmoH6Z-",
|
||||||
|
"type": "text",
|
||||||
|
"disabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "GefLBxeHyHDL9DXkGqX4qQ7Cayquqdaj",
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\r\n \"summary\": \"ATM Fault\",\r\n \"description\": \"ATM Fault\",\r\n \"additional_information\": \"{ATM ID : 678UCBA00988, debit_card : 600226543567}\",\r\n \"steps_to_reproduce\" :\"Money got debited from account\",\r\n \"project\": {\r\n \"id\": 1\r\n }\r\n}\r\n",
|
||||||
|
"options": {
|
||||||
|
"raw": {
|
||||||
|
"language": "json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": "http://localhost/mantisBT/api/rest/issues/"
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "get a issue by issue id",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "GefLBxeHyHDL9DXkGqX4qQ7Cayquqdaj",
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"url": {
|
||||||
|
"raw": "http://localhost/mantisBT/api/rest/issues/:issue_id",
|
||||||
|
"protocol": "http",
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"mantisBT",
|
||||||
|
"api",
|
||||||
|
"rest",
|
||||||
|
"issues",
|
||||||
|
":issue_id"
|
||||||
|
],
|
||||||
|
"variable": [
|
||||||
|
{
|
||||||
|
"key": "issue_id",
|
||||||
|
"value": "43"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "post note",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "DBSC0FHDatcHiScox5vm2GPuNnNrezqM",
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\r\n \"text\": \"test note\",\r\n \"view_state\": {\r\n \t\"name\": \"public\"\r\n }\r\n}",
|
||||||
|
"options": {
|
||||||
|
"raw": {
|
||||||
|
"language": "json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "http://localhost/mantisBT/api/rest/issues/:issue_id/notes",
|
||||||
|
"protocol": "http",
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"mantisBT",
|
||||||
|
"api",
|
||||||
|
"rest",
|
||||||
|
"issues",
|
||||||
|
":issue_id",
|
||||||
|
"notes"
|
||||||
|
],
|
||||||
|
"variable": [
|
||||||
|
{
|
||||||
|
"key": "issue_id",
|
||||||
|
"value": "43"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
149
README.md
149
README.md
@ -1,10 +1,67 @@
|
|||||||
# CRM Setup :
|
# CRM Setup :
|
||||||
Here we have `two` modules .
|
Here we have `two` modules .
|
||||||
- CRM Internal Module (For bank's internal team)
|
|
||||||
- CRM Customer Module (For bank's customer)
|
- CRM Customer Module (For bank's customer)
|
||||||
|
- CRM Internal Module (For bank's internal team)
|
||||||
|
|
||||||
|
## 1. CRM Customer Module (For External Use)
|
||||||
|
### Files
|
||||||
|
`env.local`
|
||||||
|
|
||||||
|
Create a `env.local` file in the root of folder of this project.
|
||||||
|
|
||||||
|
Content of the `env.local` must be as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
SESSION_COOKIE_NAME=crm_auth
|
||||||
|
SESSION_COOKIE_PASSWORD=abcdefghijklmnopqrstuvwxyz01234567890
|
||||||
|
|
||||||
|
DB_HOST=localhost
|
||||||
|
DB_PORT=5432
|
||||||
|
DB_NAME=crm
|
||||||
|
DB_USER_NAME=
|
||||||
|
DB_USER_PASS
|
||||||
|
|
||||||
|
#CBS
|
||||||
|
ONLINE_CBS_IP=
|
||||||
|
ONLINE_CBS_PORT=
|
||||||
|
|
||||||
|
#MESSAGE
|
||||||
|
WEBSERVER_IP=
|
||||||
|
WEBSERVER_PORT=
|
||||||
|
```
|
||||||
|
### Requirement :
|
||||||
|
|
||||||
|
- Node JS (Latest)
|
||||||
|
- PostgreSQL (Latest)
|
||||||
|
|
||||||
|
### Deploy :
|
||||||
|
- Install all node module
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
- In the PostgreSQL create one database, the database name will be "crm"
|
||||||
|
- Build the application
|
||||||
|
```
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
- Start the application
|
||||||
|
```
|
||||||
|
npm run start
|
||||||
|
```
|
||||||
|
- Open the following URL in the web browser for populate some data
|
||||||
|
|
||||||
|
```
|
||||||
|
http://localhost:3000/api/dev/populate
|
||||||
|
```
|
||||||
|
|
||||||
|
### Application Url :
|
||||||
|
```
|
||||||
|
http://localhost:3000/login
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## 1. CRM Internal Module (Ticket Management System)
|
|
||||||
|
## 2. CRM Internal Module (Ticket Management System)
|
||||||
|
|
||||||
Here we are using "mantisBT" (open source application) for ticket management system .
|
Here we are using "mantisBT" (open source application) for ticket management system .
|
||||||
### Requirement :
|
### Requirement :
|
||||||
@ -57,83 +114,11 @@ Here we are using "mantisBT" (open source application) for ticket management sys
|
|||||||
- password : root
|
- password : root
|
||||||
|
|
||||||
### Setup:
|
### Setup:
|
||||||
- Go to database `kccb_ticket_tracker` and search for a table `kccb_config`.
|
- create a new user "kccb".
|
||||||
- Import `csv\kccb_config.csv` (Present in the CRM internal module) file to the `kccb_config` table.
|
- Log in to this user `kccb`.
|
||||||
- Then login as `Administrator` .
|
- Go to "My account".
|
||||||
- Create new project
|
- Then create a API token.
|
||||||
```
|
- Copy the API token.
|
||||||
Project Name :KCCB
|
- And paste the token to those `route.ts` file of `CRM Customer Module` where the mantisBT API is calling.
|
||||||
Description : The kangra Central Co Operative Bank
|
- create development team's user as protected.
|
||||||
|
- create a project, and keep the name as "KCCB".
|
||||||
Note: Keep other filed as default
|
|
||||||
```
|
|
||||||
|
|
||||||
- Create a new user "kccb".
|
|
||||||
- Log in to this user `kccb`.
|
|
||||||
- Go to `My account` (Top right corner) .
|
|
||||||
- Then create a `API token`.
|
|
||||||
- Copy the API token.
|
|
||||||
- And paste the token in `.env.local` file of `CRM Customer Module` in place of `MantisBT_Token`.
|
|
||||||
> Note : For setup CRM internal module ,We need to create one user `kccb` and create the token for the user. That token will be used in CRM external module .
|
|
||||||
- Create Backend team as per requirement and enable protected option.
|
|
||||||
|
|
||||||
|
|
||||||
## 2. CRM Customer Module (For External Use)
|
|
||||||
### Files
|
|
||||||
`env.local`
|
|
||||||
|
|
||||||
Create a `env.local` file in the root of folder of this project.
|
|
||||||
|
|
||||||
Content of the `env.local` must be as follows:
|
|
||||||
|
|
||||||
```
|
|
||||||
SESSION_COOKIE_NAME=crm_auth
|
|
||||||
SESSION_COOKIE_PASSWORD=abcdefghijklmnopqrstuvwxyz01234567890
|
|
||||||
|
|
||||||
DB_HOST=localhost
|
|
||||||
DB_PORT=5432
|
|
||||||
DB_NAME=crm
|
|
||||||
DB_USER_NAME=
|
|
||||||
DB_USER_PASS=
|
|
||||||
|
|
||||||
#Mantis
|
|
||||||
MantisBT_Token=
|
|
||||||
|
|
||||||
#CBS
|
|
||||||
ONLINE_CBS_IP=
|
|
||||||
ONLINE_CBS_PORT=
|
|
||||||
|
|
||||||
#MESSAGE
|
|
||||||
WEBSERVER_IP=
|
|
||||||
WEBSERVER_PORT=
|
|
||||||
```
|
|
||||||
### Requirement :
|
|
||||||
|
|
||||||
- Node JS (Latest)
|
|
||||||
- PostgreSQL (Latest)
|
|
||||||
|
|
||||||
### Deploy :
|
|
||||||
- Install all node module
|
|
||||||
```
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
- In the PostgreSQL create one database, the database name will be "crm"
|
|
||||||
- Build the application
|
|
||||||
```
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
- Start the application
|
|
||||||
```
|
|
||||||
npm run start
|
|
||||||
```
|
|
||||||
- Open the following URL in the web browser for populate some data
|
|
||||||
|
|
||||||
```
|
|
||||||
http://localhost:3000/api/dev/populate
|
|
||||||
```
|
|
||||||
|
|
||||||
### Application Url :
|
|
||||||
```
|
|
||||||
http://localhost:3000/login
|
|
||||||
```
|
|
||||||
|
|
5
TODO.md
5
TODO.md
@ -1 +1,6 @@
|
|||||||
# Todo
|
# Todo
|
||||||
|
|
||||||
|
- Fix scroll issues with appshell main area
|
||||||
|
- Display Normal error inside the dialog box
|
||||||
|
- Give search box for fetching the user
|
||||||
|
- Apply the delete functionality
|
@ -14,6 +14,7 @@ async function queryUser() {
|
|||||||
const response = await axios.get<User>('/api/user');
|
const response = await axios.get<User>('/api/user');
|
||||||
user = response.data;
|
user = response.data;
|
||||||
} catch (error: AxiosError | any) {
|
} catch (error: AxiosError | any) {
|
||||||
|
console.log(error);
|
||||||
notifications.show({
|
notifications.show({
|
||||||
color: 'red',
|
color: 'red',
|
||||||
title: error.code,
|
title: error.code,
|
||||||
|
@ -7,9 +7,6 @@ export class Ticket {
|
|||||||
// @PrimaryColumn("int")
|
// @PrimaryColumn("int")
|
||||||
id!: number
|
id!: number
|
||||||
|
|
||||||
@PrimaryColumn()
|
|
||||||
ticket_id! : number
|
|
||||||
|
|
||||||
@Column("varchar")
|
@Column("varchar")
|
||||||
category_of_request! : string
|
category_of_request! : string
|
||||||
|
|
||||||
@ -27,9 +24,6 @@ export class Ticket {
|
|||||||
@Column('varchar')
|
@Column('varchar')
|
||||||
created_by! : string
|
created_by! : string
|
||||||
|
|
||||||
@Column('bigint')
|
|
||||||
customer_account_no! : number
|
|
||||||
|
|
||||||
@Column('varchar')
|
@Column('varchar')
|
||||||
created_date! : string
|
created_date! : string
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ class AppDataSource {
|
|||||||
username: process.env.DB_USER_NAME,
|
username: process.env.DB_USER_NAME,
|
||||||
password: process.env.DB_USER_PASS,
|
password: process.env.DB_USER_PASS,
|
||||||
database: process.env.DB_NAME,
|
database: process.env.DB_NAME,
|
||||||
// dropSchema: process.env.NODE_ENV === 'development',
|
//dropSchema: process.env.NODE_ENV === 'development',
|
||||||
synchronize: process.env.NODE_ENV === 'development',
|
synchronize: process.env.NODE_ENV === 'development',
|
||||||
logging: false,
|
logging: false,
|
||||||
entities: [User,Ticket],
|
entities: [User,Ticket],
|
||||||
|
@ -21,6 +21,7 @@ export async function GET(request: Request) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
console.error(error);
|
||||||
return Response.json(null, { status: 500 })
|
return Response.json(null, { status: 500 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ export async function POST(req: Request) {
|
|||||||
const session = await getIronSession<SessionPayload>(cookies(), getSessionOptions())
|
const session = await getIronSession<SessionPayload>(cookies(), getSessionOptions())
|
||||||
const body = await req.json();
|
const body = await req.json();
|
||||||
const { AccNo } = body;
|
const { AccNo } = body;
|
||||||
|
console.log(AccNo)
|
||||||
if (!AccNo || AccNo === '') {
|
if (!AccNo || AccNo === '') {
|
||||||
return Response.json({ message: "Account No can't be empty" })
|
return Response.json({ message: "Account No can't be empty" })
|
||||||
}
|
}
|
||||||
@ -24,16 +25,18 @@ export async function POST(req: Request) {
|
|||||||
.getRawOne();
|
.getRawOne();
|
||||||
|
|
||||||
if (!user)
|
if (!user)
|
||||||
return Response.json({ message: 'Please Enter Valid Account No Or contact to administrator.' },{status:404})
|
return Response.json({ message: 'Please Enter Valid Account No Or contact to administrator.' })
|
||||||
|
|
||||||
session.accountNo = user.bank_account_no;
|
session.accountNo = user.bank_account_no;
|
||||||
session.TwoStepAuthentication = false;
|
session.TwoStepAuthentication = false;
|
||||||
session.otp = NaN;
|
session.otp = NaN;
|
||||||
session.expiryTime = NaN;
|
session.expiryTime = NaN;
|
||||||
await session.save();
|
await session.save();
|
||||||
|
//console.log(session);
|
||||||
return Response.json({ ok: true })
|
return Response.json({ ok: true })
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
return Response.json(null, { status: 500 })
|
return Response.json(null, { status: 500 })
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -15,6 +15,7 @@ export async function POST(request: Request) {
|
|||||||
return Response.json({message: 'logout successfully'});
|
return Response.json({message: 'logout successfully'});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
return Response.json(null, { status: 500 })
|
return Response.json(null, { status: 500 })
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -31,6 +31,7 @@ export async function GET(request: Request) {
|
|||||||
return Response.json({ done: true });
|
return Response.json({ done: true });
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
return Response.json("Failed", { status: 500 });
|
return Response.json("Failed", { status: 500 });
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,13 +21,16 @@ export async function GET(request: Request , { params }: { params: { account_no:
|
|||||||
if (user_mob_no.mobile_number == null)
|
if (user_mob_no.mobile_number == null)
|
||||||
return Response.json({ message: "Mobile number is not updated.Please contact with administrator" })
|
return Response.json({ message: "Mobile number is not updated.Please contact with administrator" })
|
||||||
|
|
||||||
|
console.log(user_mob_no.mobile_number)
|
||||||
//session.otp = NaN;
|
//session.otp = NaN;
|
||||||
//session.mobileNo = user_mob_no.mobile_number;
|
//session.mobileNo = user_mob_no.mobile_number;
|
||||||
await session.save();
|
await session.save();
|
||||||
|
console.log(session);
|
||||||
return Response.json(user_mob_no)
|
return Response.json(user_mob_no)
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
console.error(error);
|
||||||
return Response.json(null, { status: 500 })
|
return Response.json(null, { status: 500 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ export async function GET(req: Request) {
|
|||||||
session.expiryTime=Date.now() + 90 * 1000;
|
session.expiryTime=Date.now() + 90 * 1000;
|
||||||
|
|
||||||
await session.save();
|
await session.save();
|
||||||
|
//console.log(session);
|
||||||
const maskedPartMobNo= 'x'.repeat(mobileNumber.toString().length -3);
|
const maskedPartMobNo= 'x'.repeat(mobileNumber.toString().length -3);
|
||||||
const lastThreeDigit= mobileNumber.toString().slice(-3);
|
const lastThreeDigit= mobileNumber.toString().slice(-3);
|
||||||
console.log( "OTP :" , otp);
|
console.log( "OTP :" , otp);
|
||||||
@ -39,6 +40,7 @@ export async function GET(req: Request) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
console.error(error);
|
||||||
return Response.json(null, { status: 500 })
|
return Response.json(null, { status: 500 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -59,12 +61,14 @@ export async function POST(req: Request) {
|
|||||||
if (!OTP || OTP === '' || Number.isNaN(OTP))
|
if (!OTP || OTP === '' || Number.isNaN(OTP))
|
||||||
return Response.json({ message: "OTP field can not be blank" })
|
return Response.json({ message: "OTP field can not be blank" })
|
||||||
|
|
||||||
|
console.log(session)
|
||||||
// Check for OTP expiry time
|
// Check for OTP expiry time
|
||||||
if(Date.now()> session.expiryTime){
|
if(Date.now()> session.expiryTime){
|
||||||
session.otp =NaN;
|
session.otp =NaN;
|
||||||
session.expiryTime=NaN;
|
session.expiryTime=NaN;
|
||||||
await session.save();
|
await session.save();
|
||||||
}
|
}
|
||||||
|
//console.log(session);
|
||||||
if(Number.isNaN(session.otp))
|
if(Number.isNaN(session.otp))
|
||||||
return Response.json({ error: "The OTP Session has timed out "},{status:401})
|
return Response.json({ error: "The OTP Session has timed out "},{status:401})
|
||||||
|
|
||||||
@ -73,10 +77,12 @@ export async function POST(req: Request) {
|
|||||||
|
|
||||||
session.TwoStepAuthentication = true;
|
session.TwoStepAuthentication = true;
|
||||||
await session.save();
|
await session.save();
|
||||||
|
//console.log(session);
|
||||||
return Response.json({ ok: true })
|
return Response.json({ ok: true })
|
||||||
}
|
}
|
||||||
catch (error)
|
catch (error)
|
||||||
{
|
{
|
||||||
|
console.error(error);
|
||||||
return Response.json(null, { status: 500 })
|
return Response.json(null, { status: 500 })
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,35 +0,0 @@
|
|||||||
import { getIronSession } from "iron-session";
|
|
||||||
import { cookies } from "next/headers";
|
|
||||||
import getSessionOptions from "@/app/api/auth/session/options";
|
|
||||||
import SessionPayload from "@/app/api/auth/session/payload";
|
|
||||||
import AppDataSource from "@/app/_data/source/app-data-source";
|
|
||||||
import { Ticket } from "@/app/_data/entities/Ticket";
|
|
||||||
|
|
||||||
|
|
||||||
export async function GET(request: Request, { params }: { params: { ticket_id: number }}) {
|
|
||||||
try {
|
|
||||||
const { ticket_id } = params;
|
|
||||||
const session = await getIronSession<SessionPayload>(cookies(), getSessionOptions());
|
|
||||||
if (!session.TwoStepAuthentication) {
|
|
||||||
return Response.json(null);
|
|
||||||
}
|
|
||||||
if (!session.accountNo) {
|
|
||||||
return Response.json({ message: `Please sign in before raise a ticket` })
|
|
||||||
}
|
|
||||||
|
|
||||||
const connection_crm = await AppDataSource.getConnection();
|
|
||||||
const ticketRepo = connection_crm.getRepository(Ticket);
|
|
||||||
// Get list of ticket raised by the user
|
|
||||||
const ticket_list = await ticketRepo.createQueryBuilder('ticket')
|
|
||||||
.select(['id','ticket_id', 'category_of_request', 'nature_of_request', 'additional_info','message','created_date'])
|
|
||||||
.where("ticket.ticket_id = :ticket_id" , { ticket_id: ticket_id })
|
|
||||||
.andWhere("ticket.customer_account_no = :customer_account_no" ,{customer_account_no:session.accountNo })
|
|
||||||
.getRawOne();
|
|
||||||
if(!ticket_list){return Response.json("No Ticket is Present.");}
|
|
||||||
return Response.json(ticket_list)
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
return Response.json(null, { status: 500 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
|||||||
import AppDataSource from "@/app/_data/source/app-data-source";
|
|
||||||
|
|
||||||
export async function assignUser(category_of_complaint :string) {
|
|
||||||
const connection_mantis = await AppDataSource.getMantisConnection();
|
|
||||||
let assign_team_id,random_assignee;
|
|
||||||
if(category_of_complaint === 'ATM Related'){
|
|
||||||
assign_team_id = 12 ;
|
|
||||||
}
|
|
||||||
if(category_of_complaint === 'Internet Banking'){
|
|
||||||
assign_team_id = 13 ;
|
|
||||||
}
|
|
||||||
if(category_of_complaint === 'Mobile Banking'){
|
|
||||||
assign_team_id = 14 ;
|
|
||||||
}
|
|
||||||
if(category_of_complaint === 'Others'){
|
|
||||||
assign_team_id = 15 ;
|
|
||||||
}
|
|
||||||
const queryRunner = connection_mantis.createQueryRunner();
|
|
||||||
await queryRunner.connect();
|
|
||||||
const query = await queryRunner.query('SELECT id,username FROM "kccb_user" WHERE access_level = $1', [assign_team_id]);
|
|
||||||
if(query.length == 0)
|
|
||||||
random_assignee = 1 ;
|
|
||||||
else{
|
|
||||||
const team_list =query.map((row: { id: any; }) =>row.id)
|
|
||||||
random_assignee = team_list[Math.floor(Math.random()* team_list.length)]
|
|
||||||
}
|
|
||||||
return random_assignee;
|
|
||||||
}
|
|
@ -5,7 +5,6 @@ import SessionPayload from "../auth/session/payload";
|
|||||||
import AppDataSource from "@/app/_data/source/app-data-source";
|
import AppDataSource from "@/app/_data/source/app-data-source";
|
||||||
import { Ticket } from "@/app/_data/entities/Ticket";
|
import { Ticket } from "@/app/_data/entities/Ticket";
|
||||||
import { User } from "@/app/_data/entities/User";
|
import { User } from "@/app/_data/entities/User";
|
||||||
import { assignUser } from "./assign_memeber";
|
|
||||||
|
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
@ -17,51 +16,36 @@ export async function POST(request: Request) {
|
|||||||
if (!session.accountNo) {
|
if (!session.accountNo) {
|
||||||
return Response.json({ message: `Please sign in before raise a ticket` })
|
return Response.json({ message: `Please sign in before raise a ticket` })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!session.TwoStepAuthentication) {
|
if (!session.TwoStepAuthentication) {
|
||||||
return Response.json(null);
|
return Response.json(null);
|
||||||
}
|
}
|
||||||
const mantisBT_Token =process.env.MantisBT_Token || ""; //MantisBT Token
|
//console.log(new Date().toLocaleString("en-IN"));
|
||||||
|
const URL = "http://localhost/crm_internal/api/rest/issues/";
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
//request_category, nature_of_request, issue, message
|
//request_category, nature_of_request, issue, message
|
||||||
const { summary, description, additional_information, steps_to_reproduce } = body;
|
const { summary, description, additional_information, steps_to_reproduce } = body;
|
||||||
const project = { id: 1 };
|
const project = { id: 1 };
|
||||||
const requestBody = { ...body, project };
|
const requestBody = { ...body, project };
|
||||||
const response_for_issue_ticket = await (await fetch(`http://localhost/crm_internal/api/rest/issues/`, {
|
const result = await (await fetch(URL, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers:
|
headers:
|
||||||
{ "Content-Type": "application/json", "Authorization": mantisBT_Token },
|
{ "Content-Type": "application/json", "Authorization": "M7xHFMiZcg_oPrGZoRY-kPoivoniK3BO" },
|
||||||
body: JSON.stringify(requestBody)
|
body: JSON.stringify(requestBody)
|
||||||
}))
|
}))
|
||||||
if (response_for_issue_ticket.status === 403) {
|
if (result.status === 403) {
|
||||||
return Response.json({ message: 'Invalid Token' }, { status: 403 })
|
return Response.json({ message: 'Invalid Token' }, { status: 403 })
|
||||||
}
|
}
|
||||||
if (response_for_issue_ticket.status === 400) {
|
if (result.status === 400) {
|
||||||
return Response.json({ message: 'Project not specified' }, { status: 400 })
|
return Response.json({ message: 'Project not specified' }, { status: 400 })
|
||||||
}
|
}
|
||||||
if (response_for_issue_ticket.status === 404) {
|
if (result.status === 404) {
|
||||||
return Response.json({ message: 'Project not found' }, { status: 404 })
|
return Response.json({ message: 'Project not found' }, { status: 404 })
|
||||||
}
|
}
|
||||||
if (response_for_issue_ticket.status === 429) {
|
if (result.status === 429) {
|
||||||
return Response.json({ message: 'Please raised ticket after sometime.' }, { status: 429 })
|
return Response.json({ message: 'Please raised ticket after sometime.' }, { status: 429 })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assign backend team to the ticket
|
|
||||||
const issue_id = parseInt(response_for_issue_ticket.statusText);
|
|
||||||
const assign_person = await assignUser(summary);
|
|
||||||
const response_for_assignee_issue_ticket = await (await fetch(`http://localhost/CRM_Internal/api/rest/issues/${issue_id}`, {
|
|
||||||
method: "PATCH",
|
|
||||||
headers:
|
|
||||||
{ "Content-Type": "application/json", "Authorization": mantisBT_Token },
|
|
||||||
body: JSON.stringify({handler :{ id : assign_person }})
|
|
||||||
}))
|
|
||||||
if (response_for_assignee_issue_ticket.status === 403) {
|
|
||||||
return Response.json({ message: 'Invalid Token' }, { status: 403 })
|
|
||||||
}
|
|
||||||
if(response_for_assignee_issue_ticket.status ==200)
|
|
||||||
{
|
|
||||||
console.log("Assignee is assign against the ticket");
|
|
||||||
}
|
|
||||||
///CRM database management
|
///CRM database management
|
||||||
const connection_crm = await AppDataSource.getConnection();
|
const connection_crm = await AppDataSource.getConnection();
|
||||||
const userRepo = connection_crm.getRepository(User);
|
const userRepo = connection_crm.getRepository(User);
|
||||||
@ -76,19 +60,19 @@ export async function POST(request: Request) {
|
|||||||
|
|
||||||
const ticketRepo = connection_crm.getRepository(Ticket);
|
const ticketRepo = connection_crm.getRepository(Ticket);
|
||||||
const ticket = new Ticket();
|
const ticket = new Ticket();
|
||||||
ticket.ticket_id = parseInt(response_for_issue_ticket.statusText);
|
|
||||||
ticket.category_of_request = summary;
|
ticket.category_of_request = summary;
|
||||||
ticket.nature_of_request = description;
|
ticket.nature_of_request = description;
|
||||||
ticket.additional_info = additional_information
|
ticket.additional_info = additional_information
|
||||||
ticket.message = steps_to_reproduce;
|
ticket.message = steps_to_reproduce;
|
||||||
ticket.created_by = user_name;
|
ticket.created_by = user_name;
|
||||||
ticket.customer_account_no = Number(session.accountNo);
|
//ticket.created_date = new Date().toString();
|
||||||
ticket.created_date=new Date().toLocaleString("en-IN");
|
ticket.created_date=new Date().toLocaleString("en-IN");
|
||||||
|
|
||||||
await ticketRepo.save(ticket);
|
await ticketRepo.save(ticket);
|
||||||
return Response.json({ message: `Ticket No:${response_for_issue_ticket.statusText} created successfully` })
|
return Response.json({ message: `Ticket No:${result.statusText} created successfully` })
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
console.error(error);
|
||||||
return Response.json(null, { status: 500 });
|
return Response.json(null, { status: 500 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,32 +84,30 @@ export async function GET(request: Request) {
|
|||||||
if (!session.TwoStepAuthentication) {
|
if (!session.TwoStepAuthentication) {
|
||||||
return Response.json(null);
|
return Response.json(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!session.accountNo) {
|
if (!session.accountNo) {
|
||||||
return Response.json({ message: `Please sign in before raise a ticket` })
|
return Response.json({ message: `Please sign in before raise a ticket` })
|
||||||
}
|
}
|
||||||
|
|
||||||
const connection_crm = await AppDataSource.getConnection();
|
const connection_crm = await AppDataSource.getConnection();
|
||||||
const connection_mantis = await AppDataSource.getMantisConnection();
|
const connection_mantis = await AppDataSource.getMantisConnection();
|
||||||
const userRepo = connection_crm.getRepository(User);
|
const userRepo = connection_crm.getRepository(User);
|
||||||
// Get full name of user
|
|
||||||
const user_table = await userRepo.createQueryBuilder('user')
|
const user_table = await userRepo.createQueryBuilder('user')
|
||||||
.select(['first_name', 'middle_name', 'last_name'])
|
.select(['first_name', 'middle_name', 'last_name'])
|
||||||
.where("user.bank_account_no = :AccountNo", { AccountNo: session.accountNo })
|
.where("user.bank_account_no = :AccountNo", { AccountNo: session.accountNo })
|
||||||
.getRawOne();
|
.getRawOne();
|
||||||
|
|
||||||
const user_name = (user_table.first_name ? user_table.first_name + ' ' : '') +
|
const user_name = (user_table.first_name ? user_table.first_name + ' ' : '') +
|
||||||
(user_table.middle_name ? user_table.middle_name + ' ' : '') +
|
(user_table.middle_name ? user_table.middle_name + ' ' : '') +
|
||||||
(user_table.last_name ? user_table.last_name + ' ' : '');
|
(user_table.last_name ? user_table.last_name + ' ' : '');
|
||||||
|
|
||||||
const ticketRepo = connection_crm.getRepository(Ticket);
|
const ticketRepo = connection_crm.getRepository(Ticket);
|
||||||
// Get list of ticket raised by the user
|
|
||||||
const ticket_list = await ticketRepo.createQueryBuilder('ticket')
|
const ticket_list = await ticketRepo.createQueryBuilder('ticket')
|
||||||
.select(['id','ticket_id', 'category_of_request', 'nature_of_request', 'created_date','additional_info'])
|
.select(['id', 'category_of_request', 'nature_of_request', 'created_date','additional_info'])
|
||||||
.where("ticket.created_by = :created_by" , { created_by: user_name })
|
.where("ticket.created_by = :created_by", { created_by: user_name }).getRawMany();
|
||||||
.andWhere("ticket.customer_account_no = :customer_account_no" ,{customer_account_no:session.accountNo })
|
|
||||||
.getRawMany();
|
|
||||||
|
|
||||||
if(!ticket_list){return Response.json('No Details Found')}
|
if(!ticket_list){return Response.json('No Details Found')}
|
||||||
const ticket_ids = ticket_list.map(ticket => ticket.ticket_id); // collect the ticket_id (ticket_id store the mantisBT's bug id)
|
const ticket_ids = ticket_list.map(ticket => ticket.id);
|
||||||
|
|
||||||
// Mantis-database fetching for "status" & "message"
|
// Mantis-database fetching for "status" & "message"
|
||||||
const queryRunner = connection_mantis.createQueryRunner();
|
const queryRunner = connection_mantis.createQueryRunner();
|
||||||
await queryRunner.connect();
|
await queryRunner.connect();
|
||||||
@ -143,31 +125,31 @@ export async function GET(request: Request) {
|
|||||||
if(ticket_status === 20){status='Resolved'}
|
if(ticket_status === 20){status='Resolved'}
|
||||||
if(ticket_status === 30){status='Reopen'}
|
if(ticket_status === 30){status='Reopen'}
|
||||||
ticketResolution[id] = status
|
ticketResolution[id] = status
|
||||||
|
|
||||||
// For Message of ticket
|
// For Message of ticket
|
||||||
const ticket_note = await queryRunner.query('SELECT bugnote_text_id FROM "kccb_bugnote" WHERE bug_id = $1', [id]);
|
const ticket_note = await queryRunner.query('SELECT id FROM "kccb_bugnote" WHERE bug_id = $1', [id]);
|
||||||
const ticket_note_ids =ticket_note.map((item:{bugnote_text_id:number}) =>item.bugnote_text_id) // collect all ids from kccb_bugnote table for a issue
|
const ticket_note_ids =ticket_note.map((item:{id:number}) =>item.id)
|
||||||
|
|
||||||
for(const note_id of ticket_note_ids ){
|
for(const note_id of ticket_note_ids ){
|
||||||
const ticket_note_text = await queryRunner.query('SELECT note FROM "kccb_bugnote_text" WHERE id = $1', [note_id]);
|
const ticket_note_text = await queryRunner.query('SELECT note FROM "kccb_bugnote_text" WHERE id = $1', [note_id]);
|
||||||
|
|
||||||
if (!ticketMessage[id]) {
|
if (!ticketMessage[id]) {
|
||||||
ticketMessage[id] = [];
|
ticketMessage[id] = [];
|
||||||
}
|
}
|
||||||
ticketMessage[id].push(...ticket_note_text);
|
ticketMessage[id].push(...ticket_note_text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// merge message & status in the response
|
const result = ticket_list.map(item =>
|
||||||
const response_for_issue_ticket = ticket_list.map(item =>
|
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
...item,status:ticketResolution[item.ticket_id]||null,message: ticketMessage[item.ticket_id]||null
|
...item,status:ticketResolution[item.id]||null,message: ticketMessage[item.id]||null
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
queryRunner.release();
|
queryRunner.release();
|
||||||
return Response.json(response_for_issue_ticket)
|
return Response.json(result)
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
|
console.error(error);
|
||||||
return Response.json(null, { status: 500 });
|
return Response.json(null, { status: 500 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,12 +24,15 @@ export async function GET(request: Request) {
|
|||||||
.select(["id","bank_account_no"])
|
.select(["id","bank_account_no"])
|
||||||
.where("user.bank_account_no = :userAccountNo", { userAccountNo: session.accountNo })
|
.where("user.bank_account_no = :userAccountNo", { userAccountNo: session.accountNo })
|
||||||
.getRawOne();
|
.getRawOne();
|
||||||
|
|
||||||
|
//console.log(session)
|
||||||
// if (!user_details || session.TwoStepAuthentication === false)
|
// if (!user_details || session.TwoStepAuthentication === false)
|
||||||
if (!user_details)
|
if (!user_details)
|
||||||
return Response.json({ message: "User_details not found" }, { status: 404 });
|
return Response.json({ message: "User_details not found" }, { status: 404 });
|
||||||
|
|
||||||
return Response.json(user_details);
|
return Response.json(user_details);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
return Response.json(null, { status: 500 });
|
return Response.json(null, { status: 500 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ async function logout() {
|
|||||||
try {
|
try {
|
||||||
await axios.post('/api/auth/logout');
|
await axios.post('/api/auth/logout');
|
||||||
} catch (error: AxiosError | any) {
|
} catch (error: AxiosError | any) {
|
||||||
|
console.log(error);
|
||||||
notifications.show({
|
notifications.show({
|
||||||
color: 'red',
|
color: 'red',
|
||||||
title: error.code,
|
title: error.code,
|
||||||
|
@ -6,13 +6,10 @@ export default function Home() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p>Welcome to CRM Portal</p>
|
<p>Welcome to CRM Portal</p>
|
||||||
|
<p>For Issue a ticket ,click on create ticket</p>
|
||||||
<UserContextConsumer>
|
<UserContextConsumer>
|
||||||
{
|
{user => user && <p>Account No: {user.bank_account_no}</p>}
|
||||||
user => user && <p><b>Your Present Login Account No: {user.bank_account_no}</b></p>
|
|
||||||
}
|
|
||||||
</UserContextConsumer>
|
</UserContextConsumer>
|
||||||
<p><li>For raise a complain or assistance ,please click on <b>Create Ticket</b></li></p>
|
|
||||||
<p><li>For track a ticket ,please click on <b>View Ticket</b></li></p>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -107,7 +107,7 @@ const ComplaintForm: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const requestBody = {
|
const requestBody = {
|
||||||
summary: category,
|
summary: description,
|
||||||
description: description,
|
description: description,
|
||||||
additional_information: additionalInformation.join(", "),
|
additional_information: additionalInformation.join(", "),
|
||||||
steps_to_reproduce: message,
|
steps_to_reproduce: message,
|
||||||
@ -116,6 +116,7 @@ const ComplaintForm: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
const response = await axios.post("/api/ticket", requestBody);
|
const response = await axios.post("/api/ticket", requestBody);
|
||||||
const data = await response.data;
|
const data = await response.data;
|
||||||
|
console.log('API response:', data);
|
||||||
alert(data.message);
|
alert(data.message);
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
@ -145,9 +146,9 @@ const ComplaintForm: React.FC = () => {
|
|||||||
<option value="" disabled>
|
<option value="" disabled>
|
||||||
Select Category
|
Select Category
|
||||||
</option>
|
</option>
|
||||||
<option value="ATM Related">ATM Related</option>
|
<option value="ATM">ATM Related</option>
|
||||||
<option value="Internet Banking">Internet Banking</option>
|
<option value="IB">Internet Banking</option>
|
||||||
<option value="Mobile Banking">Mobile Banking</option>
|
<option value="MB">Mobile Banking</option>
|
||||||
<option value="Others">Others</option>
|
<option value="Others">Others</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
@ -164,16 +165,16 @@ const ComplaintForm: React.FC = () => {
|
|||||||
<option value="" disabled>
|
<option value="" disabled>
|
||||||
Select Description
|
Select Description
|
||||||
</option>
|
</option>
|
||||||
{category === "ATM Related" && <option value="Transaction Issue">Transaction Issue</option>}
|
{category === "ATM" && <option value="Transaction Issue">Transaction Issue</option>}
|
||||||
{/* {category === "UPI" && <option value="UPI Issue">UPI Issue</option>} */}
|
{category === "UPI" && <option value="UPI Issue">UPI Issue</option>}
|
||||||
{category === "Internet Banking" && (
|
{category === "IB" && (
|
||||||
<>
|
<>
|
||||||
{/* <option value="IMPS">IMPS Funds Transfer</option> */}
|
{/* <option value="IMPS">IMPS Funds Transfer</option> */}
|
||||||
<option value="Fund Transfer failure">Fund Transfer Failure</option>
|
<option value="Fund Transfer failure">Fund Transfer Failure</option>
|
||||||
<option value="Network Issue">Network Issue</option>
|
<option value="Network Issue">Network Issue</option>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{category === "Mobile Banking" && (
|
{category === "MB" && (
|
||||||
<>
|
<>
|
||||||
{/* <option value="IMPS">IMPS Funds Transfer</option> */}
|
{/* <option value="IMPS">IMPS Funds Transfer</option> */}
|
||||||
<option value="Fund Transfer failure">Fund Transfer Failure</option>
|
<option value="Fund Transfer failure">Fund Transfer Failure</option>
|
||||||
|
@ -14,7 +14,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { useDisclosure } from "@mantine/hooks";
|
import { useDisclosure } from "@mantine/hooks";
|
||||||
import { IconEye, IconMessage, IconSearch, IconTrash } from "@tabler/icons-react";
|
import { IconEye, IconMessage, IconSearch } from "@tabler/icons-react";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
@ -23,54 +23,26 @@ interface Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Ticket {
|
interface Ticket {
|
||||||
ticket_id: string;
|
id: string;
|
||||||
category_of_request: string;
|
category_of_request: string;
|
||||||
nature_of_request: string;
|
nature_of_request: string;
|
||||||
created_date: string;
|
created_date: string;
|
||||||
status: string;
|
status: string;
|
||||||
message: Message[];
|
message: Message[];
|
||||||
}
|
}
|
||||||
interface TicketDetails {
|
|
||||||
ticket_id: string;
|
|
||||||
category_of_request: string;
|
|
||||||
nature_of_request: string;
|
|
||||||
additional_info: string;
|
|
||||||
message : string;
|
|
||||||
created_date: string;
|
|
||||||
|
|
||||||
}
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const [opened, { open, close }] = useDisclosure(false);
|
const [opened, { open, close }] = useDisclosure(false);
|
||||||
const [tickets, setTickets] = useState<Ticket[]>([]);
|
const [tickets, setTickets] = useState<Ticket[]>([]);
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [filteredTickets, setFilteredTickets] = useState<Ticket[]>([]);
|
const [filteredTickets, setFilteredTickets] = useState<Ticket[]>([]);
|
||||||
const [activeMessages, setActiveMessages] = useState<Message[] | null>(null);
|
const [activeMessages, setActiveMessages] = useState<Message[] | null>(null);
|
||||||
const [ticketDetails, setTicketDetails] = useState<TicketDetails| null>(null);
|
|
||||||
const [detailsOpened, { open: openDetails, close: closeDetails }] = useDisclosure(false);
|
|
||||||
|
|
||||||
const handleOpenDetails = async (ticketId: string) => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get<TicketDetails>(`/api/ticket/${ticketId}`); // Assuming API provides details
|
|
||||||
setTicketDetails(response.data);
|
|
||||||
openDetails();
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error("Failed to fetch ticket details:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// For parsing additional_info
|
|
||||||
const parseAdditionalInfo = (info: string) => {
|
|
||||||
return info.split(", ").map((item) => {
|
|
||||||
const [key, value] = item.split(": ");
|
|
||||||
return { key: key.trim(), value: value?.trim() || "" };
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Sort and filter tickets based on the time
|
// Sort and filter tickets based on the time
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const sortedTickets = [...tickets].sort(
|
const sortedTickets = [...tickets].sort(
|
||||||
// (a, b) => new Date(b.created_date).getTime() - new Date(a.created_date).getTime()
|
// (a, b) => new Date(b.created_date).getTime() - new Date(a.created_date).getTime()
|
||||||
(a, b) => parseInt(b.ticket_id) - parseInt(a.ticket_id)
|
(a, b) => parseInt(b.id) - parseInt(a.id)
|
||||||
);
|
);
|
||||||
const results = sortedTickets.filter((ticket) =>
|
const results = sortedTickets.filter((ticket) =>
|
||||||
ticket.category_of_request.toLowerCase().includes(searchQuery.toLowerCase())
|
ticket.category_of_request.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
@ -97,19 +69,18 @@ export default function Page() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
//Add conditional row styles
|
//Add conditional row styles
|
||||||
// const getRowStyle = (status: string) => {
|
const getRowStyle = (status: string) => {
|
||||||
// switch (status.toLowerCase()) {
|
switch (status.toLowerCase()) {
|
||||||
// case "resolved":
|
case "resolved":
|
||||||
// return { backgroundColor: "#e0e3df", color: "#000"}; // Grey for closed
|
return { backgroundColor: "#e0e3df", color: "#000"}; // Grey for closed
|
||||||
// default:
|
default:
|
||||||
// return {};
|
return {};
|
||||||
// }
|
}
|
||||||
// };
|
};
|
||||||
|
|
||||||
const rows = filteredTickets.map((ticket) => (
|
const rows = filteredTickets.map((ticket) => (
|
||||||
// <Table.Tr key={ticket.ticket_id} style={getRowStyle(ticket.status)}>
|
<Table.Tr key={ticket.id} style={getRowStyle(ticket.status)}>
|
||||||
<Table.Tr key={ticket.ticket_id}>
|
<Table.Td>{ticket.id}</Table.Td>
|
||||||
<Table.Td>{ticket.ticket_id}</Table.Td>
|
|
||||||
<Table.Td>{ticket.category_of_request}</Table.Td>
|
<Table.Td>{ticket.category_of_request}</Table.Td>
|
||||||
<Table.Td>{ticket.nature_of_request}</Table.Td>
|
<Table.Td>{ticket.nature_of_request}</Table.Td>
|
||||||
<Table.Td>{ticket.created_date}</Table.Td>
|
<Table.Td>{ticket.created_date}</Table.Td>
|
||||||
@ -121,7 +92,7 @@ export default function Page() {
|
|||||||
onClick={() => handleOpenMessage(ticket.message)}
|
onClick={() => handleOpenMessage(ticket.message)}
|
||||||
>
|
>
|
||||||
<Tooltip label="Message">
|
<Tooltip label="Message">
|
||||||
<IconMessage />
|
<IconMessage/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Group>
|
</Group>
|
||||||
@ -129,21 +100,9 @@ export default function Page() {
|
|||||||
|
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Group>
|
<Group>
|
||||||
<ActionIcon variant="subtle"
|
<ActionIcon variant="subtle">
|
||||||
onClick={() => handleOpenDetails(ticket.ticket_id)}
|
|
||||||
>
|
|
||||||
<Tooltip label="Details of the Ticket">
|
<Tooltip label="Details of the Ticket">
|
||||||
<IconEye />
|
<IconEye/>
|
||||||
</Tooltip>
|
|
||||||
</ActionIcon>
|
|
||||||
</Group>
|
|
||||||
</Table.Td>
|
|
||||||
<Table.Td>
|
|
||||||
<Group>
|
|
||||||
<ActionIcon variant="subtle"
|
|
||||||
>
|
|
||||||
<Tooltip label="Delete the Ticket">
|
|
||||||
<IconTrash/>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Group>
|
</Group>
|
||||||
@ -193,7 +152,7 @@ export default function Page() {
|
|||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
<Modal opened={opened} onClose={close} title="Message">
|
<Modal opened={opened} onClose={close} title="Message">
|
||||||
<Flex direction="column" gap="md">
|
<Flex direction="column" gap="sm">
|
||||||
{activeMessages?.map((msg, index) => (
|
{activeMessages?.map((msg, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
@ -212,33 +171,6 @@ export default function Page() {
|
|||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Modal>
|
</Modal>
|
||||||
<Modal opened={detailsOpened} onClose={closeDetails}>
|
|
||||||
{ticketDetails ? (
|
|
||||||
<Flex direction="column" gap="md">
|
|
||||||
<div><strong>Ticket ID:</strong> {ticketDetails.ticket_id}</div>
|
|
||||||
<div><strong>Category of Complaint:</strong> {ticketDetails.category_of_request}</div>
|
|
||||||
<div><strong>Description:</strong> {ticketDetails.nature_of_request}</div>
|
|
||||||
{/* <div><strong>Additional Information:</strong> {ticketDetails.additional_info}</div> */}
|
|
||||||
{/* Additional Info Table */}
|
|
||||||
<div><strong>Additional Info:</strong></div>
|
|
||||||
<Table withTableBorder withColumnBorders highlightOnHover>
|
|
||||||
<Table.Tbody>
|
|
||||||
{parseAdditionalInfo(ticketDetails.additional_info).map((item, index) => (
|
|
||||||
<Table.Tr key={index}>
|
|
||||||
<Table.Td>{item.key}</Table.Td>
|
|
||||||
<Table.Td>{item.value}</Table.Td>
|
|
||||||
</Table.Tr>
|
|
||||||
))}
|
|
||||||
</Table.Tbody>
|
|
||||||
</Table>
|
|
||||||
<div><strong>Message:</strong> {ticketDetails.message}</div>
|
|
||||||
<div><strong>Created Date:</strong> {ticketDetails.created_date}</div>
|
|
||||||
<Button onClick={closeDetails} mt="sm">Close</Button>
|
|
||||||
</Flex>
|
|
||||||
) : (
|
|
||||||
<p>Loading details...</p>
|
|
||||||
)}
|
|
||||||
</Modal>
|
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
159
src/app/home/user/view-ticket/page.tsx.backupp
Normal file
159
src/app/home/user/view-ticket/page.tsx.backupp
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ActionIcon,
|
||||||
|
Button,
|
||||||
|
Container,
|
||||||
|
Flex,
|
||||||
|
Group,
|
||||||
|
Modal,
|
||||||
|
Space,
|
||||||
|
Table,
|
||||||
|
TextInput,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import { useDisclosure } from "@mantine/hooks";
|
||||||
|
import { IconMessage, IconSearch } from "@tabler/icons-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
interface Message {
|
||||||
|
note: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Ticket {
|
||||||
|
id: string;
|
||||||
|
category_of_request: string;
|
||||||
|
nature_of_request: string;
|
||||||
|
created_date: string;
|
||||||
|
status: string;
|
||||||
|
message: Message[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
const [opened, { open, close }] = useDisclosure(false);
|
||||||
|
const [tickets, setTickets] = useState<Ticket[]>([]);
|
||||||
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
const [filteredTickets, setFilteredTickets] = useState<Ticket[]>([]);
|
||||||
|
const [filteredUsers, setFilteredUsers] = useState<Ticket[]>([]);
|
||||||
|
const [activeMessages, setActiveMessages] = useState<Message[] | null>(null);
|
||||||
|
|
||||||
|
// Fetch tickets from the API
|
||||||
|
const fetchTickets = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/ticket");
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to fetch tickets");
|
||||||
|
}
|
||||||
|
const data: Ticket[] = await response.json();
|
||||||
|
setTickets(data);
|
||||||
|
setFilteredTickets(data); // Set initial filtered tickets
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching tickets:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchTickets(); // Call API when component mounts
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Filter tickets based on the search query
|
||||||
|
useEffect(() => {
|
||||||
|
const results = tickets.filter((ticket) =>
|
||||||
|
ticket.category_of_request.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
|
);
|
||||||
|
setFilteredTickets(results);
|
||||||
|
}, [searchQuery, tickets]);
|
||||||
|
|
||||||
|
const handleOpenMessage = (messages: Message[]) => {
|
||||||
|
setActiveMessages(messages);
|
||||||
|
open();
|
||||||
|
};
|
||||||
|
|
||||||
|
const rows = filteredUsers.map((ticket) => (
|
||||||
|
<Table.Tr key={ticket.id}>
|
||||||
|
<Table.Td>{ticket.id}</Table.Td>
|
||||||
|
<Table.Td>{ticket.category_of_request}</Table.Td>
|
||||||
|
<Table.Td>{ticket.nature_of_request}</Table.Td>
|
||||||
|
<Table.Td>{ticket.created_date}</Table.Td>
|
||||||
|
<Table.Td>{ticket.status}</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<Group>
|
||||||
|
<ActionIcon
|
||||||
|
variant="subtle"
|
||||||
|
onClick={() => handleOpenMessage(ticket.message)}
|
||||||
|
>
|
||||||
|
<Tooltip label="Message">
|
||||||
|
<IconMessage />
|
||||||
|
</Tooltip>
|
||||||
|
</ActionIcon>
|
||||||
|
</Group>
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
|
));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container fluid>
|
||||||
|
<Title order={3}>View Ticket</Title>
|
||||||
|
<Space h="1rem" />
|
||||||
|
<Flex align="center" justify="space-between" w={720}>
|
||||||
|
<Flex align="center">
|
||||||
|
<Title order={5}>Tickets</Title>
|
||||||
|
<Space w="1rem" />
|
||||||
|
<TextInput
|
||||||
|
placeholder="Search"
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(event) => setSearchQuery(event.currentTarget.value)}
|
||||||
|
radius="md"
|
||||||
|
w={250}
|
||||||
|
leftSection={<IconSearch size={16} />}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Space h="1.5rem" />
|
||||||
|
<Table
|
||||||
|
w={720}
|
||||||
|
stickyHeader
|
||||||
|
stickyHeaderOffset={60}
|
||||||
|
withTableBorder
|
||||||
|
highlightOnHover
|
||||||
|
horizontalSpacing="lg"
|
||||||
|
>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Ticket ID</th>
|
||||||
|
<th>Category</th>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>Timestamp</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Message</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>{rows}</tbody>
|
||||||
|
</Table>
|
||||||
|
|
||||||
|
<Modal opened={opened} onClose={close} title="Message">
|
||||||
|
<Flex direction="column" gap="sm">
|
||||||
|
{activeMessages?.map((msg, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
padding: "0.5rem",
|
||||||
|
background: index % 2 === 0 ? "#f1f3f5" : "#dce6f2",
|
||||||
|
borderRadius: "8px",
|
||||||
|
maxWidth: "70%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{msg.note}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<Button onClick={close} mt="sm">
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Modal>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -29,6 +29,7 @@ async function handleResendOTP() {
|
|||||||
// Object.assign(response.data);
|
// Object.assign(response.data);
|
||||||
alert(response.data.message);
|
alert(response.data.message);
|
||||||
} catch (error: AxiosError | any) {
|
} catch (error: AxiosError | any) {
|
||||||
|
console.log(error);
|
||||||
notifications.show({
|
notifications.show({
|
||||||
color: 'red',
|
color: 'red',
|
||||||
title: error.code,
|
title: error.code,
|
||||||
@ -48,13 +49,13 @@ async function handleValidateOTP(OtpInput: OtpInput) {
|
|||||||
}
|
}
|
||||||
Object.assign(Result, response.data);
|
Object.assign(Result, response.data);
|
||||||
} catch (error: AxiosError | any) {
|
} catch (error: AxiosError | any) {
|
||||||
alert(error.response.data.error);
|
console.log(error);
|
||||||
// notifications.show({
|
notifications.show({
|
||||||
// color: 'red',
|
color: 'red',
|
||||||
// title: error.response.status,
|
title: error.response.status,
|
||||||
// message: error.response.data.error
|
message: error.response.data.error
|
||||||
|
|
||||||
// })
|
})
|
||||||
}
|
}
|
||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
|
@ -27,17 +27,18 @@ async function handleRequestOTP(SendAccNoInput: SendAccNoInput) {
|
|||||||
if (response.data.ok) {
|
if (response.data.ok) {
|
||||||
const otp_response = await axios.get("/api/otp");
|
const otp_response = await axios.get("/api/otp");
|
||||||
if (otp_response.status === 200) {
|
if (otp_response.status === 200) {
|
||||||
|
// redirect('/otp');
|
||||||
window.location.href = 'login/otp'
|
window.location.href = 'login/otp'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Object.assign(Result, response.data);
|
Object.assign(Result, response.data);
|
||||||
} catch (error: AxiosError | any) {
|
} catch (error: AxiosError | any) {
|
||||||
alert(error.response.data.message)
|
console.log(error);
|
||||||
// notifications.show({
|
notifications.show({
|
||||||
// color: 'red',
|
color: 'red',
|
||||||
// title: error.response.status,
|
title: error.response.status,
|
||||||
// message: error.response.data.message
|
message: error.response.data.message
|
||||||
// })
|
})
|
||||||
}
|
}
|
||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user