fixes for support pages, admin panel

This commit is contained in:
merelendor 2022-08-10 11:40:49 +03:00
parent 6502705f3f
commit 93f3e46730
18 changed files with 1005 additions and 497 deletions

View File

@ -42,10 +42,13 @@ export const getCalendar = ({ dispatch, date_from, date_to }) =>
let payments = [];
for(let i in contract.payments)
{
const mdate = moment(contract.payments[i].date, "DD-MM-YYYY");
periods[mdate.format('YYYYMM')] = mdate.format('YYYYMM');
payments.push({ ...contract.payments[i], ...{ contract: { number: contract.number, date: contract.date }, date: mdate.format("YYYY-MM-DD"), js_date: mdate.toDate() } });
if(contract.payments[i].date !== null)
{
const mdate = moment(contract.payments[i].date, "DD-MM-YYYY");
periods[mdate.format('YYYYMM')] = mdate.format('YYYYMM');
payments.push({ ...contract.payments[i], ...{ contract: { number: contract.number, date: contract.date }, date: mdate.format("YYYY-MM-DD"), js_date: mdate.toDate() } });
}
}
let query = nSQL(payments)

View File

@ -19,6 +19,59 @@ if(process.browser)
};
}
export const setAppealsRead = ({ dispatch, appeals }) =>
{
return new Promise((resolve, reject) =>
{
axios.post(`${ process.env.NEXT_PUBLIC_SELF_API_HOST }/api/support/read`, { appeals }, {
withCredentials: true,
})
.then((response) =>
{
dispatch({ type: actionTypes.SUPPORT_APPEALS, data: { appeals: { new: 0, } } });
resolve();
})
.catch((error) =>
{
console.log("error");
console.error(error);
reject();
});
});
}
export const getAppeals = ({ dispatch }) =>
{
console.log("ACTION", "support", "getAppeals", `${ process.env.NEXT_PUBLIC_SELF_API_HOST }/api/support/appeals`);
return new Promise((resolve, reject) =>
{
axios.post(`${ process.env.NEXT_PUBLIC_SELF_API_HOST }/api/support/appeals`, {}, {
withCredentials: true,
})
.then((response) =>
{
console.log("getEvents", "response", response.data);
const events = response.data;
const filtered_events = [];
console.log("events");
console.log(events);
dispatch({ type: actionTypes.SUPPORT_APPEALS, data: { appeals: { list: response.data.appeals, new: response.data.new, } } });
resolve();
})
.catch((error) =>
{
console.log("error");
console.error(error);
reject();
});
});
}
export const getSupportThemes = ({ dispatch, query, }) =>
{
console.log("ACTION", "support", "getSupportThemes", { query });

View File

@ -0,0 +1,169 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import axios from 'axios';
import { Cookies } from 'react-cookie';
import cookie from 'cookie';
import moment from 'moment';
import jwt from 'jsonwebtoken';
import Redis from 'ioredis';
import { cors } from '../../../lib/cors';
const redis = new Redis(process.env.REDIS_URL);
export default async function handler(req, res)
{
await cors(req, res);
console.log("API", "support", "appeals", req.headers);
if(req.headers.cookie !== undefined)
{
const cookies = cookie.parse(req.headers?.cookie ? req.headers?.cookie : "");
if(cookies.jwt !== undefined && cookies.jwt !== null)
{
var client_jwt_decoded = jwt.verify(cookies.jwt, process.env.JWT_SECRET_CLIENT);
var crm_jwt = jwt.sign(client_jwt_decoded, process.env.JWT_SECRET_CRM, { noTimestamp: true });
console.log("client_jwt_decoded");
console.log(client_jwt_decoded);
const read = await redis.keys(`${ client_jwt_decoded.acc_number }_appeal_*`);
console.log("API", "support", "appeals", "read", read);
try
{
await axios.get(`${ process.env.CRM_API_HOST }/lk/Account/GetIncidents/`, {
params: client_jwt_decoded,
headers: {
"Authorization": `Bearer ${ crm_jwt }`,
}
})
.then((crm_response) =>
{
let unread = 0;
console.log("API", "support", "appeals", "response", crm_response.data);
const appeals = [
{
number: "CAS-02991-J2M9B8",
created_date: "2021-09-06",
subject: "Вопросы по пени и штрафам",
owner: "Анастасия Ломакина",
status: "cancelled",
answer: "",
contracts: "No2021_1889, No2021_2453, No2021_2465, No2021_2857",
documents: [
{
doc_name: "Письмо от ЛП",
doc_url: "24bfe404-9905-4b69-8898-09a7229a8fc1",
doc_extension: "pdf",
},
{
doc_name: "Пояснения СБ",
doc_url: "24bfe404-9905-4b69-8898-09a7229a8fc1",
doc_extension: "pdf",
},
],
},
{
number: "CAS-01779-S2Q3Q6",
created_date: "2021-06-22",
subject: "Изменение графика",
owner: "Анастасия Ломакина",
status: "active",
answer: "",
contracts: "No2021_1889, No2021_1891, No2021_1914, No2021_2453, No2021_3023, No2021_7349, No2021_9969, No2021_125 95",
documents: [
{
doc_name: "Запрос на выезд за границу",
doc_url: "4291c8b0-2aff-47a1-b246-fc8e5e196c24",
doc_extension: "pdf",
},
{
doc_name: "согласование УЭБ",
doc_url: "4291c8b0-2aff-47a1-b246-fc8e5e196c24",
doc_extension: "pdf",
},
],
},
{
number: "CAS-01691-L2Z7H2",
created_date: "2021-06-08",
subject: "Выдача документов по сделке",
owner: "Анастасия Ломакина",
status: "closed",
answer: "",
contracts: "No2021_1889, No2021_3023, No2021_7349, No2021_9969, No2021_12595",
documents: [
{
doc_name: "Доп. соглашение по КАСКО",
doc_url: "7e11a7b0-0246-44ea-8f92-65698b79ea36",
doc_extension: "pdf",
},
{
doc_name: "Запрос на выезд за границу",
doc_url: "7e11a7b0-0246-44ea-8f92-65698b79ea36",
doc_extension: "pdf",
},
{
doc_name: "письмо о назначении платежей",
doc_url: "7e11a7b0-0246-44ea-8f92-65698b79ea36",
doc_extension: "pdf",
},
{
doc_name: "ПП по платежу за июнь",
doc_url: "7e11a7b0-0246-44ea-8f92-65698b79ea36",
doc_extension: "pdf",
},
{
doc_name: "ПП по расширению страхового покрытия",
doc_url: "7e11a7b0-0246-44ea-8f92-65698b79ea36",
doc_extension: "pdf",
}
],
}
];
for(let i in appeals)
{
const key = `${ client_jwt_decoded.acc_number }_appeal_${ appeals[i].number }_${ appeals[i].status }`;
console.log("LOOK", key);
if(read.indexOf(key) > -1)
{
appeals[i].read = true;
}
else
{
unread++;
}
}
//res.status(200).json(crm_response.data);
res.status(200).json({
new: unread,
appeals
});
})
.catch((error) =>
{
console.error(error);
res.status(500).send();
});
}
catch(e)
{
console.error(e);
res.status(500).send();
}
}
else
{
res.status(403).send();
}
}
else
{
res.status(403).send();
}
}

53
pages/api/support/read.js Normal file
View File

@ -0,0 +1,53 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import axios from 'axios';
import { Cookies } from 'react-cookie';
import cookie from 'cookie';
import moment from 'moment';
import jwt from 'jsonwebtoken';
import Redis from 'ioredis';
import md5 from 'md5';
import { cors } from '../../../lib/cors';
const redis = new Redis(process.env.REDIS_URL);
export default async function handler(req, res)
{
await cors(req, res);
let { appeals } = req.body;
if(req.headers.cookie !== undefined)
{
const cookies = cookie.parse(req.headers?.cookie ? req.headers?.cookie : "");
if(cookies.jwt !== undefined && cookies.jwt !== null)
{
var client_jwt_decoded = jwt.verify(cookies.jwt, process.env.JWT_SECRET_CLIENT);
var crm_jwt = jwt.sign(client_jwt_decoded, process.env.JWT_SECRET_CRM, { noTimestamp: true });
console.log("client_jwt_decoded");
console.log(client_jwt_decoded);
try
{
for(let i in appeals)
{
const key = `${ client_jwt_decoded.acc_number }_appeal_${ appeals[i].number }_${ appeals[i].status }`;
console.log("API", "support", "read", "key", key);
await redis.set(key, appeals[i].status);
}
}
catch(exception)
{
console.error("EXCEPTION", "API", "support", "read");
console.error(exception);
}
}
}
res.status(200).json({
status: "success",
});
}

View File

@ -122,6 +122,30 @@ export default class NotificationMessage extends React.Component
</li>
)
}
case "return_pts":
{
return (
<li className="new">
{ event.important ? (
<p className="name"><b>Внимание! Просрочена дата возврата СТС по договору { event.contract_number }. Посмотрите порядок возврата СТС или загрузите скан СТС по ссылке.</b></p>
) : (
<p className="name"><b>Приближается дата возврата СТС по договору { event.contract_number }. Посмотрите порядок возврата СТС или загрузите скан СТС по ссылке.</b></p>
) }
{ event.important && (<p className="type">Важное</p>) }
<p className="date">{ event.event_date }</p>
<p className="action">
<Link href="/support/faq">
<a>Посмотрите порядок возврата СТС</a>
</Link>
&nbsp; или &nbsp;
<Link href="/support/request">
<a>Загрузите скан СТС <span style={{ color: "red" }}>(какой раздел FAQ ?)</span></a>
</Link>
</p>
</li>
)
}
}
return null;

