Connect update to backend (#55)

This commit is contained in:
Prajwal Moharana 2025-01-07 12:13:57 -05:00 committed by GitHub
parent 0daf80d222
commit 55a03ff3fd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 271 additions and 69 deletions

View File

@ -25,9 +25,9 @@ def get_all(uuid: str, user_svc: UserService = Depends()):
return user_svc.all()
@api.get("/{user_id}", response_model=User, tags=["Users"])
def get_by_uuid(user_id: str, user_svc: UserService = Depends()):
return user_svc.get_user_by_uuid(user_id)
@api.get("/{uuid}", response_model=User, tags=["Users"])
def get_by_uuid(uuid: str, user_svc: UserService = Depends()):
return user_svc.get_user_by_uuid(uuid)
@api.post("/", response_model=User, tags=["Users"])
@ -37,3 +37,12 @@ def create_user(uuid: str, user: User, user_svc: UserService = Depends()):
raise Exception(f"Insufficient permissions for user {subject.uuid}")
return user_svc.create(user)
@api.put("/", response_model=User, tags=["Users"])
def update_user(uuid: str, user: User, user_svc: UserService = Depends()):
subject = user_svc.get_user_by_uuid(uuid)
if subject.role != UserTypeEnum.ADMIN:
raise Exception(f"Insufficient permissions for user {subject.uuid}")
return user_svc.update(user)

View File

@ -17,7 +17,7 @@ class ResourceService:
def get_resource_by_user(self, subject: User) -> list[Resource]:
"""Resource method getting all of the resources that a user has access to based on role"""
if subject.role != UserTypeEnum.VOLUNTEER:
query = select(ResourceEntity)
query = select(ResourceEntity).order_by(ResourceEntity.id)
entities = self._session.scalars(query).all()
return [resource.to_model() for resource in entities]
else:
@ -86,10 +86,10 @@ class ResourceService:
raise ResourceNotFoundException(
f"No resource found with matching id: {resource.id}"
)
entity.name = resource.name
entity.summary = resource.summary
entity.link = resource.link
entity.program = resource.program
entity.name = resource.name if resource.name else entity.name
entity.summary = resource.summary if resource.summary else entity.summary
entity.link = resource.link if resource.link else entity.link
entity.program = resource.program if resource.program else entity.program
self._session.commit()
return entity.to_model()

View File

@ -22,14 +22,18 @@ class ServiceService:
def get_service_by_user(self, subject: User) -> list[Service]:
"""Resource method getting all of the resources that a user has access to based on role"""
if subject.role != UserTypeEnum.VOLUNTEER:
query = select(ServiceEntity)
query = select(ServiceEntity).order_by(ServiceEntity.id)
entities = self._session.scalars(query).all()
return [service.to_model() for service in entities]
else:
programs = subject.program
services = []
for program in programs:
entities = self._session.query(ServiceEntity).where(ServiceEntity.program == program).all()
entities = (
self._session.query(ServiceEntity)
.where(ServiceEntity.program == program)
.all()
)
for entity in entities:
services.append(entity.to_model())
return [service for service in services]
@ -37,14 +41,14 @@ class ServiceService:
def get_service_by_name(self, name: str, subject: User) -> Service:
"""Service method getting services by id."""
query = select(ServiceEntity).where(
and_(
ServiceEntity.name == name, ServiceEntity.program.in_(subject.program)
)
and_(ServiceEntity.name == name, ServiceEntity.program.in_(subject.program))
)
entity = self._session.scalars(query).one_or_none()
if entity is None:
raise ServiceNotFoundException(f"Service with name: {name} does not exist or program has not been assigned")
raise ServiceNotFoundException(
f"Service with name: {name} does not exist or program has not been assigned"
)
return entity.to_model()
@ -66,7 +70,7 @@ class ServiceService:
raise ProgramNotAssignedException(
f"User is not {UserTypeEnum.ADMIN}, cannot update service"
)
query = select(ServiceEntity).where(ServiceEntity.id == service.id)
entity = self._session.scalars(query).one_or_none()
@ -75,11 +79,13 @@ class ServiceService:
"The service you are searching for does not exist."
)
entity.name = service.name
entity.status = service.status
entity.summary = service.summary
entity.requirements = service.requirements
entity.program = service.program
entity.name = service.name if service.name else entity.name
entity.status = service.status if service.status else entity.status
entity.summary = service.summary if service.summary else entity.summary
entity.requirements = (
service.requirements if service.requirements else entity.requirements
)
entity.program = service.program if service.program else entity.program
self._session.commit()
return entity.to_model()
@ -88,7 +94,7 @@ class ServiceService:
"""Deletes a service from the table."""
if subject.role != UserTypeEnum.ADMIN:
raise ProgramNotAssignedException(f"User is not {UserTypeEnum.ADMIN}")
query = select(ServiceEntity).where(ServiceEntity.id == service.id)
entity = self._session.scalars(query).one_or_none()

