Polish form #2

Manually merged
system-praktyk merged 5 commits from feature_basic_form into master 2020-06-13 00:16:28 +02:00
29 changed files with 2418 additions and 3919 deletions

34
.babelrc.js Normal file
View File

@ -0,0 +1,34 @@
const plugins = [
[
'babel-plugin-import',
{
'libraryName': '@material-ui/lab',
'libraryDirectory': 'esm',
'camel2DashComponentName': false
},
'lab'
],
[
'babel-plugin-import',
{
'libraryName': '@material-ui/core',
'libraryDirectory': 'esm',
'camel2DashComponentName': false
},
'core'
],
[
'babel-plugin-import',
{
'libraryName': '@material-ui/icons',
'libraryDirectory': 'esm',
'camel2DashComponentName': false
},
'icons'
]
];
module.exports = {
presets: ["react-app"],
plugins
};

View File

@ -3,30 +3,52 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@babel/core": "7.9.0",
"@babel/preset-typescript": "^7.10.1",
"@date-io/moment": "^1.3.13",
"@material-ui/core": "^4.10.1",
"@material-ui/lab": "^4.0.0-alpha.55",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/moment": "^2.13.0",
"@material-ui/pickers": "^3.2.10",
"@types/node": "^12.0.0",
"@types/react": "^16.9.0",
"@types/react-dom": "^16.9.0",
"@typescript-eslint/eslint-plugin": "^2.10.0",
"@typescript-eslint/parser": "^2.10.0",
"babel-core": "^6.26.3",
"babel-loader": "^8.1.0",
"babel-plugin-import": "^1.13.0",
"babel-preset-react-app": "^9.1.2",
"clean-webpack-plugin": "^3.0.0",
"css-loader": "3.4.2",
"date-holidays": "^1.5.3",
"file-loader": "4.3.0",
"html-webpack-plugin": "4.0.0-beta.11",
"moment": "^2.26.0",
"node-sass": "^4.14.1",
"optimize-css-assets-webpack-plugin": "5.0.3",
"postcss-flexbugs-fixes": "4.1.0",
"postcss-loader": "3.0.0",
"postcss-normalize": "8.0.1",
"postcss-preset-env": "6.7.0",
"postcss-safe-parser": "4.0.1",
"react": "^16.13.1",
"react-app-polyfill": "^1.0.6",
"react-dev-utils": "^10.2.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.1",
"typescript": "~3.7.2"
"sass-loader": "8.0.2",
"style-loader": "0.23.1",
"ts-loader": "^7.0.5",
"typescript": "~3.7.2",
"url-loader": "2.3.0",
"webpack": "4.42.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "3.10.3",
"workbox-webpack-plugin": "4.3.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
"serve": "webpack-dev-server --mode development",
"watch": "webpack --watch --mode development",
"build": "webpack --mode production"
},
"browserslist": {
"production": [

View File

@ -2,42 +2,14 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap&subset=latin,latin-ext" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View File

@ -1,22 +0,0 @@
import React from 'react';
import { Container, ThemeProvider, Typography } from "@material-ui/core";
import { CompanyForm } from "./forms/company";
import { studentTheme } from "./ui/theme";
function App() {
return (
<ThemeProvider theme={ studentTheme }>
<div className="app">
<Container maxWidth={"md"}>
<Typography variant="h3">Zgłoszenie Praktyki</Typography>
<Typography variant="subtitle1" style={{ marginBottom: '100px' }}>UX Demo</Typography>
<Typography variant="h5">Miejsce odbywania praktyki</Typography>
<CompanyForm />
</Container>
</div>
</ThemeProvider>
);
}
export default App;

40
src/app.tsx Normal file
View File

@ -0,0 +1,40 @@
import React from 'react';
import { Container, Typography } from "@material-ui/core";
import { MuiThemeProvider as ThemeProvider, StylesProvider } from "@material-ui/core/styles";
import { studentTheme } from "./ui/theme";
import { InternshipForm } from "@/forms/Internship";
import { MuiPickersUtilsProvider } from "@material-ui/pickers";
import MomentUtils from "@date-io/moment";
import "moment/locale/pl"
moment.locale("pl")
import '@/styles/overrides.scss'
import moment, { Moment } from "moment";
class LocalizedMomentUtils extends MomentUtils {
getDatePickerHeaderText(date: Moment): string {
return this.format(date, "d MMM yyyy");
}
}
function App() {
return (
<StylesProvider injectFirst>
<MuiPickersUtilsProvider utils={ LocalizedMomentUtils } libInstance={ moment }>
<ThemeProvider theme={ studentTheme }>
<div className="app">
<Container maxWidth={"md"}>
<Typography variant="h3">Zgłoszenie Praktyki</Typography>
<Typography variant="subtitle1">UX Demo</Typography>
<InternshipForm />
</Container>
</div>
</ThemeProvider>
</MuiPickersUtilsProvider>
</StylesProvider>
);
}
export default App;

9
src/data/course.ts Normal file
View File

@ -0,0 +1,9 @@
import { InternshipProgramEntry } from "./internship";
import { Semester } from "./student";
import { Identifiable } from "./common";
export interface Course extends Identifiable {
name: string,
desiredSemesters: Semester[],
possibleProgramEntries: InternshipProgramEntry[];
}

6
src/data/index.ts Normal file
View File

@ -0,0 +1,6 @@
export * from './common'
export * from './company'
export * from './course'
export * from './internship'
export * from './student'
export { emptyInternship } from "@/provider/dummy/internship";

View File

@ -1,23 +1,64 @@
import { Moment } from "moment";
import { Nullable } from "../helpers";
import { Identifiable } from "./common";
import { countWorkingWeeksInRange } from "@/utils/date";
import { Student } from "@/data/student";
export enum InternshipType { }
export enum InternshipProgram { }
export enum InternshipType {
FreeInternship = "FreeInternship",
GraduateInternship = "GraduateInternship",
FreeApprenticeship = "FreeApprenticeship",
PaidApprenticeship = "PaidApprenticeship",
ForeignInternship = "ForeignInternship",
UOP = "UOP",
UD = "UD",
UZ = "UZ",
Other = "Other",
}
export interface Internship {
export const internshipTypeLabels: { [type in InternshipType]: { label: string, description?: string } } = {
[InternshipType.FreeInternship]: {
label: "Umowa o organizację praktyki",
description: "Praktyka bezpłatna"
},
[InternshipType.GraduateInternship]: {
label: "Umowa o praktykę absolwencką"
},
[InternshipType.FreeApprenticeship]: {
label: "Umowa o staż bezpłatny"
},
[InternshipType.PaidApprenticeship]: {
label: "Umowa o staż płatny",
description: "np. przemysłowy"
},
[InternshipType.ForeignInternship]: {
label: "Praktyka zagraniczna",
description: "np. IAESTE, ERASMUS"
},
[InternshipType.UOP]: {
label: "Umowa o pracę"
},
[InternshipType.UD]: {
label: "Umowa o dzieło (w tym B2B)"
},
[InternshipType.UZ]: {
label: "Umowa o zlecenie (w tym B2B)"
},
[InternshipType.Other]: {
label: "Inna",
description: "Należy wprowadzić samodzielnie"
},
}
export interface InternshipProgramEntry extends Identifiable {
description: string;
}
export interface Internship extends Identifiable {
intern: Student,
type: InternshipType;
program: InternshipProgram;
program: InternshipProgramEntry[];
startDate: Moment;
endDate: Moment;
isAccepted: boolean;
lengthInWeeks: number;
}
export const emptyInternship: Nullable<Internship> = {
endDate: null,
startDate: null,
type: null,
program: null,
isAccepted: false,
lengthInWeeks: 0,
}

13
src/data/student.ts Normal file
View File

@ -0,0 +1,13 @@
import { Course } from "./course";
import { Identifiable } from "./common";
export type Semester = number;
export interface Student extends Identifiable{
name: string;
surname: string;
email: string;
albumNumber: string;
semester: Semester;
course: Course;
}

View File

@ -1,21 +1,157 @@
import React from "react";
import { useFormik } from "formik";
import { emptyInternship, Internship } from "../data/internship";
import { Nullable } from "../helpers";
import { TextField } from "@material-ui/core";
import React, { ChangeEvent, HTMLProps, useMemo, useState } from "react";
import {
FormControl,
Grid,
Input,
InputLabel,
Typography,
FormHelperText,
TextField,
FormGroup,
FormControlLabel,
Checkbox,
FormLabel,
Button
} from "@material-ui/core";
import { KeyboardDatePicker as DatePicker } from "@material-ui/pickers";
import { CompanyForm } from "@/forms/company";
import { StudentForm } from "@/forms/student";
import { sampleStudent } from "@/provider/dummy/student";
import { Company, Course, emptyInternship, Internship, InternshipType, internshipTypeLabels } from "@/data";
import { Nullable } from "@/helpers";
import moment, { Moment } from "moment";
import { computeWorkingHours } from "@/utils/date";
import { Autocomplete } from "@material-ui/lab";
import { formFieldProps } from "@/forms/helpers";
export type InternshipFormProps = {
export type InternshipFormProps = {}
export type InternshipFormSectionProps = {
internship: Nullable<Internship>,
onChange: (internship: Nullable<Internship>) => void,
}
export const InternshipForm: React.FunctionComponent<InternshipFormProps> = props => {
const formik = useFormik<Nullable<Internship>>({ onSubmit: values => console.log(values), initialValues: emptyInternship });
const formikProps = (prop: keyof Internship) => ({ onChange: formik.handleChange, value: formik.values[prop], name: prop })
export const InternshipTypeItem = ({ type, ...props }: { type: InternshipType } & HTMLProps<any>) => {
const info = internshipTypeLabels[type];
return (
<div className="internship-form">
<TextField {...formikProps("startDate")} />
<div className="internship=type-item" { ...props }>
<div>{ info.label }</div>
{ info.description && <Typography variant="caption">{ info.description }</Typography> }
</div>
)
}
const InternshipProgramForm = ({ internship, onChange }: InternshipFormSectionProps) => {
const fieldProps = formFieldProps(internship, onChange);
const course = internship.intern?.course as Course;
return (
<Grid container>
<Grid item md={4}>
<Autocomplete renderInput={ props => <TextField { ...props } label="Rodzaj praktyki/umowy" fullWidth/> }
getOptionLabel={ (option: InternshipType) => internshipTypeLabels[option].label }
renderOption={ (option: InternshipType) => <InternshipTypeItem type={ option } /> }
options={ Object.values(InternshipType) as InternshipType[] }
disableClearable
{ ...fieldProps("type", (event, value) => value) as any }
/>
</Grid>
<Grid item md={8}>
{ internship.type === InternshipType.Other && <TextField label={"Inny - Wprowadź"} fullWidth/> }
</Grid>
<Grid item>
<FormGroup>
<FormLabel component="legend" className="subsection-header">Realizowane punkty programu praktyk (minimum 3)</FormLabel>
{ course.possibleProgramEntries.map(entry => {
return (
<FormControlLabel label={ entry.description } key={ entry.id }
control={ <Checkbox /> }
/>
)
}) }
</FormGroup>
</Grid>
</Grid>
)
}
const InternshipDurationForm = ({ internship }: InternshipFormSectionProps) => {
const [startDate, setStartDate] = useState<Moment | null>(internship.startDate);
const [endDate, setEndDate] = useState<Moment | null>(internship.endDate);
const [overrideHours, setHoursOverride] = useState<number | null>(null)
const [workingHours, setWorkingHours] = useState<number>(40)
const computedHours = useMemo(() => startDate && endDate && computeWorkingHours(startDate, endDate, workingHours / 5), [startDate, endDate, workingHours]);
const hours = useMemo(() => overrideHours !== null ? overrideHours : computedHours || null, [overrideHours, computedHours]);
const weeks = useMemo(() => hours !== null ? Math.floor(hours / 40) : null, [ hours ]);
return (
<Grid container>
<Grid item md={ 6 }>
<DatePicker value={ startDate } onChange={ setStartDate }
format="DD MMMM yyyy"
clearable disableToolbar fullWidth
variant="inline" label={ "Data rozpoczęcia praktyki" }
/>
</Grid>
<Grid item md={ 6 }>
<DatePicker value={ endDate } onChange={ setEndDate }
format="DD MMMM yyyy"
clearable disableToolbar fullWidth
variant="inline" label={ "Data zakończenia praktyki" }
minDate={ startDate || moment() }
/>
</Grid>
<Grid item md={ 4 }>
<FormControl fullWidth>
<InputLabel>Wymiar etatu</InputLabel>
<Input value={ workingHours }
onChange={ ev => setWorkingHours(parseInt(ev.target.value) || 0) }
fullWidth
/>
<FormHelperText>Liczba godzin w tygodniu roboczym</FormHelperText>
</FormControl>
</Grid>
<Grid item md={ 4 }>
<FormControl fullWidth>
<InputLabel>Łączna liczba godzin</InputLabel>
<Input value={ hours || "" }
onChange={ ev => setHoursOverride(parseInt(ev.target.value) || 0) }
fullWidth
/>
</FormControl>
</Grid>
<Grid item md={ 4 }>
<FormControl fullWidth>
<InputLabel>Liczba tygodni</InputLabel>
<Input value={ weeks || "" }
disabled
fullWidth
/>
<FormHelperText>Wyliczona automatycznie</FormHelperText>
</FormControl>
</Grid>
</Grid>
);
}
export const InternshipForm: React.FunctionComponent<InternshipFormProps> = props => {
const [internship, setInternship] = useState<Nullable<Internship>>({ ...emptyInternship, intern: sampleStudent })
return (
<div className="internship-form">
<Typography variant="h5" className="section-header">Dane osoby odbywającej praktykę</Typography>
<StudentForm student={ sampleStudent }/>
<Typography variant="h5" className="section-header">Rodzaj i program praktyki</Typography>
<InternshipProgramForm internship={ internship } onChange={ setInternship }/>
<Typography variant="h5" className="section-header">Czas trwania praktyki</Typography>
<InternshipDurationForm internship={ internship } onChange={ setInternship }/>
<Typography variant="h5" className="section-header">Miejsce odbywania praktyki</Typography>
<CompanyForm/>
<Button variant="contained" color="primary">Wyślij</Button>
</div>
)
}

View File

@ -1,6 +1,6 @@
import React, { HTMLProps, useEffect, useMemo, useState } from "react";
import { BranchOffice, Company, emptyAddress, emptyBranchOffice, emptyCompany, formatAddress } from "../data/company";
import { sampleCompanies } from "../provider/company";
import { BranchOffice, Company, emptyAddress, emptyBranchOffice, emptyCompany, formatAddress } from "@/data";
import { sampleCompanies } from "@/provider/dummy";
import { Autocomplete } from "@material-ui/lab";
import { Grid, TextField, Typography } from "@material-ui/core";
import { formFieldProps } from "./helpers";
@ -63,33 +63,35 @@ export const BranchForm: React.FC<BranchOfficeProps> = ({ company, disabled = fa
useEffect(() => void (office.id && setOffice(emptyBranchOffice)), [company])
return (
<Grid container>
<Grid item md={ 7 }>
<Autocomplete options={ company?.offices || [] }
disabled={ disabled }
getOptionLabel={ office => typeof office == "string" ? office : office.address.city }
renderOption={ office => <OfficeItem office={ office }/> }
renderInput={ props => <TextField { ...props } label={ "Miasto" } fullWidth/> }
onChange={ handleCityChange }
onInputChange={ handleCityInput }
inputValue={ office.address.city }
value={ office.id ? office : null }
freeSolo
/>
<div>
<Grid container>
<Grid item md={ 7 }>
<Autocomplete options={ company?.offices || [] }
disabled={ disabled }
getOptionLabel={ office => typeof office == "string" ? office : office.address.city }
renderOption={ office => <OfficeItem office={ office }/> }
renderInput={ props => <TextField { ...props } label={ "Miasto" } fullWidth/> }
onChange={ handleCityChange }
onInputChange={ handleCityInput }
inputValue={ office.address.city }
value={ office.id ? office : null }
freeSolo
/>
</Grid>
<Grid item md={ 2 }>
<TextField label={ "Kod pocztowy" } fullWidth disabled={ !canEdit } { ...fieldProps("postalCode") }/>
</Grid>
<Grid item md={ 3 }>
<TextField label={ "Kraj" } fullWidth disabled={ !canEdit } { ...fieldProps("country") }/>
</Grid>
<Grid item md={ 10 }>
<TextField label={ "Ulica" } fullWidth disabled={ !canEdit } { ...fieldProps("street") }/>
</Grid>
<Grid item md={ 2 }>
<TextField label={ "Nr Budynku" } fullWidth disabled={ !canEdit } { ...fieldProps("building") }/>
</Grid>
</Grid>
<Grid item md={ 2 }>
<TextField label={ "Kod pocztowy" } fullWidth disabled={ !canEdit } { ...fieldProps("postalCode") }/>
</Grid>
<Grid item md={ 3 }>
<TextField label={ "Kraj" } fullWidth disabled={ !canEdit } { ...fieldProps("country") }/>
</Grid>
<Grid item md={ 10 }>
<TextField label={ "Ulica" } fullWidth disabled={ !canEdit } { ...fieldProps("street") }/>
</Grid>
<Grid item md={ 2 }>
<TextField label={ "Nr Budynku" } fullWidth disabled={ !canEdit } { ...fieldProps("building") }/>
</Grid>
</Grid>
</div>
)
}
@ -114,7 +116,7 @@ export const CompanyForm: React.FunctionComponent<CompanyFormProps> = props => {
return (
<>
<Grid container style={ { marginBottom: 0, marginTop: 0 } }>
<Grid container>
<Grid item>
<Autocomplete options={ sampleCompanies }
getOptionLabel={ option => option.name }
@ -127,11 +129,11 @@ export const CompanyForm: React.FunctionComponent<CompanyFormProps> = props => {
<Grid item md={ 4 }>
<TextField label={ "NIP" } fullWidth { ...fieldProps("nip") } disabled={ !canEdit }/>
</Grid>
<Grid item md={ 8 }>
<TextField label={ "Url" } fullWidth { ...fieldProps("url") } disabled={ !canEdit }/>
</Grid>
{/*<Grid item md={ 8 }>*/}
{/* <TextField label={ "Url" } fullWidth { ...fieldProps("url") } disabled={ !canEdit }/>*/}
{/*</Grid>*/}
</Grid>
<Typography variant="subtitle1">Oddział</Typography>
<Typography variant="subtitle1" className="subsection-header">Oddział</Typography>
<BranchForm company={ company }/>
</>
)

View File

@ -1,4 +1,4 @@
import { DOMEvent } from "../helpers";
import { DOMEvent } from "@/helpers";
type UpdatingEvent = "onBlur" | "onChange" | "onInput";
type FormFieldHelperOptions<T> = {
@ -10,11 +10,14 @@ export function formFieldProps<T>(subject: T, update: (value: T) => void, option
event = "onChange"
} = options;
return (field: keyof T) => ({
return <P extends keyof T, TArgs extends any[]>(
field: P,
extractor: (...args: TArgs) => T[P] = ((event: DOMEvent<HTMLInputElement>) => event.target.value as unknown as T[P]) as any
) => ({
value: subject[field],
[event]: (event: DOMEvent<HTMLInputElement>) => update({
[event]: (...args: TArgs) => update({
...subject,
[field]: event.target.value,
[field]: extractor(...args),
} as T)
})
}

44
src/forms/student.tsx Normal file
View File

@ -0,0 +1,44 @@
import { Course, Student } from "@/data";
import { Button, Grid, TextField } from "@material-ui/core";
import { Alert, Autocomplete } from "@material-ui/lab";
import React from "react";
import { sampleCourse } from "@/provider/dummy/student";
type StudentFormProps = {
student: Student
}
export const StudentForm = ({ student }: StudentFormProps) => {
return (
<>
<Grid container>
<Grid item md={4}>
<TextField label="Imię" value={ student.name } disabled fullWidth/>
</Grid>
<Grid item md={4}>
<TextField label="Nazwisko" value={ student.surname } disabled fullWidth/>
</Grid>
<Grid item md={4}>
<TextField label="Nr Indeksu" value={ student.albumNumber } disabled fullWidth/>
</Grid>
<Grid item md={9}>
<Autocomplete
getOptionLabel={ (course: Course) => course.name }
renderInput={ props => <TextField { ...props } label={ "Kierunek" } fullWidth/> }
options={[ sampleCourse ]}
value={ student.course }
disabled
/>
</Grid>
<Grid item md={3}>
<TextField label="Semestr" value={ student.semester } disabled fullWidth/>
</Grid>
<Grid item>
<Alert severity="warning" action={ <Button color="inherit" size="small">skontaktuj się z opiekunem</Button> }>
Powyższe dane nie poprawne?
</Alert>
</Grid>
</Grid>
</>
);
}

View File

@ -1,7 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
import App from './app';
ReactDOM.render(
<React.StrictMode>
@ -9,8 +8,3 @@ ReactDOM.render(
</React.StrictMode>,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

View File

@ -1,4 +1,4 @@
import { Company } from "../data/company";
import { Company } from "@/data";
import { makeIdSequence } from "./helpers";
const companySequence = makeIdSequence();

View File

@ -0,0 +1,15 @@
import { Identifiable } from "@/data";
type SequenceGenerator = {
(): string;
assignIds<T>(objects: T[]): T[];
}
export const makeIdSequence = (start: number = 1): SequenceGenerator => {
let i = start;
const sequence = () => (i++).toString();
sequence.assignIds = <T extends Identifiable>(objects: T[]): T[] => objects.map(obj => ({ ...obj, id: sequence() }))
return sequence as SequenceGenerator;
}

View File

@ -0,0 +1 @@
export * from './company'

View File

@ -0,0 +1,12 @@
import { Nullable } from "@/helpers";
import { Internship, InternshipType } from "@/data";
export const emptyInternship: Nullable<Internship> = {
intern: null,
endDate: null,
startDate: null,
type: null,
program: null,
isAccepted: false,
lengthInWeeks: 0,
}

View File

@ -0,0 +1,39 @@
import { Course, InternshipProgramEntry, Student } from "@/data"
import { makeIdSequence } from "./helpers";
const programEntryIdSequence = makeIdSequence();
const courseIdSequence = makeIdSequence();
const studentIdSequence = makeIdSequence();
export const sampleProgramEntries: InternshipProgramEntry[] = programEntryIdSequence.assignIds([
{ description: "Instalacja, konfiguracja i administracja niewielkich sieci komputerowych, w tym bezprzewodowych." },
{ description: "Implementacja polityki bezpieczeństwa informacji w firmie lub instytucji, instalacja ochrony antywirusowej, konfiguracja zapór ogniowych." },
{ description: "Instalacja, konfiguracja i administracja oprogramowania, w szczególnościsystemów operacyjnychiserwerów aplikacji." },
{ description: "Projektowanie, implementacja i modyfikacjeoprogramowaniaw różnych technologiach i dla różnych zastosowań." },
{ description: "Testowanie oprogramowania, także z wykorzystaniem narzędzi do testowania automatycznego." },
{ description: "Wykorzystanie otwartych komponentów programowych z uwzględnieniem prawnych zależności pomiędzy nimi a produktem wynikowym." },
{ description: "Projektowanie i implementacja baz danych oraz badanie ich wydajności." },
{ description: "Posługiwanie się zaawansowanymi metodami i technologiami przetwarzania, składowania, transformacji i analizy danych(Big Data, Business Intelligence, hurtownie danych)" },
{ description: "Projektowanie i prototypowaniezaawansowanychinterfejsów użytkownika. " },
{ description: "Posługiwanie się zaawansowanymi narzędziami informatycznymi do przetwarzania plików dźwiękowych, obrazów i wideo." },
{ description: "Konfiguracjaurządzeń zewnętrznych komputera, rozbudowa i modyfikacja jego struktury modułówi urządzeń wewnętrznych." },
{ description: "Przygotowywanie i testowanie oprogramowania prostych mikrokontrolerów i systemów wbudowanych." },
{ description: "Przygotowywanie i analiza dokumentacjitechnicznej przedsięwzięć informatycznych,wykorzystanie modeli i narzędzi zarządzania dla e-biznesu." },
]);
export const sampleCourse: Course = {
id: courseIdSequence(),
name: "Informatyka",
desiredSemesters: [6],
possibleProgramEntries: sampleProgramEntries,
}
export const sampleStudent: Student = {
id: studentIdSequence(),
name: "Jan",
surname: "Kowalski",
albumNumber: "123456",
email: "s123456@student.pg.edu.pl",
course: sampleCourse,
semester: 6,
}

View File

@ -1,4 +0,0 @@
export const makeIdSequence = (start: number = 1) => {
let i = start;
return () => i++;
}

7
src/provider/index.ts Normal file
View File

@ -0,0 +1,7 @@
import * as dummy from './dummy'
export {
dummy,
}
export default dummy;

View File

@ -1 +1,66 @@
/// <reference types="react-scripts" />
/// <reference types="node" />
/// <reference types="react" />
/// <reference types="react-dom" />
declare namespace NodeJS {
interface ProcessEnv {
readonly NODE_ENV: 'development' | 'production' | 'test';
readonly PUBLIC_URL: string;
}
}
declare module '*.bmp' {
const src: string;
export default src;
}
declare module '*.gif' {
const src: string;
export default src;
}
declare module '*.jpg' {
const src: string;
export default src;
}
declare module '*.jpeg' {
const src: string;
export default src;
}
declare module '*.png' {
const src: string;
export default src;
}
declare module '*.webp' {
const src: string;
export default src;
}
declare module '*.svg' {
import * as React from 'react';
export const ReactComponent: React.FunctionComponent<React.SVGProps<
SVGSVGElement
> & { title?: string }>;
const src: string;
export default src;
}
declare module '*.module.css' {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module '*.module.scss' {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module '*.module.sass' {
const classes: { readonly [key: string]: string };
export default classes;
}

View File

@ -1,149 +0,0 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
type Config = {
onSuccess?: (registration: ServiceWorkerRegistration) => void;
onUpdate?: (registration: ServiceWorkerRegistration) => void;
};
export function register(config?: Config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(
process.env.PUBLIC_URL,
window.location.href
);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl: string, config?: Config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl: string, config?: Config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { 'Service-Worker': 'script' }
})
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready
.then(registration => {
registration.unregister();
})
.catch(error => {
console.error(error.message);
});
}
}

16
src/styles/overrides.scss Normal file
View File

@ -0,0 +1,16 @@
.MuiGrid-container {
margin-bottom: 0;
margin-top: 0;
}
.section-header {
margin-top: 2.5rem;
}
.subsection-header {
margin-top: 1.5rem;
}
#root {
margin: 100px 0;
}

View File

@ -1,9 +1,9 @@
import { createMuiTheme } from "@material-ui/core";
import { createMuiTheme } from "@material-ui/core/styles";
export const studentTheme = createMuiTheme({
props: {
MuiGrid: {
spacing: 4,
spacing: 3,
xs: 12,
}
}

29
src/utils/date.ts Normal file
View File

@ -0,0 +1,29 @@
import { Moment } from "moment";
import Holidays from "date-holidays";
const holidays = new Holidays()
export function countWorkingWeeksInRange(start: Moment, end: Moment) {
const iterator = start.clone();
let i = 0;
while (iterator.isBefore(end)) {
iterator.add(1, 'day');
const holiday = holidays.isHoliday(iterator.toDate());
const isHoliday = holiday && holiday.type == "public";
const isWeekend = [6, 7].includes(iterator.isoWeekday())
if (isHoliday || isWeekend) {
continue;
}
i++;
}
return i;
}
export function computeWorkingHours(start: Moment, end: Moment, perDay: number = 8) {
return countWorkingWeeksInRange(start, end) * perDay;
}

View File

@ -12,12 +12,17 @@
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"module": "ES6",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react"
"jsx": "react",
"sourceMap": true,
"baseUrl": "./",
"paths": {
"@/*": ["./src/*"]
}
},
"include": [
"src"

65
webpack.config.js Normal file
View File

@ -0,0 +1,65 @@
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const { GenerateSW } = require('workbox-webpack-plugin');
const HtmlWebpackPlugin = require("html-webpack-plugin")
const config = {
entry: {
main: ['./src/index.tsx'],
},
output: {
path: path.resolve('./build/'),
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
alias: {
'@': path.resolve(__dirname, './src'),
}
},
module: {
rules: [{
test: /\.s[ac]ss$/,
use: ["style-loader", "css-loader?sourceMap", "sass-loader?sourceMap"]
}, {
test: /\.css$/,
use: ["style-loader", "css-loader"]
}, {
test: /\.tsx?$/,
use: 'babel-loader',
exclude: /node_modules/
}, {
test: /\.(png|svg|jpg|gif)$/,
use: 'file-loader',
}, {
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: 'file-loader'
}]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({ // Also generate a test.html
filename: 'index.html',
template: 'public/index.html'
}),
],
devServer: {
contentBase: path.resolve("./public/"),
port: 3000,
host: 'system-praktyk-front.localhost',
disableHostCheck: true,
},
optimization: {
usedExports: true
}
};
module.exports = (env, argv) => {
if (argv.mode === 'development') {
config.devtool = 'inline-source-map';
}
process.env.BABEL_ENV = argv.mode || 'production';
return config;
};

5354
yarn.lock

File diff suppressed because it is too large Load Diff