View File

@ -2,7 +2,7 @@ import React from "react";
import Link from "next/link";
import { connect } from "react-redux";
import { logout, getEvents } from "../../../actions";
import { logout, getEvents, getAppeals } from "../../../actions";
import NotificationsList from "./NotificationsList";
class Header extends React.Component
@ -17,6 +17,7 @@ class Header extends React.Component
messagesOpened: false,
events: [],
events_loaded: false,
appeals: 0,
};
}
@ -24,7 +25,8 @@ class Header extends React.Component
{
return {
observer: nextProps.observer,
events: nextProps.events,
events: nextProps.events,
appeals: nextProps.appeals,
};
}
@ -32,6 +34,8 @@ class Header extends React.Component
{
console.log("Header", "CDM");
getEvents({ dispatch: this.props.dispatch });
console.log("Header", "CDM", "222222");
getAppeals({ dispatch: this.props.dispatch });
}
componentDidUpdate(prevProps, prevState)
@ -138,7 +142,7 @@ class Header extends React.Component
render()
{
const { observer, menuOpened, notificationsOpened, messagesOpened, events, events_loaded } = this.state;
const { observer, menuOpened, notificationsOpened, messagesOpened, events, events_loaded, appeals } = this.state;
console.log("events", events);
return (
@ -187,13 +191,11 @@ class Header extends React.Component
</a>
</Link>
</li>
{/*}
<li>
<Link href="/support/faq/" shallow>
<a className={ this.props.router && this.props.router.route.indexOf("/support") === 0 ? "active" : "" }>Обращения</a>
</Link>
</li>
{*/}
{/*
<li>
<Link href={ process.env.NEXT_PUBLIC_MAIN_SITE }>
@ -227,15 +229,13 @@ class Header extends React.Component
</div>
</div>
</li>
{/*}
<li>
<Link href="/support/" shallow>
<a data-icon="message" data-notify="0" className="button" onClick={ this._handle_onToggleMessages }>
<Link href={ `/support/appeals` } shallow>
<a data-icon="message" data-notify={ appeals } className="button" onClick={ this._handle_onToggleMessages }>
Сообщения
</a>
</Link>
</li>
{*/}
<li>
<button
className="lk"
@ -255,6 +255,7 @@ function mapStateToProps(state, ownProps)
return {
observer: state.auth.observer,
events: state.events.list,
appeals: state.support.appeals.new,
}
}

View File

@ -1,4 +1,5 @@
import React from "react";
import * as ReactDOM from 'react-dom';
import Head from 'next/head';
import Image from 'next/image';
import Link from "next/link";
@ -25,7 +26,6 @@ class IndexPage extends React.Component
{
super(props);
this.state = {
user: {},
};
}
@ -37,11 +37,41 @@ class IndexPage extends React.Component
};
}
componentDidMount()
{
ReactDOM.findDOMNode(this).parentNode.style.height = "100%";
ReactDOM.findDOMNode(this).parentNode.style.display = "flex";
ReactDOM.findDOMNode(this).parentNode.style.flexDirection = "column";
ReactDOM.findDOMNode(this).parentNode.style.justifyContent = "spaceBetween";
ReactDOM.findDOMNode(this).parentNode.style.alignItems = "stretch";
document.documentElement.style.height = "100%";
document.documentElement.style.display = "flex";
document.documentElement.style.flexDirection = "column";
document.body.style.height = "100%";
document.body.style.display = "flex";
document.body.style.flexDirection = "column";
}
componentWillUnmount()
{
ReactDOM.findDOMNode(this).parentNode.style.height = "unset";
ReactDOM.findDOMNode(this).parentNode.style.display = "unset";
ReactDOM.findDOMNode(this).parentNode.style.flexDirection = "unset";
ReactDOM.findDOMNode(this).parentNode.style.justifyContent = "unset";
ReactDOM.findDOMNode(this).parentNode.style.alignItems = "unset";
document.documentElement.style.height = "unset";
document.documentElement.style.display = "unset";
document.documentElement.style.flexDirection = "unset";
document.body.style.height = "unset";
document.body.style.display = "unset";
document.body.style.flexDirection = "unset";
}
render()
{
const { user } = this.state;
return (
<React.Fragment>
@ -53,7 +83,7 @@ class IndexPage extends React.Component
/>
</Head>
<Header { ...this.props }/>
<main>
<main style={{ flex: 1, display: "flex", flexDirection: "column" }}>
<section>
<div className="clear"></div>
<div className="container">
@ -68,13 +98,11 @@ class IndexPage extends React.Component
<article>
<div className="settings_user_control">
<p>Настройки доступа к личному кабинету</p>
<div>
<button className="button button-blue">Добавить пользователя</button>
<button className="button button-blue">Редактировать</button>
</div>
</div>
<div className="settings_table editable">
<div className="table_header table_row">
<div className="table_cell">ФИО пользователя</div>
@ -84,26 +112,26 @@ class IndexPage extends React.Component
<div className="table_cell">Статус</div>
<div className="table_cell"></div>
</div>
{ user !== undefined && user !== null && user.email !== undefined && user.email !== null && (
<div className="table_row">
<div className="table_cell" data-title="ФИО пользователя">{ `${ user.lastname } ${ user.name } ${ user.secondname }` }</div>
<div className="table_cell" data-title="Почта">{ user.email }</div>
<div className="table_cell" data-title="Роль">Администратор</div>
<div className="table_cell" data-title="Доступные организации">Все организации</div>
<div className="table_cell" data-title="Статус">Активен</div>
<div className="table_cell delete" style={{ position: 'relative' }}>
</div>
</div>
) }
{/*}
<div className="table_row">
<div className="table_cell" data-title="ФИО пользователя">Иванов Иван Иванович</div>
<div className="table_cell" data-title="Почта">iivanov@mail.com</div>
<div className="table_cell" data-title="Роль">Администратор</div>
<div className="table_cell" data-title="Доступные организации">Все организации</div>
<div className="table_cell" data-title="Статус">Активен</div>
<div className="table_cell delete">
<button className="delete_user" title="Удалить пользователя">Удалить</button>
</div>
</div>
<div className="table_row">
<div className="table_cell" data-title="ФИО пользователя">Иванов Иван Иванович</div>
<div className="table_cell" data-title="Почта">iivanov@mail.com</div>
<div className="table_cell" data-title="Роль">Администратор</div>
<div className="table_cell" data-title="Доступные организации">Все организации</div>
<div className="table_cell" data-title="Статус">Активен</div>
<div className="table_cell delete">
<button className="delete_user" title="Удалить пользователя">Удалить</button>
<div className="table_cell delete" style={{ position: 'relative' }}>
<button className="delete_user" title="Удалить пользователя"></button>
</div>
</div>
<div className="table_row editable">
@ -140,10 +168,11 @@ class IndexPage extends React.Component
</div>
</div>
<div className="table_cell" data-title="Статус">-</div>
<div className="table_cell delete">
<button className="delete_user" title="Удалить пользователя">Удалить</button>
<div className="table_cell delete" style={{ position: 'relative' }}>
<button className="delete_user" title="Удалить пользователя"></button>
</div>
</div>
{*/}
</div>
</article>
</div>

View File

@ -4,6 +4,7 @@ import Link from "next/link";
const menu = [
{id: 1, name: "Номер телефона", link: "/settings/phone"},
{id: 2, name: "Пароль", link: "/settings/password"},
{id: 3, name: "Настройки доступа", link: "/settings/admin"},
]
export default class InnerMenu extends React.Component

View File

@ -1,4 +1,5 @@
import React from "react";
import * as ReactDOM from 'react-dom';
import Head from 'next/head';
import Image from 'next/image';
import Link from "next/link";
@ -43,6 +44,38 @@ class IndexPage extends React.Component
};
}
componentDidMount()
{
ReactDOM.findDOMNode(this).parentNode.style.height = "100%";
ReactDOM.findDOMNode(this).parentNode.style.display = "flex";
ReactDOM.findDOMNode(this).parentNode.style.flexDirection = "column";
ReactDOM.findDOMNode(this).parentNode.style.justifyContent = "spaceBetween";
ReactDOM.findDOMNode(this).parentNode.style.alignItems = "stretch";
document.documentElement.style.height = "100%";
document.documentElement.style.display = "flex";
document.documentElement.style.flexDirection = "column";
document.body.style.height = "100%";
document.body.style.display = "flex";
document.body.style.flexDirection = "column";
}
componentWillUnmount()
{
ReactDOM.findDOMNode(this).parentNode.style.height = "unset";
ReactDOM.findDOMNode(this).parentNode.style.display = "unset";
ReactDOM.findDOMNode(this).parentNode.style.flexDirection = "unset";
ReactDOM.findDOMNode(this).parentNode.style.justifyContent = "unset";
ReactDOM.findDOMNode(this).parentNode.style.alignItems = "unset";
document.documentElement.style.height = "unset";
document.documentElement.style.display = "unset";
document.documentElement.style.flexDirection = "unset";
document.body.style.height = "unset";
document.body.style.display = "unset";
document.body.style.flexDirection = "unset";
}
_handle_onPasswordSubmit = (event) =>
{
event.preventDefault();
@ -130,7 +163,7 @@ class IndexPage extends React.Component
/>
</Head>
<Header { ...this.props }/>
<main>
<main style={{ flex: 1, display: "flex", flexDirection: "column" }}>
<section>
<div className="clear"></div>
<div className="container">

View File

@ -1,4 +1,5 @@
import React from "react";
import * as ReactDOM from 'react-dom';
import Head from 'next/head';
import Image from 'next/image';
import Link from "next/link";
@ -49,6 +50,38 @@ class IndexPage extends React.Component
};
}
componentDidMount()
{
ReactDOM.findDOMNode(this).parentNode.style.height = "100%";
ReactDOM.findDOMNode(this).parentNode.style.display = "flex";
ReactDOM.findDOMNode(this).parentNode.style.flexDirection = "column";
ReactDOM.findDOMNode(this).parentNode.style.justifyContent = "spaceBetween";
ReactDOM.findDOMNode(this).parentNode.style.alignItems = "stretch";
document.documentElement.style.height = "100%";
document.documentElement.style.display = "flex";
document.documentElement.style.flexDirection = "column";
document.body.style.height = "100%";
document.body.style.display = "flex";
document.body.style.flexDirection = "column";
}
componentWillUnmount()
{
ReactDOM.findDOMNode(this).parentNode.style.height = "unset";
ReactDOM.findDOMNode(this).parentNode.style.display = "unset";
ReactDOM.findDOMNode(this).parentNode.style.flexDirection = "unset";
ReactDOM.findDOMNode(this).parentNode.style.justifyContent = "unset";
ReactDOM.findDOMNode(this).parentNode.style.alignItems = "unset";
document.documentElement.style.height = "unset";
document.documentElement.style.display = "unset";
document.documentElement.style.flexDirection = "unset";
document.body.style.height = "unset";
document.body.style.display = "unset";
document.body.style.flexDirection = "unset";
}
_handle_onPhoneSubmit = (event) =>
{
event.preventDefault();
@ -161,7 +194,7 @@ class IndexPage extends React.Component
/>
</Head>
<Header { ...this.props }/>
<main>
<main style={{ flex: 1, display: "flex", flexDirection: "column" }}>
<section>
<div className="clear"></div>
<div className="container">

252
pages/support/appeals.js Normal file
View File

@ -0,0 +1,252 @@
import React from "react";
import * as ReactDOM from 'react-dom';
import Head from "next/head";
import Image from "next/image";
import { connect } from "react-redux";
import { withRouter } from "next/router";
import moment from "moment";
import { SpinnerCircular } from "spinners-react";
import { reduxWrapper } from "../../store";
import Header from "../components/Header";
import Footer from "../components/Footer";
import Company from "../components/Company";
import InnerMenu from "./components/InnerMenu";
import {
getContractInfo,
getContractAgreement,
getContractRules,
getFile,
setAppealsRead,
} from "../../actions";
class SupportAppealsPage extends React.Component
{
constructor(props)
{
super(props);
this.state = {
appeals: null,
loading: false,
};
}
static getDerivedStateFromProps(nextProps, prevState)
{
return {
appeals: nextProps.appeals,
};
}
componentDidMount()
{
ReactDOM.findDOMNode(this).parentNode.style.height = "100%";
ReactDOM.findDOMNode(this).parentNode.style.display = "flex";
ReactDOM.findDOMNode(this).parentNode.style.flexDirection = "column";
ReactDOM.findDOMNode(this).parentNode.style.justifyContent = "spaceBetween";
ReactDOM.findDOMNode(this).parentNode.style.alignItems = "stretch";
document.documentElement.style.height = "100%";
document.documentElement.style.display = "flex";
document.documentElement.style.flexDirection = "column";
document.body.style.height = "100%";
document.body.style.display = "flex";
document.body.style.flexDirection = "column";
setTimeout(() =>
{
const appeals = [];
if(this.state.appeals !== undefined && this.state.appeals !== null && this.state.appeals.list !== undefined && this.state.appeals.list !== null)
{
for(let i in this.state.appeals.list)
{
if(this.state.appeals.list[i].read !== true)
{
appeals.push({ number: this.state.appeals.list[i].number, status: this.state.appeals.list[i].status });
}
}
}
console.log("appeals");
console.log(appeals);
setAppealsRead({ dispatch: this.props.dispatch, appeals });
}, 1000);
}
componentWillUnmount()
{
ReactDOM.findDOMNode(this).parentNode.style.height = "unset";
ReactDOM.findDOMNode(this).parentNode.style.display = "unset";
ReactDOM.findDOMNode(this).parentNode.style.flexDirection = "unset";
ReactDOM.findDOMNode(this).parentNode.style.justifyContent = "unset";
ReactDOM.findDOMNode(this).parentNode.style.alignItems = "unset";
document.documentElement.style.height = "unset";
document.documentElement.style.display = "unset";
document.documentElement.style.flexDirection = "unset";
document.body.style.height = "unset";
document.body.style.display = "unset";
document.body.style.flexDirection = "unset";
}
render()
{
const { loading, appeals } = this.state;
const { number } = this.props;
const status = {
active: { title: "Активное", color: "#04A8A4" },
closed: { title: "Закрыто", color: "#000", },
cancelled: { title: "Отменено", color: "#A8026B", }
};
console.log(appeals);
return (
<React.Fragment>
<Head>
<title>ЛК Эволюция автолизинга</title>
<meta name="description" content="ЛК Эволюция автолизинга" />
</Head>
<Header { ...this.props } />
<main style={{ flex: 1, display: "flex", flexDirection: "column" }}>
<section>
<div className="clear"></div>
<div className="container">
<div className="title_wrapper">
<div className="left" style={{ flexDirection: "column" }}>
<h1 className="section_title">Мои обращения</h1>
</div>
<Company />
</div>
<div className="aside_container about">
<InnerMenu { ...this.props } />
<article>
<div className="appeal_list">
{ appeals !== undefined && appeals !== null && appeals.list !== undefined && appeals.list !== null && (
<>
{ appeals.list.length > 0 ? (
<>
{ appeals.list.map((appeal, index) =>
(
<div className="appeal_item" key={ index }>
<div className="item_header">
<p>Номер обращения: <b>{ appeal.number } от { moment(appeal.created_date, "YYYY-MM-DD").format("DD.MM.YYYY") }</b></p>
<p>Отвественный ОРК: <b>{ appeal.owner }</b></p>
<p>Договор(ы): <b>{ appeal.contracts }</b></p>
<div className="status" style={{ backgroundColor: status[appeal.status].color }}>{ status[appeal.status].title }</div>
</div>
<div className="item_body">
<div className="item_text">
<p><b>Тема запроса</b></p>
<p>{ appeal.subject }</p>
</div>
<div className="item_text">
<p><b>Текст ответа ОРК</b></p>
<p>{ appeal.answer === "" ? "Без ответа" : appeal.answer }</p>
{ appeal.documents.length > 0 && (
<div className="dosc_list medium-icon">
{ appeal.documents.map((document, document_index) =>
(
<div className="row" key={ document_index }>
<p className="doc_name i-pdf extension" data-format={ document.doc_extension }>
{ document.doc_name }
<span>Скачать документ</span>
</p>
</div>
)) }
</div>
) }
</div>
</div>
</div>
)) }
</>
) : (
<p>У Вас пока нет обращений.</p>
) }
</>
)}
{/*}
<div className="appeal_item">
<div className="item_header">
<p>Номер обращения: <b>123 от 13.04.2022</b></p>
<p>Отвественный ОРК: <b>Иванов И.И.</b></p>
<p>Договор: <b>2021_1655, 2021_1655</b></p>
<div className="status">Активное</div>
</div>
<div className="item_body">
<div className="item_text">
<p><b>Тема запроса</b></p>
<p>Текстовый контент</p>
</div>
<div className="item_text">
<p><b>Текст ответа ОРК</b></p>
<p>Текстовый контент</p>
</div>
</div>
</div>
<div className="appeal_item">
<div className="item_header">
<p>Номер обращения: <b>123 от 13.04.2022</b></p>
<p>Отвественный ОРК: <b>Иванов И.И.</b></p>
<p>Договор: <b>2021_1655, 2021_1655</b></p>
</div>
<div className="item_body">
<div className="item_text">
<p><b>Тема запроса</b></p>
<p>Текстовый контент</p>
</div>
<div className="item_text">
<p><b>Текст ответа ОРК</b></p>
<p>Текстовый контент</p>
<div className="dosc_list medium-icon">
<div className="row">
<p className="doc_name i-pdf extension">
2021_1655 от 20.04.2021
<span>2021_1655 от 20.04.2021</span>
</p>
</div>
<div className="row">
<p className="doc_name i-pdf extension">
2021_1655 от 20.04.2021
<span>2021_1655 от 20.04.2021</span>
</p>
</div>
</div>
</div>
</div>
</div>
{*/}
</div>
</article>
</div>
</div>
</section>
</main>
<Footer />
</React.Fragment>
);
}
}
function mapStateToProps(state, ownProps)
{
return {
appeals: state.support.appeals,
};
}
export const getServerSideProps = reduxWrapper.getServerSideProps(
(store) =>
async ({ req, res, query }) => {
return {
props: {
},
};
}
);
export default withRouter(connect(mapStateToProps)(SupportAppealsPage));

View File

@ -1,14 +1,26 @@
import React from "react";
import Link from "next/link";
import { connect } from "react-redux";
export default class InnerMenu extends React.Component
class InnerMenu extends React.Component
{
constructor(props)
{
super(props);
this.state = {
appeals: 0,
}
this.menuRef = React.createRef();
}
static getDerivedStateFromProps(nextProps, prevState)
{
return {
appeals: nextProps.appeals,
};
}
componentDidMount()
{
let l = 0;
@ -33,26 +45,25 @@ export default class InnerMenu extends React.Component
_handle_onNewAppeal = () =>
{
this.props.router.push('/support/new/');
this.props.router.push('/support/request/');
}
render()
{
const { appeals } = this.state;
const { number } = this.props;
return (
<aside className="flex">
<button className="nav_toggle">
Меню
</button>
<button className="nav_toggle">Меню</button>
<ul className="aside_nav" ref={ this.menuRef }>
<li>
<Link href={`/support/faq`} shallow>
<a className={ this.props.router && this.props.router.asPath.indexOf("faq") > -1 ? "active" : "" }>FAQ</a></Link>
</li>
<li>
<Link href={`/support/messages`} shallow>
<a className={ this.props.router && this.props.router.asPath.indexOf("messages") > -1 ? "active" : "" }>Мои обращения <span>3</span></a>
<Link href={`/support/appeals`} shallow>
<a className={ this.props.router && this.props.router.asPath.indexOf("appeals") > -1 ? "active" : "" }>Мои обращения { appeals > 0 && (<span>{ appeals }</span> ) }</a>
</Link>
</li>
</ul>
@ -60,4 +71,13 @@ export default class InnerMenu extends React.Component
</aside>
)
}
}
}
function mapStateToProps(state, ownProps)
{
return {
appeals: state.support.appeals.new,
}
}
export default connect(mapStateToProps)(InnerMenu);

View File

@ -15,10 +15,6 @@ import Company from "../components/Company";
import InnerMenu from "./components/InnerMenu";
import {
getContractInfo,
getContractAgreement,
getContractRules,
getFile,
getSupportThemes,
} from "../../actions";
@ -34,7 +30,7 @@ class ContractPage extends React.Component
appeal: null,
searched: null,
loading: false,
search: "",
query: "",
};
}
@ -61,7 +57,7 @@ class ContractPage extends React.Component
document.documentElement.style.flexDirection = "column";
document.body.style.height = "100%";
document.body.style.display = "flex";
document.body.style.flexDirection = "column";
document.body.style.flexDirection = "column";
if (!this.state.loading)
{
@ -105,12 +101,19 @@ class ContractPage extends React.Component
}
}
_handle_onRequest = (question) =>
{
this.props.router.push(`/support/request#${ question }`);
}
_handle_onChangeSearchQuery = (value) =>
{
this.setState({ query: value });
}
render()
{
const { loading, themes, question_selected, searched, appleals, search } = this.state;
console.log("themesthemesthemesthemesthemes");
console.log(themes);
const { loading, themes, question_selected, searched, appleals, query } = this.state;
return (
<React.Fragment>
@ -141,10 +144,10 @@ class ContractPage extends React.Component
<div className="contract_search">
<form onSubmit={(event) => { event.preventDefault(); }}>
<div className="form_field full">
<input type="search" value={ search } placeholder="Поиск" onChange={(event) => { this._handle_onChange_search(event.target.value);} }
<input type="search" value={ query } placeholder="Поиск" onChange={(event) => { this._handle_onChangeSearchQuery(event.target.value);} }
/>
</div>
<button className="button" disabled={ search !== "" ? false : true } onClick={ this._handle_onSearch }>
<button className="button" disabled={ query !== "" ? false : true } onClick={ this._handle_onSearch }>
Поиск
</button>
</form>
@ -162,7 +165,7 @@ class ContractPage extends React.Component
<p>{ question.title }</p>
<button></button>
</div>
<div className="block_body">
<div className="block_body" style={{ paddingBottom: "20px" }}>
<div className="column full">
<div className="column_text_block">
<p><b>Процедура</b></p>
@ -187,7 +190,7 @@ class ContractPage extends React.Component
</div>
</div>
) }
<button className="button button-blue">Создать обращение</button>
<button className="button button-blue" onClick={ () => this._handle_onRequest(question.id) }>Создать обращение</button>
</div>
</div>
</div>

