diff --git a/build/index.html b/build/index.html index ea56627..c5f0a2e 100644 --- a/build/index.html +++ b/build/index.html @@ -1 +1,138 @@ -CODEZILLA hours
\ No newline at end of file + + + + + + + + + + + + + CODEZILLA hours + + + + +
+ + + + + diff --git a/src/App.tsx b/src/App.tsx index f70433d..4da21c7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,12 +1,8 @@ import React from "react"; import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; -import { CssBaseline, Snackbar, Typography } from "@material-ui/core"; +import { CssBaseline, Snackbar } from "@material-ui/core"; -import { - makeStyles, - ThemeProvider, - createMuiTheme, -} from "@material-ui/core/styles"; +import { ThemeProvider, createMuiTheme } from "@material-ui/core/styles"; import firebase from "./firebase/firebase.component"; @@ -18,6 +14,9 @@ import HoursContainer from "./hours/hours-container/HoursContainer.component"; import PreLoad from "./navigation/pre-load/preLoad.component"; import Admin from "./admin/Admin.component"; import AdminDetail from "./admin/detail/Detail.component"; +import Settings from "./settings/Settings.component"; + +import Profile from "./firebase/data/Profile"; require("dotenv").config(); @@ -32,31 +31,7 @@ const theme = createMuiTheme({ }, }); -const useStyles = makeStyles((theme) => ({ - root: {}, - menuButton: { - marginRight: 36, - }, - hide: { - display: "none", - }, - toolbar: { - display: "flex", - alignItems: "center", - justifyContent: "center", - padding: theme.spacing(0, 1), - ...theme.mixins.toolbar, - }, - content: { - flexGrow: 1, - }, - title: { - margin: theme.spacing(1, 1), - }, -})); - export default function App() { - const classes = useStyles(); const [profile, setProfile] = React.useState({ displayName: "", email: "", @@ -111,26 +86,31 @@ export default function App() { }, []); const fetchProfile = async (user: IUser) => { - const db = firebase.firestore(); - const snapshot = await db.collection("profile").get(); - const response = snapshot.docs.find((doc) => { - return doc.data().microsoftId === user.uid; - }); - - if (!response) { - createNewProfile(user); - return; - } - - const data = response.data(); - setProfile({ - id: response.id, - isAdmin: Boolean(data.isAdmin), - displayName: data.displayName, - email: data.email, - microsoftId: data.microsoftId, - }); - setIsLoading(false); + Profile.getProfile(user.uid) + .then((response) => { + if (response && response?.docs.length > 0) { + const doc = response.docs[0]; + const data = doc.data(); + if (data) { + setProfile({ + displayName: data.displayName, + email: data.email, + id: doc.id, + isAdmin: Boolean(data.isAdmin), + microsoftId: data.microsoftId, + }); + setIsLoading(false); + } + } else { + createNewProfile(user); + } + }) + .catch((error) => { + notification( + "Het is niet gelukt een profiel op te halen: " + error, + ); + setIsLoading(false); + }); }; const createNewProfile = (user: IUser) => { @@ -156,88 +136,69 @@ export default function App() { return ( -
- {isLoading ? ( - - ) : ( - - -
- - ( - - )} - /> - - + {isLoading ? ( + + ) : ( + + +
+ + ( - - {profile.isAdmin && ( - <> - ( - - )} - /> - ( - - )} - /> - )} - - - - )} - - - - - - - -
+ /> + + + + {profile.isAdmin && ( + <> + ( + + )} + /> + ( + + )} + /> + + )} + + + + )} + + + + + + +
); } - -const TemplateHeader = ({ classes }: { classes: any }) => ( - <> - - Maak hier een template voor je gemiddelde werkweek. Pas het template - toe op de hele urenstaat met een klik op de knop. - - - - Uren die je al hebt ingevuld worden niet overschreven. - - -); diff --git a/src/firebase/data/Hours.tsx b/src/firebase/data/Hours.tsx index abe9365..43c2ac0 100644 --- a/src/firebase/data/Hours.tsx +++ b/src/firebase/data/Hours.tsx @@ -52,6 +52,7 @@ export default { profile: document.profile, profileId: this.transformToString(document.profileId), project: this.transformToString(document.project), + hoursPerWeek: this.transformToString(document.hoursPerWeek), year: this.transformToString(document.year), month: this.transformToString(document.month), approved: document.approved ? document.approved : false, diff --git a/src/firebase/data/Profile.tsx b/src/firebase/data/Profile.tsx new file mode 100644 index 0000000..0641091 --- /dev/null +++ b/src/firebase/data/Profile.tsx @@ -0,0 +1,35 @@ +import firebase from "../firebase.component"; + +import IProfile from "../../common/interfaces/IProfile"; + +export default { + async getProfile(microsoftId: string): Promise { + const db = firebase.firestore(); + return await db + .collection("profile") + .where("microsoftId", "==", microsoftId) + .get(); + }, + + async updateProfile(document: IProfile): Promise { + const db = firebase.firestore(); + return await db + .collection("profile") + .doc(document.id) + .set({ + displayName: this.transformToString(document.displayName), + email: this.transformToString(document.email), + isAdmin: Boolean(document.isAdmin), + microsoftId: this.transformToString(document.microsoftId), + }); + }, + + async deleteProfile(documentId: string): Promise { + const db = firebase.firestore(); + return await db.collection("profile").doc(documentId).delete(); + }, + + transformToString(value: any): string { + return value ? value : ""; + }, +}; diff --git a/src/hours/hours-cell/HoursCell.component.tsx b/src/hours/hours-cell/HoursCell.component.tsx index 316fa52..209fe42 100644 --- a/src/hours/hours-cell/HoursCell.component.tsx +++ b/src/hours/hours-cell/HoursCell.component.tsx @@ -35,7 +35,9 @@ const HoursCell = ({ setValue(row[column]); }, [row, column, days]); - const handleHoursInput = (value: string, column: string, day: number) => { + const handleHoursInput = (input: string, column: string, day: number) => { + let value = input ? Number(input).toFixed(0) : ""; + value = value.toString(); const daysInput = [...days]; daysInput[day][column] = value; setValue(value); diff --git a/src/hours/hours-container/HoursContainer.component.tsx b/src/hours/hours-container/HoursContainer.component.tsx index 3e8da63..7143970 100644 --- a/src/hours/hours-container/HoursContainer.component.tsx +++ b/src/hours/hours-container/HoursContainer.component.tsx @@ -28,13 +28,14 @@ class HoursContainer extends Component { expandColumns: true, client: "", project: "", + hoursPerWeek: "40", profileId: "", profile: { - id: "", displayName: "", - microsoftId: "", email: "", + id: "", isAdmin: false, + microsoftId: "", }, saved: false, approved: false, @@ -287,6 +288,7 @@ class HoursContainer extends Component { profile: this.state.profile, profileId: this.state.profileId, project: this.state.project, + hoursPerWeek: this.state.hoursPerWeek, year: this.state.year, month: this.state.month, approved: this.state.approved, @@ -361,6 +363,7 @@ class HoursContainer extends Component { const totalHoursValidation = Validators.validateTotalHoursOfMonth( this.state.days, + this.state.hoursPerWeek ); if (totalHoursValidation) { validationMessages.push(totalHoursValidation); @@ -406,8 +409,10 @@ class HoursContainer extends Component {
{ days={mockDays} handleChange={() => {}} save={() => {}} - isTemplate={false} + readOnly={false} />, ); expect(wrapper).toMatchSnapshot(); @@ -49,7 +49,7 @@ describe("HoursGrid", () => { days={mockDays} handleChange={() => {}} save={() => {}} - isTemplate={false} + readOnly={false} />, ); @@ -68,11 +68,11 @@ describe("HoursGrid", () => { days={mockDays} handleChange={onBlur} save={() => {}} - isTemplate={false} + readOnly={false} />, ); const explanations = wrapper.find("input"); - explanations.first().simulate("blur", { target: { value: 8 } }); + explanations.first().simulate("blur", { target: { value: "8" } }); expect(onBlur).toHaveBeenCalledWith("days", mockDaysChanged); }); @@ -84,7 +84,7 @@ describe("HoursGrid", () => { days={mockWeekend} handleChange={() => {}} save={() => {}} - isTemplate={false} + readOnly={false} />, ); @@ -99,7 +99,7 @@ describe("HoursGrid", () => { days={mockWeekendTemplate} handleChange={() => {}} save={() => {}} - isTemplate + readOnly />, ); diff --git a/src/hours/hours-header/HoursHeader.component.tsx b/src/hours/hours-header/HoursHeader.component.tsx index 888e5e4..e36d335 100644 --- a/src/hours/hours-header/HoursHeader.component.tsx +++ b/src/hours/hours-header/HoursHeader.component.tsx @@ -21,8 +21,10 @@ interface IProps { isTemplate: boolean; client: string; project: string; + hoursPerWeek: string; expandColumns: boolean; handleInputChange: any; + validateHours: any; applyTemplate: any; getReport: any; getCSV: Function; @@ -58,8 +60,10 @@ const HoursHeader = ({ isTemplate, client, project, + hoursPerWeek, expandColumns, handleInputChange, + validateHours, applyTemplate, getReport, getCSV, @@ -91,8 +95,10 @@ const HoursHeader = ({ classes={classes} client={client} project={project} + hoursPerWeek={hoursPerWeek} approved={approved} handleInputChange={handleInputChange} + validateHours={validateHours} /> ); return ( @@ -139,8 +145,10 @@ const HoursHeader = ({ classes={classes} client={client} project={project} + hoursPerWeek={hoursPerWeek} approved={approved} handleInputChange={handleInputChange} + validateHours={validateHours} /> {validationMessages?.length > 0 ? ( @@ -244,7 +252,9 @@ interface IClientAndProjectProps { classes: any; client: string; project: string; + hoursPerWeek: string; handleInputChange: any; + validateHours: any; approved: boolean; } @@ -252,7 +262,9 @@ const ClientAndProject = ({ classes, client, project, + hoursPerWeek, handleInputChange, + validateHours, approved, }: IClientAndProjectProps) => ( <> @@ -276,5 +288,18 @@ const ClientAndProject = ({ handleInputChange("project", event.target.value) } /> + + handleInputChange("hoursPerWeek", event.target.value) + } + onBlur={(event) => + validateHours("hoursPerWeek", event.target.value) + } + /> ); diff --git a/src/hours/hours-header/hoursHeader.test.js b/src/hours/hours-header/hoursHeader.test.js index 82bff91..de12135 100644 --- a/src/hours/hours-header/hoursHeader.test.js +++ b/src/hours/hours-header/hoursHeader.test.js @@ -30,6 +30,7 @@ describe("HoursHeader", () => { handleInputChange={jest.fn()} client={"Codezilla"} project={"Hours"} + hoursPerWeek={"40"} expandColumns isTemplate={false} applyTemplate={jest.fn()} @@ -40,11 +41,13 @@ describe("HoursHeader", () => { it("should only render client and project on template", () => { const client = "Codezilla"; const project = "Hours"; + const hoursPerWeek = "40"; const wrapper = mount( { ); const inputs = wrapper.find("input"); - expect(inputs).toHaveLength(2); + expect(inputs).toHaveLength(3); const clientInput = inputs.find("#client"); expect(clientInput.props().value).toEqual(client); @@ -117,6 +120,7 @@ describe("HoursHeader", () => { handleInputChange={jest.fn()} client={"Codezilla"} project={"Hours"} + hoursPerWeek={"40"} expandColumns isTemplate={false} getReport={getReport} diff --git a/src/hours/hours.mock.js b/src/hours/hours.mock.js index 0dff215..80fd629 100644 --- a/src/hours/hours.mock.js +++ b/src/hours/hours.mock.js @@ -80,7 +80,7 @@ export const mockDaysChanged = [ { day: 1, date: new Date(2020, 3, 1), - worked: 8, + worked: "8", overtime: "", sick: "", holiday: "", diff --git a/src/hours/validation/Validators.tsx b/src/hours/validation/Validators.tsx index 179027a..d20a4f7 100644 --- a/src/hours/validation/Validators.tsx +++ b/src/hours/validation/Validators.tsx @@ -18,7 +18,7 @@ export default { } return; }, - validateTotalHoursOfMonth(days: IDay[]): string { + validateTotalHoursOfMonth(days: IDay[], hoursPerWeek: string): string { let userTotalHours = 0; let potentialTotalHours = 0; days.forEach((day: IDay) => { @@ -29,6 +29,8 @@ export default { potentialTotalHours += 8; }); + potentialTotalHours *= (Number(hoursPerWeek) / 40); + if (userTotalHours < potentialTotalHours) { return ( "Er zijn te weinig uren (" + diff --git a/src/navigation/header.component.tsx b/src/navigation/header.component.tsx index 4f32f31..5d6829a 100644 --- a/src/navigation/header.component.tsx +++ b/src/navigation/header.component.tsx @@ -61,10 +61,10 @@ export default function Header({ profile }: IProps) { {profile.isAdmin && (