add stars

feature/star
Ernest Litvinenko 2023-11-21 01:00:24 +03:00
parent ce70aa6250
commit 5c3c2d39ad
7 changed files with 259 additions and 102 deletions

View File

@ -24,6 +24,7 @@
<h1 class="text-base sm:text-xl font-mania flex-1 text-center">Список филиалов</h1>
</div>
</header>
<main>
<div class="block sm:flex container">
<aside class="w-full sm:w-1/2 mr-8 shadow-2xl text-black text-base

View File

@ -25,6 +25,7 @@
"@types/react": "^18.2.33",
"axios": "^1.6.0",
"flexsearch": "^0.7.31",
"izitoast": "^1.4.0",
"jquery": "^3.7.1",
"lodash": "^4.17.21",
"node-html-parser": "^6.1.11"

View File

@ -36,6 +36,11 @@ class Office extends BaseHandler {
async update_office(office: UpdateOfficeRequest) {
await this.instance.post('offices/', office)
}
async getById(idx: string | number) {
const {data} = await this.instance.get<ListOfficesResponse[0]>(`offices/${idx}`)
return data
}
}
class Profile extends BaseHandler {
@ -44,9 +49,16 @@ class Profile extends BaseHandler {
return data
}
async getById(idx: string | number): Promise<ListProfilesResponse> {
const {data} = await this.instance.get<ListProfilesResponse>(`profile/${idx}`)
const {data} = await this.instance.get<ListProfilesResponse>(`profiles/${idx}`)
return data
}
async createUser(profile: { full_name: string, phone: string, email: string }) {
return (await this.instance.post('profiles/', profile)).data
}
async updateUser(profile: {id: number, full_name: string, phone: string, email: string}) {
await this.instance.post('profiles/', profile)
}
}
export default new Backend()

View File

