Add TV Mode
This commit is contained in:
parent
eb44093f09
commit
5a809e5903
@ -2,7 +2,7 @@ import "~/styles/globals.css";
|
|||||||
import { Inter as FontSans } from "next/font/google";
|
import { Inter as FontSans } from "next/font/google";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
import { SessionProvider } from "next-auth/react";
|
import { SessionProvider } from "next-auth/react";
|
||||||
import Sign_Out from "~/components/auth/Sign_Out";
|
import { TVModeProvider } from "~/components/context/TVModeContext";
|
||||||
|
|
||||||
import { type Metadata } from "next";
|
import { type Metadata } from "next";
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
@ -43,10 +43,9 @@ export default function RootLayout({
|
|||||||
fontSans.variable)}
|
fontSans.variable)}
|
||||||
>
|
>
|
||||||
<SessionProvider>
|
<SessionProvider>
|
||||||
<div className="absolute top-4 right-6">
|
<TVModeProvider>
|
||||||
<Sign_Out />
|
{children}
|
||||||
</div>
|
</TVModeProvider>
|
||||||
{children}
|
|
||||||
</SessionProvider>
|
</SessionProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -3,7 +3,7 @@ import { auth } from "~/auth";
|
|||||||
import No_Session from "~/components/ui/No_Session";
|
import No_Session from "~/components/ui/No_Session";
|
||||||
import Header from "~/components/ui/Header";
|
import Header from "~/components/ui/Header";
|
||||||
import { getEmployees } from "~/server/functions";
|
import { getEmployees } from "~/server/functions";
|
||||||
import TechTable from "~/components/ui/TechTable";
|
import Tech_Table from "~/components/ui/Tech_Table";
|
||||||
|
|
||||||
export default async function HomePage() {
|
export default async function HomePage() {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
@ -14,8 +14,8 @@ export default async function HomePage() {
|
|||||||
return (
|
return (
|
||||||
<main className="min-h-screen
|
<main className="min-h-screen
|
||||||
bg-gradient-to-b from-[#111111] to-[#212325]">
|
bg-gradient-to-b from-[#111111] to-[#212325]">
|
||||||
<Header />
|
<Header />
|
||||||
<TechTable employees={employees}/>
|
<Tech_Table employees={employees}/>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
15
src/components/auth/client/Sign_In.tsx
Normal file
15
src/components/auth/client/Sign_In.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { signIn } from "next-auth/react";
|
||||||
|
import { Button } from "~/components/ui/shadcn/button";
|
||||||
|
|
||||||
|
export default function Sign_In() {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
onClick={() => signIn()}
|
||||||
|
className="bg-gradient-to-tl from-[#35363F] to=[#24191A] rounded-xl
|
||||||
|
px-4 py-2 md:py-2.5 font-semibold text-white hover:bg-gradient-to-tr
|
||||||
|
hover:from-[#35363F] hover:to-[#23242F]"
|
||||||
|
>
|
||||||
|
<h1 className="md:text-2xl my-auto font-semibold">Sign In</h1>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
30
src/components/auth/client/Sign_Out.tsx
Normal file
30
src/components/auth/client/Sign_Out.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { signOut } from "next-auth/react";
|
||||||
|
import { useSession } from "next-auth/react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { Button } from "~/components/ui/shadcn/button";
|
||||||
|
|
||||||
|
export default function Sign_Out() {
|
||||||
|
const { data: session } = useSession();
|
||||||
|
if (!session) {
|
||||||
|
return <div/>;
|
||||||
|
} else {
|
||||||
|
const pfp = session?.user?.image ? session.user.image : "/images/default_user_pfp.png";
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<Image src={pfp} alt="" width={35} height={35}
|
||||||
|
className="rounded-full border-2 border-white m-auto mr-1 md:mr-2
|
||||||
|
max-w-[25px] md:max-w-[35px]"
|
||||||
|
/>
|
||||||
|
<Button onClick={() => signOut()}
|
||||||
|
className="w-full p-2 rounded-xl text-sm md:text-lg"
|
||||||
|
//bg-gradient-to-tl from-[#35363F] to=[#24191A]
|
||||||
|
//hover:bg-gradient-to-tr hover:from-[#35363F] hover:to-[#23242F]"
|
||||||
|
>
|
||||||
|
Sign Out
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//<User_Avatar session={session} />
|
@ -10,7 +10,7 @@ export default async function Sign_Out() {
|
|||||||
// Add User profile picture next to Sign Out button
|
// Add User profile picture next to Sign Out button
|
||||||
const pfp = session?.user?.image ? session.user.image : "/images/default_user_pfp.png";
|
const pfp = session?.user?.image ? session.user.image : "/images/default_user_pfp.png";
|
||||||
return (
|
return (
|
||||||
<form className="w-full flex flex-row pt-2 pr-0 md:pt-4 md:pr-8"
|
<form className="flex flex-row"
|
||||||
action={async () => {
|
action={async () => {
|
||||||
"use server"
|
"use server"
|
||||||
await signOut()
|
await signOut()
|
32
src/components/context/TVModeContext.tsx
Normal file
32
src/components/context/TVModeContext.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
"use client";
|
||||||
|
import React, { createContext, useContext, useState } from 'react';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
|
interface TVModeContextProps {
|
||||||
|
tvMode: boolean;
|
||||||
|
toggleTVMode: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TVModeContext = createContext<TVModeContextProps | undefined>(undefined);
|
||||||
|
|
||||||
|
export const TVModeProvider = ({ children }: { children: ReactNode }) => {
|
||||||
|
const [tvMode, setTVMode] = useState(false);
|
||||||
|
|
||||||
|
const toggleTVMode = () => {
|
||||||
|
setTVMode((prev) => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TVModeContext.Provider value={{ tvMode, toggleTVMode }}>
|
||||||
|
{children}
|
||||||
|
</TVModeContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useTVMode = () => {
|
||||||
|
const context = useContext(TVModeContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useTVMode must be used within a TVModeProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
@ -1,20 +1,41 @@
|
|||||||
|
"use client";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import Sign_Out from "~/components/auth/client/Sign_Out";
|
||||||
|
import TV_Toggle from "~/components/ui/TV_Toggle";
|
||||||
|
import { useTVMode } from "~/components/context/TVModeContext";
|
||||||
|
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
return (
|
const { tvMode } = useTVMode();
|
||||||
<header className="w-full py-2 pt-6 md:py-5">
|
if (tvMode) {
|
||||||
<div className="flex flex-row items-center text-center
|
return (
|
||||||
sm:justify-center ml-4 sm:ml-0 p-4">
|
<div className="absolute top-4 right-6">
|
||||||
<Image src="/images/tech_tracker_logo.png"
|
<div className="flex flex-row my-auto items-center pt-2 pr-0 md:pt-4 md:pr-8">
|
||||||
alt="Tech Tracker Logo" width={100} height={100}
|
< TV_Toggle />
|
||||||
className="max-w-[40px] md:max-w-[120px]"
|
</div>
|
||||||
/>
|
</div>
|
||||||
<h1 className="title-text text-sm md:text-4xl lg:text-8xl
|
);
|
||||||
bg-gradient-to-r from-[#bec8e6] via-[#F0EEE4] to-[#FFF8E7]
|
} else {
|
||||||
font-bold pl-2 md:pl-12 text-transparent bg-clip-text">
|
return (
|
||||||
Tech Tracker
|
<header className="w-full py-2 pt-6 md:py-5">
|
||||||
</h1>
|
<div className="absolute top-4 right-6">
|
||||||
</div>
|
<div className="flex flex-row my-auto items-center pt-2 pr-0 md:pt-4 md:pr-8">
|
||||||
</header>
|
< TV_Toggle />
|
||||||
);
|
< Sign_Out />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row items-center text-center
|
||||||
|
sm:justify-center ml-4 sm:ml-0 p-4">
|
||||||
|
<Image src="/images/tech_tracker_logo.png"
|
||||||
|
alt="Tech Tracker Logo" width={100} height={100}
|
||||||
|
className="max-w-[40px] md:max-w-[120px]"
|
||||||
|
/>
|
||||||
|
<h1 className="title-text text-sm md:text-4xl lg:text-8xl
|
||||||
|
bg-gradient-to-r from-[#bec8e6] via-[#F0EEE4] to-[#FFF8E7]
|
||||||
|
font-bold pl-2 md:pl-12 text-transparent bg-clip-text">
|
||||||
|
Tech Tracker
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Sign_In from "~/components/auth/Sign_In";
|
import Sign_In from "~/components/auth/server/Sign_In";
|
||||||
import Header from "~/components/ui/Header";
|
import Header from "~/components/ui/Header";
|
||||||
|
|
||||||
export default function No_Session() {
|
export default function No_Session() {
|
||||||
|
19
src/components/ui/TV_Toggle.tsx
Normal file
19
src/components/ui/TV_Toggle.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
"use client";
|
||||||
|
import { Switch } from "~/components/ui/shadcn/switch";
|
||||||
|
import { useTVMode } from "~/components/context/TVModeContext";
|
||||||
|
import { useSession } from "next-auth/react";
|
||||||
|
|
||||||
|
export default function TV_Toggle() {
|
||||||
|
const { tvMode, toggleTVMode } = useTVMode();
|
||||||
|
const { data: session } = useSession();
|
||||||
|
if (!session) return <div/>;
|
||||||
|
return (
|
||||||
|
<Switch
|
||||||
|
checked={tvMode}
|
||||||
|
onCheckedChange={toggleTVMode}
|
||||||
|
className="bg-gradient-to-br from-[#595959] to-[#444444]
|
||||||
|
hover:bg-gradient-to-bl hover:from-[#484848] hover:to-[#333333]
|
||||||
|
mx-4 mt-2"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
'use client';
|
"use client";
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
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";
|
||||||
|
|
||||||
// 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 {
|
||||||
@ -11,8 +12,9 @@ interface Employee {
|
|||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TechTable({ employees }: { employees: Employee[] }) {
|
export default function Tech_Table({ employees }: { employees: Employee[] }) {
|
||||||
const { data: session, status } = useSession();
|
const { data: session, status } = useSession();
|
||||||
|
const { tvMode } = useTVMode();
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [selectedIds, setSelectedIds] = useState<number[]>([]);
|
const [selectedIds, setSelectedIds] = useState<number[]>([]);
|
||||||
const [selectAll, setSelectAll] = useState(false);
|
const [selectAll, setSelectAll] = useState(false);
|
||||||
@ -34,7 +36,7 @@ export default function TechTable({ employees }: { employees: Employee[] }) {
|
|||||||
alert("You must be signed in to update status.");
|
alert("You must be signed in to update status.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// If no employee is selected and status is not empty
|
|
||||||
if (selectedIds.length === 0 && employeeStatus.trim() !== '') {
|
if (selectedIds.length === 0 && employeeStatus.trim() !== '') {
|
||||||
const cur_user = employees.find(employee => employee.name === session.user?.name);
|
const cur_user = employees.find(employee => employee.name === session.user?.name);
|
||||||
if (cur_user) {
|
if (cur_user) {
|
||||||
@ -57,6 +59,7 @@ export default function TechTable({ employees }: { employees: Employee[] }) {
|
|||||||
body: JSON.stringify({ employeeIds: selectedIds, newStatus: employeeStatus }),
|
body: JSON.stringify({ employeeIds: selectedIds, newStatus: employeeStatus }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedEmployees = await fetch_employees();
|
const updatedEmployees = await fetch_employees();
|
||||||
setEmployeeData(updatedEmployees);
|
setEmployeeData(updatedEmployees);
|
||||||
setSelectedIds([]);
|
setSelectedIds([]);
|
||||||
@ -88,11 +91,9 @@ export default function TechTable({ employees }: { employees: Employee[] }) {
|
|||||||
const handleKeyDown = async (e: React.KeyboardEvent<HTMLInputElement>) => {
|
const handleKeyDown = async (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
await update_status();
|
await update_status();
|
||||||
// if key is i then focus text input
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Format time for display
|
|
||||||
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', {
|
||||||
@ -104,41 +105,37 @@ export default function TechTable({ employees }: { employees: Employee[] }) {
|
|||||||
return `${time} - ${month} ${day}`;
|
return `${time} - ${month} ${day}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Loading bar while we wait for auth
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (status !== "loading") {
|
if (status !== "loading") {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [status]);
|
}, [status]);
|
||||||
|
|
||||||
// Refresh employee data if needed after state updates
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setEmployeeData(employees);
|
setEmployeeData(employees);
|
||||||
}, [employees]);
|
}, [employees]);
|
||||||
|
|
||||||
// Fetch employees from the server every 10 seconds
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchAndUpdateEmployees = async () => {
|
const fetchAndUpdateEmployees = async () => {
|
||||||
const updatedEmployees = await fetch_employees();
|
const updatedEmployees = await fetch_employees();
|
||||||
setEmployeeData(updatedEmployees);
|
setEmployeeData(updatedEmployees);
|
||||||
};
|
};
|
||||||
fetchAndUpdateEmployees()
|
|
||||||
.catch((error) => {
|
fetchAndUpdateEmployees().catch((error) => {
|
||||||
console.error('Error fetching employees:', error);
|
console.error('Error fetching employees:', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
const intervalId = setInterval(() => {
|
const intervalId = setInterval(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
await fetchAndUpdateEmployees();
|
await fetchAndUpdateEmployees();
|
||||||
})()
|
})().catch((error) => {
|
||||||
.catch((error) => {
|
|
||||||
console.error('Error fetching employees:', error);
|
console.error('Error fetching employees:', error);
|
||||||
});
|
});
|
||||||
}, 10000); // Poll every 10 seconds
|
}, 10000);
|
||||||
|
|
||||||
return () => clearInterval(intervalId); // Clear interval on component unmount
|
return () => clearInterval(intervalId);
|
||||||
}, [fetch_employees]);
|
}, [fetch_employees]);
|
||||||
|
|
||||||
// Handle checkbox changes
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedIds.length === employeeData.length && employeeData.length > 0) {
|
if (selectedIds.length === employeeData.length && employeeData.length > 0) {
|
||||||
setSelectAll(true);
|
setSelectAll(true);
|
||||||
@ -150,10 +147,10 @@ export default function TechTable({ employees }: { employees: Employee[] }) {
|
|||||||
if (loading) return <Loading interval_amount={3} />;
|
if (loading) return <Loading interval_amount={3} />;
|
||||||
else {
|
else {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={`techtable-wrapper ${tvMode ? 'content-fullscreen' : ''}`}>
|
||||||
<table className="techtable rounded-2xl w-5/6 m-auto text-center text-[42px]">
|
<table className={`techtable rounded-2xl m-auto text-center text-[42px] ${tvMode ? 'techtable-fullscreen' : 'w-5/6'}`}>
|
||||||
<thead className="tabletitles border border-[#3e4446]
|
<thead className="tabletitles border border-[#3e4446] bg-gradient-to-b
|
||||||
bg-gradient-to-b from-[#282828] to-[#383838] text-[48px]">
|
from-[#282828] to-[#383838] text-[48px]">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="py-3 px-4 border border-[#3e4446]">
|
<th className="py-3 px-4 border border-[#3e4446]">
|
||||||
<input
|
<input
|
||||||
@ -170,8 +167,9 @@ export default function TechTable({ employees }: { employees: Employee[] }) {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{employeeData.map((employee) => (
|
{employeeData.map((employee) => (
|
||||||
<tr className="even:bg-gradient-to-br from-[#272727] to-[#313131]
|
<tr
|
||||||
odd:bg-gradient-to-bl odd:from-[#252525] odd:to-[#212125]"
|
className="even:bg-gradient-to-br from-[#272727] to-[#313131]
|
||||||
|
odd:bg-gradient-to-bl odd:from-[#252525] odd:to-[#212125]"
|
||||||
key={employee.id}
|
key={employee.id}
|
||||||
>
|
>
|
||||||
<td className="p-1 border border-[#3e4446]">
|
<td className="p-1 border border-[#3e4446]">
|
||||||
@ -195,28 +193,30 @@ export default function TechTable({ employees }: { employees: Employee[] }) {
|
|||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div className="m-auto flex flex-row items-center justify-center py-5">
|
{!tvMode && (
|
||||||
<input
|
<div className="m-auto flex flex-row items-center justify-center py-5">
|
||||||
autoFocus
|
<input
|
||||||
type="text"
|
autoFocus
|
||||||
placeholder="New Status"
|
type="text"
|
||||||
className="min-w-[120px] lg:min-w-[400px] bg-[#F9F6EE]
|
placeholder="New Status"
|
||||||
py-2 px-3 border-none rounded-xl text-[#111111] lg:text-2xl"
|
className="min-w-[120px] lg:min-w-[400px] bg-[#F9F6EE] py-2 px-3
|
||||||
value={employeeStatus}
|
border-none rounded-xl text-[#111111] lg:text-2xl"
|
||||||
onChange={handleStatusChange}
|
value={employeeStatus}
|
||||||
onKeyDown={handleKeyDown}
|
onChange={handleStatusChange}
|
||||||
/>
|
onKeyDown={handleKeyDown}
|
||||||
<button
|
/>
|
||||||
type="submit"
|
<button
|
||||||
className="min-w-[100px] lg:min-w-[160px] m-2 p-2 border-none
|
type="submit"
|
||||||
rounded-xl text-center font-semibold lg:text-2xl hover:text-slate-300
|
className="min-w-[100px] lg:min-w-[160px] m-2 p-2 border-none rounded-xl
|
||||||
hover:bg-gradient-to-bl hover:from-[#484848] hover:to-[#333333]
|
text-center font-semibold lg:text-2xl hover:text-slate-300
|
||||||
bg-gradient-to-br from-[#595959] to-[#444444]"
|
hover:bg-gradient-to-bl hover:from-[#484848] hover:to-[#333333]
|
||||||
onClick={update_status}
|
bg-gradient-to-br from-[#595959] to-[#444444]"
|
||||||
>
|
onClick={update_status}
|
||||||
Update
|
>
|
||||||
</button>
|
Update
|
||||||
</div>
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -119,3 +119,14 @@
|
|||||||
font-size:18px;
|
font-size:18px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content-fullscreen {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.techtable-fullscreen {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user