Mostly Front End & UI fixes
This commit is contained in:
parent
8e02288d3d
commit
5f020bf542
@ -1,18 +0,0 @@
|
|||||||
"use server";
|
|
||||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
||||||
import { getEmployees } from '~/server/functions';
|
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|
||||||
if (req.method === 'GET') {
|
|
||||||
try {
|
|
||||||
const employees = await getEmployees();
|
|
||||||
res.status(200).json(employees);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching employees:', error);
|
|
||||||
res.status(500).json({ message: 'Internal server error' });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.setHeader('Allow', ['GET']);
|
|
||||||
res.status(405).json({ message: `Method ${req.method} Not Allowed` });
|
|
||||||
}
|
|
||||||
}
|
|
18
src/app/api/get_employees/route.ts
Normal file
18
src/app/api/get_employees/route.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
"use server";
|
||||||
|
|
||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import { getEmployees } from '~/server/functions';
|
||||||
|
import { auth } from '~/auth';
|
||||||
|
|
||||||
|
export const GET = async () => {
|
||||||
|
try {
|
||||||
|
const session = await auth();
|
||||||
|
if (!session)
|
||||||
|
return NextResponse.json({ message: 'Unauthorized' }, { status: 401 });
|
||||||
|
const employees = await getEmployees();
|
||||||
|
return NextResponse.json(employees, { status: 200 });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching employees:', error);
|
||||||
|
return NextResponse.json({ message: 'Internal server error' }, { status: 500 });
|
||||||
|
}
|
||||||
|
};
|
@ -1,29 +0,0 @@
|
|||||||
"use server";
|
|
||||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
||||||
import { updateEmployeeStatus } from '~/server/functions';
|
|
||||||
|
|
||||||
type UpdateStatusBody = {
|
|
||||||
employeeIds: number[];
|
|
||||||
newStatus: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|
||||||
if (req.method === 'POST') {
|
|
||||||
const { employeeIds, newStatus } = req.body as UpdateStatusBody;
|
|
||||||
|
|
||||||
if (!Array.isArray(employeeIds) || typeof newStatus !== 'string') {
|
|
||||||
return res.status(400).json({ message: 'Invalid input' });
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await updateEmployeeStatus(employeeIds, newStatus);
|
|
||||||
return res.status(200).json({ message: 'Status updated successfully' });
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error updating status:', error);
|
|
||||||
return res.status(500).json({ message: 'Internal server error' });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.setHeader('Allow', ['POST']);
|
|
||||||
return res.status(405).json({ message: `Method ${req.method} Not Allowed` });
|
|
||||||
}
|
|
||||||
}
|
|
30
src/app/api/update_status/route.ts
Normal file
30
src/app/api/update_status/route.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
"use server";
|
||||||
|
|
||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { updateEmployeeStatus } from '~/server/functions';
|
||||||
|
import { auth } from '~/auth';
|
||||||
|
|
||||||
|
type UpdateStatusBody = {
|
||||||
|
employeeIds: string[];
|
||||||
|
newStatus: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const POST = async (req: NextRequest) => {
|
||||||
|
const session = await auth();
|
||||||
|
if (!session)
|
||||||
|
return NextResponse.json({ message: 'Unauthorized' }, { status: 401 });
|
||||||
|
|
||||||
|
const { employeeIds, newStatus } = await req.json() as UpdateStatusBody;
|
||||||
|
|
||||||
|
if (!Array.isArray(employeeIds) || typeof newStatus !== 'string') {
|
||||||
|
return NextResponse.json({ message: 'Invalid input' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await updateEmployeeStatus(employeeIds, newStatus);
|
||||||
|
return NextResponse.json({ message: 'Status updated successfully' }, { status: 200 });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating status:', error);
|
||||||
|
return NextResponse.json({ message: 'Internal server error' }, { status: 500 });
|
||||||
|
}
|
||||||
|
};
|
@ -1,7 +1,7 @@
|
|||||||
import { auth } from "~/auth";
|
import { auth } from "~/auth";
|
||||||
import No_Session from "~/components/auth/No_Session";
|
import No_Session from "~/components/auth/No_Session";
|
||||||
import TT_Header from "~/components/ui/TT_Header";
|
import TT_Header from "~/components/ui/TT_Header";
|
||||||
import Techs_Table from "~/components/ui/Techs_Table";
|
import Techs from "~/components/ui/Techs";
|
||||||
|
|
||||||
export default async function HomePage() {
|
export default async function HomePage() {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
@ -12,7 +12,7 @@ export default async function HomePage() {
|
|||||||
<main className="min-h-screen bg-gradient-to-b
|
<main className="min-h-screen bg-gradient-to-b
|
||||||
from-[#111111] to-[#111325]">
|
from-[#111111] to-[#111325]">
|
||||||
<TT_Header />
|
<TT_Header />
|
||||||
<Techs_Table />
|
<Techs />
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,12 @@ import Image from "next/image";
|
|||||||
|
|
||||||
export default function TT_Header() {
|
export default function TT_Header() {
|
||||||
return (
|
return (
|
||||||
<header className="w-full">
|
<header className="w-full py-5">
|
||||||
<div className="flex flex-row items-center text-center justify-center p-8">
|
<div className="flex flex-row items-center text-center justify-center p-8">
|
||||||
<Image src="/images/tech_tracker_logo.png"
|
<Image src="/images/tech_tracker_logo.png"
|
||||||
alt="Tech Tracker Logo" width={100} height={100}
|
alt="Tech Tracker Logo" width={80} height={80}
|
||||||
/>
|
/>
|
||||||
<h1 className="text-6xl font-semibold pl-4">
|
<h1 className="title-text text-6xl font-semibold pl-4">
|
||||||
Tech Tracker
|
Tech Tracker
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
|
|
||||||
// Define the Employee interface to match data fetched on the server
|
// Define the Employee interface to match data fetched on the server
|
||||||
interface Employee {
|
interface Employee {
|
||||||
@ -20,6 +20,39 @@ export default function Table({ employees }: { employees: Employee[] }) {
|
|||||||
setEmployeeData(employees);
|
setEmployeeData(employees);
|
||||||
}, [employees]);
|
}, [employees]);
|
||||||
|
|
||||||
|
const fetchEmployees = useCallback(async (): Promise<Employee[]> => {
|
||||||
|
const res = await fetch('/api/get_employees', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${process.env.API_KEY}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return res.json() as Promise<Employee[]>;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchAndUpdateEmployees = async () => {
|
||||||
|
const updatedEmployees = await fetchEmployees();
|
||||||
|
setEmployeeData(updatedEmployees);
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchAndUpdateEmployees()
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error fetching employees:', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
const intervalId = setInterval(() => {
|
||||||
|
(async () => {
|
||||||
|
await fetchAndUpdateEmployees();
|
||||||
|
})()
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error fetching employees:', error);
|
||||||
|
});
|
||||||
|
}, 10000); // Poll every 10 seconds
|
||||||
|
|
||||||
|
return () => clearInterval(intervalId); // Clear interval on component unmount
|
||||||
|
}, [fetchEmployees]);
|
||||||
|
|
||||||
const handleCheckboxChange = (id: number) => {
|
const handleCheckboxChange = (id: number) => {
|
||||||
setSelectedIds((prevSelected) =>
|
setSelectedIds((prevSelected) =>
|
||||||
prevSelected.includes(id)
|
prevSelected.includes(id)
|
||||||
@ -50,14 +83,10 @@ export default function Table({ employees }: { employees: Employee[] }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchEmployees = async (): Promise<Employee[]> => {
|
const handleKeyPress = async (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
const res = await fetch('/api/get_employees', {
|
if (e.key === 'Enter') {
|
||||||
method: 'GET',
|
await handleSubmit();
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${process.env.API_KEY}`
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
return res.json() as Promise<Employee[]>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatTime = (timestamp: Date) => {
|
const formatTime = (timestamp: Date) => {
|
||||||
@ -73,13 +102,13 @@ export default function Table({ employees }: { employees: Employee[] }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<table className="w-5/6 m-auto text-center border-collapse text-[42px]">
|
<table className="techtable w-2/3 min-h-[600px] m-auto text-center border-collapse text-[42px]">
|
||||||
<thead className="bg-gradient-to-br from-[#212121] to-[#333333]">
|
<thead className="bg-gradient-to-br from-[#212121] to-[#333333]">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="p-5 border border-[#3e4446] text-[48px]" />
|
<th className="tabletitles p-5 border border-[#3e4446] text-[48px]" />
|
||||||
<th className="p-2 border border-[#3e4446] text-[48px]">Name</th>
|
<th className="tabletitles p-2 border border-[#3e4446] text-[48px]">Name</th>
|
||||||
<th className="p-2 border border-[#3e4446] text-[48px]">Status</th>
|
<th className="tabletitles p-2 border border-[#3e4446] text-[48px]">Status</th>
|
||||||
<th className="p-2 border border-[#3e4446] text-[48px]">Updated At</th>
|
<th className="tabletitles p-2 border border-[#3e4446] text-[48px]">Updated At</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -94,23 +123,27 @@ export default function Table({ employees }: { employees: Employee[] }) {
|
|||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td className="p-1 border border-[#3e4446]">{employee.name}</td>
|
<td className="p-1 border border-[#3e4446]">{employee.name}</td>
|
||||||
<td className="p-1 border border-[#3e4446]">{employee.status}</td>
|
<td className="s-column p-1 border border-[#3e4446]">{employee.status}</td>
|
||||||
<td className="p-1 border border-[#3e4446]">{formatTime(employee.updatedAt)}</td>
|
<td className="ua-column p-1 border border-[#3e4446]">{formatTime(employee.updatedAt)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div className="m-auto flex flex-row items-center justify-center">
|
<div className="m-auto flex flex-row items-center justify-center py-5">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="New Status"
|
placeholder="New Status"
|
||||||
className="w-1/5 p-2 border-none rounded-md"
|
className="min-w-[100px] p-3 border-none rounded-xl text-[#111111] md:text-xl"
|
||||||
value={status}
|
value={status}
|
||||||
onChange={handleStatusChange}
|
onChange={handleStatusChange}
|
||||||
|
onKeyDown={handleKeyPress}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="m-2 px-2 py-5 border-none rounded-md text-center bg-gradient-to-br from-[#484848] to-[#333333]"
|
className="m-2 p-3 border-none rounded-2xl text-center
|
||||||
|
font-semibold md:text-xl hover:text-slate-300
|
||||||
|
hover:bg-gradient-to-bl hover:from-[#484848] hover:to-[#333333]
|
||||||
|
bg-gradient-to-br from-[#595959] to-[#444444]"
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
>
|
>
|
||||||
Update
|
Update
|
||||||
|
8
src/components/ui/Techs.tsx
Normal file
8
src/components/ui/Techs.tsx
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { getEmployees } from "~/server/functions";
|
||||||
|
import Table from "~/components/ui/Table";
|
||||||
|
|
||||||
|
export default async function Techs() {
|
||||||
|
|
||||||
|
const employees = await getEmployees();
|
||||||
|
return <Table employees={employees} />;
|
||||||
|
};
|
@ -1,70 +0,0 @@
|
|||||||
//import { auth } from "~/auth";
|
|
||||||
import { getEmployees } from "~/server/functions";
|
|
||||||
import Table from "~/components/ui/Table";
|
|
||||||
|
|
||||||
//export const dynamic = "force-dynamic";
|
|
||||||
|
|
||||||
export default async function Techs_Table() {
|
|
||||||
|
|
||||||
const employees = await getEmployees();
|
|
||||||
return <Table employees={employees} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
//const formatTime = (timestamp: Date) => {
|
|
||||||
//const date = new Date(timestamp);
|
|
||||||
//const time = date.toLocaleTimeString("en-US",
|
|
||||||
//{hour: "numeric", minute: "numeric",});
|
|
||||||
//const day = date.getDate();
|
|
||||||
//const month = date.toLocaleString("default", { month: "long" });
|
|
||||||
//return `${time} - ${month} ${day}`;
|
|
||||||
//}
|
|
||||||
//const session = await auth();
|
|
||||||
//const users_name = session?.user?.name;
|
|
||||||
|
|
||||||
//return (
|
|
||||||
//<div>
|
|
||||||
//<table className="w-5/6 m-auto text-center border-collapse text-[42px]">
|
|
||||||
//<thead className="bg-gradient-to-br from-[#121212] to-[#333333]">
|
|
||||||
//<tr>
|
|
||||||
//<th className="p-5 border border-[#3e4446] text-[48px]"/>
|
|
||||||
//<th className="p-2 border border-[#3e4446] text-[48px]">
|
|
||||||
//Name
|
|
||||||
//</th>
|
|
||||||
//<th className="p-2 border border-[#3e4446] text-[48px]">
|
|
||||||
//Status
|
|
||||||
//</th>
|
|
||||||
//<th className="p-2 border border-[#3e4446] text-[48px]">
|
|
||||||
//Updated At
|
|
||||||
//</th>
|
|
||||||
//</tr>
|
|
||||||
//</thead>
|
|
||||||
//<tbody>
|
|
||||||
//{employees.map((employee) => (
|
|
||||||
//<tr className="even:bg-gradient-to-bl from-[#222222] to-[#323232]" key={employee.id}>
|
|
||||||
//<td className="p-1 border border-[#3e4446]">
|
|
||||||
//<input type="checkbox"
|
|
||||||
//className="m-0 cursor-pointer transform scale-150"
|
|
||||||
////checked={}
|
|
||||||
///>
|
|
||||||
//</td>
|
|
||||||
//<td className="p-1 border border-[#3e4446]">{employee.name}</td>
|
|
||||||
//<td className="p-1 border border-[#3e4446]">{employee.status}</td>
|
|
||||||
//<td className="p-1 border border-[#3e4446]">{formatTime(employee.updatedAt)}</td>
|
|
||||||
//</tr>
|
|
||||||
//))}
|
|
||||||
//</tbody>
|
|
||||||
//</table>
|
|
||||||
//<div className="m-auto flex flex-row items-center justify-center">
|
|
||||||
//<input type="text" placeholder="New Status"
|
|
||||||
//className="w-1/5 p-2 border-none rounded-xl"
|
|
||||||
////value={}
|
|
||||||
///>
|
|
||||||
//<button type="submit"
|
|
||||||
//className="m-2 p-3 border-none rounded-2xl text-center bg-gradient-to-br from-[#484848] to-[#333333]"
|
|
||||||
//>
|
|
||||||
//Update
|
|
||||||
//</button>
|
|
||||||
//</div>
|
|
||||||
//</div>
|
|
||||||
//);
|
|
||||||
//};
|
|
@ -15,7 +15,7 @@ export const users = createTable(
|
|||||||
id: bigint("id", {mode: "number"}).primaryKey().autoincrement(),
|
id: bigint("id", {mode: "number"}).primaryKey().autoincrement(),
|
||||||
name: varchar("name", { length: 256 }).notNull(),
|
name: varchar("name", { length: 256 }).notNull(),
|
||||||
status: varchar("status", { length: 256 }).notNull(),
|
status: varchar("status", { length: 256 }).notNull(),
|
||||||
updatedAt: timestamp("updated_at")
|
updatedAt: timestamp("updatedAt")
|
||||||
.default(sql`CURRENT_TIMESTAMP`)
|
.default(sql`CURRENT_TIMESTAMP`)
|
||||||
.notNull(),
|
.notNull(),
|
||||||
},
|
},
|
||||||
@ -27,6 +27,6 @@ export const history = createTable(
|
|||||||
id: bigint("id", {mode: "number"}).primaryKey().autoincrement(),
|
id: bigint("id", {mode: "number"}).primaryKey().autoincrement(),
|
||||||
user_id: bigint("user_id", {mode: "number"}).references(() => users.id),
|
user_id: bigint("user_id", {mode: "number"}).references(() => users.id),
|
||||||
status: varchar("status", { length: 256 }).notNull(),
|
status: varchar("status", { length: 256 }).notNull(),
|
||||||
updatedAt: timestamp("updated_at").notNull(),
|
updatedAt: timestamp("updatedAt").notNull(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -10,20 +10,21 @@ export const getEmployees = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Function to Update Employee Status using Raw SQL
|
// Function to Update Employee Status using Raw SQL
|
||||||
export const updateEmployeeStatus = async (employeeIds: number[], newStatus: string) => {
|
export const updateEmployeeStatus = async (employeeIds: string[], newStatus: string) => {
|
||||||
try {
|
try {
|
||||||
// Convert array of ids to a format suitable for SQL query (comma-separated string)
|
// Convert array of ids to a format suitable for SQL query (comma-separated string)
|
||||||
const idString = employeeIds.join(",");
|
const idList = employeeIds.map(id => parseInt(id, 10));
|
||||||
|
const updatedAt = new Date();
|
||||||
|
|
||||||
// Prepare the raw SQL query with embedded variables
|
// Prepare the query using drizzle-orm's template-like syntax for escaping variables
|
||||||
const query = `
|
const query = sql`
|
||||||
UPDATE users
|
UPDATE users
|
||||||
SET status = '${newStatus}', updatedAt = '${new Date().toISOString()}'
|
SET status = ${newStatus}, updatedAt = ${updatedAt}
|
||||||
WHERE id IN (${idString})
|
WHERE id IN ${idList}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Execute the raw SQL query using the execute method
|
// Execute the query
|
||||||
await db.execute(sql`${query}`);
|
await db.execute(query);
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -67,3 +67,55 @@
|
|||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (min-width: 2000px) {
|
||||||
|
.ua-column {
|
||||||
|
min-width: 500px;
|
||||||
|
}
|
||||||
|
.s-column {
|
||||||
|
max-width: 800px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 2000px) {
|
||||||
|
.techtable {
|
||||||
|
width: 90%;
|
||||||
|
font-size: 36;
|
||||||
|
}
|
||||||
|
.tabletitles {
|
||||||
|
font-size: 40;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1500px) {
|
||||||
|
.ua-column {
|
||||||
|
min-width: 400px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1500px) {
|
||||||
|
.techtable {
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
.tabletitles {
|
||||||
|
font-size:36px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1000px) {
|
||||||
|
.ua-column {
|
||||||
|
min-width: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1000px) {
|
||||||
|
.title-text {
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
.techtable {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
.tabletitles {
|
||||||
|
font-size:24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user