@ -1,6 +1,20 @@
import {BlockPropsType, QueryBlockType} from "./types.ts";
import $ from "jquery";
function generateStars(rating: number) {
let str = ''
for (let i = 0; i < rating; i++) {
str += '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\n' +
'<path d="M5.825 22L7.45 14.975L2 10.25L9.2 9.625L12 3L14.8 9.625L22 10.25L16.55 14.975L18.175 22L12 18.275L5.825 22Z" fill="#E5352D"/>\n' +
'</svg>'
}
for (let i = rating; i < 5; i++) {
str += '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">\n' +
'<path d="M8.85 17.825L12 15.925L15.15 17.85L14.325 14.25L17.1 11.85L13.45 11.525L12 8.125L10.55 11.5L6.9 11.825L9.675 14.25L8.85 17.825ZM5.825 22L7.45 14.975L2 10.25L9.2 9.625L12 3L14.8 9.625L22 10.25L16.55 14.975L18.175 22L12 18.275L5.825 22Z" fill="#E5352D"/>\n' +
'</svg>'
}
return str
}
const Block = (props: BlockPropsType) => {
const query: QueryBlockType = {
@ -23,6 +37,7 @@ const Block = (props: BlockPropsType) => {
if (props.changeable_info) {
query.person_count = `${props.changeable_info.person_count}` || query.person_count
query.features_changeable = `${props.changeable_info.features}` || ''
query.rating = `${props.changeable_info.rating}`
if (props.changeable_info.contact_person) {
query.contact_profile = {
id: props.changeable_info.contact_person.id!,
@ -41,6 +56,9 @@ const Block = (props: BlockPropsType) => {
<span class="text-base opacity-50">${props.addr}</span>
</div>
<div>
<span class="flex">
${generateStars(query.rating)}
</span>
<span class="text-sm sm:text-base font-bold block">${props.code}</span>
<span class="text-sm sm:text-base opacity-50">КЛАДР: ${props.kladr_code}</span>
</div>

View File

@ -1,14 +1,15 @@
import './style.css'
import './fonts/ManiaExtended/stylesheet.css'
import './fonts/OfficinaSerifBoldSCC/stylesheet.css'
import 'izitoast/dist/css/iziToast.min.css';
import $ from 'jquery';
import _ from 'lodash';
import _, {min} from 'lodash';
import {BlocksDataValueType, FilterType} from "./types.ts";
import Block, {toggleBlocks} from "./block.ts";
import {renderModal} from "./modal.ts";
import Backend from "./backend";
let min_rating = 0
// use this for search
class Search {
@ -84,6 +85,14 @@ const filterUpdate = (value: string | null, name: Exclude<keyof FilterType, 'ful
return filtratedBlocks.map(el => el.data.code)
}
const filterRating = () => {
return _.filter(blocksData, obj => {
const {data} = obj
console.log(data)
return data.changeable_info && data.changeable_info.rating && data.changeable_info.rating >= min_rating
}).map(el => el.data.code)
}
const submitHandler = (e: JQuery.SubmitEvent<HTMLFormElement> | JQuery.ChangeEvent<HTMLFormElement>) => {
e.preventDefault();
@ -99,7 +108,8 @@ const submitHandler = (e: JQuery.SubmitEvent<HTMLFormElement> | JQuery.ChangeEve
max_l_gm: filterUpdate(formData.get('max_l_gm') as string | null, 'max_l_gm'),
max_w_gm: filterUpdate(formData.get('max_w_gm') as string | null, 'max_w_gm'),
max_h_gm: filterUpdate(formData.get('max_h_gm') as string | null, 'max_h_gm'),
people: filterUpdate(formData.get('people') as string | null, 'people')
rating: filterRating()
// people: filterUpdate(formData.get('people') as string | null, 'people')
}
const dd = _.intersection(...Object.values(values))
updateDisplayingData(...dd)
@ -126,21 +136,8 @@ const reRenderHandler = () => {
dataContainer.append(Object.values(blocksData).map(elem => elem.block))
Object.values(blocksData).forEach(elem => {
elem.block.find('button').on('click', () => {
const contact = {}
if (elem.data.changeable_info && elem.data.changeable_info.contact_person) {
contact.id = elem.data.changeable_info.contact_person.id
contact.fullName = elem.data.changeable_info.contact_person.full_name
contact.email = elem.data.changeable_info.contact_person.email
contact.phone = elem.data.changeable_info.contact_person.phone
}
renderModal({
code: elem.data.code,
title: elem.data.title,
rating: (elem.data.changeable_info && elem.data.changeable_info.rating) || 1,
features: (elem.data.changeable_info && elem.data.changeable_info.features) || '',
personalCount: (elem.data.changeable_info && elem.data.changeable_info.person_count) || 0,
contact,
}, reRenderHandler)
})
})
@ -156,3 +153,41 @@ $("#fullTextSearch").on('input', () => {
{preventDefault: () => null, target: document.querySelector('#filterForm')}
)
})
$('#star1, #star2, #star3, #star4, #star5').on('click', (e)=> {
const func = (e: JQuery.Event) => {
const getParentObj = (elem) => {
if (typeof $(elem).attr('id') === 'undefined') {
return getParentObj(elem.parentElement)
}
return $(elem)
}
let idx: string | number | undefined = getParentObj(e.target).attr('id')
idx = +idx!.replace('star', '')
min_rating = idx
for (let i = 1; i <= 5; i++) {
const block = $(`#star${i}`)
const star_filled = `
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.825 22L7.45 14.975L2 10.25L9.2 9.625L12 3L14.8 9.625L22 10.25L16.55 14.975L18.175 22L12 18.275L5.825 22Z" fill="#E5352D"/>
</svg>
`
const star_stroked = `
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.85 17.825L12 15.925L15.15 17.85L14.325 14.25L17.1 11.85L13.45 11.525L12 8.125L10.55 11.5L6.9 11.825L9.675 14.25L8.85 17.825ZM5.825 22L7.45 14.975L2 10.25L9.2 9.625L12 3L14.8 9.625L22 10.25L16.55 14.975L18.175 22L12 18.275L5.825 22Z" fill="#E5352D"/>
</svg>
`
const obj = i <= idx ? $(star_filled) : $(star_stroked)
obj.on('click', e => func(e))
block.empty()
block.append(obj)
}
}
func(e)
})

View File

@ -1,19 +1,10 @@
import $ from 'jquery';
import Backend from "./backend";
import {UpdateOfficeRequest} from './backend/types.ts'
import iziToast from "izitoast";
type ModalPropsType = {
title: string
code: string
rating?: number | string
features?: string
contact: {
id?: number,
fullName?: string
email?: string
phone?: string
}
personalCount?: number
}
let selectedValueUser = 0;
@ -24,44 +15,36 @@ let selectedUser = {
phone: ""
}
const updateCurrentUser = new Proxy({html: ``}, {
set(target, p: string, newValue: any): boolean {
target[p] = newValue
return true
},
get(target, prop) {
if (prop === 'html') {
Backend.profile.listProfiles()
.then(data => data.map(
elem => `<option value="${elem.id}">${elem.full_name}</option>`
))
.then(data => `<select name="selectUser">${data.join('')}</select>`)
.then(data => updateCurrentUser.html = data)
return target.html
}
}
})
const UserCreationBlock = (props: ModalPropsType) => {
const UserCreationBlock = (props) => {
const createUser = `
<div>
<input class="border-2 border-secondary flex-1" type="text" name="profileFullName" placeholder="ФИО" value=""/>
<input class="border-2 border-secondary flex-1" type="email" name="profileEmail" placeholder="Email" />
<input class="border-2 border-secondary flex-1" type="email" name="profilePhone" placeholder="+79999999999" />
<input class="border-2 border-secondary flex-1" type="text" name="profileFullName" placeholder="ФИО" required/>
<input class="border-2 border-secondary flex-1" type="email" name="profileEmail" placeholder="Email" required/>
<input class="border-2 border-secondary flex-1" type="tel" name="profilePhone" placeholder="+79999999999" required/>
<button class="bg-primary text-white px-4 py-2" id="createProfileBtn">Создать профиль</button>
</div>
`
return $(createUser)
const block = $(createUser)
block.find('#createProfileBtn').on('click', (e) => {
e.preventDefault()
Backend.profile.createUser({
full_name: String(block.find('input[name="profileFullName"]').val()),
phone: String(block.find('input[name="profilePhone"]').val()),
email: String(block.find('input[name="profileEmail"]').val())
}).then(
(data) => {iziToast.success({
title: "Пользователь успешно создан",
position: "topRight",
zindex: 30
}); return data}
).then((data) => block.append(`<input type="hidden" name="profileId" value="${data.id}">`))
})
return block
}
const UserSelectingBlock = (props: ModalPropsType) => {
console.log('user id: ', selectedUser.id)
if (selectedUser.id === '') {
selectedUser.id = props.contact.id ? String(props.contact.id) : ''
selectedUser.phone = props.contact.phone || ''
selectedUser.email = props.contact.email || ''
selectedUser.full_name = props.contact.fullName || ''
}
console.log(selectedUser)
const query = `
<div>
<select name="profileId" id="selectUserField">
@ -70,32 +53,105 @@ const UserSelectingBlock = (props: ModalPropsType) => {
<input class="border-2 border-secondary flex-1" type="text" name="profileFullName" placeholder="ФИО" value="${selectedUser.full_name}"/>
<input class="border-2 border-secondary flex-1" type="email" name="profileEmail" placeholder="Email" value="${selectedUser.email}"/>
<input class="border-2 border-secondary flex-1" type="email" name="profilePhone" placeholder="+79999999999" value="${selectedUser.phone}" />
<input class="border-2 border-secondary flex-1" type="tel" name="profilePhone" placeholder="+79999999999" value="${selectedUser.phone}" />
<button class="bg-primary text-white px-4 py-2">Изменить профиль</button>
</div>
`
const queryBlock = $(query)
Backend.profile.listProfiles()
.then(data => data.map(elem => `<option value="${elem.id}">${elem.full_name}</option>`).join(""))
Backend.office.getById(props.code).then(
data => {
console.log(data)
if (data.changeable_info) {
return data.changeable_info.contact_person_id
}
return null
}
)
.then(idx => idx && Backend.profile.getById(idx))
.then(data => {
if (!data) {
return
}
selectedUser.id = String(data.id)
selectedUser.phone = String(data.phone)
selectedUser.email = String(data.email)
selectedUser.full_name = String(data.full_name)
})
.then(() => Backend.profile.listProfiles())
.then(data => data.map(elem => `<option value="${elem.id}" ${elem.id === +selectedUser.id ? 'checked' : ''}>${elem.full_name}</option>`).join(""))
.then(e => queryBlock.find('select').append(e))
.then(() => selectHandler(selectedUser.id))
function selectHandler(idx: string | number) {
Backend.profile.getById(idx).then(
data => {
console.log(data)
$('[name="profileId"]').val(String(data.id))
$('[name="profileFullName"]').val(String(data.full_name))
$('[name="profileEmail"]').val(String(data.email))
$('[name="profilePhone"]').val(String(data.phone))
}
)
}
queryBlock.find('select').on('change', (e) => selectHandler(e.target.value))
return queryBlock
}
const Modal = (props: ModalPropsType) => `
function generateStars(rating: number) {
let str = ''
for (let i = 1; i <= +rating; i++) {
str += `
<div class="ratingBlock">
<label for="ratingField${i}" class="block">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.825 22L7.45 14.975L2 10.25L9.2 9.625L12 3L14.8 9.625L22 10.25L16.55 14.975L18.175 22L12 18.275L5.825 22Z" fill="#E5352D"/>
</svg>
</label>
<input class="border-2 border-secondary flex-1 hidden" type="radio" name="rating"
value="${i}" id="ratingField${i}" ${i == rating ? 'checked' : ''}>
</div>
`
}
for (let i = +rating+1; i <= 5; i++) {
str += `
<div class="ratingBlock">
<label for="ratingField${i}" class="block">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.85 17.825L12 15.925L15.15 17.85L14.325 14.25L17.1 11.85L13.45 11.525L12 8.125L10.55 11.5L6.9 11.825L9.675 14.25L8.85 17.825ZM5.825 22L7.45 14.975L2 10.25L9.2 9.625L12 3L14.8 9.625L22 10.25L16.55 14.975L18.175 22L12 18.275L5.825 22Z" fill="#E5352D"/>
</svg>
</label>
<input class="border-2 border-secondary flex-1 hidden" type="radio" name="rating"
value="${i}" id="ratingField${i}">
</div>`
}
return str
}
const Modal = () => `
<div class="fixed top-0 left-0 w-full h-full bg-secondary bg-opacity-50 z-10 flex flex-col justify-center" id="modal">
<button class="text-white absolute top-14 right-14" id="removeModal">x</button>
<div class="container flex flex-col mx-auto bg-white px-4 py-2">
<span class="text-xl">${props.title}</span>
<span class="text-xl" id="modalTitle"></span>
<form class="[&_div]:mb-4 [&_input]:px-2 [&_textarea]:px-2" id="changeForm">
<input type="hidden" name="code" id="codeField" value="${props.code}">
<input type="hidden" name="code" id="codeField" value="">
<div class="inline-flex w-1/2 [&_*]:mr-4 mb-6">
<span>Рейтинг</span>
<input class="border-2 border-secondary flex-1" type="number" name="rating" max="5" min="1"
value="${props.rating || '1'}" id="ratingField">
<div class="ratingBlocks flex">
</div>
</div>
<div class="inline-flex w-1/2 [&_*]:mr-4 flex-col">
<span>Особенности филиала</span>
<textarea class="border-2 border-secondary flex-1" type="text" name="features" id="featuresField">${props.features || ''}</textarea>
<textarea class="border-2 border-secondary flex-1" type="text" name="features" id="featuresField"></textarea>
</div>
<div class="inline-flex w-1/2 [&_*]:mb-2 flex-col">
<span>Контактное лицо</span>
@ -105,7 +161,7 @@ const Modal = (props: ModalPropsType) => `
<label class="mr-4" for="userCreationFieldFalse">Выбрать пользователя из уже существующих</label>
<input type="radio" name="userCreationField" value="1" id="userCreationFieldTrue" ${selectedValueUser === 1 && 'checked'}>
<label for="userCreationFieldTrue">Создать нового пользователя</label>
</div
</div>
<div id="userBlock">
</div>
@ -113,7 +169,7 @@ const Modal = (props: ModalPropsType) => `
</div>
<div class="inline-flex w-1/2 [&_*]:mr-4 flex-col">
<span>Количество сотрудников</span>
<input class="border-2 border-secondary flex-1" type="number" name="personalCount" min="0" value="${props.personalCount}" id="personalCountField"/>
<input class="border-2 border-secondary flex-1" type="number" name="personalCount" min="0" value="" id="personalCountField"/>
</div>
<button class="block bg-primary font-bold text-white px-4 py-2" type="submit">Изменить</button>
</form>
@ -121,12 +177,45 @@ const Modal = (props: ModalPropsType) => `
</div>
`
function afterRendering(modal: JQuery<HTMLElement>, props: ModalPropsType) {
const func = (value) => {
const blockWrapper = modal.find(".ratingBlocks")
console.log('call')
blockWrapper.empty()
blockWrapper.append(generateStars(value))
blockWrapper.find('[name="rating"]').on('change', (e) => func(e.target.value))
}
Backend.office.getById(props.code).then(
data => {
if (data.changeable_info) {
// modal.find("#ratingField").val(data.changeable_info.rating || 1)
modal.find('.ratingBlocks').append(generateStars(data.changeable_info.rating || 1))
modal.find("#featuresField").val(data.changeable_info.features || '')
modal.find("#featuresField").val(data.changeable_info.features || '')
modal.find("#personalCountField").val(data.changeable_info.person_count || 0)
}
else {
modal.find('.ratingBlocks').append(generateStars(0))
}
modal.find('#modalTitle').text(data.title)
modal.find('#codeField').val(props.code)
}
)
.then(
() => modal.find('[name="rating"]').on('change', e => {
func(e.target.value)
})
)
}
export const renderModal = (props: ModalPropsType, reRenderHandler: () => void) => {
const modal = $(Modal(props))
registerEvents(modal, reRenderHandler, props)
const modal = $(Modal())
afterRendering(modal, props)
$('body').append(modal)
registerEvents(modal, reRenderHandler, props)
}
const addModulesToModal = (props: ModalPropsType) => {
@ -134,23 +223,34 @@ const addModulesToModal = (props: ModalPropsType) => {
}
const registerEvents = (modal: JQuery<HTMLElement>, reRenderHandler: () => void, props: ModalPropsType) => {
addModulesToModal(props);
function selectedUserChangeHandler(val) {
selectedValueUser = +val
const mdNew = $(Modal())
afterRendering(mdNew, props)
modal.replaceWith(mdNew)
registerEvents(mdNew, reRenderHandler, props)
}
modal.find('form').on('submit',
(e) => {
e.preventDefault()
const fields = {
code: $('#codeField'),
contact_person_id: $('#profileIdField'),
features: $('#featuresField'),
person_count: $('#personalCountField'),
rating: $('#ratingField'),
code: () => modal.find('#codeField'),
contact_person_id: () => modal.find('[name=profileId]'),
features: () => modal.find('#featuresField'),
person_count: () => modal.find('#personalCountField'),
rating: () => modal.find('[name="rating"]:checked'),
}
const query: UpdateOfficeRequest = {
code: +fields.code.val()!,
contact_person_id: null,
features: fields.features.val() ? String(fields.features.val()) : null,
person_count: fields.person_count.val() ? +fields.person_count.val()! : null,
rating: fields.rating.val() ? +fields.rating.val()! : null,
code: +fields.code().val()!,
contact_person_id: +fields.contact_person_id().val()!,
features: fields.features().val() ? String(fields.features().val()) : null,
person_count: fields.person_count().val() ? +fields.person_count().val()! : null,
rating: fields.rating().val() ? +fields.rating().val()! : null,
}
console.log(query)
Backend.office.update_office(query).then(
() => {
@ -164,23 +264,8 @@ const registerEvents = (modal: JQuery<HTMLElement>, reRenderHandler: () => void,
modal.remove()
reRenderHandler()
})
modal.find('[type="radio"]').on('change', (e) => {
selectedValueUser = +e.target.value
console.log(selectedValueUser)
const mdNew = $(Modal(props))
modal.replaceWith(mdNew)
registerEvents(mdNew, reRenderHandler, props)
})
modal.find('select').on('change', e => {
console.log(e.target.value)
Backend.profile.getById(e.target.value).then(
data => {
selectedUser.id = String(data.id)
selectedUser.email = data.email || ''
selectedUser.phone = data.phone || ''
selectedUser.full_name = data.full_name || ''
}
)
})
addModulesToModal(props);
modal.find('[name="userCreationField"]').on('change', ({target: {value}}) => selectedUserChangeHandler(value))
}

View File

@ -625,6 +625,11 @@ is-number@^7.0.0:
resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
izitoast@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/izitoast/-/izitoast-1.4.0.tgz#1aa4e3408b7159fba743506af66d8be54fd929fb"
integrity sha512-Oc1X2wiQtPp39i5VpIjf3GJf5sfCtHKXZ5szx7RareyEeFLUlcEW0FSfBni28+Ul6KNKZRKzhVuWzSP4Xngh0w==
jiti@^1.19.1:
version "1.20.0"
resolved "https://registry.npmjs.org/jiti/-/jiti-1.20.0.tgz"