View File

@ -43,10 +43,10 @@ class UserService:
def all(self) -> list[User]:
"""
Returns a list of all Users
Returns a list of all Users ordered by id
"""
query = select(UserEntity)
query = select(UserEntity).order_by(UserEntity.id)
entities = self._session.scalars(query).all()
return [entity.to_model() for entity in entities]
@ -102,12 +102,12 @@ class UserService:
if obj is None:
raise Exception(f"No matching user found")
obj.username = user.username
obj.role = user.role
obj.email = user.email
obj.program = user.program
obj.experience = user.experience
obj.group = user.group
obj.username = user.username if user.username else obj.username
obj.role = user.role if user.role else obj.role
obj.email = user.email if user.email else obj.email
obj.program = user.program if user.program else obj.program
obj.experience = user.experience if user.experience else obj.experience
obj.group = user.group if user.group else obj.group
self._session.commit()

View File

@ -1,5 +1,5 @@
import { NextResponse } from "next/server";
import User from "@/utils/models/User";
import Resource from "@/utils/models/Resource";
export async function POST(request: Request) {
const apiEndpoint = `${process.env.NEXT_PUBLIC_API_HOST}/api/resource`;
@ -25,7 +25,7 @@ export async function POST(request: Request) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const createdResource: User = await response.json();
const createdResource: Resource = await response.json();
return NextResponse.json(createdResource, { status: response.status });
} catch (error) {
console.error("Error creating resource:", error);

View File

@ -0,0 +1,37 @@
import Resource from "@/utils/models/Resource";
import { NextResponse } from "next/server";
export async function POST(request: Request) {
const apiEndpoint = `${process.env.NEXT_PUBLIC_API_HOST}/api/resource`;
try {
const resourceData = await request.json();
console.log("RESOURCE DATA", JSON.stringify(resourceData));
const { searchParams } = new URL(request.url);
const uuid = searchParams.get("uuid");
// Send the POST request to the backend
const response = await fetch(`${apiEndpoint}?uuid=${uuid}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(resourceData),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const resource: Resource = await response.json();
return NextResponse.json(resource, { status: response.status });
} catch (error) {
console.error("Error creating user:", error);
return NextResponse.json(
{ error: "Failed to update resource" },
{ status: 500 }
);
}
}

View File

@ -0,0 +1,37 @@
import { NextResponse } from "next/server";
import Service from "@/utils/models/Service";
export async function POST(request: Request) {
const apiEndpoint = `${process.env.NEXT_PUBLIC_API_HOST}/api/service`;
try {
const serviceData = await request.json();
console.log("SERVICE DATA", JSON.stringify(serviceData));
const { searchParams } = new URL(request.url);
const uuid = searchParams.get("uuid");
// Send the POST request to the backend
const response = await fetch(`${apiEndpoint}?uuid=${uuid}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(serviceData),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const createdService: Service = await response.json();
return NextResponse.json(createdService, { status: response.status });
} catch (error) {
console.error("Error creating service:", error);
return NextResponse.json(
{ error: "Failed to update service" },
{ status: 500 }
);
}
}

View File

@ -0,0 +1,37 @@
import { NextResponse } from "next/server";
import User from "@/utils/models/User";
export async function POST(request: Request) {
const apiEndpoint = `${process.env.NEXT_PUBLIC_API_HOST}/api/user`;
try {
const userData = await request.json();
console.log("USER DATA", JSON.stringify(userData));
const { searchParams } = new URL(request.url);
const uuid = searchParams.get("uuid");
// Send the POST request to the backend
const response = await fetch(`${apiEndpoint}?uuid=${uuid}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(userData),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const createdUser: User = await response.json();
return NextResponse.json(createdUser, { status: response.status });
} catch (error) {
console.error("Error creating user:", error);
return NextResponse.json(
{ error: "Failed to create user" },
{ status: 500 }
);
}
}

View File

@ -32,6 +32,7 @@ type DrawerProps = {
rowContent?: any;
setRowContent?: Dispatch<SetStateAction<any>>;
isAdmin?: boolean;
updateRoute: string;
};
const Drawer: FunctionComponent<DrawerProps> = ({
@ -40,6 +41,7 @@ const Drawer: FunctionComponent<DrawerProps> = ({
rowContent,
setRowContent,
isAdmin,
updateRoute,
}: DrawerProps) => {
const [isOpen, setIsOpen] = useState(false);
const [isFull, setIsFull] = useState(false);
@ -67,18 +69,39 @@ const Drawer: FunctionComponent<DrawerProps> = ({
}
};
const handleUpdate = async () => {
const response = await fetch(updateRoute, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(tempRowContent),
});
return response;
};
const toggleDrawer = () => {
if (setRowContent && isOpen) {
setRowContent((prev: any) => {
return prev.map((row: any) => {
if (row.id === tempRowContent.id) {
return tempRowContent;
}
return row;
});
});
// Check if any values have changed
const hasChanges = Object.keys(tempRowContent).some(
(key) => tempRowContent[key] !== rowContent[key]
);
console.log("Send API request to update row content");
if (hasChanges) {
handleUpdate().then((response) => {
if (response.ok) {
setRowContent((prev: any) => {
return prev.map((row: any) => {
if (row.id === tempRowContent.id) {
return tempRowContent;
}
return row;
});
});
}
});
}
}
setIsOpen(!isOpen);

View File

@ -81,6 +81,7 @@ export default function ResourceTable({
setData={setData}
details={resourceDetails}
isAdmin={user?.role === "ADMIN"}
updateRoute={`/api/resource/update?uuid=${user?.uuid}`}
/>
),
}),

View File

@ -14,6 +14,7 @@ type RowOpenActionProps<T extends DataPoint> = {
setData: Dispatch<SetStateAction<T[]>>;
details: Details[];
isAdmin?: boolean;
updateRoute: string;
};
export function RowOpenAction<T extends DataPoint>({
@ -23,6 +24,7 @@ export function RowOpenAction<T extends DataPoint>({
setData,
details,
isAdmin,
updateRoute,
}: RowOpenActionProps<T>) {
return (
<div className="font-semibold group flex flex-row items-center justify-between pr-2">
@ -34,6 +36,7 @@ export function RowOpenAction<T extends DataPoint>({
details={details}
setRowContent={setData}
isAdmin={isAdmin}
updateRoute={updateRoute}
/>
</span>
</div>

View File

@ -39,13 +39,34 @@ export default function ServiceTable({
]);
const [requirementPresets, setRequirementPresets] = useState([
"ANONYMOUS",
"CONFIDENTIAL",
"REFERRAL REQUIRED",
"SAFETY ASSESSMENT",
"INTAKE REQUIRED",
"INCOME ELIGIBILITY",
"INITIAL ASSESSMENT",
"Anonymous",
"Confidential",
"Referral required",
"Safety assessment",
"Intake required",
"Income eligibility",
"Initial assessment",
"Insurance accepted",
"Open to parents",
"18+",
"Application required",
"Proof of income",
"Background check",
"Enrollment required",
"Registration required",
"Parental consent",
"Age-appropriate",
"Collaborative",
"Open to the public",
"Registration preferred",
"Legal case",
"Scheduling required",
"Limited availability",
"Eligibility assessment",
"Pre-registration required",
"Commitment to attend",
"Training required",
"Based on individual needs",
]);
const serviceDetails: Details[] = [
@ -100,6 +121,8 @@ export default function ServiceTable({
rowData={info.row.original}
setData={setData}
details={serviceDetails}
updateRoute={`/api/service/update?uuid=${user?.uuid}`}
isAdmin={user?.role === "ADMIN"}
/>
),
}),
@ -137,7 +160,7 @@ export default function ServiceTable({
</>
),
cell: (info) => (
<div className="flex flex-wrap gap-2 items-center px-2">
<div className="flex flex-wrap gap-2 items-center p-2">
{info.getValue().length > 0 ? (
info.getValue().map((tag: string, index: number) => {
return <Tag key={index}>{tag}</Tag>;

View File

@ -81,6 +81,8 @@ export default function Table<T extends DataPoint>({
createEndpoint,
isAdmin = false,
}: TableProps<T>) {
console.log(data);
const columnHelper = createColumnHelper<T>();
const createRow = async (newItem: any) => {

View File

@ -84,6 +84,7 @@ export default function UserTable({ data, setData, user }: UserTableProps) {
setData={setData}
details={userDetails}
isAdmin={user?.role === "ADMIN"}
updateRoute={`/api/user/update?uuid=${user?.uuid}`}
/>
),
}),
@ -122,7 +123,7 @@ export default function UserTable({ data, setData, user }: UserTableProps) {
</>
),
cell: (info) => (
<div className="flex ml-2 flex-wrap gap-2 items-center">
<div className="flex p-2 flex-wrap gap-2 items-center">
{info.getValue().length > 0 ? (
info.getValue().map((tag: string, index: number) => {
return <Tag key={index}>{tag}</Tag>;

View File

@ -15,7 +15,7 @@ export const TagDropdown = ({
handleAdd,
}: TagDropdownProps) => {
return (
<div className="z-50 flex flex-col space-y-2 mt-2">
<div className="z-50 flex flex-col space-y-2 mt-2 max-h-60 overflow-y-auto scrollbar-thin scrollbar-track-gray-100 scrollbar-thumb-gray-300 pr-2">
{Array.from(tags).length > 0 ? (
Array.from(tags).map((tag, index) => (
<div

View File

@ -21,7 +21,7 @@ const config: Config = {
},
},
},
plugins: [],
plugins: [require("tailwind-scrollbar")],
};
export default config;

26
package-lock.json generated
View File

@ -1,12 +1,13 @@
{
"name": "compass",
"name": "workspace",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"devDependencies": {
"husky": "^9.0.11",
"lint-staged": "^15.2.2"
"lint-staged": "^15.2.2",
"tailwind-scrollbar": "^4.0.0-beta.0"
}
},
"node_modules/ansi-escapes": {
@ -649,6 +650,27 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/tailwind-scrollbar": {
"version": "4.0.0-beta.0",
"resolved": "https://registry.npmjs.org/tailwind-scrollbar/-/tailwind-scrollbar-4.0.0-beta.0.tgz",
"integrity": "sha512-d6qwt3rYDgsKNaQGLW0P6N1TN/87xYZDjH6/PimtFvij2NgC5i3M6mEuVKR4Ixb2u3SvMBT95t7+xzJGJRzXtA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.13.0"
},
"peerDependencies": {
"tailwindcss": "^4.0.0-beta.8"
}
},
"node_modules/tailwindcss": {
"version": "4.0.0-beta.8",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-beta.8.tgz",
"integrity": "sha512-21HmdRq9tHDLJZavb2cRBGJxBvRODpwb0/t3tRbMOl65hJE6zG6K6lD6lLS3IOC35u4SOjKjdZiJJi9AuWCf+Q==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",

View File

@ -1,21 +1,22 @@
{
"devDependencies": {
"husky": "^9.0.11",
"lint-staged": "^15.2.2"
"lint-staged": "^15.2.2",
"tailwind-scrollbar": "^4.0.0-beta.0"
},
"scripts": {
"precommit": "lint-staged"
},
"lint-staged": {
"*.ts": [
"cd compass",
"prettier --write",
"git add"
],
"*.tsx": [
"cd compass",
"prettier --write",
"git add"
]
}
"scripts": {
"precommit": "lint-staged"
},
"lint-staged": {
"*.ts": [
"cd compass",
"prettier --write",
"git add"
],
"*.tsx": [
"cd compass",
"prettier --write",
"git add"
]
}
}