From ce70aa6250608a3b8a3c1f25456e27db13ebe9db Mon Sep 17 00:00:00 2001 From: Ernest Litvinenko Date: Wed, 15 Nov 2023 14:28:43 +0300 Subject: [PATCH] add user selecting --- package.json | 1 + src/backend/index.ts | 52 +++++ src/backend/openapi.d.ts | 261 +++++++++++++++++++++++++ src/backend/openapi_local/openapi.json | 1 + src/backend/types.ts | 15 ++ src/helplers.ts | 3 + src/main.ts | 12 +- src/modal.ts | 157 +++++++++++++-- src/state.ts | 0 src/types.ts | 34 +--- yarn.lock | 16 ++ 11 files changed, 493 insertions(+), 59 deletions(-) create mode 100644 src/backend/index.ts create mode 100644 src/backend/openapi.d.ts create mode 100644 src/backend/openapi_local/openapi.json create mode 100644 src/backend/types.ts create mode 100644 src/helplers.ts create mode 100644 src/state.ts diff --git a/package.json b/package.json index 72ba58a..6a40060 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "vite": "^4.4.5" }, "dependencies": { + "@types/axios": "^0.14.0", "@types/react": "^18.2.33", "axios": "^1.6.0", "flexsearch": "^0.7.31", diff --git a/src/backend/index.ts b/src/backend/index.ts new file mode 100644 index 0000000..6053ef3 --- /dev/null +++ b/src/backend/index.ts @@ -0,0 +1,52 @@ +import axios, {AxiosInstance} from "axios"; +import {ListOfficesResponse, UpdateOfficeRequest, ListProfilesResponse} from "./types.ts"; + +class Backend { + instance: AxiosInstance + office: Office + profile: Profile + + constructor() { + this.instance = axios.create({ + baseURL: 'http://localhost/api/v2/', + headers: { + 'Content-Type': 'application/json' + } + }) + + this.office = new Office({instance: this.instance}) + this.profile = new Profile({instance: this.instance}) + } +} + +class BaseHandler { + instance: AxiosInstance + + constructor({instance}: { instance: AxiosInstance }) { + this.instance = instance + } +} + +class Office extends BaseHandler { + async list_offices() { + const {data} = await this.instance.get('offices') + return data + } + + async update_office(office: UpdateOfficeRequest) { + await this.instance.post('offices/', office) + } +} + +class Profile extends BaseHandler { + async listProfiles(): Promise { + const {data} = await this.instance.get('profiles') + return data + } + async getById(idx: string | number): Promise { + const {data} = await this.instance.get(`profile/${idx}`) + return data + } +} + +export default new Backend() \ No newline at end of file diff --git a/src/backend/openapi.d.ts b/src/backend/openapi.d.ts new file mode 100644 index 0000000..af6b664 --- /dev/null +++ b/src/backend/openapi.d.ts @@ -0,0 +1,261 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + + +export interface paths { + "/api/v2/offices/": { + /** List Offices */ + get: operations["list_offices_api_v2_offices__get"]; + /** Update Office */ + post: operations["update_office_api_v2_offices__post"]; + }; + "/api/v2/profiles/": { + /** List Profiles */ + get: operations["list_profiles_api_v2_profiles__get"]; + /** Create Profile */ + post: operations["create_profile_api_v2_profiles__post"]; + }; + "/api/v2/profiles/{profile_id}": { + /** Get Profile */ + get: operations["get_profile_api_v2_profiles__profile_id__get"]; + /** Update Profile */ + put: operations["update_profile_api_v2_profiles__profile_id__put"]; + /** Delete Profile */ + delete: operations["delete_profile_api_v2_profiles__profile_id__delete"]; + }; +} + +export type webhooks = Record; + +export interface components { + schemas: { + /** ExtendedResponse */ + ExtendedResponse: { + /** Features */ + features: string | null; + contact_person: components["schemas"]["ProfileResponse"] | null; + /** Person Count */ + person_count: number | null; + /** Rating */ + rating: number | null; + }; + /** HTTPValidationError */ + HTTPValidationError: { + /** Detail */ + detail?: components["schemas"]["ValidationError"][]; + }; + /** JdeOfficeDetailResponse */ + JdeOfficeDetailResponse: { + /** Code */ + code: string; + /** Title */ + title: string; + /** Kladr Code */ + kladr_code: string; + /** Aex Only */ + aex_only: string; + /** Mst Pr Aex */ + mst_pr_aex: string; + /** Mst Pr Virt */ + mst_pr_virt: string; + /** Addr */ + addr: string; + /** Features */ + features: string; + /** Coords */ + coords: { + [key: string]: string; + }; + /** City */ + city: string; + /** Country Code */ + country_code: string; + /** Contry Name */ + contry_name: string; + /** Max Ves */ + max_ves: string; + /** Max Obyom */ + max_obyom: string; + /** Max Ves Gm */ + max_ves_gm: string; + /** Max Obyom Gm */ + max_obyom_gm: string; + /** Max L Gm */ + max_l_gm: string; + /** Max W Gm */ + max_w_gm: string; + /** Max H Gm */ + max_h_gm: string; + changeable_info: components["schemas"]["ExtendedResponse"] | null; + }; + /** ProfileResponse */ + ProfileResponse: { + /** Id */ + id: number; + /** Full Name */ + full_name: string | null; + /** Phone */ + phone: string | null; + /** Email */ + email: string | null; + }; + /** UpdateOfficeRequest */ + UpdateOfficeRequest: { + /** Code */ + code: number; + /** Features */ + features: string | null; + /** Contact Person Id */ + contact_person_id: number | null; + /** Person Count */ + person_count: number | null; + /** Rating */ + rating: number | null; + }; + /** ValidationError */ + ValidationError: { + /** Location */ + loc: (string | number)[]; + /** Message */ + msg: string; + /** Error Type */ + type: string; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} + +export type $defs = Record; + +export type external = Record; + +export interface operations { + + /** List Offices */ + list_offices_api_v2_offices__get: { + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["JdeOfficeDetailResponse"][]; + }; + }; + }; + }; + /** Update Office */ + update_office_api_v2_offices__post: { + requestBody: { + content: { + "application/json": components["schemas"]["UpdateOfficeRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** List Profiles */ + list_profiles_api_v2_profiles__get: { + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": unknown; + }; + }; + }; + }; + /** Create Profile */ + create_profile_api_v2_profiles__post: { + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": unknown; + }; + }; + }; + }; + /** Get Profile */ + get_profile_api_v2_profiles__profile_id__get: { + parameters: { + path: { + profile_id: number; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** Update Profile */ + update_profile_api_v2_profiles__profile_id__put: { + parameters: { + path: { + profile_id: number; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + /** Delete Profile */ + delete_profile_api_v2_profiles__profile_id__delete: { + parameters: { + path: { + profile_id: number; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; +} diff --git a/src/backend/openapi_local/openapi.json b/src/backend/openapi_local/openapi.json new file mode 100644 index 0000000..4ce91f8 --- /dev/null +++ b/src/backend/openapi_local/openapi.json @@ -0,0 +1 @@ +{"openapi":"3.1.0","info":{"title":"FastAPI","version":"0.1.0"},"paths":{"/api/v2/offices/":{"get":{"summary":"List Offices","operationId":"list_offices_api_v2_offices__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/JdeOfficeDetailResponse"},"type":"array","title":"Response List Offices Api V2 Offices Get"}}}}}},"post":{"summary":"Update Office","operationId":"update_office_api_v2_offices__post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateOfficeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/profiles/":{"get":{"summary":"List Profiles","operationId":"list_profiles_api_v2_profiles__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"summary":"Create Profile","operationId":"create_profile_api_v2_profiles__post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v2/profiles/{profile_id}":{"get":{"summary":"Get Profile","operationId":"get_profile_api_v2_profiles__profile_id__get","parameters":[{"name":"profile_id","in":"path","required":true,"schema":{"type":"integer","title":"Profile Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete Profile","operationId":"delete_profile_api_v2_profiles__profile_id__delete","parameters":[{"name":"profile_id","in":"path","required":true,"schema":{"type":"integer","title":"Profile Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"summary":"Update Profile","operationId":"update_profile_api_v2_profiles__profile_id__put","parameters":[{"name":"profile_id","in":"path","required":true,"schema":{"type":"integer","title":"Profile Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"ExtendedResponse":{"properties":{"features":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Features"},"contact_person":{"anyOf":[{"$ref":"#/components/schemas/ProfileResponse"},{"type":"null"}]},"person_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Person Count"},"rating":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rating"}},"type":"object","required":["features","contact_person","person_count","rating"],"title":"ExtendedResponse"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"JdeOfficeDetailResponse":{"properties":{"code":{"type":"string","title":"Code"},"title":{"type":"string","title":"Title"},"kladr_code":{"type":"string","title":"Kladr Code"},"aex_only":{"type":"string","title":"Aex Only"},"mst_pr_aex":{"type":"string","title":"Mst Pr Aex"},"mst_pr_virt":{"type":"string","title":"Mst Pr Virt"},"addr":{"type":"string","title":"Addr"},"features":{"type":"string","title":"Features"},"coords":{"additionalProperties":{"type":"string"},"type":"object","title":"Coords"},"city":{"type":"string","title":"City"},"country_code":{"type":"string","title":"Country Code"},"contry_name":{"type":"string","title":"Contry Name"},"max_ves":{"type":"string","title":"Max Ves"},"max_obyom":{"type":"string","title":"Max Obyom"},"max_ves_gm":{"type":"string","title":"Max Ves Gm"},"max_obyom_gm":{"type":"string","title":"Max Obyom Gm"},"max_l_gm":{"type":"string","title":"Max L Gm"},"max_w_gm":{"type":"string","title":"Max W Gm"},"max_h_gm":{"type":"string","title":"Max H Gm"},"changeable_info":{"anyOf":[{"$ref":"#/components/schemas/ExtendedResponse"},{"type":"null"}]}},"type":"object","required":["code","title","kladr_code","aex_only","mst_pr_aex","mst_pr_virt","addr","features","coords","city","country_code","contry_name","max_ves","max_obyom","max_ves_gm","max_obyom_gm","max_l_gm","max_w_gm","max_h_gm","changeable_info"],"title":"JdeOfficeDetailResponse"},"ProfileResponse":{"properties":{"id":{"type":"integer","title":"Id"},"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name"},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone"},"email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Email"}},"type":"object","required":["id","full_name","phone","email"],"title":"ProfileResponse"},"UpdateOfficeRequest":{"properties":{"code":{"type":"integer","title":"Code"},"features":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Features"},"contact_person_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Contact Person Id"},"person_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Person Count"},"rating":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rating"}},"type":"object","required":["code","features","contact_person_id","person_count","rating"],"title":"UpdateOfficeRequest"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}}} \ No newline at end of file diff --git a/src/backend/types.ts b/src/backend/types.ts new file mode 100644 index 0000000..60ba3af --- /dev/null +++ b/src/backend/types.ts @@ -0,0 +1,15 @@ +import types from './openapi' + +// Responses +export type ListOfficesResponse = types.paths['/api/v2/offices/']['get']['responses'][200]['content']['application/json'] + +export type ListProfilesResponse = { + id: number, + full_name : string | null, + phone: string | null, + email: string | null +} + + +//Requests +export type UpdateOfficeRequest = types.paths['/api/v2/offices/']['post']['requestBody']['content']['application/json'] \ No newline at end of file diff --git a/src/helplers.ts b/src/helplers.ts new file mode 100644 index 0000000..c42d4d5 --- /dev/null +++ b/src/helplers.ts @@ -0,0 +1,3 @@ +export function gen_unique_id(): string { + return String(Date.now()) +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 66a143f..e0f7f5e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,13 +1,12 @@ import './style.css' import './fonts/ManiaExtended/stylesheet.css' import './fonts/OfficinaSerifBoldSCC/stylesheet.css' -import axios from "axios"; import $ from 'jquery'; import _ from 'lodash'; -import FlexSearch from 'flexsearch'; -import {BlocksDataValueType, FilterType, ServerResponseType} from "./types.ts"; +import {BlocksDataValueType, FilterType} from "./types.ts"; import Block, {toggleBlocks} from "./block.ts"; import {renderModal} from "./modal.ts"; +import Backend from "./backend"; // use this for search @@ -38,10 +37,7 @@ let blocksData: { [x: string]: BlocksDataValueType } = {} // Containers const dataContainer = $('#data') const menuContainer = $('#filterForm') -const getDataFromServer = async () => { - const r = await axios.get('http://localhost/api/v2/offices') - return r.data -} + const updateDisplayingData = (...ids: Array) => { console.log(ids) @@ -116,7 +112,7 @@ const reRenderHandler = () => { dataContainer.children().remove() index = new Search() - getDataFromServer().then( + Backend.office.list_offices().then( d => { d.forEach( data => { diff --git a/src/modal.ts b/src/modal.ts index 93758ff..4cafa9f 100644 --- a/src/modal.ts +++ b/src/modal.ts @@ -1,5 +1,6 @@ -import axios from "axios"; import $ from 'jquery'; +import Backend from "./backend"; +import {UpdateOfficeRequest} from './backend/types.ts' type ModalPropsType = { title: string @@ -15,35 +16,104 @@ type ModalPropsType = { personalCount?: number } +let selectedValueUser = 0; +let selectedUser = { + id: "", + full_name: "", + email: "", + 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 => `` + )) + .then(data => ``) + .then(data => updateCurrentUser.html = data) + return target.html + } + } +}) + +const UserCreationBlock = (props: ModalPropsType) => { + const createUser = ` +
+ + + +
+` + return $(createUser) +} + +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 = ` +
+ + + + + + +
+ ` + const queryBlock = $(query) + Backend.profile.listProfiles() + .then(data => data.map(elem => ``).join("")) + .then(e => queryBlock.find('select').append(e)) + return queryBlock +} + const Modal = (props: ModalPropsType) => ` ` + + export const renderModal = (props: ModalPropsType, reRenderHandler: () => void) => { const modal = $(Modal(props)) - modal.find('form').on('submit', (e) => { - e.preventDefault() - const formData = new FormData(e.target) - axios.post('http://localhost/api/v2/office/edit', formData).then(() => { - modal.remove() - reRenderHandler() + registerEvents(modal, reRenderHandler, props) + $('body').append(modal) +} + +const addModulesToModal = (props: ModalPropsType) => { + $('#userBlock').append(selectedValueUser === 0 ? UserSelectingBlock(props): UserCreationBlock(props)) +} + +const registerEvents = (modal: JQuery, reRenderHandler: () => void, props: ModalPropsType) => { + modal.find('form').on('submit', + (e) => { + e.preventDefault() + const fields = { + code: $('#codeField'), + contact_person_id: $('#profileIdField'), + features: $('#featuresField'), + person_count: $('#personalCountField'), + rating: $('#ratingField'), + } + 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, + } + + Backend.office.update_office(query).then( + () => { + modal.remove(); + reRenderHandler(); + } + ) }) - }) + modal.find('#removeModal').on('click', () => { modal.remove() reRenderHandler() }) - $('body').append(modal) + 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); } diff --git a/src/state.ts b/src/state.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/types.ts b/src/types.ts index ef71f89..ed22005 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,37 +1,9 @@ -import {components} from "./server"; +import {ListOfficesResponse} from './backend/types.ts' -export type QueryBlockType = { - aex_only: string - mst_pr_aex: string - mst_pr_virt: string - max_ves: string - max_obyom: string - max_ves_gm: string - max_obyom_gm: string - max_l_gm: string - max_w_gm: string - max_h_gm: string - person_count: string - features: string - contact_profile: { - id: number - fullName: string - email: string, - phone: string - } | null - features_changeable: string -} - -export interface BlockPropsType extends ServerResponseType { -} - -export type ServerResponseType = { - [x in keyof components["schemas"]['JdeOfficeDetailResponse']]: components["schemas"]['JdeOfficeDetailResponse'][x] -} export type BlocksDataValueType = { block: JQuery, - data: ServerResponseType + data: ListOfficesResponse[0] } -export type FilterType = Omit<{ [x in keyof ServerResponseType]?: ServerResponseType[x] | null } & { +export type FilterType = Omit \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index cec10ba..7ff284c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -170,6 +170,13 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@types/axios@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@types/axios/-/axios-0.14.0.tgz#ec2300fbe7d7dddd7eb9d3abf87999964cafce46" + integrity sha512-KqQnQbdYE54D7oa/UmYVMZKq7CO4l8DEENzOKc4aBRwxCXSlJXGz83flFx5L7AWrOQnmuN3kVsRdt+GZPPjiVQ== + dependencies: + axios "*" + "@types/flexsearch@^0.7.5": version "0.7.5" resolved "https://registry.yarnpkg.com/@types/flexsearch/-/flexsearch-0.7.5.tgz#23827f2bacbc0cec386acc5f0383586a9202ad20" @@ -253,6 +260,15 @@ autoprefixer@^10.4.16: picocolors "^1.0.0" postcss-value-parser "^4.2.0" +axios@*: + version "1.6.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.1.tgz#76550d644bf0a2d469a01f9244db6753208397d7" + integrity sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g== + dependencies: + follow-redirects "^1.15.0" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + axios@^1.6.0: version "1.6.0" resolved "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz"