History Table is now functioning completely

This commit is contained in:
Gabriel Brown 2024-07-30 10:02:27 -05:00
parent b14383f8fd
commit 44497ebe7b
7 changed files with 440 additions and 281 deletions

View File

@ -23,6 +23,7 @@
"@radix-ui/react-popover": "^1.1.1", "@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-progress": "^1.1.0", "@radix-ui/react-progress": "^1.1.0",
"@radix-ui/react-radio-group": "^1.2.0", "@radix-ui/react-radio-group": "^1.2.0",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-select": "^2.1.1", "@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.0", "@radix-ui/react-switch": "^1.1.0",
@ -37,13 +38,13 @@
"drizzle-orm": "^0.30.10", "drizzle-orm": "^0.30.10",
"geist": "^1.3.1", "geist": "^1.3.1",
"lucide-react": "^0.411.0", "lucide-react": "^0.411.0",
"mysql2": "^3.10.3", "mysql2": "^3.11.0",
"next": "^14.2.5", "next": "^14.2.5",
"next-auth": "5.0.0-beta.19", "next-auth": "5.0.0-beta.19",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",
"pm2": "^5.4.2", "pm2": "^5.4.2",
"react": "^18.3.1", "react": "^18.3.1",
"react-day-picker": "^9.0.3", "react-day-picker": "^9.0.5",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-hook-form": "^7.52.1", "react-hook-form": "^7.52.1",
"server-only": "^0.0.1", "server-only": "^0.0.1",
@ -56,19 +57,19 @@
}, },
"devDependencies": { "devDependencies": {
"@types/eslint": "^8.56.11", "@types/eslint": "^8.56.11",
"@types/node": "^20.14.12", "@types/node": "^20.14.13",
"@types/react": "^18.3.3", "@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.17.0", "@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.17.0", "@typescript-eslint/parser": "^7.18.0",
"drizzle-kit": "^0.21.4", "drizzle-kit": "^0.21.4",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-config-next": "^14.2.5", "eslint-config-next": "^14.2.5",
"eslint-plugin-drizzle": "^0.2.3", "eslint-plugin-drizzle": "^0.2.3",
"postcss": "^8.4.39", "postcss": "^8.4.40",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.5", "prettier-plugin-tailwindcss": "^0.6.5",
"tailwindcss": "^3.4.6", "tailwindcss": "^3.4.7",
"typescript": "^5.5.4" "typescript": "^5.5.4"
}, },
"ct3aMetadata": { "ct3aMetadata": {

383
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,13 @@
"use server"; "use server";
import { NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { getHistory } from '~/server/functions'; import { get_history } from '~/server/functions';
import { auth } from '~/auth'; import { auth } from '~/auth';
export const GET = async (request: Request) => { export const GET = async (request: Request) => {
try { try {
const url = new URL(request.url); const url = new URL(request.url);
const apiKey = url.searchParams.get('apikey'); const apiKey = url.searchParams.get('apikey');
const userId = Number(url.searchParams.get('user_id')) || -1;
const page = Number(url.searchParams.get('page')) || 1; const page = Number(url.searchParams.get('page')) || 1;
const perPage = Number(url.searchParams.get('per_page')) || 50; const perPage = Number(url.searchParams.get('per_page')) || 50;
if (apiKey !== process.env.API_KEY) { if (apiKey !== process.env.API_KEY) {
@ -17,7 +18,7 @@ export const GET = async (request: Request) => {
{ status: 401 } { status: 401 }
); );
} }
const historyData = await getHistory(page, perPage); const historyData = await get_history(userId, page, perPage);
return NextResponse.json(historyData, { status: 200 }); return NextResponse.json(historyData, { status: 200 });
} catch (error) { } catch (error) {
console.error('Error fetching history data:', error); console.error('Error fetching history data:', error);

View File

@ -1,8 +1,7 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import Image from "next/image"; import Image from "next/image";
import { ScrollArea } from "~/components/ui/shadcn/scroll-area";
import { import {
Drawer,
DrawerTrigger,
DrawerClose, DrawerClose,
DrawerContent, DrawerContent,
DrawerFooter, DrawerFooter,
@ -30,7 +29,7 @@ type HistoryEntry = {
name: string; name: string;
status: string; status: string;
updatedAt: Date; updatedAt: Date;
} };
type PaginatedHistory = { type PaginatedHistory = {
data: HistoryEntry[]; data: HistoryEntry[];
meta: { meta: {
@ -39,20 +38,21 @@ type PaginatedHistory = {
total_pages: number; total_pages: number;
total_count: number; total_count: number;
} }
} };
type History_Drawer_Props = {
user_id: number;
};
export default function History_Drawer() { const History_Drawer: React.FC<History_Drawer_Props> = ({ user_id }) => {
const [history, setHistory] = useState<HistoryEntry[]>([]); const [history, setHistory] = useState<HistoryEntry[]>([]);
const [page, setPage] = useState<number>(1); const [page, setPage] = useState<number>(1);
const [totalPages, setTotalPages] = useState<number>(1); const [totalPages, setTotalPages] = useState<number>(1);
const perPage = 5; const perPage = 50;
useEffect(() => { const fetchHistory = async (currentPage: number, user_id: number) => {
const fetchHistory = async (currentPage: number) => {
try { try {
const response = await fetch(`/api/get_paginated_history?page=${currentPage}&per_page=${perPage}`); const response = await fetch(`/api/get_paginated_history?user_id=${user_id}&page=${currentPage}&per_page=${perPage}`);
if (!response.ok) if (!response.ok) throw new Error('Failed to fetch history');
throw new Error('Failed to fetch history');
const data: PaginatedHistory = await response.json() as PaginatedHistory; const data: PaginatedHistory = await response.json() as PaginatedHistory;
setHistory(data.data); setHistory(data.data);
setTotalPages(data.meta.total_pages); setTotalPages(data.meta.total_pages);
@ -60,56 +60,49 @@ export default function History_Drawer() {
console.error('Error fetching history:', error); console.error('Error fetching history:', error);
} }
}; };
fetchHistory(page)
.catch((error) => { useEffect(() => {
fetchHistory(page, user_id).catch((error) => {
console.error('Error fetching history:', error); console.error('Error fetching history:', error);
}); });
}, [page]); }, [page, user_id]);
const handlePageChange = (newPage: number) => { const handlePageChange = (newPage: number) => {
setPage(newPage); setPage(newPage);
}; };
return ( return (
<Drawer>
<DrawerTrigger>
Status
</DrawerTrigger>
<DrawerContent> <DrawerContent>
<DrawerHeader> <DrawerHeader>
<DrawerTitle> <DrawerTitle>
<div className="flex flex-row items-center text-center <div className="flex flex-row items-center text-center sm:justify-center sm:ml-0 py-4">
sm:justify-center sm:ml-0 py-4"> <Image src="/images/tech_tracker_logo.png" alt="Tech Tracker Logo" width={60} height={60} className="max-w-[40px] md:max-w-[120px]" />
<Image src="/images/tech_tracker_logo.png" <h1 className="title-text text-sm md:text-2xl lg:text-6xl bg-gradient-to-r from-[#bec8e6] via-[#F0EEE4] to-[#FFF8E7] font-bold pl-2 md:pl-4 text-transparent bg-clip-text">
alt="Tech Tracker Logo" width={60} height={60}
className="max-w-[40px] md:max-w-[120px]"
/>
<h1 className="title-text text-sm md:text-2xl lg:text-6xl
bg-gradient-to-r from-[#bec8e6] via-[#F0EEE4] to-[#FFF8E7]
font-bold pl-2 md:pl-4 text-transparent bg-clip-text">
History History
</h1> </h1>
</div> </div>
</DrawerTitle> </DrawerTitle>
</DrawerHeader> </DrawerHeader>
<Table className="w-5/6 lg:w-1/2 m-auto "> <ScrollArea className="w-full sm:w-5/6 lg:w-5/12 m-auto h-80">
<Table className="w-full m-auto">
<TableHeader> <TableHeader>
<TableRow> <TableRow>
<TableHead>Name</TableHead> <TableHead className="font-semibold lg:max-w-[100px]">Name</TableHead>
<TableHead>Status</TableHead> <TableHead className="font-semibold lg:max-w-[100px]">Status</TableHead>
<TableHead>Updated At</TableHead> <TableHead className="font-semibold lg:max-w-[100px] justify-end items-end text-right">Updated At</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{history.map((entry, index) => ( {history.map((entry, index) => (
<TableRow key={index}> <TableRow key={index}>
<TableCell className="font-medium">{entry.name}</TableCell> <TableCell className="font-medium lg:max-w-[100px]">{entry.name}</TableCell>
<TableCell>{entry.status}</TableCell> <TableCell className="font-medium lg:max-w-[100px]">{entry.status}</TableCell>
<TableCell>{new Date(entry.updatedAt).toLocaleString()}</TableCell> <TableCell className="font-medium lg:max-w-[100px] justify-end items-end text-right">{new Date(entry.updatedAt).toLocaleString()}</TableCell>
</TableRow> </TableRow>
))} ))}
</TableBody> </TableBody>
</Table> </Table>
</ScrollArea>
<DrawerFooter> <DrawerFooter>
<Pagination> <Pagination>
<PaginationContent> <PaginationContent>
@ -124,7 +117,16 @@ export default function History_Drawer() {
/> />
</PaginationItem> </PaginationItem>
)} )}
<h3 className="text-center font-semibold">Page {page}</h3> <h3 className="text-center flex flex-row">
Page
<h3 className="font-bold mx-1">
{page}
</h3>
of
<h3 className="font-semibold ml-1">
{totalPages}
</h3>
</h3>
{page < totalPages && ( {page < totalPages && (
<PaginationItem> <PaginationItem>
<PaginationNext <PaginationNext
@ -142,9 +144,10 @@ export default function History_Drawer() {
</DrawerClose> </DrawerClose>
</DrawerFooter> </DrawerFooter>
</DrawerContent> </DrawerContent>
</Drawer>
); );
} };
export default History_Drawer;
// If you want to show all page numbers: // If you want to show all page numbers:
//{Array.from({ length: totalPages }).map((_, idx) => ( //{Array.from({ length: totalPages }).map((_, idx) => (
//<PaginationItem key={idx}> //<PaginationItem key={idx}>
@ -158,4 +161,3 @@ export default function History_Drawer() {
//</PaginationLink> //</PaginationLink>
//</PaginationItem> //</PaginationItem>
//))} //))}

View File

@ -3,6 +3,8 @@ import { useState, useEffect, useCallback } from 'react';
import { useSession } from "next-auth/react"; import { useSession } from "next-auth/react";
import Loading from "~/components/ui/Loading"; import Loading from "~/components/ui/Loading";
import { useTVMode } from "~/components/context/TVModeContext"; import { useTVMode } from "~/components/context/TVModeContext";
import { Drawer, DrawerTrigger } from "~/components/ui/shadcn/drawer";
import History_Drawer from "~/components/ui/History_Drawer"; import History_Drawer from "~/components/ui/History_Drawer";
type Employee = { type Employee = {
@ -20,6 +22,7 @@ export default function Tech_Table({ employees }: { employees: Employee[] }) {
const [selectAll, setSelectAll] = useState(false); const [selectAll, setSelectAll] = useState(false);
const [employeeStatus, setStatus] = useState(''); const [employeeStatus, setStatus] = useState('');
const [employeeData, setEmployeeData] = useState(employees); const [employeeData, setEmployeeData] = useState(employees);
const [selectedUserId, setSelectedUserId] = useState(-1);
const fetch_employees = useCallback(async (): Promise<Employee[]> => { const fetch_employees = useCallback(async (): Promise<Employee[]> => {
const res = await fetch('/api/get_technicians', { const res = await fetch('/api/get_technicians', {
@ -95,6 +98,10 @@ export default function Tech_Table({ employees }: { employees: Employee[] }) {
} }
}; };
const handleStatusClick = (id: number) => {
setSelectedUserId(id);
};
const formatTime = (timestamp: Date) => { const formatTime = (timestamp: Date) => {
const date = new Date(timestamp); const date = new Date(timestamp);
const time = date.toLocaleTimeString('en-US', { const time = date.toLocaleTimeString('en-US', {
@ -167,7 +174,12 @@ export default function Tech_Table({ employees }: { employees: Employee[] }) {
)} )}
<th className="border border-[#3e4446] py-3">Name</th> <th className="border border-[#3e4446] py-3">Name</th>
<th className="border border-[#3e4446] py-3"> <th className="border border-[#3e4446] py-3">
<History_Drawer /> <Drawer>
<DrawerTrigger>
Status
</DrawerTrigger>
<History_Drawer user_id={-1}/>
</Drawer>
</th> </th>
<th className="border border-[#3e4446] py-3">Updated At</th> <th className="border border-[#3e4446] py-3">Updated At</th>
</tr> </tr>
@ -193,9 +205,16 @@ export default function Tech_Table({ employees }: { employees: Employee[] }) {
{employee.name} {employee.name}
</td> </td>
<td className="s-column max-w-[700px] px-1 md:py-3 border border-[#3e4446]"> <td className="s-column max-w-[700px] px-1 md:py-3 border border-[#3e4446]">
<button> <Drawer>
<DrawerTrigger>
<button onClick={() => handleStatusClick(employee.id)}>
{employee.status} {employee.status}
</button> </button>
</DrawerTrigger>
{selectedUserId !== -1 && (
<History_Drawer key={selectedUserId} user_id={selectedUserId} />
)}
</Drawer>
</td> </td>
<td className="ua-column px-1 md:py-3 border border-[#3e4446]"> <td className="ua-column px-1 md:py-3 border border-[#3e4446]">
{formatTime(employee.updatedAt)} {formatTime(employee.updatedAt)}

View File

@ -0,0 +1,48 @@
"use client"
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import { cn } from "~/lib/utils"
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn("relative overflow-hidden", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
))
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"flex touch-none select-none transition-colors",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
))
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
export { ScrollArea, ScrollBar }

View File

@ -68,32 +68,46 @@ type PaginatedHistory = {
} }
} }
export const getHistory = export const get_history = async (user_id: number, page: number, perPage: number): Promise<PaginatedHistory> => {
async (page: number, perPage: number): Promise<PaginatedHistory> => {
const offset = (page - 1) * perPage; const offset = (page - 1) * perPage;
const historyQuery = sql` let historyQuery = sql`
SELECT u.name, h.status, h.updatedAt
FROM history h
JOIN users u ON h.user_id = u.id
WHERE h.user_id = ${user_id}
ORDER BY h.id DESC
LIMIT ${perPage} OFFSET ${offset}
`;
let countQuery = sql`
SELECT COUNT(*) AS total_count
FROM history
WHERE user_id = ${user_id}
`;
if (user_id === -1) {
historyQuery = sql`
SELECT u.name, h.status, h.updatedAt SELECT u.name, h.status, h.updatedAt
FROM history h FROM history h
JOIN users u ON h.user_id = u.id JOIN users u ON h.user_id = u.id
ORDER BY h.id DESC ORDER BY h.id DESC
LIMIT ${perPage} OFFSET ${offset} LIMIT ${perPage} OFFSET ${offset}
`; `;
const countQuery = sql` countQuery = sql`
SELECT COUNT(*) AS total_count SELECT COUNT(*) AS total_count
FROM history FROM history
`; `;
const [historyResults, countResults] = await Promise.all([ }
const [historyresults, countresults] = await Promise.all([
db.execute(historyQuery), db.execute(historyQuery),
db.execute(countQuery), db.execute(countQuery),
]); ]);
// Safely cast results // Safely cast results
const historyRows = historyResults[0] as unknown as const historyrows = historyresults[0] as unknown as
{ name: string, status: string, updatedAt: Date }[]; { name: string, status: string, updatedAt: Date }[];
const countRow = countResults[0] as unknown as { total_count: number }[]; const countrow = countresults[0] as unknown as { total_count: number }[];
const totalCount = countRow[0]?.total_count ?? 0; const totalCount = countrow[0]?.total_count ?? 0;
const totalPages = Math.ceil(totalCount / perPage); const totalPages = Math.ceil(totalCount / perPage);
// Format and map results // Format and map results
const formattedResults: HistoryEntry[] = historyRows.map(row => ({ const formattedResults: HistoryEntry[] = historyrows.map(row => ({
name: row.name, name: row.name,
status: row.status, status: row.status,
updatedAt: new Date(row.updatedAt), updatedAt: new Date(row.updatedAt),
@ -109,3 +123,44 @@ export const getHistory =
}; };
}; };
//export const getHistory =
//async (page: number, perPage: number): Promise<PaginatedHistory> => {
//const offset = (page - 1) * perPage;
//const historyQuery = sql`
//SELECT u.name, h.status, h.updatedAt
//FROM history h
//JOIN users u ON h.user_id = u.id
//ORDER BY h.id DESC
//LIMIT ${perPage} OFFSET ${offset}
//`;
//const countQuery = sql`
//SELECT COUNT(*) AS total_count
//FROM history
//`;
//const [historyResults, countResults] = await Promise.all([
//db.execute(historyQuery),
//db.execute(countQuery),
//]);
//// Safely cast results
//const historyRows = historyResults[0] as unknown as
//{ name: string, status: string, updatedAt: Date }[];
//const countRow = countResults[0] as unknown as { total_count: number }[];
//const totalCount = countRow[0]?.total_count ?? 0;
//const totalPages = Math.ceil(totalCount / perPage);
//// Format and map results
//const formattedResults: HistoryEntry[] = historyRows.map(row => ({
//name: row.name,
//status: row.status,
//updatedAt: new Date(row.updatedAt),
//}));
//return {
//data: formattedResults,
//meta: {
//current_page: page,
//per_page: perPage,
//total_pages: totalPages,
//total_count: totalCount,
//}
//};
//};