I think finished?

This commit is contained in:
Leons Aleksandrovs
2025-07-02 22:25:03 +03:00
parent acb81d671d
commit 46dd731eab
9 changed files with 129 additions and 27 deletions

Binary file not shown.

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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<User[]> }) {
// 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 <div className={`${baseRowClass} text-gray-400`}>Loading...</div>;
@@ -14,7 +15,13 @@ function RenderList({ list }: { list: UseQueryResult<User[]> }) {
// Render all users to the list
return list.data?.map((u) => {
return (
<p className={`${baseRowClass} transition cursor-pointer hover:bg-panel hover:text-accent`}>{u.username}</p>
<Link
to="/$userId"
params={{ userId: u.id.toString() }}
className={`${baseRowClass} transition cursor-pointer hover:bg-panel hover:text-accent`}
>
{u.username}
</Link>
);
});
}
@@ -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)}
/>
<ul className="space-y-2 mt-2">
<div className="flex flex-col gap-2 mt-2">
<RenderList list={users} />
</ul>
</div>
</div>
);
}

View File

@@ -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)

View File

@@ -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<UserInfo> }) {
// Base classes for headers
const baseClasses = "text-4xl font-bold text-center";
// Display loading and error states
if (info.isLoading) return <h1 className={`${baseClasses} text-gray-400`}>Loading user info ...</h1>;
if (info.isError) return <h1 className={`${baseClasses} text-red-400`}>Error: {info.error.message}</h1>;
// Check if user is null
if (!info.data?.username) return <h1 className={`${baseClasses} text-red-400`}>User not found</h1>;
return <h1 className={`${baseClasses} text-accent`}>{capitalizeFirstLetter(info.data.username)}</h1>;
}
function DisplayUserTable({ info }: { info: UseQueryResult<UserInfo> }) {
// 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 <p className={`text-gray-400 text-center`}>No attendance</p>; // Empty attendance, return msg
// Display attendance in a table
return (
<table className="w-full bg-panel rounded">
<thead>
<th className={`${baseClasses}`}>Date</th>
<th className={`${baseClasses}`}>Hours worked</th>
</thead>
<tbody>
{info.data.attendance.map((a) => (
<tr key={a.date} className="odd:bg-body/50">
<td className={`${baseClasses}`}>{a.date}</td>
<td className={`${baseClasses}`}>{a.hours_worked}</td>
</tr>
))}
</tbody>
</table>
);
}
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<UserInfo>({ queryKey: ["user", userId], queryFn: fetchUserInfo });
return (
<MainContainer className="pt-8 space-y-4">
<DisplayUserName info={userInfo} />
<DisplayUserTable info={userInfo} />
</MainContainer>
);
}

View File

@@ -9,7 +9,7 @@ export const Route = createFileRoute("/")({
function App() {
return (
<MainContainer className="pt-8">
<h1 className="text-4xl font-bold text-center text-accent">Darbinieki</h1>
<h1 className="text-4xl font-bold text-center text-accent">Employees</h1>
<UsersTable />
</MainContainer>
);

View File

@@ -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[];
}

View File

@@ -0,0 +1,3 @@
export function capitalizeFirstLetter(val: string) {
return String(val).charAt(0).toUpperCase() + String(val).slice(1);
}