I think finished?
This commit is contained in:
Binary file not shown.
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
67
frontend/src/routes/$userId.tsx
Normal file
67
frontend/src/routes/$userId.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
3
frontend/src/utils/capitalizeFirstLetter.ts
Normal file
3
frontend/src/utils/capitalizeFirstLetter.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function capitalizeFirstLetter(val: string) {
|
||||
return String(val).charAt(0).toUpperCase() + String(val).slice(1);
|
||||
}
|
||||
Reference in New Issue
Block a user