This commit is contained in:
merelendor 2022-08-22 07:37:06 +03:00
commit e333541147
6 changed files with 483 additions and 95 deletions

View File

@ -1,4 +1,5 @@
import axios from 'axios';
import { eachSeries } from 'async';
import * as actionTypes from '../constants/actionTypes';
import * as currentState from '../reducers/initialState';
@ -105,24 +106,61 @@ export const sendNewAppeal = (appeal) =>
return new Promise((resolve, reject) =>
{
return new Promise((resolve, reject) =>
axios.post(`${ process.env.NEXT_PUBLIC_SELF_API_HOST }/api/support/request`, appeal,
{
axios.post(`${ process.env.NEXT_PUBLIC_SELF_API_HOST }/api/support/request`, {
query
},
withCredentials: true,
})
.then(async (response) =>
{
console.log("sendNewAppeal", "response.data", response.data);
resolve(response.data);
})
.catch((error) =>
{
console.error(error);
reject();
});
});
}
export const sendAppealAttachments = (payload) =>
{
console.log("ACTION", "support", "sendAppealAttachments", payload);
return new Promise((resolve, reject) =>
{
eachSeries(payload.files, (file, callback) =>
{
let data = new FormData();
data.append('file', file);
axios.post(`${ process.env.NEXT_PUBLIC_SELF_API_HOST }/api/support/attachment?request_client_number=${ payload.request_client_number }`, data,
{
headers: {
"Content-Type": "multipart/form-data",
},
withCredentials: true,
})
.then(async (response) =>
{
console.log("sendNewAppeal", "response.data", response.data);
resolve();
console.log("sendAppealAttachments", "response.data", response.data);
callback(null);
})
.catch((error) =>
{
console.error(error);
reject();
callback(true);
});
}, (err) =>
{
if(!err)
{
resolve();
}
else
{
reject();
}
});
});
}

View File

@ -5,51 +5,190 @@ import cookie from 'cookie';
import moment from 'moment';
import jwt from 'jsonwebtoken';
import { cors } from '../../../lib/cors';
import { inspect } from 'util';
//import formidable from 'formidable';
export default async function handler(req, res)
{
console.log("API", "support", "attachment");
await cors(req, res);
console.log("API", "support", "request");
console.log(req.body);
console.log("-".repeat(50));
console.log("req.query", req.query);
if(req.headers.cookie !== undefined)
return new Promise((resolve) =>
{
const cookies = cookie.parse(req.headers?.cookie ? req.headers?.cookie : "");
if(cookies.jwt !== undefined && cookies.jwt !== null)
if(req.headers.cookie !== undefined)
{
if(jwt.verify(cookies.jwt, process.env.JWT_SECRET_CLIENT))
const cookies = cookie.parse(req.headers?.cookie ? req.headers?.cookie : "");
if(cookies.jwt !== undefined && cookies.jwt !== null)
{
const response = await new Promise((resolve, reject) =>
{
axios.post(`${ process.env.CRM_API_HOST }/lk/incident/UploadDocument`, req.body)
.then((api_response) =>
{
console.log("RESPONSE");
console.log(api_response.data);
console.log("cookies.jwt");
console.log(cookies.jwt);
var client_jwt_decoded = jwt.verify(cookies.jwt, process.env.JWT_SECRET_CLIENT);
var crm_jwt = jwt.sign({ acc_number: client_jwt_decoded.acc_number }, process.env.JWT_SECRET_CRM, { noTimestamp: true });
resolve(api_response.data);
//const path = `${ process.env.CRM_API_HOST }/lk/incident/RequestClient/UploadDocument?request_client_number=${ fields.request_client_number }`;
const path = `${ process.env.CRM_API_HOST }/lk/incident/RequestClient/UploadDocument?request_client_number=${ req.query.request_client_number }`;
console.log("API", "support", "request", "path", path);
console.log("*".repeat(50));
console.log(req.headers);
console.log("*".repeat(50));
try
{
console.log("??????", req.headers['content-type']);
axios.post(path, req.body,
{
headers: {
"Content-Type": req.headers['content-type'],
//"Content-Type": "multipart/form-data",
"Authorization": `Bearer ${ crm_jwt }`,
},
withCredentials: true,
})
.then((crm_response) =>
{
console.log("crm_response for", path);
console.log(inspect(crm_response.data, true, null, true));
res.status(200).json(crm_response.data);
resolve();
})
.catch((error) =>
{
console.log("error");
console.error(error);
console.error("-".repeat(30), "error.response.data:");
console.error(error.response.data);
reject([]);
res.status(500).json(error.response.data);
resolve();
});
});
res.status(200).json(response);
}
catch(e)
{
console.error(e);
res.status(500).end(e);
resolve();
}
}
else
{
res.status(403);
resolve();
}
}
else
{
res.status(403);
resolve();
}
});
}
/*
export default async function handler(req, res)
{
console.log("API", "support", "attachment");
await cors(req, res);
return new Promise((resolve) =>
{
if(req.headers.cookie !== undefined)
{
const cookies = cookie.parse(req.headers?.cookie ? req.headers?.cookie : "");
if(cookies.jwt !== undefined && cookies.jwt !== null)
{
console.log("cookies.jwt");
console.log(cookies.jwt);
var client_jwt_decoded = jwt.verify(cookies.jwt, process.env.JWT_SECRET_CLIENT);
var crm_jwt = jwt.sign({ acc_number: client_jwt_decoded.acc_number }, process.env.JWT_SECRET_CRM, { noTimestamp: true });
const form = formidable({});
form.parse(req, (err, fields, files) =>
{
if(err)
{
console.error(err);
res.status(500).end("");
resolve();
}
else
{
console.log("fields", fields);
console.log("files", files);
console.log("API", "support", "attachment", "-".repeat(50));
//const path = `${ process.env.CRM_API_HOST }/lk/incident/RequestClient/UploadDocument?request_client_number=${ fields.request_client_number }`;
const path = `${ process.env.CRM_API_HOST }/lk/incident/RequestClient/UploadDocument?request_client_number=REQ2022_0000092`;
console.log("API", "support", "request", "path", path);
try
{
axios.post(path, req.body,
{
headers: {
//"Content-Type": "application/json",
"Content-Type": "multipart/form-data",
"Authorization": `Bearer ${ crm_jwt }`,
},
withCredentials: true,
})
.then((crm_response) =>
{
console.log("crm_response for", path);
console.log(inspect(crm_response.data, true, null, true));
res.status(200).json(crm_response.data);
resolve();
})
.catch((error) =>
{
console.error(error);
console.error("-".repeat(30), "error.response.data:");
console.error(error.response.data);
res.status(500).json(error.response.data);
resolve();
});
}
catch(e)
{
console.error(e);
res.status(500).end(e);
resolve();
}
}
});
}
else
{
res.status(403);
resolve();
}
}
else
{
res.status(403);
resolve();
}
});
}
export const config = {
api: {
bodyParser: false
}
}
*/
export const config = {
api: {
bodyParser: {
sizeLimit: '1000mb'
}
}
}

View File

@ -1,11 +1,69 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import CRMRequestPost from '../../../lib/CRMRequestPost';
import axios from 'axios';
import { Cookies } from 'react-cookie';
import cookie from 'cookie';
import moment from 'moment';
import jwt from 'jsonwebtoken';
import { cors } from '../../../lib/cors';
import { inspect } from 'util';
export default async function handler(req, res)
{
console.log("API", "support", "request");
console.log(req.body);
console.log("-".repeat(50));
await cors(req, res);
await CRMRequestPost(req, res, `${ process.env.CRM_API_HOST }/lk/incident/CreateRequestClient`, req.body);
if(req.headers.cookie !== undefined)
{
const cookies = cookie.parse(req.headers?.cookie ? req.headers?.cookie : "");
if(cookies.jwt !== undefined && cookies.jwt !== null)
{
console.log("cookies.jwt");
console.log(cookies.jwt);
var client_jwt_decoded = jwt.verify(cookies.jwt, process.env.JWT_SECRET_CLIENT);
var crm_jwt = jwt.sign({ acc_number: client_jwt_decoded.acc_number }, process.env.JWT_SECRET_CRM, { noTimestamp: true });
const path = `${ process.env.CRM_API_HOST }/lk/incident/RequestClient/CreateRequestClient?acc_number=${ client_jwt_decoded.acc_number }`;
console.log("API", "support", "request", "path", path);
try
{
await axios.post(path, req.body,
{
headers: {
//"Content-Type": "application/json",
"Authorization": `Bearer ${ crm_jwt }`,
},
withCredentials: true,
})
.then((crm_response) =>
{
console.log("crm_response for", path);
console.log(inspect(crm_response.data, true, null, true));
res.status(200).json(crm_response.data);
})
.catch((error) =>
{
console.error(error);
console.error("-".repeat(30), "error.response.data:");
console.error(error.response.data);
res.status(500).json(error.response.data);
});
}
catch(e)
{
console.error(e);
res.status(500).send(e);
}
}
else
{
res.status(403);
}
}
}

View File

@ -19,15 +19,116 @@ import Company from "../components/Company";
import { getUsers, sendPhoneChangeNumber, sendPhoneChangeNumberSmsCode, setUserPhone } from '../../actions';
class IndexPage extends React.Component
class Form extends React.Component
{
constructor(props)
{
super(props);
this.state = {
name: "",
email: "",
selection: false,
selected_companies_all: false,
selected_companies_list: [],
};
}
_handle_onChange = (field, value) =>
{
this.setState({ [ field ]: value });
}
_handle_onCompaniesSelection = () =>
{
this.setState({ selection: this.state.selection ? false : true });
}
_handle_onChangeCompanies_all = () =>
{
const { selected_companies_all } = this.state;
this.setState({ selected_companies_all: selected_companies_all ? false : true });
}
_handle_onCompanySelect = (company) =>
{
const { selected_companies_list } = this.state;
const selected_companies = [];
let add = true;
for(let i in selected_companies_list)
{
if(selected_companies_list[i].inn !== company.inn)
{
selected_companies.push(selected_companies_list[i]);
}
else
{
add = false;
}
}
if(add)
{
selected_companies.push(company);
}
this.setState({ selected_companies_list: selected_companies });
}
render()
{
const { companies } = this.props;
const { name, email, selection, selected_companies_all, selected_companies_list, } = this.state;
return (
<div className="table_row editable">
<div className="table_cell" data-title="ФИО пользователя">
<input type="text" placeholder="Введите ФИО" value={ name } onChange={ (event) => { this._handle_onChange("name", event.target.value) } }/>
</div>
<div className="table_cell" data-title="Почта">
<input type="email" placeholder="Введите почту" value={ email } onChange={ (event) => { this._handle_onChange("email", event.target.value) } }/>
</div>
<div className="table_cell" data-title="Роль">Пользователь</div>
<div className="table_cell" data-title="Доступные организации">
<button className="settings_dropdown" data-selected="false" onClick={ this._handle_onCompaniesSelection }>{ selected_companies_all || selected_companies_list.length > 0 ? selected_companies_all ? "Все организации" : selected_companies_list.map((company, index) => <p key={ index }>{ company.title }</p>) : "Выберите организацию" }</button>
<div className={ `dropdown_list ${ selection ? 'opened' : '' }` }>
<div className="list_item">
<input type="checkbox" value="" name="companies_list" id="company_1" hidden checked={ selected_companies_all } onChange={ this._handle_onChangeCompanies_all }/>
<label htmlFor="company_1">
Все организации
</label>
</div>
{ companies !== undefined && companies !== null && companies.map((company, index) => (
<div className="list_item">
<input type="checkbox" value={ company.inn } name="companies_list" id={ `company_${ index }` } hidden onChange={ () => this._handle_onCompanySelect(company) }/>
<label htmlFor={ `company_${ index }` }>
{ company.title }
<span>ИНН: { company.inn } { company.kpp !== undefined && company.kpp !== null && `КПП: ${ company.kpp }` }</span>
</label>
</div>
)) }
</div>
</div>
<div className="table_cell" data-title="Статус">-</div>
<div className="table_cell delete" style={{ position: 'relative' }}>
<button className="delete_user" title="Удалить пользователя"></button>
</div>
</div>
)
}
}
class AdminPage extends React.Component
{
constructor(props)
{
super(props);
this.state = {
user: {},
loading: false,
user: {},
users: null,
companies: null,
add: false,
edit: false,
};
}
@ -36,6 +137,7 @@ class IndexPage extends React.Component
return {
user: nextProps.user,
users: nextProps.users,
companies: nextProps.companies,
};
}
@ -55,9 +157,34 @@ class IndexPage extends React.Component
});
}
_handle_onAdd = () =>
{
this.setState({ add: true });
}
_handle_onEdit = () =>
{
this.setState({ edit: true });
}
_handle_onSave = () =>
{
this.setState({ add: false, edit: false });
}
render()
{
const { user, users } = this.state;
const { user, users, companies, add, edit } = this.state;
console.log("users");
console.log(users);
console.log(".".repeat(100));
console.log("user");
console.log(user);
console.log(".".repeat(100));
console.log("companies");
console.log(companies);
return (
<React.Fragment>
@ -85,8 +212,14 @@ class IndexPage extends React.Component
<div className="settings_user_control">
<p>Настройки доступа к личному кабинету</p>
<div>
<button className="button button-blue">Добавить пользователя</button>
<button className="button button-blue">Редактировать</button>
{ add || edit ? (
<button className="button button-blue" onClick={ this._handle_onSave }>Сохранить</button>
) : (
<>
<button className="button button-blue" onClick={ this._handle_onAdd }>Добавить пользователя</button>
<button className="button button-blue" onClick={ this._handle_onEdit }>Редактировать</button>
</>
) }
</div>
</div>
<div className="settings_table editable">
@ -104,8 +237,8 @@ class IndexPage extends React.Component
{
return (
<div className="table_row" key={ index }>
<div className="table_cell" data-title="ФИО пользователя">{ entry.name }</div>
<div className="table_cell" data-title="Почта">{ user.email }</div>
<div className="table_cell" data-title="ФИО пользователя">{ entry.name } (Вы)</div>
<div className="table_cell" data-title="Почта">{ entry.email }</div>
<div className="table_cell" data-title="Роль">Администратор</div>
<div className="table_cell" data-title="Доступные организации">Все организации</div>
<div className="table_cell" data-title="Статус">Активен</div>
@ -123,7 +256,7 @@ class IndexPage extends React.Component
return (
<div className="table_row" key={ index }>
<div className="table_cell" data-title="ФИО пользователя">{ entry.name }</div>
<div className="table_cell" data-title="Почта">{ user.email }</div>
<div className="table_cell" data-title="Почта">{ entry.email }</div>
<div className="table_cell" data-title="Роль">Администратор</div>
<div className="table_cell" data-title="Доступные организации">{ entry.companies.map((company, c_index) => (
<p key={ c_index }>{ company.title }</p>
@ -160,44 +293,11 @@ class IndexPage extends React.Component
<button className="delete_user" title="Удалить пользователя"></button>
</div>
</div>
<div className="table_row editable">
<div className="table_cell" data-title="ФИО пользователя">
<input type="text" placeholder="Введите ФИО" />
</div>
<div className="table_cell" data-title="Почта">
<input type="email" placeholder="Введите почту" />
</div>
<div className="table_cell" data-title="Роль">Пользователь</div>
<div className="table_cell" data-title="Доступные организации">
<button className="settings_dropdown" data-selected="false">Выберите организацию</button>
<div className="dropdown_list opened">
<div className="list_item">
<input type="checkbox" value="" name="companies_list" id="company_1" hidden />
<label htmlFor="company_1">
Все организации
</label>
</div>
<div className="list_item">
<input type="checkbox" value="" name="companies_list" id="company_2" hidden />
<label htmlFor="company_2">
ООО Еще одно название
<span>ИНН: 12345678765 КПП: 13432-02</span>
</label>
</div>
<div className="list_item">
<input type="checkbox" value="" name="companies_list" id="company_3" hidden />
<label htmlFor="company_3">
ООО Друзья и КО
<span>ИНН: 12345678765 КПП: 13432-02</span>
</label>
</div>
</div>
</div>
<div className="table_cell" data-title="Статус">-</div>
<div className="table_cell delete" style={{ position: 'relative' }}>
<button className="delete_user" title="Удалить пользователя"></button>
</div>
</div>
{*/}
{ add && (
<Form companies={ companies }/>
) }
{/*}
{*/}
</div>
</article>
@ -216,6 +316,7 @@ function mapStateToProps(state, ownProps)
return {
user: state.user,
users: state.admin.users,
companies: state.companies.list,
}
}
@ -254,4 +355,4 @@ export const getServerSideProps = reduxWrapper.getServerSideProps(store =>
}
);
export default withRouter(connect(mapStateToProps)(IndexPage));
export default withRouter(connect(mapStateToProps)(AdminPage));

View File

@ -19,7 +19,7 @@ export default class SuccessMessage extends React.Component
<div className="compare_message">
<p>{ comment }</p>
<br/>
<p>Перейти в раздел <Link href={`/contract/${ number }/agreement`}>«Документы по договору» </Link></p>
<p>Сообщение успешно отправлено, ожидайте, пожалуйста, ответа от ответсвенного сотрудника Отдела <br/>по работе с клиентами.<br/><br/><Link href={`/support/appeals`}>Перейти к списку обращений</Link></p>
</div>
</article>
);

View File

@ -19,7 +19,9 @@ import SuccessMessage from "./components/SuccessMessage";
import {
getSupportThemes,
getContractsList,
getBitrixFile
getBitrixFile,
sendNewAppeal,
sendAppealAttachments
} from "../../actions";
class TemplateFile extends React.Component
@ -78,7 +80,7 @@ class FileDropzone extends React.Component
<div className="column_text_block">
<p><b>Приложенные файлы</b></p>
{ files.map((file, index) => (
<p key={ index }>{ file.name } <small style={{ color: "red", textDecoration: "underline", cursor: "pointer" }} onClick={ () => onDeleteFile(file.name) }>[ удалить ]</small></p>
<p key={ index }>{ file.name } <small style={{ color: "#A8026B", textDecoration: "underline", cursor: "pointer" }} onClick={ () => onDeleteFile(file.name) }>[ удалить ]</small></p>
)) }
</div>
</div>
@ -166,15 +168,55 @@ class SupportRequestPage extends React.Component
this.setState({ opened_theme: index, opened_question: 0 });
}
_handle_onSendAppeal = () =>
_handle_onChangeField = (field, value) =>
{
const { name, phone, email, question, selected_contracts, } = this.state;
this.setState({ [ field ]: value, });
}
const appeal = {
phone: phone,
email: email,
description: question,
contract_numbers: selected_contracts
_handle_onSendAppeal = (event) =>
{
event.preventDefault();
if(!this._checkDisabled())
{
const { name, phone, email, question, selected_contracts, files } = this.state;
this.setState({ loading: true }, () =>
{
sendNewAppeal({
phone: phone,
email: email,
description: question,
contract_numbers: selected_contracts
})
.then((result) =>
{
console.log("SupportRequestPage", "_handle_onSendAppeal", result);
if(files.length > 0)
{
sendAppealAttachments({
request_client_number: result.request_client_number,
files: files,
})
.then(() =>
{
this.setState({ loading: false, success: true, });
})
.catch(() =>
{
this.setState({ loading: false });
});
}
else
{
this.setState({ loading: false, success: true, });
}
})
.catch(() =>
{
this.setState({ loading: false });
});
});
}
}
@ -224,6 +266,17 @@ class SupportRequestPage extends React.Component
this.setState({ files });
}
_checkDisabled = () =>
{
const { phone, email, question, } = this.state;
if(phone === "" || email === "" || question === "")
{
return true;
}
return false;
}
_renderForm = () =>
{
const { loading, contracts, themes, name, phone, email, question, file, files, opened_theme, opened_question } = this.state;
@ -237,7 +290,7 @@ class SupportRequestPage extends React.Component
}
return (
<form>
<form onSubmit={ this._handle_onSendAppeal }>
<div className="form_field">
<Select
options={ contracts_list }
@ -249,22 +302,21 @@ class SupportRequestPage extends React.Component
/>
</div>
<div className="form_field">
<input
type="text"
name="name"
placeholder="Ваше ФИО"
/>
<input type="text" name="name" placeholder="Ваше ФИО" value={ name } onChange={ (event) => { this._handle_onChangeField("name", event.target.value) } }/>
</div>
<div className="form_field">
<input type="tel" name="name" placeholder="Телефон" />
<input type="tel" name="phone" placeholder="Телефон" value={ phone } onChange={ (event) => { this._handle_onChangeField("phone", event.target.value) } }/>
</div>
<div className="form_field">
<input type="email" name="name" placeholder="Email" />
<input type="email" name="email" placeholder="Email" value={ email } onChange={ (event) => { this._handle_onChangeField("email", event.target.value) } }/>
</div>
<div className="form_field">
<textarea placeholder="Введите текст запроса" style={{ resize: "none" }}></textarea>
<textarea placeholder="Введите текст запроса" style={{ resize: "none" }} value={ question } onChange={ (event) => { this._handle_onChangeField("question", event.target.value) } }/>
</div>
<FileDropzone files={ files } onAddFile={ this._handle_onAddFile } onDeleteFile={ this._handle_onDeleteFile }/>
<div className="form_field" style={{ paddingTop: 24, display: "flex", justifyContent: "flex-end", alignItems: "flex-end" }}>
<button className="button button-blue" disabled={ this._checkDisabled() ? true : false }>Отправить</button>
</div>
</form>
)
}