View File

@ -1,217 +0,0 @@
import React from "react";
import Head from "next/head";
import Image from "next/image";
import { connect } from "react-redux";
import { withRouter } from "next/router";
import moment from "moment";
import { SpinnerCircular } from "spinners-react";
import { reduxWrapper } from "../../store";
import Header from "../components/Header";
import Footer from "../components/Footer";
import Company from "../components/Company";
import InnerMenu from "./components/InnerMenu";
import {
getContractInfo,
getContractAgreement,
getContractRules,
getFile,
} from "../../actions";
class ContractPage extends React.Component
{
constructor(props)
{
super(props);
this.state = {
date: null,
car: null,
contract_date: null,
agreement: null,
rules: null,
loading: false,
};
}
static getDerivedStateFromProps(nextProps, prevState)
{
return {
date: nextProps.date,
car: nextProps.car,
contract_date: nextProps.contract_date,
agreement: nextProps.agreement,
rules: nextProps.rules,
};
}
componentDidMount() {}
render()
{
const { loading, date, car, contract_date, agreement, rules } = this.state;
const { number } = this.props;
return (
<React.Fragment>
<Head>
<title>ЛК Эволюция автолизинга</title>
<meta name="description" content="ЛК Эволюция автолизинга" />
</Head>
<Header { ...this.props } />
<main>
<section>
<div className="clear"></div>
<div className="container">
<div className="title_wrapper">
<div className="left" style={{ flexDirection: "column" }}>
<h1 className="section_title">Мои обращения</h1>
</div>
<Company />
</div>
<div className="aside_container about">
<InnerMenu number={number} {...this.props} />
<article>
<div className="appeal_list">
<div className="appeal_item">
<div className="item_header">
<p>
Номер обращения: <b>123 от 13.04.2022</b>
</p>
<p>
Отвественный ОРК: <b>Иванов И.И.</b>
</p>
<p>
Договор: <b>2021_1655, 2021_1655</b>
</p>
<div className="status">Активное</div>
</div>
<div className="item_body">
<div className="item_text">
<p>
<b>Тема запроса</b>
</p>
<p>
Текстовый контент Текстовый контент Текстовый
контент Текстовый контент Текстовый контент
Текстовый контент Текстовый контент Текстовый
контент Текстовый контент Текстовый контент
Текстовый контент Текстовый контент Текстовый
контент Текстовый контент Текстовый контент
Текстовый контент Текстовый контент Текстовый
контент Текстовый контент
</p>
</div>
<div className="item_text">
<p>
<b>Текст ответа ОРК</b>
</p>
<p>
Текстовый контент Текстовый контент Текстовый
контент Текстовый контент Текстовый контент
Текстовый контент Текстовый контент Текстовый
контент Текстовый контент Текстовый контент
Текстовый контент Текстовый контент Текстовый
контент Текстовый контент Текстовый контент
Текстовый контент Текстовый контент Текстовый
контент Текстовый контент
</p>
</div>
</div>
</div>
<div className="appeal_item">
<div className="item_header">
<p>
Номер обращения: <b>123 от 13.04.2022</b>
</p>
<p>
Отвественный ОРК: <b>Иванов И.И.</b>
</p>
<p>
Договор: <b>2021_1655, 2021_1655</b>
</p>
</div>
<div className="item_body">
<div className="item_text">
<p>
<b>Тема запроса</b>
</p>
<p>
Текстовый контент Текстовый контент Текстовый
контент Текстовый контент Текстовый контент
Текстовый контент Текстовый контент Текстовый
контент Текстовый контент Текстовый контент
Текстовый контент Текстовый контент Текстовый
контент Текстовый контент Текстовый контент
Текстовый контент Текстовый контент Текстовый
контент Текстовый контент
</p>
</div>
<div className="item_text">
<p>
<b>Текст ответа ОРК</b>
</p>
<p>
Текстовый контент Текстовый контент Текстовый
контент Текстовый контент Текстовый контент
Текстовый контент Текстовый контент Текстовый
контент Текстовый контент Текстовый контент
Текстовый контент Текстовый контент Текстовый
контент Текстовый контент Текстовый контент
Текстовый контент Текстовый контент Текстовый
контент Текстовый контент
</p>
<div className="dosc_list medium-icon">
<div className="row">
<p className="doc_name i-pdf extension">
2021_1655 от 20.04.2021
<span>2021_1655 от 20.04.2021</span>
</p>
</div>
<div className="row">
<p className="doc_name i-pdf extension">
2021_1655 от 20.04.2021
<span>2021_1655 от 20.04.2021</span>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</article>
</div>
</div>
</section>
</main>
<Footer />
</React.Fragment>
);
}
}
function mapStateToProps(state, ownProps)
{
return {
contract_date: state.contract.date,
date: state.contract.date,
car: state.contract.car,
agreement: state.contract.agreement,
rules: state.contract.rules,
};
}
export const getServerSideProps = reduxWrapper.getServerSideProps(
(store) =>
async ({ req, res, query }) => {
return {
props: {
//number: query.number,
number: null,
},
};
}
);
export default withRouter(connect(mapStateToProps)(ContractPage));

