mirror of
https://github.com/cssgunc/compass.git
synced 2025-04-09 22:00:18 -04:00
Prevent employee/volunteer from editting and revamp loading spinner
This commit is contained in:
parent
f6b0838c99
commit
04e23626be
|
@ -4,45 +4,98 @@ import { PageLayout } from "@/components/PageLayout";
|
||||||
import UserTable from "@/components/Table/UserTable";
|
import UserTable from "@/components/Table/UserTable";
|
||||||
import User from "@/utils/models/User";
|
import User from "@/utils/models/User";
|
||||||
import { createClient } from "@/utils/supabase/client";
|
import { createClient } from "@/utils/supabase/client";
|
||||||
|
|
||||||
import { UsersIcon } from "@heroicons/react/24/solid";
|
import { UsersIcon } from "@heroicons/react/24/solid";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const [users, setUsers] = useState<User[]>([]);
|
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(() => {
|
useEffect(() => {
|
||||||
async function getUser() {
|
async function getUsers() {
|
||||||
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) {
|
if (authError) {
|
||||||
console.log("Accessed admin page but not logged in");
|
throw new Error("Authentication failed. Please sign in.");
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
// 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 (
|
return (
|
||||||
<div className="min-h-screen flex flex-col">
|
<div className="min-h-screen flex flex-col">
|
||||||
{/* icon + title */}
|
|
||||||
<PageLayout title="Users" icon={<UsersIcon />}>
|
<PageLayout title="Users" icon={<UsersIcon />}>
|
||||||
{/* TODO: REPLACE UUID WITH HTTP BEARER TOKEN */}
|
{isLoading ? (
|
||||||
<UserTable data={users} setData={setUsers} uuid={uuid} />
|
<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>
|
</PageLayout>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,34 +4,68 @@ import { PageLayout } from "@/components/PageLayout";
|
||||||
import Resource from "@/utils/models/Resource";
|
import Resource from "@/utils/models/Resource";
|
||||||
import ResourceTable from "@/components/Table/ResourceTable";
|
import ResourceTable from "@/components/Table/ResourceTable";
|
||||||
import { createClient } from "@/utils/supabase/client";
|
import { createClient } from "@/utils/supabase/client";
|
||||||
|
|
||||||
import { BookmarkIcon } from "@heroicons/react/24/solid";
|
import { BookmarkIcon } from "@heroicons/react/24/solid";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import User from "@/utils/models/User";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const [resources, setResources] = useState<Resource[]>([]);
|
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(() => {
|
useEffect(() => {
|
||||||
async function getResources() {
|
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) {
|
if (authError) {
|
||||||
console.log("Accessed admin page but not logged in");
|
throw new Error("Authentication failed. Please sign in.");
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
// 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();
|
getResources();
|
||||||
|
@ -39,13 +73,25 @@ export default function Page() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex flex-col">
|
<div className="min-h-screen flex flex-col">
|
||||||
{/* icon + title */}
|
|
||||||
<PageLayout title="Resources" icon={<BookmarkIcon />}>
|
<PageLayout title="Resources" icon={<BookmarkIcon />}>
|
||||||
<ResourceTable
|
{isLoading ? (
|
||||||
data={resources}
|
<div className="flex justify-center items-center h-64">
|
||||||
setData={setResources}
|
<div className="animate-spin rounded-full h-24 w-24 border-b-2 border-purple-700" />
|
||||||
uuid={uuid}
|
</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>
|
</PageLayout>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,34 +3,69 @@
|
||||||
import { PageLayout } from "@/components/PageLayout";
|
import { PageLayout } from "@/components/PageLayout";
|
||||||
import ServiceTable from "@/components/Table/ServiceTable";
|
import ServiceTable from "@/components/Table/ServiceTable";
|
||||||
import Service from "@/utils/models/Service";
|
import Service from "@/utils/models/Service";
|
||||||
|
import User from "@/utils/models/User";
|
||||||
import { createClient } from "@/utils/supabase/client";
|
import { createClient } from "@/utils/supabase/client";
|
||||||
|
|
||||||
import { ClipboardIcon } from "@heroicons/react/24/solid";
|
import { ClipboardIcon } from "@heroicons/react/24/solid";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const [services, setServices] = useState<Service[]>([]);
|
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(() => {
|
useEffect(() => {
|
||||||
async function getServices() {
|
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) {
|
if (authError) {
|
||||||
console.log("Accessed admin page but not logged in");
|
throw new Error("Authentication failed. Please sign in.");
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
// 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();
|
getServices();
|
||||||
|
@ -38,13 +73,25 @@ export default function Page() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex flex-col">
|
<div className="min-h-screen flex flex-col">
|
||||||
{/* icon + title */}
|
|
||||||
<PageLayout title="Services" icon={<ClipboardIcon />}>
|
<PageLayout title="Services" icon={<ClipboardIcon />}>
|
||||||
<ServiceTable
|
{isLoading ? (
|
||||||
data={services}
|
<div className="flex justify-center items-center h-64">
|
||||||
setData={setServices}
|
<div className="animate-spin rounded-full h-24 w-24 border-b-2 border-purple-700" />
|
||||||
uuid={uuid}
|
</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>
|
</PageLayout>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
import { Dispatch, FunctionComponent, ReactNode, SetStateAction } from "react";
|
import { Dispatch, FunctionComponent, ReactNode, SetStateAction } from "react";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { ChevronDoubleLeftIcon } from "@heroicons/react/24/solid";
|
import { ChevronDoubleLeftIcon } from "@heroicons/react/24/solid";
|
||||||
import {
|
import { StarIcon as SolidStarIcon, UserIcon } from "@heroicons/react/24/solid";
|
||||||
StarIcon as SolidStarIcon,
|
|
||||||
EnvelopeIcon,
|
|
||||||
UserIcon,
|
|
||||||
} from "@heroicons/react/24/solid";
|
|
||||||
import {
|
import {
|
||||||
ArrowsPointingOutIcon,
|
ArrowsPointingOutIcon,
|
||||||
ArrowsPointingInIcon,
|
ArrowsPointingInIcon,
|
||||||
StarIcon as OutlineStarIcon,
|
StarIcon as OutlineStarIcon,
|
||||||
ListBulletIcon,
|
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
import TagsInput from "../TagsInput/Index";
|
import TagsInput from "../TagsInput/Index";
|
||||||
|
import { Tag } from "../TagsInput/Tag";
|
||||||
|
|
||||||
type InputType =
|
type InputType =
|
||||||
| "text"
|
| "text"
|
||||||
|
@ -35,6 +31,7 @@ type DrawerProps = {
|
||||||
details: Details[];
|
details: Details[];
|
||||||
rowContent?: any;
|
rowContent?: any;
|
||||||
setRowContent?: Dispatch<SetStateAction<any>>;
|
setRowContent?: Dispatch<SetStateAction<any>>;
|
||||||
|
isAdmin?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Drawer: FunctionComponent<DrawerProps> = ({
|
const Drawer: FunctionComponent<DrawerProps> = ({
|
||||||
|
@ -42,6 +39,7 @@ const Drawer: FunctionComponent<DrawerProps> = ({
|
||||||
details,
|
details,
|
||||||
rowContent,
|
rowContent,
|
||||||
setRowContent,
|
setRowContent,
|
||||||
|
isAdmin,
|
||||||
}: DrawerProps) => {
|
}: DrawerProps) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [isFull, setIsFull] = useState(false);
|
const [isFull, setIsFull] = useState(false);
|
||||||
|
@ -161,35 +159,48 @@ const Drawer: FunctionComponent<DrawerProps> = ({
|
||||||
valueToRender = (
|
valueToRender = (
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="rounded-md px-2 py-1">
|
<div className="rounded-md px-2 py-1">
|
||||||
<TagsInput
|
{isAdmin ? (
|
||||||
presetValue={
|
<TagsInput
|
||||||
typeof value ===
|
presetValue={
|
||||||
"string"
|
typeof value ===
|
||||||
? [value]
|
"string"
|
||||||
: value || []
|
? [value]
|
||||||
}
|
: value || []
|
||||||
presetOptions={
|
}
|
||||||
detail.presetOptionsValues ||
|
presetOptions={
|
||||||
[]
|
detail.presetOptionsValues ||
|
||||||
}
|
[]
|
||||||
setPresetOptions={
|
}
|
||||||
detail.presetOptionsSetter ||
|
setPresetOptions={
|
||||||
(() => {})
|
detail.presetOptionsSetter ||
|
||||||
}
|
(() => {})
|
||||||
singleValue={true}
|
}
|
||||||
onTagsChange={(
|
singleValue={true}
|
||||||
tags: Set<string>
|
onTagsChange={(
|
||||||
) => {
|
tags: Set<string>
|
||||||
const tagsArray =
|
) => {
|
||||||
Array.from(tags);
|
const tagsArray =
|
||||||
handleTempRowContentChange(
|
Array.from(
|
||||||
detail.key,
|
tags
|
||||||
tagsArray.length > 0
|
);
|
||||||
? tagsArray[0]
|
handleTempRowContentChange(
|
||||||
: null
|
detail.key,
|
||||||
);
|
tagsArray.length >
|
||||||
}}
|
0
|
||||||
/>
|
? tagsArray[0]
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="flex">
|
||||||
|
<Tag>
|
||||||
|
{value
|
||||||
|
? value
|
||||||
|
: "no value"}
|
||||||
|
</Tag>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -198,30 +209,56 @@ const Drawer: FunctionComponent<DrawerProps> = ({
|
||||||
valueToRender = (
|
valueToRender = (
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="rounded-md px-2 py-1">
|
<div className="rounded-md px-2 py-1">
|
||||||
<TagsInput
|
{isAdmin ? (
|
||||||
presetValue={
|
<TagsInput
|
||||||
typeof value ===
|
presetValue={
|
||||||
"string"
|
typeof value ===
|
||||||
? [value]
|
"string"
|
||||||
: value || []
|
? [value]
|
||||||
}
|
: value || []
|
||||||
presetOptions={
|
}
|
||||||
detail.presetOptionsValues ||
|
presetOptions={
|
||||||
[]
|
detail.presetOptionsValues ||
|
||||||
}
|
[]
|
||||||
setPresetOptions={
|
}
|
||||||
detail.presetOptionsSetter ||
|
setPresetOptions={
|
||||||
(() => {})
|
detail.presetOptionsSetter ||
|
||||||
}
|
(() => {})
|
||||||
onTagsChange={(
|
}
|
||||||
tags: Set<string>
|
onTagsChange={(
|
||||||
) => {
|
tags: Set<string>
|
||||||
handleTempRowContentChange(
|
) => {
|
||||||
detail.key,
|
handleTempRowContentChange(
|
||||||
Array.from(tags)
|
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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -238,6 +275,7 @@ const Drawer: FunctionComponent<DrawerProps> = ({
|
||||||
}
|
}
|
||||||
onKeyDown={handleEnterPress}
|
onKeyDown={handleEnterPress}
|
||||||
rows={4}
|
rows={4}
|
||||||
|
disabled={!isAdmin}
|
||||||
onInput={(e) => {
|
onInput={(e) => {
|
||||||
const target =
|
const target =
|
||||||
e.target as HTMLTextAreaElement;
|
e.target as HTMLTextAreaElement;
|
||||||
|
@ -261,6 +299,7 @@ const Drawer: FunctionComponent<DrawerProps> = ({
|
||||||
type={detail.inputType}
|
type={detail.inputType}
|
||||||
name={detail.key}
|
name={detail.key}
|
||||||
value={value}
|
value={value}
|
||||||
|
disabled={!isAdmin}
|
||||||
onChange={
|
onChange={
|
||||||
handleTempRowContentChangeHTML
|
handleTempRowContentChangeHTML
|
||||||
}
|
}
|
||||||
|
@ -283,6 +322,7 @@ const Drawer: FunctionComponent<DrawerProps> = ({
|
||||||
handleTempRowContentChangeHTML
|
handleTempRowContentChangeHTML
|
||||||
}
|
}
|
||||||
onKeyDown={handleEnterPress}
|
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"
|
className="w-full p-1 font-normal hover:text-gray-400 focus:outline-gray-200 underline text-gray-500 bg-transparent"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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";
|
} from "@heroicons/react/24/solid";
|
||||||
import { SidebarItem } from "./SidebarItem";
|
import { SidebarItem } from "./SidebarItem";
|
||||||
import { UserProfile } from "../resource/UserProfile";
|
import { UserProfile } from "../resource/UserProfile";
|
||||||
import LoadingIcon from "./LoadingIcon";
|
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
setIsSidebarOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
setIsSidebarOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
@ -67,7 +66,9 @@ const Sidebar: React.FC<SidebarProps> = ({
|
||||||
{/* Loading indicator*/}
|
{/* Loading indicator*/}
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<div className="fixed top-2 left-2">
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -6,19 +6,17 @@ import {
|
||||||
UserIcon,
|
UserIcon,
|
||||||
} from "@heroicons/react/24/solid";
|
} from "@heroicons/react/24/solid";
|
||||||
import { Dispatch, SetStateAction, useState } from "react";
|
import { Dispatch, SetStateAction, useState } from "react";
|
||||||
import useTagsHandler from "@/components/TagsInput/TagsHandler";
|
|
||||||
import { ColumnDef, createColumnHelper } from "@tanstack/react-table";
|
import { ColumnDef, createColumnHelper } from "@tanstack/react-table";
|
||||||
import { RowOpenAction } from "@/components/Table/RowOpenAction";
|
import { RowOpenAction } from "@/components/Table/RowOpenAction";
|
||||||
import Table from "@/components/Table/Table";
|
import Table from "@/components/Table/Table";
|
||||||
import TagsInput from "@/components/TagsInput/Index";
|
|
||||||
import Resource from "@/utils/models/Resource";
|
import Resource from "@/utils/models/Resource";
|
||||||
import { Details } from "../Drawer/Drawer";
|
import { Details } from "../Drawer/Drawer";
|
||||||
import { Tag } from "../TagsInput/Tag";
|
import { Tag } from "../TagsInput/Tag";
|
||||||
|
import User from "@/utils/models/User";
|
||||||
type ResourceTableProps = {
|
type ResourceTableProps = {
|
||||||
data: Resource[];
|
data: Resource[];
|
||||||
setData: Dispatch<SetStateAction<Resource[]>>;
|
setData: Dispatch<SetStateAction<Resource[]>>;
|
||||||
uuid: string;
|
user?: User;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,7 +27,7 @@ type ResourceTableProps = {
|
||||||
export default function ResourceTable({
|
export default function ResourceTable({
|
||||||
data,
|
data,
|
||||||
setData,
|
setData,
|
||||||
uuid,
|
user,
|
||||||
}: ResourceTableProps) {
|
}: ResourceTableProps) {
|
||||||
const columnHelper = createColumnHelper<Resource>();
|
const columnHelper = createColumnHelper<Resource>();
|
||||||
|
|
||||||
|
@ -82,6 +80,7 @@ export default function ResourceTable({
|
||||||
rowData={info.row.original}
|
rowData={info.row.original}
|
||||||
setData={setData}
|
setData={setData}
|
||||||
details={resourceDetails}
|
details={resourceDetails}
|
||||||
|
isAdmin={user?.role === "ADMIN"}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
|
@ -142,7 +141,8 @@ export default function ResourceTable({
|
||||||
setData={setData}
|
setData={setData}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
details={resourceDetails}
|
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;
|
rowData: T;
|
||||||
setData: Dispatch<SetStateAction<T[]>>;
|
setData: Dispatch<SetStateAction<T[]>>;
|
||||||
details: Details[];
|
details: Details[];
|
||||||
|
isAdmin?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function RowOpenAction<T extends DataPoint>({
|
export function RowOpenAction<T extends DataPoint>({
|
||||||
|
@ -21,6 +22,7 @@ export function RowOpenAction<T extends DataPoint>({
|
||||||
rowData,
|
rowData,
|
||||||
setData,
|
setData,
|
||||||
details,
|
details,
|
||||||
|
isAdmin,
|
||||||
}: RowOpenActionProps<T>) {
|
}: RowOpenActionProps<T>) {
|
||||||
return (
|
return (
|
||||||
<div className="font-semibold group flex flex-row items-center justify-between pr-2">
|
<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}
|
rowContent={rowData}
|
||||||
details={details}
|
details={details}
|
||||||
setRowContent={setData}
|
setRowContent={setData}
|
||||||
|
isAdmin={isAdmin}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,11 +12,12 @@ import { RowOpenAction } from "@/components/Table/RowOpenAction";
|
||||||
import Service from "@/utils/models/Service";
|
import Service from "@/utils/models/Service";
|
||||||
import { Details } from "../Drawer/Drawer";
|
import { Details } from "../Drawer/Drawer";
|
||||||
import { Tag } from "../TagsInput/Tag";
|
import { Tag } from "../TagsInput/Tag";
|
||||||
|
import User from "@/utils/models/User";
|
||||||
|
|
||||||
type ServiceTableProps = {
|
type ServiceTableProps = {
|
||||||
data: Service[];
|
data: Service[];
|
||||||
setData: Dispatch<SetStateAction<Service[]>>;
|
setData: Dispatch<SetStateAction<Service[]>>;
|
||||||
uuid: string;
|
user?: User;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,7 +28,7 @@ type ServiceTableProps = {
|
||||||
export default function ServiceTable({
|
export default function ServiceTable({
|
||||||
data,
|
data,
|
||||||
setData,
|
setData,
|
||||||
uuid,
|
user,
|
||||||
}: ServiceTableProps) {
|
}: ServiceTableProps) {
|
||||||
const columnHelper = createColumnHelper<Service>();
|
const columnHelper = createColumnHelper<Service>();
|
||||||
|
|
||||||
|
@ -170,7 +171,8 @@ export default function ServiceTable({
|
||||||
setData={setData}
|
setData={setData}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
details={serviceDetails}
|
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>[];
|
columns: ColumnDef<T, any>[];
|
||||||
details: Details[];
|
details: Details[];
|
||||||
createEndpoint: string;
|
createEndpoint: string;
|
||||||
|
isAdmin?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Validates that all required fields in a new item have values */
|
/** Validates that all required fields in a new item have values */
|
||||||
|
@ -78,6 +79,7 @@ export default function Table<T extends DataPoint>({
|
||||||
columns,
|
columns,
|
||||||
details,
|
details,
|
||||||
createEndpoint,
|
createEndpoint,
|
||||||
|
isAdmin = false,
|
||||||
}: TableProps<T>) {
|
}: TableProps<T>) {
|
||||||
const columnHelper = createColumnHelper<T>();
|
const columnHelper = createColumnHelper<T>();
|
||||||
|
|
||||||
|
@ -226,33 +228,37 @@ export default function Table<T extends DataPoint>({
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot>
|
<tfoot>
|
||||||
<tr>
|
{isAdmin && ( // Only show create drawer for admins
|
||||||
<td
|
<tr>
|
||||||
className="p-3 border-y border-gray-200"
|
<td
|
||||||
colSpan={100}
|
className="p-3 border-y border-gray-200"
|
||||||
>
|
colSpan={100}
|
||||||
<CreateDrawer
|
>
|
||||||
details={details}
|
<CreateDrawer
|
||||||
onCreate={(newItem) => {
|
details={details}
|
||||||
if (!validateNewItem(newItem, details)) {
|
onCreate={(newItem) => {
|
||||||
return false;
|
if (
|
||||||
}
|
!validateNewItem(newItem, details)
|
||||||
|
) {
|
||||||
createRow(newItem).then((response) => {
|
return false;
|
||||||
if (response.ok) {
|
|
||||||
newItem.visible = true;
|
|
||||||
setData((prev) => [
|
|
||||||
...prev,
|
|
||||||
newItem,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
createRow(newItem).then((response) => {
|
||||||
}}
|
if (response.ok) {
|
||||||
/>
|
newItem.visible = true;
|
||||||
</td>
|
setData((prev) => [
|
||||||
</tr>
|
...prev,
|
||||||
|
newItem,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,17 +1,12 @@
|
||||||
import {
|
import {
|
||||||
ArrowDownCircleIcon,
|
|
||||||
AtSymbolIcon,
|
|
||||||
Bars2Icon,
|
|
||||||
EnvelopeIcon,
|
EnvelopeIcon,
|
||||||
ListBulletIcon,
|
ListBulletIcon,
|
||||||
UserIcon,
|
UserIcon,
|
||||||
} from "@heroicons/react/24/solid";
|
} from "@heroicons/react/24/solid";
|
||||||
import { Dispatch, SetStateAction, useState } from "react";
|
import { Dispatch, SetStateAction, useState } from "react";
|
||||||
import useTagsHandler from "@/components/TagsInput/TagsHandler";
|
|
||||||
import { ColumnDef, createColumnHelper } from "@tanstack/react-table";
|
import { ColumnDef, createColumnHelper } from "@tanstack/react-table";
|
||||||
import Table from "@/components/Table/Table";
|
import Table from "@/components/Table/Table";
|
||||||
import { RowOpenAction } from "@/components/Table/RowOpenAction";
|
import { RowOpenAction } from "@/components/Table/RowOpenAction";
|
||||||
import TagsInput from "@/components/TagsInput/Index";
|
|
||||||
import User from "@/utils/models/User";
|
import User from "@/utils/models/User";
|
||||||
import { Details } from "../Drawer/Drawer";
|
import { Details } from "../Drawer/Drawer";
|
||||||
import { Tag } from "../TagsInput/Tag";
|
import { Tag } from "../TagsInput/Tag";
|
||||||
|
@ -19,7 +14,7 @@ import { Tag } from "../TagsInput/Tag";
|
||||||
type UserTableProps = {
|
type UserTableProps = {
|
||||||
data: User[];
|
data: User[];
|
||||||
setData: Dispatch<SetStateAction<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.data Stateful list of users to be displayed by the table
|
||||||
* @param props.setData State setter for the list of users
|
* @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 columnHelper = createColumnHelper<User>();
|
||||||
|
|
||||||
const [rolePresets, setRolePresets] = useState([
|
const [rolePresets, setRolePresets] = useState([
|
||||||
|
@ -88,6 +83,7 @@ export default function UserTable({ data, setData, uuid }: UserTableProps) {
|
||||||
rowData={info.row.original}
|
rowData={info.row.original}
|
||||||
setData={setData}
|
setData={setData}
|
||||||
details={userDetails}
|
details={userDetails}
|
||||||
|
isAdmin={user?.role === "ADMIN"}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
|
@ -145,7 +141,8 @@ export default function UserTable({ data, setData, uuid }: UserTableProps) {
|
||||||
setData={setData}
|
setData={setData}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
details={userDetails}
|
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" }}
|
style={{ height: "auto", width: "auto" }}
|
||||||
priority
|
priority
|
||||||
/>
|
/>
|
||||||
<h1 className={styles.loadingTitle}>Loading...</h1>
|
<h1 className="text-2xl font-semibold text-gray-700 mt-4 mb-6">
|
||||||
<div className={styles.loadingSpinner}></div>
|
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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user