diff --git a/app/components/troi.client.tsx b/app/components/troi.client.tsx index d4063e0c..c6607adb 100644 --- a/app/components/troi.client.tsx +++ b/app/components/troi.client.tsx @@ -12,6 +12,8 @@ import { import moment from "moment"; import { TimeEntries, TimeEntry } from "~/troi/TimeEntry"; import { CalculationPosition } from "~/troi/CalculationPosition"; +import { useFetcher } from "@remix-run/react"; +import { convertToCacheFormat } from "~/utils/TimeEntryCache"; interface Props { username: string; @@ -71,21 +73,25 @@ export default function Troi(props: Props) { selectedDate, ); + const fetcher = useFetcher(); + async function onAddEntryClicked( position: CalculationPosition, hours: number, description: string, ) { - // await troiController?.addEntry( - // selectedDate, - // position, - // hours, - // description, - // () => {} - // ); - // troiController?.getEntriesFor(selectedDate).then((entries) => { - // setEntriesForSelectedDate(entries); - // }); + fetcher.submit( + { + hours, + description, + }, + { + method: "POST", + action: `/calculation_postions/${ + position.id + }/time_entries/${convertToCacheFormat(selectedDate)}`, + }, + ); } async function onUpdateEntryClicked( @@ -98,12 +104,7 @@ export default function Troi(props: Props) { // }); } - async function onDeleteEntryClicked(entry: TimeEntry, positionId: number) { - // await troiController?.deleteEntry(entry, positionId, () => {}); - // troiController?.getEntriesFor(selectedDate).then((entries) => { - // setEntriesForSelectedDate(entries); - // }); - } + async function onDeleteEntryClicked(entry: TimeEntry, positionId: number) {} return (
diff --git a/app/routes/calculation_postions.$calculationPositionId.time_entries.$date.tsx b/app/routes/calculation_postions.$calculationPositionId.time_entries.$date.tsx new file mode 100644 index 00000000..1ea52ab1 --- /dev/null +++ b/app/routes/calculation_postions.$calculationPositionId.time_entries.$date.tsx @@ -0,0 +1,41 @@ +import { ActionFunctionArgs } from "@remix-run/node"; +import TroiApiService from "troi-library"; +import { login } from "~/cookies.server"; +import { addTimeEntry } from "~/troi/troiControllerServer"; + +export async function action({ request, params }: ActionFunctionArgs) { + if (request.method !== "POST") { + throw new Error("MethodNotAllowed"); + // todo (Malte Laukötter, 2023-12-15): throw a error with the correct error type (405) + } + + if (params.calculationPositionId === undefined) { + throw new Error("Missing calculationPositionId"); + } + + if (params.date === undefined) { + throw new Error("Missing date"); + } + + const body = await request.formData(); + + const hours = body.get("hours"); + if (typeof hours !== "string") { + throw new Error("Missing hours"); + } + + const description = body.get("description"); + if (typeof description !== "string") { + throw new Error("Missing description"); + } + + addTimeEntry( + request, + parseInt(params.calculationPositionId, 10), + params.date, + parseFloat(hours), + description, + ); + + return; +} diff --git a/app/troi/troiControllerServer.ts b/app/troi/troiControllerServer.ts index fa3a3a7b..bbf3e8e2 100644 --- a/app/troi/troiControllerServer.ts +++ b/app/troi/troiControllerServer.ts @@ -102,8 +102,14 @@ async function fetchCalculationPositionsAndSaveToSession(request: Request) { clientId: clientId.toString(), favoritesOnly: true.toString(), }, - })) as any[] - ).map((obj: any) => ({ + })) as { + Id: number; + DisplayPath: string; + Subproject: { + id: number; + }; + }[] + ).map((obj) => ({ name: obj.DisplayPath, id: obj.Id, subprojectId: obj.Subproject.id, @@ -169,28 +175,37 @@ async function fetchTimeEntriesAndSaveToSession(request: Request) { const calculationPositions = await getCalculationPositions(request); const entries: TimeEntry[] = ( await Promise.all( - calculationPositions.map((calcPos) => - troiApi.makeRequest({ - url: "/billings/hours", - params: { - clientId: clientId.toString(), - employeeId: employeeId.toString(), - calculationPositionId: calcPos.id.toString(), - startDate: formatDateToYYYYMMDD(addDaysToDate(new Date(), -366)), - endDate: formatDateToYYYYMMDD(addDaysToDate(new Date(), 366)), - }, - }), + calculationPositions.map( + (calcPos) => + troiApi.makeRequest({ + url: "/billings/hours", + params: { + clientId: clientId.toString(), + employeeId: employeeId.toString(), + calculationPositionId: calcPos.id.toString(), + startDate: formatDateToYYYYMMDD(addDaysToDate(new Date(), -366)), + endDate: formatDateToYYYYMMDD(addDaysToDate(new Date(), 366)), + }, + }) as Promise<{ + id: number; + Date: string; + Quantity: string; + Remark: string; + CalculationPosition: { + id: number; + }; + }>, ), ) ) .flat() - .map((e: any) => { + .map((entry) => { return { - id: e.id, - date: e.Date, - hours: e.Quantity, - description: e.Remark, - calculationPosition: e.CalculationPosition.id, + id: entry.id, + date: entry.Date, + hours: parseFloat(entry.Quantity), + description: entry.Remark, + calculationPosition: entry.CalculationPosition.id, }; }); @@ -213,6 +228,50 @@ export async function getTimeEntries(request: Request): Promise { ); } +export async function addTimeEntry( + request: Request, + calculationPostionId: number, + date: string, + hours: number, + description: string, +) { + const cookieHeader = request.headers.get("Cookie"); + const session = await getSession(cookieHeader); + + const troiApi = await getTroiApi( + session.get("username"), + session.get("troiPassword"), + await getClientId(request), + await getEmployeeId(request), + ); + + const result = (await troiApi.postTimeEntry( + calculationPostionId, + date, + hours, + description, + )) as { + id: number; + Name: string; + Quantity: string; + }; + + const existingEntries = session.get("troiTimeEntries"); + if (existingEntries !== undefined) { + existingEntries[result.id] = { + id: result.id, + date: date, + hours: parseFloat(result.Quantity), + description: result.Name, + calculationPosition: calculationPostionId, + }; + } + + await commitSession(session); + + return result; +} + /** * Return data from the session cache and revalidate cached data in the background. *