diff --git a/package.json b/package.json index d48b98e..83556ed 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ "@types/node": "^12.0.0", "@types/react": "^16.9.0", "@types/react-dom": "^16.9.0", - "formik": "^2.1.4", "moment": "^2.26.0", "react": "^16.13.1", "react-dom": "^16.13.1", diff --git a/src/App.tsx b/src/App.tsx index f6ed80e..1ec6596 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,14 +1,21 @@ import React from 'react'; -import { Container } from "@material-ui/core"; +import { Container, ThemeProvider, Typography } from "@material-ui/core"; import { CompanyForm } from "./forms/company"; +import { studentTheme } from "./ui/theme"; function App() { return ( -
- - - -
+ +
+ + Zgłoszenie Praktyki + UX Demo + + Miejsce odbywania praktyki + + +
+
); } diff --git a/src/data/common.ts b/src/data/common.ts new file mode 100644 index 0000000..62671b8 --- /dev/null +++ b/src/data/common.ts @@ -0,0 +1,3 @@ +export interface Identifiable { + id?: string +} diff --git a/src/data/company.ts b/src/data/company.ts index c73333d..5e6f2db 100644 --- a/src/data/company.ts +++ b/src/data/company.ts @@ -1,3 +1,5 @@ +import { Identifiable } from "./common"; + export interface Address { street: string; building: string; @@ -6,14 +8,14 @@ export interface Address { country: string; } -export interface Company { +export interface Company extends Identifiable { name: string; url?: string; nip: string; offices: BranchOffice[]; } -export interface BranchOffice { +export interface BranchOffice extends Identifiable { address: Address; } @@ -24,4 +26,16 @@ export const emptyCompany: Company = { nip: "", } +export const emptyAddress: Address = { + street: "", + city: "", + country: "", + postalCode: "", + building: "" +} + +export const emptyBranchOffice: BranchOffice = { + address: emptyAddress, +} + export const formatAddress = ({ postalCode, building, street, city, country }: Address): string => `${street} ${building}, ${postalCode} ${city}, ${country}` diff --git a/src/forms/company.tsx b/src/forms/company.tsx index 4ff47bf..78729e1 100644 --- a/src/forms/company.tsx +++ b/src/forms/company.tsx @@ -1,69 +1,139 @@ -import React, { HTMLProps, useState } from "react"; -import { useFormik } from "formik"; -import { Nullable } from "../helpers"; -import { formatAddress, Company, emptyCompany, BranchOffice } from "../data/company"; +import React, { HTMLProps, useEffect, useMemo, useState } from "react"; +import { BranchOffice, Company, emptyAddress, emptyBranchOffice, emptyCompany, formatAddress } from "../data/company"; import { sampleCompanies } from "../provider/company"; import { Autocomplete } from "@material-ui/lab"; -import { Box, Grid, TextField, Typography } from "@material-ui/core"; +import { Grid, TextField, Typography } from "@material-ui/core"; +import { formFieldProps } from "./helpers"; -export type CompanyFormProps = { +export type CompanyFormProps = {} +export type BranchOfficeProps = { + company: Company, + disabled?: boolean } export const CompanyItem = ({ company, ...props }: { company: Company } & HTMLProps) => ( -
+
{ company.name }
NIP: { company.nip }
) export const OfficeItem = ({ office, ...props }: { office: BranchOffice } & HTMLProps) => ( -
-
{ office.address.city }, { office.address.street }
+
+
{ office.address.city }
{ formatAddress(office.address) }
) -export const CompanyForm: React.FunctionComponent = props => { - const formik = useFormik>({ onSubmit: values => console.log(values), initialValues: emptyCompany }); +export const BranchForm: React.FC = ({ company, disabled = false }) => { + const [office, setOffice] = useState(emptyBranchOffice) - const formikProps = (prop: keyof Company) => ({ onChange: formik.handleChange, value: formik.values[prop], name: prop }) + const canEdit = useMemo(() => !office.id && !disabled, [office.id, disabled]); - const [company, setCompany] = useState(null); + const fieldProps = formFieldProps(office.address, address => setOffice({ ...office, address })) + + const handleCityChange = (event: any, value: BranchOffice | string | null) => { + if (typeof value === "string") { + setOffice({ + ...emptyBranchOffice, + address: { + ...emptyAddress, + city: value, + } + }); + } else if (typeof value === "object" && value !== null) { + setOffice(value); + } else { + setOffice(emptyBranchOffice); + } + } + + const handleCityInput = (event: any, value: string) => { + const base = office.id ? emptyBranchOffice : office; + setOffice({ + ...base, + address: { + ...base.address, + city: value, + } + }) + } + + useEffect(() => void (office.id && setOffice(emptyBranchOffice)), [company]) return ( - - - option.name } - renderOption={ company => } - renderInput={ props => } - onChange={ (ev, value: Company | null) => { - setCompany(value) - - formik.setFieldValue("name", value?.name || "") - formik.setFieldValue("url", value?.url || ""); - formik.setFieldValue("nip", value?.nip || ""); - } } - /> - - - - - - - - - - + + office.address.city } - renderOption={ office => } - renderInput={ props => } + disabled={ disabled } + getOptionLabel={ office => typeof office == "string" ? office : office.address.city } + renderOption={ office => } + renderInput={ props => } + onChange={ handleCityChange } + onInputChange={ handleCityInput } + inputValue={ office.address.city } + value={ office.id ? office : null } + freeSolo /> + + + + + + + + + + + + ) } +export const CompanyForm: React.FunctionComponent = props => { + const [company, setCompany] = useState(emptyCompany); + const canEdit = useMemo(() => !company.id, [company.id]); + + const fieldProps = formFieldProps(company, setCompany) + + const handleCompanyChange = (event: any, value: Company | string | null) => { + if (typeof value === "string") { + setCompany({ + ...emptyCompany, + name: value, + }); + } else if (typeof value === "object" && value !== null) { + setCompany(value); + } else { + setCompany(emptyCompany); + } + } + + return ( + <> + + + option.name } + renderOption={ company => } + renderInput={ props => } + onChange={ handleCompanyChange } + freeSolo + /> + + + + + + + + + Oddział + + + ) +} + diff --git a/src/forms/helpers.ts b/src/forms/helpers.ts new file mode 100644 index 0000000..4fafb88 --- /dev/null +++ b/src/forms/helpers.ts @@ -0,0 +1,20 @@ +import { DOMEvent } from "../helpers"; + +type UpdatingEvent = "onBlur" | "onChange" | "onInput"; +type FormFieldHelperOptions = { + event: UpdatingEvent +} + +export function formFieldProps(subject: T, update: (value: T) => void, options: Partial> = {}) { + const { + event = "onChange" + } = options; + + return (field: keyof T) => ({ + value: subject[field], + [event]: (event: DOMEvent) => update({ + ...subject, + [field]: event.target.value, + } as T) + }) +} diff --git a/src/helpers.ts b/src/helpers.ts index 157ce58..e128e6c 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1 +1,5 @@ export type Nullable = { [P in keyof T]: T[P] | null } + +export interface DOMEvent extends Event { + target: TTarget; +} diff --git a/src/provider/company.ts b/src/provider/company.ts index e0379f0..be861f9 100644 --- a/src/provider/company.ts +++ b/src/provider/company.ts @@ -1,11 +1,17 @@ import { Company } from "../data/company"; +import { makeIdSequence } from "./helpers"; + +const companySequence = makeIdSequence(); +const officeSequence = makeIdSequence(); export const sampleCompanies: Company[] = [ { + id: companySequence(), name: "Intel", url: "https://www.intel.com/content/www/us/en/jobs/locations/poland.html", nip: "9570752316", offices: [{ + id: officeSequence(), address: { city: "Gdańsk", street: "ul. Słowackiego", @@ -16,10 +22,12 @@ export const sampleCompanies: Company[] = [ }] }, { + id: companySequence(), name: "IHS Markit", url: "http://ihsgdansk.com/", nip: "5842068320", offices: [{ + id: officeSequence(), address: { city: "Gdańsk", street: "ul. Marynarki Polskiej", @@ -30,10 +38,12 @@ export const sampleCompanies: Company[] = [ }] }, { + id: companySequence(), name: "Asseco Poland", url: "http://pl.asseco.com/", nip: "5842068320", offices: [{ + id: officeSequence(), address: { city: "Gdynia", street: "ul. Podolska", @@ -42,6 +52,7 @@ export const sampleCompanies: Company[] = [ country: "Poland" } }, { + id: officeSequence(), address: { city: "Łódź", street: "al. Marszałka Józefa Piłsudskiego", @@ -50,6 +61,7 @@ export const sampleCompanies: Company[] = [ country: "Poland" } }, { + id: officeSequence(), address: { city: "Wrocław", street: "Traugutta", diff --git a/src/provider/helpers.ts b/src/provider/helpers.ts new file mode 100644 index 0000000..20b4496 --- /dev/null +++ b/src/provider/helpers.ts @@ -0,0 +1,4 @@ +export const makeIdSequence = (start: number = 1) => { + let i = start; + return () => i++; +} diff --git a/src/ui/theme.ts b/src/ui/theme.ts new file mode 100644 index 0000000..05e110a --- /dev/null +++ b/src/ui/theme.ts @@ -0,0 +1,10 @@ +import { createMuiTheme } from "@material-ui/core"; + +export const studentTheme = createMuiTheme({ + props: { + MuiGrid: { + spacing: 4, + xs: 12, + } + } +}) diff --git a/yarn.lock b/yarn.lock index b908545..1489c71 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3672,11 +3672,6 @@ deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= -deepmerge@^2.1.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170" - integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA== - default-gateway@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b" @@ -4751,20 +4746,6 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" -formik@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/formik/-/formik-2.1.4.tgz#8deef07ec845ea98f75e03da4aad7aab4ac46570" - integrity sha512-oKz8S+yQBzuQVSEoxkqqJrKQS5XJASWGVn6mrs+oTWrBoHgByVwwI1qHiVc9GKDpZBU9vAxXYAKz2BvujlwunA== - dependencies: - deepmerge "^2.1.1" - hoist-non-react-statics "^3.3.0" - lodash "^4.17.14" - lodash-es "^4.17.14" - react-fast-compare "^2.0.1" - scheduler "^0.18.0" - tiny-warning "^1.0.2" - tslib "^1.10.0" - forwarded@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" @@ -5121,7 +5102,7 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: +hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -6694,11 +6675,6 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" -lodash-es@^4.17.14: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78" - integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ== - lodash._reinterpolate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" @@ -8832,11 +8808,6 @@ react-error-overlay@^6.0.7: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108" integrity sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA== -react-fast-compare@^2.0.1: - version "2.0.4" - resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" - integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== - react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.4: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -9404,14 +9375,6 @@ saxes@^3.1.9: dependencies: xmlchars "^2.1.1" -scheduler@^0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.18.0.tgz#5901ad6659bc1d8f3fdaf36eb7a67b0d6746b1c4" - integrity sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - scheduler@^0.19.1: version "0.19.1" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196"