View File

@ -1,219 +0,0 @@
import React from "react";
import Head from "next/head";
import Image from "next/image";
import { connect } from "react-redux";
import { withRouter } from "next/router";
import moment from "moment";
import { SpinnerCircular } from "spinners-react";
import { reduxWrapper } from "../../store";
import Header from "../components/Header";
import Footer from "../components/Footer";
import Company from "../components/Company";
import InnerMenu from "./components/InnerMenu";
import {
getContractInfo,
getContractAgreement,
getContractRules,
getFile,
} from "../../actions";
class ContractPage extends React.Component
{
constructor(props) {
super(props);
this.state = {
date: null,
car: null,
contract_date: null,
agreement: null,
rules: null,
loading: false,
};
}
static getDerivedStateFromProps(nextProps, prevState)
{
return {
date: nextProps.date,
car: nextProps.car,
contract_date: nextProps.contract_date,
agreement: nextProps.agreement,
rules: nextProps.rules,
};
}
componentDidMount() {}
_handle_onBack = () =>
{
this.props.router.push('/support/faq/');
}
render()
{
const { loading, date, car, contract_date, agreement, rules } = this.state;
const { number } = this.props;
return (
<React.Fragment>
<Head>
<title>ЛК Эволюция автолизинга</title>
<meta name="description" content="ЛК Эволюция автолизинга" />
</Head>
<Header { ...this.props } />
<main>
<section>
<div className="clear"></div>
<div className="container">
<div className="title_wrapper">
<div className="left" style={{ alignItems: "center", flexWrap: "wrap" }}>
<button className="back" onClick={ this._handle_onBack }>Назад</button>
<h1 className="section_title">Новое обращение</h1>
</div>
<Company />
</div>
<div className="aside_container about">
<article className="full">
<div className="new_appeal">
<div className="column">
<div className="dropdown_blocks_list appeal_list visible">
<div className="appeal_item dropdown_block">
<div className="block_header">
<p>
Процедура досрочного прекращения срока действия
договора лизинга
</p>
<button className="rotate"></button>
</div>
</div>
<div className="appeal_item dropdown_block">
<div className="block_header">
<p>
Как изменить график платежей по договору лизинга?
</p>
<button className="rotate"></button>
</div>
</div>
<div className="appeal_item dropdown_block">
<div className="block_header">
<p>Как получить консультацию по Цессии?</p>
<button className="rotate"></button>
</div>
</div>
<div className="appeal_item dropdown_block open">
<div className="block_header">
<p>
Оформление переуступки прав и обязательств по
договору лизинга
</p>
<button className="rotate"></button>
</div>
</div>
</div>
</div>
<div className="column">
<div className="column_text_block">
<p>
<b>Процедура</b>
</p>
<p>
К каждой теме свободное html поле для миниинструкции
(со ссылками на <a>формы документов</a> и{" "}
<a>документы</a>). Привязка к теме обращения в CRM
</p>
</div>
<div className="column_text_block">
<p>
<b>Документы</b>
</p>
<div className="dosc_list medium-icon">
<div className="row">
<p className="doc_name i-pdf extension">
2021_1655 от 20.04.2021
<span>2021_1655 от 20.04.2021</span>
</p>
</div>
<div className="row">
<p className="doc_name i-pdf extension">
2021_1655 от 20.04.2021
<span>2021_1655 от 20.04.2021</span>
</p>
</div>
</div>
</div>
<form>
<div className="form_field">
<select>
<option default selected disabled>
Выберите договоры
</option>
<option>Договор 1</option>
</select>
</div>
<div className="form_field">
<input
type="text"
name="name"
placeholder="Ваше ФИО"
/>
</div>
<div className="form_field">
<input type="tel" name="name" placeholder="Телефон" />
</div>
<div className="form_field">
<input type="email" name="name" placeholder="Email" />
</div>
<div className="form_field">
<textarea placeholder="Введите текст запроса"></textarea>
</div>
<div className="file_upload dropzone">
<div className="files"></div>
<div>
<p data-sm-text="Выберите файлы">
<span>Перенесите файлы на экран для быстрой загрузки или выберите файл с компьютера </span>
</p>
<label htmlFor="" className="button button-blue">Загрузить файл</label>
</div>
<input type="file" accept="" />
</div>
</form>
</div>
</div>
</article>
</div>
</div>
</section>
</main>
<Footer />
</React.Fragment>
);
}
}
function mapStateToProps(state, ownProps)
{
return {
contract_date: state.contract.date,
date: state.contract.date,
car: state.contract.car,
agreement: state.contract.agreement,
rules: state.contract.rules,
};
}
export const getServerSideProps = reduxWrapper.getServerSideProps(
(store) =>
async ({ req, res, query }) => {
return {
props: {
//number: query.number,
number: null,
},
};
}
);
export default withRouter(connect(mapStateToProps)(ContractPage));

270
pages/support/request.js Normal file
View File

@ -0,0 +1,270 @@
import React from "react";
import * as ReactDOM from 'react-dom';
import Head from "next/head";
import Image from "next/image";
import { connect } from "react-redux";
import { withRouter } from "next/router";
import moment from "moment";
import { SpinnerCircular } from "spinners-react";
import { reduxWrapper } from "../../store";
import Header from "../components/Header";
import Footer from "../components/Footer";
import Company from "../components/Company";
import InnerMenu from "./components/InnerMenu";
import {
getSupportThemes,
getContractsList,
} from "../../actions";
class SupportRequestPage extends React.Component
{
constructor(props) {
super(props);
this.state = {
loading: false,
contracts: null,
themes: null,
name: "",
phone: "",
email: "",
question: "",
file: null,
opened_theme: 0,
opened_question: 0,
};
}
static getDerivedStateFromProps(nextProps, prevState)
{
return {
contracts: nextProps.contracts,
themes: nextProps.themes,
};
}
componentDidMount()
{
console.log("CDM", "SupportRequestPage", this.state);
ReactDOM.findDOMNode(this).parentNode.style.height = "100%";
ReactDOM.findDOMNode(this).parentNode.style.display = "flex";
ReactDOM.findDOMNode(this).parentNode.style.flexDirection = "column";
ReactDOM.findDOMNode(this).parentNode.style.justifyContent = "spaceBetween";
ReactDOM.findDOMNode(this).parentNode.style.alignItems = "stretch";
document.documentElement.style.height = "100%";
document.documentElement.style.display = "flex";
document.documentElement.style.flexDirection = "column";
document.body.style.height = "100%";
document.body.style.display = "flex";
document.body.style.flexDirection = "column";
if (!this.state.loading)
{
this.setState({ loading: true }, () =>
{
Promise.all([
getContractsList({ dispatch: this.props.dispatch, all: true }),
getSupportThemes({ dispatch: this.props.dispatch })
])
.then(() =>
{
this.setState({ loading: false });
})
.catch(() => {});
});
}
}
componentWillUnmount()
{
ReactDOM.findDOMNode(this).parentNode.style.height = "unset";
ReactDOM.findDOMNode(this).parentNode.style.display = "unset";
ReactDOM.findDOMNode(this).parentNode.style.flexDirection = "unset";
ReactDOM.findDOMNode(this).parentNode.style.justifyContent = "unset";
ReactDOM.findDOMNode(this).parentNode.style.alignItems = "unset";
document.documentElement.style.height = "unset";
document.documentElement.style.display = "unset";
document.documentElement.style.flexDirection = "unset";
document.body.style.height = "unset";
document.body.style.display = "unset";
document.body.style.flexDirection = "unset";
}
componentDidUpdate(prevProps, prevState)
{
}
_handle_onBack = () =>
{
this.props.router.push('/support/faq/');
}
_handle_onChangeTheme = (index) =>
{
this.setState({ opened_theme: index, opened_question: 0 });
}
_handle_onSelectContracts = (event) =>
{
}
_renderForm = () =>
{
const { loading, contracts, themes, name, phone, email, question, file, opened_theme, opened_question } = this.state;
return (
<form>
<div className="form_field">
<select multiple={ false } onChange={ this._handle_onSelectContracts }>
<option default selected disabled>Выберите договоры</option>
{ contracts !== undefined && contracts !== null && contracts.map((contract, index) => (
<option index={ index } value={ contract.number }>{ contract.number }</option>
)) }
</select>
</div>
<div className="form_field">
<input
type="text"
name="name"
placeholder="Ваше ФИО"
/>
</div>
<div className="form_field">
<input type="tel" name="name" placeholder="Телефон" />
</div>
<div className="form_field">
<input type="email" name="name" placeholder="Email" />
</div>
<div className="form_field">
<textarea placeholder="Введите текст запроса"></textarea>
</div>
<div className="file_upload dropzone">
<div className="files"></div>
<div>
<p data-sm-text="Выберите файлы">
<span>Перенесите файлы на экран для быстрой загрузки или выберите файл с компьютера </span>
</p>
<label htmlFor="" className="button button-blue">Загрузить файл</label>
</div>
<input type="file" accept="" />
</div>
</form>
)
}
render()
{
const { loading, themes, opened_theme, opened_question } = this.state;
const { number } = this.props;
const procedure = themes !== undefined && themes !== null ? themes[opened_theme].questions[opened_question] : undefined;
return (
<React.Fragment>
<Head>
<title>ЛК Эволюция автолизинга</title>
<meta name="description" content="ЛК Эволюция автолизинга" />
</Head>
<Header { ...this.props } />
<main style={{ flex: 1, display: "flex", flexDirection: "column" }}>
<section>
<div className="clear"></div>
<div className="container">
<div className="title_wrapper">
<div className="left" style={{ alignItems: "center", flexWrap: "wrap" }}>
<button className="back" onClick={ this._handle_onBack }>Назад</button>
<h1 className="section_title">Новое обращение</h1>
</div>
<Company />
</div>
<div className="aside_container about">
<article className="full">
<div className="new_appeal">
<div className="column">
<div className="dropdown_blocks_list appeal_list visible">
{ themes !== undefined && themes !== null && themes.map((theme, theme_index) =>
(
<React.Fragment key={ `theme_${ theme_index }` } >
<div className={ `appeal_item dropdown_block ${ theme_index === opened_theme && "open" }` } style={ theme_index === opened_theme ? { backgroundColor: "unset"} : {} } onClick={ () => this._handle_onChangeTheme(theme_index) }>
<div className="block_header">
<p style={{ fontWeight: "bold" }}>{ theme.name }</p>
<button className="rotate"></button>
</div>
</div>
{ theme_index === opened_theme && theme.questions.map((question, question_index) => (
<div key={ `question_${ question_index }` } className={ `appeal_item dropdown_block ${ question_index === opened_question && "open" }` } style={{ paddingLeft: "20px" }} onClick={ () => this.setState({ opened_question: question_index }) }>
<div className="block_header">
<p>{ question.title }</p>
<button className="rotate"></button>
</div>
</div>
) )}
</React.Fragment>
)) }
</div>
</div>
{ themes !== undefined && themes !== null && (
<div className="column">
<div className="column_text_block">
<p><b>Процедура</b></p>
<p dangerouslySetInnerHTML={{ __html: procedure.answer }}/>
</div>
{ procedure.documents !== null && (
<div className="column_text_block">
<p><b>Документы</b></p>
<p dangerouslySetInnerHTML={{ __html: procedure.documents }}/>
</div>
) }
{ procedure.templates !== null && (
<div className="column_text_block">
<p><b>Документы</b></p>
<div className="dosc_list medium-icon">
{ procedure.templates.map((template, index) =>
(
<div className="row" key={ `template_${ index }` }>
<p className="doc_name i-pdf extension" data-format={ template.extension }>{ template.filename }<span>Скачать шаблон</span></p>
</div>
)) }
</div>
</div>
) }
{ this._renderForm() }
</div>
) }
</div>
</article>
</div>
</div>
</section>
</main>
<Footer />
</React.Fragment>
);
}
}
function mapStateToProps(state, ownProps)
{
return {
contracts: state.contracts.list,
themes: state.support.themes,
appeal: state.support.appeal,
};
}
export const getServerSideProps = reduxWrapper.getServerSideProps(
(store) =>
async ({ req, res, query }) => {
return {
props: {
},
};
}
);
export default withRouter(connect(mapStateToProps)(SupportRequestPage));

View File

@ -73,7 +73,7 @@ export const defaultState = {
{
themes: null,
searched: null,
appeals: null,
appeals: { list: null, new: 0 },
appeal: null,
request: null,
}

View File

@ -35,7 +35,7 @@ const supportReducer = (state = initialState.support, action) =>
{
return {
...state,
appeals: action.data.appeals,
appeals: { ...state.appeals, ...action.data.appeals, },
};
}