mirror of
https://github.com/cssgunc/compass.git
synced 2025-04-03 19:40:16 -04:00
Prevent employee/volunteer from editting and revamp loading spinner (#54)
This commit is contained in:
parent
f6b0838c99
commit
0daf80d222
|
@ -4,45 +4,98 @@ import { PageLayout } from "@/components/PageLayout";
|
|||
import UserTable from "@/components/Table/UserTable";
|
||||
import User from "@/utils/models/User";
|
||||
import { createClient } from "@/utils/supabase/client";
|
||||
|
||||
import { UsersIcon } from "@heroicons/react/24/solid";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function Page() {
|
||||
const [users, setUsers] = useState<User[]>([]);
|
||||
const [uuid, setUuid] = useState<string>("");
|
||||
const [currUser, setCurrUser] = useState<User | undefined>(undefined);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
async function getUser() {
|
||||
const supabase = createClient();
|
||||
async function getUsers() {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
const { data, error } = await supabase.auth.getUser();
|
||||
const supabase = createClient();
|
||||
const { data: userData, error: authError } =
|
||||
await supabase.auth.getUser();
|
||||
|
||||
if (error) {
|
||||
console.log("Accessed admin page but not logged in");
|
||||
return;
|
||||
if (authError) {
|
||||
throw new Error("Authentication failed. Please sign in.");
|
||||
}
|
||||
|
||||
// Fetch users list and current user data in parallel
|
||||
const [usersResponse, userResponse] = await Promise.all([
|
||||
fetch(`/api/user/all?uuid=${userData.user.id}`),
|
||||
fetch(`/api/user?uuid=${userData.user.id}`),
|
||||
]);
|
||||
|
||||
// Check for HTTP errors
|
||||
if (!usersResponse.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch users: ${usersResponse.statusText}`
|
||||
);
|
||||
}
|
||||
if (!userResponse.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch user data: ${userResponse.statusText}`
|
||||
);
|
||||
}
|
||||
|
||||
// Parse the responses
|
||||
const [usersAPI, currUserData] = await Promise.all([
|
||||
usersResponse.json(),
|
||||
userResponse.json(),
|
||||
]);
|
||||
|
||||
// Verify admin status
|
||||
if (currUserData.role !== "ADMIN") {
|
||||
throw new Error("Unauthorized: Admin access required");
|
||||
}
|
||||
|
||||
setUsers(usersAPI);
|
||||
setCurrUser(currUserData);
|
||||
} catch (err) {
|
||||
console.error("Error fetching data:", err);
|
||||
setError(
|
||||
err instanceof Error
|
||||
? err.message
|
||||
: "An unexpected error occurred"
|
||||
);
|
||||
setUsers([]);
|
||||
setCurrUser(undefined);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
setUuid(data.user.id);
|
||||
|
||||
const userListData = await fetch(
|
||||
`/api/user/all?uuid=${data.user.id}`
|
||||
);
|
||||
|
||||
const users: User[] = await userListData.json();
|
||||
|
||||
setUsers(users);
|
||||
}
|
||||
|
||||
getUser();
|
||||
getUsers();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col">
|
||||
{/* icon + title */}
|
||||
<PageLayout title="Users" icon={<UsersIcon />}>
|
||||
{/* TODO: REPLACE UUID WITH HTTP BEARER TOKEN */}
|
||||
<UserTable data={users} setData={setUsers} uuid={uuid} />
|
||||
{isLoading ? (
|
||||
<div className="flex justify-center items-center h-64">
|
||||
<div className="animate-spin rounded-full h-24 w-24 border-b-2 border-purple-700" />
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="flex justify-center items-center h-64">
|
||||
<div className="text-red-500 text-center">
|
||||
<p className="text-lg font-semibold">Error</p>
|
||||
<p className="text-sm">{error}</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<UserTable
|
||||
data={users}
|
||||
setData={setUsers}
|
||||
user={currUser}
|
||||
/>
|
||||
)}
|
||||
</PageLayout>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -4,34 +4,68 @@ import { PageLayout } from "@/components/PageLayout";
|
|||
import Resource from "@/utils/models/Resource";
|
||||
import ResourceTable from "@/components/Table/ResourceTable";
|
||||
import { createClient } from "@/utils/supabase/client";
|
||||
|
||||
import { BookmarkIcon } from "@heroicons/react/24/solid";
|
||||
import { useEffect, useState } from "react";
|
||||
import User from "@/utils/models/User";
|
||||
|
||||
export default function Page() {
|
||||
const [resources, setResources] = useState<Resource[]>([]);
|
||||
const [uuid, setUuid] = useState<string>("");
|
||||
const [currUser, setCurrUser] = useState<User | undefined>(undefined);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
async function getResources() {
|
||||
const supabase = createClient();
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
const { data, error } = await supabase.auth.getUser();
|
||||
const supabase = createClient();
|
||||
const { data: userData, error: authError } =
|
||||
await supabase.auth.getUser();
|
||||
|
||||
if (error) {
|
||||
console.log("Accessed admin page but not logged in");
|
||||
return;
|
||||
if (authError) {
|
||||
throw new Error("Authentication failed. Please sign in.");
|
||||
}
|
||||
|
||||
// Fetch resources and user data in parallel
|
||||
const [resourceResponse, userResponse] = await Promise.all([
|
||||
fetch(`/api/resource/all?uuid=${userData.user.id}`),
|
||||
fetch(`/api/user?uuid=${userData.user.id}`),
|
||||
]);
|
||||
|
||||
// Check for HTTP errors
|
||||
if (!resourceResponse.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch resources: ${resourceResponse.statusText}`
|
||||
);
|
||||
}
|
||||
if (!userResponse.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch user data: ${userResponse.statusText}`
|
||||
);
|
||||
}
|
||||
|
||||
// Parse the responses
|
||||
const [resourcesAPI, currUserData] = await Promise.all([
|
||||
resourceResponse.json(),
|
||||
userResponse.json(),
|
||||
]);
|
||||
|
||||
setResources(resourcesAPI);
|
||||
setCurrUser(currUserData);
|
||||
} catch (err) {
|
||||
console.error("Error fetching data:", err);
|
||||
setError(
|
||||
err instanceof Error
|
||||
? err.message
|
||||
: "An unexpected error occurred"
|
||||
);
|
||||
setResources([]);
|
||||
setCurrUser(undefined);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
setUuid(data.user.id);
|
||||
|
||||
const userListData = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_HOST}/api/resource/all?uuid=${data.user.id}`
|
||||
);
|
||||
|
||||
const resourcesAPI: Resource[] = await userListData.json();
|
||||
|
||||
setResources(resourcesAPI);
|
||||
}
|
||||
|
||||
getResources();
|
||||
|
@ -39,13 +73,25 @@ export default function Page() {
|
|||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col">
|
||||
{/* icon + title */}
|
||||
<PageLayout title="Resources" icon={<BookmarkIcon />}>
|
||||
<ResourceTable
|
||||
data={resources}
|
||||
setData={setResources}
|
||||
uuid={uuid}
|
||||
/>
|
||||
{isLoading ? (
|
||||
<div className="flex justify-center items-center h-64">
|
||||
<div className="animate-spin rounded-full h-24 w-24 border-b-2 border-purple-700" />
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="flex justify-center items-center h-64">
|
||||
<div className="text-red-500 text-center">
|
||||
<p className="text-lg font-semibold">Error</p>
|
||||
<p className="text-sm">{error}</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<ResourceTable
|
||||
data={resources}
|
||||
setData={setResources}
|
||||
user={currUser}
|
||||
/>
|
||||
)}
|
||||
</PageLayout>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -3,34 +3,69 @@
|
|||
import { PageLayout } from "@/components/PageLayout";
|
||||
import ServiceTable from "@/components/Table/ServiceTable";
|
||||
import Service from "@/utils/models/Service";
|
||||
import User from "@/utils/models/User";
|
||||
import { createClient } from "@/utils/supabase/client";
|
||||
|
||||
import { ClipboardIcon } from "@heroicons/react/24/solid";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function Page() {
|
||||
const [services, setServices] = useState<Service[]>([]);
|
||||
const [uuid, setUuid] = useState<string>("");
|
||||
const [currUser, setCurrUser] = useState<User | undefined>(undefined);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
async function getServices() {
|
||||
const supabase = createClient();
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
const { data, error } = await supabase.auth.getUser();
|
||||
const supabase = createClient();
|
||||
const { data: userData, error: authError } =
|
||||
await supabase.auth.getUser();
|
||||
|
||||
if (error) {
|
||||
console.log("Accessed admin page but not logged in");
|
||||
return;
|
||||
if (authError) {
|
||||
throw new Error("Authentication failed. Please sign in.");
|
||||
}
|
||||
|
||||
// Fetch services and user data in parallel
|
||||
const [serviceResponse, userResponse] = await Promise.all([
|
||||
fetch(`/api/service/all?uuid=${userData.user.id}`),
|
||||
fetch(`/api/user?uuid=${userData.user.id}`),
|
||||
]);
|
||||
|
||||
// Check for HTTP errors
|
||||
if (!serviceResponse.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch services: ${serviceResponse.statusText}`
|
||||
);
|
||||
}
|
||||
if (!userResponse.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch user data: ${userResponse.statusText}`
|
||||
);
|
||||
}
|
||||
|
||||
// Parse the responses
|
||||
const [servicesAPI, currUserData] = await Promise.all([
|
||||
serviceResponse.json(),
|
||||
userResponse.json(),
|
||||
]);
|
||||
|
||||
setCurrUser(currUserData);
|
||||
setServices(servicesAPI);
|
||||
} catch (err) {
|
||||
console.error("Error fetching data:", err);
|
||||
setError(
|
||||
err instanceof Error
|
||||
? err.message
|
||||
: "An unexpected error occurred"
|
||||
);
|
||||
setServices([]);
|
||||
setCurrUser(undefined);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
setUuid(data.user.id);
|
||||
|
||||
const serviceListData = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_HOST}/api/service/all?uuid=${data.user.id}`
|
||||
);
|
||||
|
||||
const servicesAPI: Service[] = await serviceListData.json();
|
||||
setServices(servicesAPI);
|
||||
}
|
||||
|
||||
getServices();
|
||||
|
@ -38,13 +73,25 @@ export default function Page() {
|
|||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col">
|
||||
{/* icon + title */}
|
||||
<PageLayout title="Services" icon={<ClipboardIcon />}>
|
||||
<ServiceTable
|
||||
data={services}
|
||||
setData={setServices}
|
||||
uuid={uuid}
|
||||
/>
|
||||
{isLoading ? (
|
||||
<div className="flex justify-center items-center h-64">
|
||||
<div className="animate-spin rounded-full h-24 w-24 border-b-2 border-purple-700" />
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="flex justify-center items-center h-64">
|
||||
<div className="text-red-500 text-center">
|
||||
<p className="text-lg font-semibold">Error</p>
|
||||
<p className="text-sm">{error}</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<ServiceTable
|
||||
data={services}
|
||||
setData={setServices}
|
||||
user={currUser}
|
||||
/>
|
||||
)}
|
||||
</PageLayout>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
import { Dispatch, FunctionComponent, ReactNode, SetStateAction } from "react";
|
||||
import React, { useState } from "react";
|
||||
import { ChevronDoubleLeftIcon } from "@heroicons/react/24/solid";
|
||||
import {
|
||||
StarIcon as SolidStarIcon,
|
||||
EnvelopeIcon,
|
||||
UserIcon,
|
||||
} from "@heroicons/react/24/solid";
|
||||
import { StarIcon as SolidStarIcon, UserIcon } from "@heroicons/react/24/solid";
|
||||
import {
|
||||
ArrowsPointingOutIcon,
|
||||
ArrowsPointingInIcon,
|
||||
StarIcon as OutlineStarIcon,
|
||||
ListBulletIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import TagsInput from "../TagsInput/Index";
|
||||
import { Tag } from "../TagsInput/Tag";
|
||||
|
||||
type InputType =
|
||||
| "text"
|
||||
|
@ -35,6 +31,7 @@ type DrawerProps = {
|
|||
details: Details[];
|
||||
rowContent?: any;
|
||||
setRowContent?: Dispatch<SetStateAction<any>>;
|
||||
isAdmin?: boolean;
|
||||
};
|
||||
|
||||
const Drawer: FunctionComponent<DrawerProps> = ({
|
||||
|
@ -42,6 +39,7 @@ const Drawer: FunctionComponent<DrawerProps> = ({
|
|||
details,
|
||||
rowContent,
|
||||
setRowContent,
|
||||
isAdmin,
|
||||
}: DrawerProps) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isFull, setIsFull] = useState(false);
|
||||
|
@ -161,35 +159,48 @@ const Drawer: FunctionComponent<DrawerProps> = ({
|
|||
valueToRender = (
|
||||
<div className="flex-1">
|
||||
<div className="rounded-md px-2 py-1">
|
||||
<TagsInput
|
||||
presetValue={
|
||||
typeof value ===
|
||||
"string"
|
||||
? [value]
|
||||
: value || []
|
||||
}
|
||||
presetOptions={
|
||||
detail.presetOptionsValues ||
|
||||
[]
|
||||
}
|
||||
setPresetOptions={
|
||||
detail.presetOptionsSetter ||
|
||||
(() => {})
|
||||
}
|
||||
singleValue={true}
|
||||
onTagsChange={(
|
||||
tags: Set<string>
|
||||
) => {
|
||||
const tagsArray =
|
||||
Array.from(tags);
|
||||
handleTempRowContentChange(
|
||||
detail.key,
|
||||
tagsArray.length > 0
|
||||
? tagsArray[0]
|
||||
: null
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{isAdmin ? (
|
||||
<TagsInput
|
||||
presetValue={
|
||||
typeof value ===
|
||||
"string"
|
||||
? [value]
|
||||
: value || []
|
||||
}
|
||||
presetOptions={
|
||||
detail.presetOptionsValues ||
|
||||
[]
|
||||
}
|
||||
setPresetOptions={
|
||||
detail.presetOptionsSetter ||
|
||||
(() => {})
|
||||
}
|
||||
singleValue={true}
|
||||
onTagsChange={(
|
||||
tags: Set<string>
|
||||
) => {
|
||||
const tagsArray =
|
||||
Array.from(
|
||||
tags
|
||||
);
|
||||
handleTempRowContentChange(
|
||||
detail.key,
|
||||
tagsArray.length >
|
||||
0
|
||||
? tagsArray[0]
|
||||
: null
|
||||
);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex">
|
||||
<Tag>
|
||||
{value
|
||||
? value
|
||||
: "no value"}
|
||||
</Tag>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -198,30 +209,56 @@ const Drawer: FunctionComponent<DrawerProps> = ({
|
|||
valueToRender = (
|
||||
<div className="flex-1">
|
||||
<div className="rounded-md px-2 py-1">
|
||||
<TagsInput
|
||||
presetValue={
|
||||
typeof value ===
|
||||
"string"
|
||||
? [value]
|
||||
: value || []
|
||||
}
|
||||
presetOptions={
|
||||
detail.presetOptionsValues ||
|
||||
[]
|
||||
}
|
||||
setPresetOptions={
|
||||
detail.presetOptionsSetter ||
|
||||
(() => {})
|
||||
}
|
||||
onTagsChange={(
|
||||
tags: Set<string>
|
||||
) => {
|
||||
handleTempRowContentChange(
|
||||
detail.key,
|
||||
Array.from(tags)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{isAdmin ? (
|
||||
<TagsInput
|
||||
presetValue={
|
||||
typeof value ===
|
||||
"string"
|
||||
? [value]
|
||||
: value || []
|
||||
}
|
||||
presetOptions={
|
||||
detail.presetOptionsValues ||
|
||||
[]
|
||||
}
|
||||
setPresetOptions={
|
||||
detail.presetOptionsSetter ||
|
||||
(() => {})
|
||||
}
|
||||
onTagsChange={(
|
||||
tags: Set<string>
|
||||
) => {
|
||||
handleTempRowContentChange(
|
||||
detail.key,
|
||||
Array.from(tags)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{value &&
|
||||
value.length > 0 ? (
|
||||
value.map(
|
||||
(
|
||||
tag: string,
|
||||
index: number
|
||||
) => (
|
||||
<Tag
|
||||
key={
|
||||
index
|
||||
}
|
||||
>
|
||||
{tag}
|
||||
</Tag>
|
||||
)
|
||||
)
|
||||
) : (
|
||||
<Tag>
|
||||
no requirements
|
||||
</Tag>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -238,6 +275,7 @@ const Drawer: FunctionComponent<DrawerProps> = ({
|
|||
}
|
||||
onKeyDown={handleEnterPress}
|
||||
rows={4}
|
||||
disabled={!isAdmin}
|
||||
onInput={(e) => {
|
||||
const target =
|
||||
e.target as HTMLTextAreaElement;
|
||||
|
@ -261,6 +299,7 @@ const Drawer: FunctionComponent<DrawerProps> = ({
|
|||
type={detail.inputType}
|
||||
name={detail.key}
|
||||
value={value}
|
||||
disabled={!isAdmin}
|
||||
onChange={
|
||||
handleTempRowContentChangeHTML
|
||||
}
|
||||
|
@ -283,6 +322,7 @@ const Drawer: FunctionComponent<DrawerProps> = ({
|
|||
handleTempRowContentChangeHTML
|
||||
}
|
||||
onKeyDown={handleEnterPress}
|
||||
disabled={!isAdmin}
|
||||
className="w-full p-1 font-normal hover:text-gray-400 focus:outline-gray-200 underline text-gray-500 bg-transparent"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
/* components/LoadingIcon.module.css */
|
||||
.loader {
|
||||
width: 24px; /* Larger for better visibility */
|
||||
height: 24px;
|
||||
border: 4px solid #5b21b6; /* Primary color */
|
||||
border-top: 4px solid #ffffff; /* Contrasting color */
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite; /* Smooth continuous spin */
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg); /* Start position */
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg); /* Full rotation */
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
// components/Loading.js
|
||||
import styles from "./LoadingIcon.module.css";
|
||||
|
||||
const LoadingIcon = () => {
|
||||
return (
|
||||
<div className={styles.loadingOverlay}>
|
||||
<div className={styles.loadingContent}>
|
||||
<div className={styles.loader}></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoadingIcon;
|
|
@ -10,7 +10,6 @@ import {
|
|||
} from "@heroicons/react/24/solid";
|
||||
import { SidebarItem } from "./SidebarItem";
|
||||
import { UserProfile } from "../resource/UserProfile";
|
||||
import LoadingIcon from "./LoadingIcon";
|
||||
|
||||
interface SidebarProps {
|
||||
setIsSidebarOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
|
@ -67,7 +66,9 @@ const Sidebar: React.FC<SidebarProps> = ({
|
|||
{/* Loading indicator*/}
|
||||
{isLoading && (
|
||||
<div className="fixed top-2 left-2">
|
||||
<LoadingIcon />
|
||||
<div className="flex justify-center items-center">
|
||||
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-purple-700" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
@ -6,19 +6,17 @@ import {
|
|||
UserIcon,
|
||||
} from "@heroicons/react/24/solid";
|
||||
import { Dispatch, SetStateAction, useState } from "react";
|
||||
import useTagsHandler from "@/components/TagsInput/TagsHandler";
|
||||
import { ColumnDef, createColumnHelper } from "@tanstack/react-table";
|
||||
import { RowOpenAction } from "@/components/Table/RowOpenAction";
|
||||
import Table from "@/components/Table/Table";
|
||||
import TagsInput from "@/components/TagsInput/Index";
|
||||
import Resource from "@/utils/models/Resource";
|
||||
import { Details } from "../Drawer/Drawer";
|
||||
import { Tag } from "../TagsInput/Tag";
|
||||
|
||||
import User from "@/utils/models/User";
|
||||
type ResourceTableProps = {
|
||||
data: Resource[];
|
||||
setData: Dispatch<SetStateAction<Resource[]>>;
|
||||
uuid: string;
|
||||
user?: User;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -29,7 +27,7 @@ type ResourceTableProps = {
|
|||
export default function ResourceTable({
|
||||
data,
|
||||
setData,
|
||||
uuid,
|
||||
user,
|
||||
}: ResourceTableProps) {
|
||||
const columnHelper = createColumnHelper<Resource>();
|
||||
|
||||
|
@ -82,6 +80,7 @@ export default function ResourceTable({
|
|||
rowData={info.row.original}
|
||||
setData={setData}
|
||||
details={resourceDetails}
|
||||
isAdmin={user?.role === "ADMIN"}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
|
@ -142,7 +141,8 @@ export default function ResourceTable({
|
|||
setData={setData}
|
||||
columns={columns}
|
||||
details={resourceDetails}
|
||||
createEndpoint={`/api/resource/create?uuid=${uuid}`}
|
||||
createEndpoint={`/api/resource/create?uuid=${user?.uuid}`}
|
||||
isAdmin={user?.role === "ADMIN"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ type RowOpenActionProps<T extends DataPoint> = {
|
|||
rowData: T;
|
||||
setData: Dispatch<SetStateAction<T[]>>;
|
||||
details: Details[];
|
||||
isAdmin?: boolean;
|
||||
};
|
||||
|
||||
export function RowOpenAction<T extends DataPoint>({
|
||||
|
@ -21,6 +22,7 @@ export function RowOpenAction<T extends DataPoint>({
|
|||
rowData,
|
||||
setData,
|
||||
details,
|
||||
isAdmin,
|
||||
}: RowOpenActionProps<T>) {
|
||||
return (
|
||||
<div className="font-semibold group flex flex-row items-center justify-between pr-2">
|
||||
|
@ -31,6 +33,7 @@ export function RowOpenAction<T extends DataPoint>({
|
|||
rowContent={rowData}
|
||||
details={details}
|
||||
setRowContent={setData}
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
@ -12,11 +12,12 @@ import { RowOpenAction } from "@/components/Table/RowOpenAction";
|
|||
import Service from "@/utils/models/Service";
|
||||
import { Details } from "../Drawer/Drawer";
|
||||
import { Tag } from "../TagsInput/Tag";
|
||||
import User from "@/utils/models/User";
|
||||
|
||||
type ServiceTableProps = {
|
||||
data: Service[];
|
||||
setData: Dispatch<SetStateAction<Service[]>>;
|
||||
uuid: string;
|
||||
user?: User;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -27,7 +28,7 @@ type ServiceTableProps = {
|
|||
export default function ServiceTable({
|
||||
data,
|
||||
setData,
|
||||
uuid,
|
||||
user,
|
||||
}: ServiceTableProps) {
|
||||
const columnHelper = createColumnHelper<Service>();
|
||||
|
||||
|
@ -170,7 +171,8 @@ export default function ServiceTable({
|
|||
setData={setData}
|
||||
columns={columns}
|
||||
details={serviceDetails}
|
||||
createEndpoint={`/api/service/create?uuid=${uuid}`}
|
||||
createEndpoint={`/api/service/create?uuid=${user?.uuid}`}
|
||||
isAdmin={user?.role === "ADMIN"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ type TableProps<T extends DataPoint> = {
|
|||
columns: ColumnDef<T, any>[];
|
||||
details: Details[];
|
||||
createEndpoint: string;
|
||||
isAdmin?: boolean;
|
||||
};
|
||||
|
||||
/** Validates that all required fields in a new item have values */
|
||||
|
@ -78,6 +79,7 @@ export default function Table<T extends DataPoint>({
|
|||
columns,
|
||||
details,
|
||||
createEndpoint,
|
||||
isAdmin = false,
|
||||
}: TableProps<T>) {
|
||||
const columnHelper = createColumnHelper<T>();
|
||||
|
||||
|
@ -226,33 +228,37 @@ export default function Table<T extends DataPoint>({
|
|||
})}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td
|
||||
className="p-3 border-y border-gray-200"
|
||||
colSpan={100}
|
||||
>
|
||||
<CreateDrawer
|
||||
details={details}
|
||||
onCreate={(newItem) => {
|
||||
if (!validateNewItem(newItem, details)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
createRow(newItem).then((response) => {
|
||||
if (response.ok) {
|
||||
newItem.visible = true;
|
||||
setData((prev) => [
|
||||
...prev,
|
||||
newItem,
|
||||
]);
|
||||
{isAdmin && ( // Only show create drawer for admins
|
||||
<tr>
|
||||
<td
|
||||
className="p-3 border-y border-gray-200"
|
||||
colSpan={100}
|
||||
>
|
||||
<CreateDrawer
|
||||
details={details}
|
||||
onCreate={(newItem) => {
|
||||
if (
|
||||
!validateNewItem(newItem, details)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
createRow(newItem).then((response) => {
|
||||
if (response.ok) {
|
||||
newItem.visible = true;
|
||||
setData((prev) => [
|
||||
...prev,
|
||||
newItem,
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
@ -1,17 +1,12 @@
|
|||
import {
|
||||
ArrowDownCircleIcon,
|
||||
AtSymbolIcon,
|
||||
Bars2Icon,
|
||||
EnvelopeIcon,
|
||||
ListBulletIcon,
|
||||
UserIcon,
|
||||
} from "@heroicons/react/24/solid";
|
||||
import { Dispatch, SetStateAction, useState } from "react";
|
||||
import useTagsHandler from "@/components/TagsInput/TagsHandler";
|
||||
import { ColumnDef, createColumnHelper } from "@tanstack/react-table";
|
||||
import Table from "@/components/Table/Table";
|
||||
import { RowOpenAction } from "@/components/Table/RowOpenAction";
|
||||
import TagsInput from "@/components/TagsInput/Index";
|
||||
import User from "@/utils/models/User";
|
||||
import { Details } from "../Drawer/Drawer";
|
||||
import { Tag } from "../TagsInput/Tag";
|
||||
|
@ -19,7 +14,7 @@ import { Tag } from "../TagsInput/Tag";
|
|||
type UserTableProps = {
|
||||
data: User[];
|
||||
setData: Dispatch<SetStateAction<User[]>>;
|
||||
uuid: string;
|
||||
user?: User;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -27,7 +22,7 @@ type UserTableProps = {
|
|||
* @param props.data Stateful list of users to be displayed by the table
|
||||
* @param props.setData State setter for the list of users
|
||||
*/
|
||||
export default function UserTable({ data, setData, uuid }: UserTableProps) {
|
||||
export default function UserTable({ data, setData, user }: UserTableProps) {
|
||||
const columnHelper = createColumnHelper<User>();
|
||||
|
||||
const [rolePresets, setRolePresets] = useState([
|
||||
|
@ -88,6 +83,7 @@ export default function UserTable({ data, setData, uuid }: UserTableProps) {
|
|||
rowData={info.row.original}
|
||||
setData={setData}
|
||||
details={userDetails}
|
||||
isAdmin={user?.role === "ADMIN"}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
|
@ -145,7 +141,8 @@ export default function UserTable({ data, setData, uuid }: UserTableProps) {
|
|||
setData={setData}
|
||||
columns={columns}
|
||||
details={userDetails}
|
||||
createEndpoint={`/api/user/create?uuid=${uuid}`}
|
||||
createEndpoint={`/api/user/create?uuid=${user?.uuid}`}
|
||||
isAdmin={user?.role === "ADMIN"}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -14,8 +14,12 @@ const Loading = () => {
|
|||
style={{ height: "auto", width: "auto" }}
|
||||
priority
|
||||
/>
|
||||
<h1 className={styles.loadingTitle}>Loading...</h1>
|
||||
<div className={styles.loadingSpinner}></div>
|
||||
<h1 className="text-2xl font-semibold text-gray-700 mt-4 mb-6">
|
||||
Loading...
|
||||
</h1>
|
||||
<div className="flex justify-center">
|
||||
<div className="animate-spin rounded-full h-24 w-24 border-b-2 border-gray-700"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue
Block a user