From 46dd731eab29675aa3500c5a20d95bb28c9f5527 Mon Sep 17 00:00:00 2001 From: Leons Aleksandrovs <58330666+Skrazzo@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:25:03 +0300 Subject: [PATCH] I think finished? --- backend/db/attendance.db | Bin 16384 -> 16384 bytes backend/db/users.py | 28 ++++---- backend/main.py | 6 +- frontend/src/components/UsersTable.tsx | 15 +++-- frontend/src/routeTree.gen.ts | 24 ++++++- frontend/src/routes/$userId.tsx | 67 ++++++++++++++++++++ frontend/src/routes/index.tsx | 2 +- frontend/src/types/api.ts | 11 ++++ frontend/src/utils/capitalizeFirstLetter.ts | 3 + 9 files changed, 129 insertions(+), 27 deletions(-) create mode 100644 frontend/src/routes/$userId.tsx create mode 100644 frontend/src/utils/capitalizeFirstLetter.ts diff --git a/backend/db/attendance.db b/backend/db/attendance.db index 80d95877cac10d46ec161eb10ee3a92aa9f254c7..bc8167ea9091567645756056af9ea4e43c3f676a 100644 GIT binary patch delta 133 zcmV;00DAv`fB}Gj0gxL31(6&>1qA>ua-y+hpbriO56}P)_7Ck3;}6;o(6bQ`z7Glr z1p@#VY-Mk5bFs5fBs)6A=mp00AKZ0x~c%G%YYPEio`flS4nfqvIj= delta 98 zcmV-o0GE50gVb Ey?@CZA^-pY diff --git a/backend/db/users.py b/backend/db/users.py index ed6bad5..2b364dc 100644 --- a/backend/db/users.py +++ b/backend/db/users.py @@ -6,7 +6,6 @@ conn = get_db() def get_users(search): # Get search param search_param = f"%{search}%" - print(search_param) # Generate cursor cursor = conn.cursor() @@ -25,29 +24,26 @@ def get_attendance(id): # Set up query for worked hours, group by and ordered by date cursor = conn.cursor() cursor.execute(""" - SELECT date, sum(hours_worked) as hours_worked, e.username - FROM Attendance a - JOIN Employees e ON a.employee_id = e.id - WHERE a.employee_id = ? + SELECT date, sum(hours_worked) as hours_worked + FROM Attendance + WHERE employee_id = ? GROUP BY date ORDER BY date DESC """, (id,)) - data = cursor.fetchall() + attendance = cursor.fetchall() - # Check if there's any attendance - if not data: + # Fetch username from database + # I am not using table JOIN because when theres no attendance, the query returns no rows (and shows user doesnt exist) + cursor.execute("SELECT username FROM Employees WHERE id = ?", (id,)) + username = cursor.fetchone() + + # Check if user exists + if not username: return {"username": None, "attendance": []} - # Remove repetetive username, and return clean api response - username = data[0]["username"] # get username from first row - attendance = [ - {"date": row["date"], "hours_worked": row["hours_worked"]} - for row in data - ] - # Return clean api response return { - "username": username, + "username": username[0], "attendance": attendance } diff --git a/backend/main.py b/backend/main.py index f1aaa17..74ba38d 100644 --- a/backend/main.py +++ b/backend/main.py @@ -19,6 +19,6 @@ app.add_middleware( def list_users(s: str = None): return get_users(s) -@app.get("/{eployee_id}") -def list_attendance(eployee_id: int): - return get_attendance(eployee_id) +@app.get("/{employee_id}") +def list_attendance(employee_id: int): + return get_attendance(employee_id) diff --git a/frontend/src/components/UsersTable.tsx b/frontend/src/components/UsersTable.tsx index 2335e9c..4172ce0 100644 --- a/frontend/src/components/UsersTable.tsx +++ b/frontend/src/components/UsersTable.tsx @@ -1,11 +1,12 @@ import { API_BASE_URL } from "@/consts"; import type { User } from "@/types/api"; import { useQuery, type UseQueryResult } from "@tanstack/react-query"; +import { Link } from "@tanstack/react-router"; import { useEffect, useState } from "react"; function RenderList({ list }: { list: UseQueryResult }) { // Base classes for each row - const baseRowClass = "text-xl p-2 px-4 rounded capitalize bg-panel/75"; + const baseRowClass = "flex text-xl p-2 px-4 rounded capitalize bg-panel/75"; // Display loading and error states if (list.isLoading) return
Loading...
; @@ -14,7 +15,13 @@ function RenderList({ list }: { list: UseQueryResult }) { // Render all users to the list return list.data?.map((u) => { return ( -

{u.username}

+ + {u.username} + ); }); } @@ -45,9 +52,9 @@ export default function UsersTable() { className="bg-panel/75 transition focus:bg-panel border border-transparent outline-none focus:border-accent rounded px-4 py-2 mt-8 w-full " onChange={(e) => setSearch(e.target.value)} /> -
    +
    -
+ ); } diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index d204c26..0ca4b88 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -9,8 +9,14 @@ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './routes/__root' +import { Route as UserIdRouteImport } from './routes/$userId' import { Route as IndexRouteImport } from './routes/index' +const UserIdRoute = UserIdRouteImport.update({ + id: '/$userId', + path: '/$userId', + getParentRoute: () => rootRouteImport, +} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', @@ -19,28 +25,39 @@ const IndexRoute = IndexRouteImport.update({ export interface FileRoutesByFullPath { '/': typeof IndexRoute + '/$userId': typeof UserIdRoute } export interface FileRoutesByTo { '/': typeof IndexRoute + '/$userId': typeof UserIdRoute } export interface FileRoutesById { __root__: typeof rootRouteImport '/': typeof IndexRoute + '/$userId': typeof UserIdRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/' + fullPaths: '/' | '/$userId' fileRoutesByTo: FileRoutesByTo - to: '/' - id: '__root__' | '/' + to: '/' | '/$userId' + id: '__root__' | '/' | '/$userId' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexRoute: typeof IndexRoute + UserIdRoute: typeof UserIdRoute } declare module '@tanstack/react-router' { interface FileRoutesByPath { + '/$userId': { + id: '/$userId' + path: '/$userId' + fullPath: '/$userId' + preLoaderRoute: typeof UserIdRouteImport + parentRoute: typeof rootRouteImport + } '/': { id: '/' path: '/' @@ -53,6 +70,7 @@ declare module '@tanstack/react-router' { const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, + UserIdRoute: UserIdRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) diff --git a/frontend/src/routes/$userId.tsx b/frontend/src/routes/$userId.tsx new file mode 100644 index 0000000..b027e62 --- /dev/null +++ b/frontend/src/routes/$userId.tsx @@ -0,0 +1,67 @@ +import MainContainer from "@/components/MainContainer"; +import { API_BASE_URL } from "@/consts"; +import type { UserInfo } from "@/types/api"; +import { capitalizeFirstLetter } from "@/utils/capitalizeFirstLetter"; +import { useQuery, type UseQueryResult } from "@tanstack/react-query"; +import { createFileRoute, useParams } from "@tanstack/react-router"; + +export const Route = createFileRoute("/$userId")({ + component: RouteComponent, +}); + +function DisplayUserName({ info }: { info: UseQueryResult }) { + // Base classes for headers + const baseClasses = "text-4xl font-bold text-center"; + + // Display loading and error states + if (info.isLoading) return

Loading user info ...

; + if (info.isError) return

Error: {info.error.message}

; + + // Check if user is null + if (!info.data?.username) return

User not found

; + + return

{capitalizeFirstLetter(info.data.username)}

; +} + +function DisplayUserTable({ info }: { info: UseQueryResult }) { + // Base classes for headers + const baseClasses = "p-2 text-center"; + + if (!info.data) return; // return nothing when no data + if (!info.data.username) return; // Return nothing when user is null + if (info.data.attendance.length === 0) return

No attendance

; // Empty attendance, return msg + + // Display attendance in a table + return ( + + + + + + + {info.data.attendance.map((a) => ( + + + + + ))} + +
DateHours worked
{a.date}{a.hours_worked}
+ ); +} + +function RouteComponent() { + const { userId } = Route.useParams(); + + // Define url for user info fetch + const userInfoURL = new URL(`${API_BASE_URL}/${userId}`); + const fetchUserInfo = () => fetch(userInfoURL).then((res) => res.json()); + const userInfo = useQuery({ queryKey: ["user", userId], queryFn: fetchUserInfo }); + + return ( + + + + + ); +} diff --git a/frontend/src/routes/index.tsx b/frontend/src/routes/index.tsx index c73e939..21203a8 100644 --- a/frontend/src/routes/index.tsx +++ b/frontend/src/routes/index.tsx @@ -9,7 +9,7 @@ export const Route = createFileRoute("/")({ function App() { return ( -

Darbinieki

+

Employees

); diff --git a/frontend/src/types/api.ts b/frontend/src/types/api.ts index cc5b6fd..365dfa7 100644 --- a/frontend/src/types/api.ts +++ b/frontend/src/types/api.ts @@ -1,3 +1,14 @@ export interface User { + id: number; username: string; } + +export interface Attendance { + date: string; + hours_worked: number; +} + +export interface UserInfo { + username: string | null; + attendance: Attendance[]; +} diff --git a/frontend/src/utils/capitalizeFirstLetter.ts b/frontend/src/utils/capitalizeFirstLetter.ts new file mode 100644 index 0000000..fea7a6b --- /dev/null +++ b/frontend/src/utils/capitalizeFirstLetter.ts @@ -0,0 +1,3 @@ +export function capitalizeFirstLetter(val: string) { + return String(val).charAt(0).toUpperCase() + String(val).slice(1); +}