API Routes for Resources and Services (#40)

* Implemented API routes for getting all, creating, updating, and deleting resources, services, and tags.

* Updated main.py for API routes to include tags and rolled entities back.

* Created API routes for create, update, delete, get_all, and get_by_name. Deleted service methods for get by id.

* Defaults created_at to current time

* Write markdown file for HTTP requests using curl

---------

Co-authored-by: pmoharana-cmd <pmoharana032474@gmail.com>
This commit is contained in:
Aidan Kim 2024-11-05 19:12:03 -05:00 committed by GitHub
parent 99e43c7b30
commit 7d705ac743
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 1455 additions and 1226 deletions

View File

@ -1,4 +1,6 @@
from fastapi import APIRouter, Depends
from backend.models.user_model import User
from ..services import ResourceService, UserService
from ..models.resource_model import Resource
@ -15,12 +17,40 @@ openapi_tags = {
# TODO: Add security using HTTP Bearer Tokens
# TODO: Enable authorization by passing user uuid to API
# TODO: Create custom exceptions
@api.post("", response_model=Resource, tags=["Resource"])
def create(
uuid: str, resource: Resource, user_svc: UserService = Depends(), resource_svc: ResourceService = Depends()
):
subject = user_svc.get_user_by_uuid(uuid)
return resource_svc.create(subject, resource)
@api.get("", response_model=List[Resource], tags=["Resource"])
def get_all(
user_id: str,
resource_svc: ResourceService = Depends(),
user_svc: UserService = Depends(),
uuid: str, user_svc: UserService = Depends(), resource_svc: ResourceService = Depends()
):
subject = user_svc.get_user_by_uuid(user_id)
subject = user_svc.get_user_by_uuid(uuid)
return resource_svc.get_resource_by_user(subject)
@api.get("/{name}", response_model=Resource, tags=["Resource"])
def get_by_name(
name:str, uuid:str, user_svc: UserService = Depends(), resource_svc: ResourceService = Depends()
):
subject = user_svc.get_user_by_uuid(uuid)
return resource_svc.get_resource_by_name(name, subject)
@api.put("", response_model=Resource, tags=["Resource"])
def update(
uuid: str, resource: Resource, user_svc: UserService = Depends(), resource_svc: ResourceService = Depends()
):
subject = user_svc.get_user_by_uuid(uuid)
return resource_svc.update(subject, resource)
@api.delete("", response_model=None, tags=["Resource"])
def delete(
uuid: str, resource: Resource, user_svc: UserService = Depends(), resource_svc: ResourceService = Depends()
):
subject = user_svc.get_user_by_uuid(uuid)
resource_svc.delete(subject, resource)

View File

@ -1,4 +1,6 @@
from fastapi import APIRouter, Depends
from backend.models.user_model import User
from ..services import ServiceService, UserService
from ..models.service_model import Service
@ -15,12 +17,38 @@ openapi_tags = {
# TODO: Add security using HTTP Bearer Tokens
# TODO: Enable authorization by passing user uuid to API
# TODO: Create custom exceptions
@api.post("", response_model=Service, tags=["Service"])
def create(
uuid: str, service: Service, user_svc: UserService = Depends(), service_svc: ServiceService = Depends()
):
subject = user_svc.get_user_by_uuid(uuid)
return service_svc.create(subject, service)
@api.get("", response_model=List[Service], tags=["Service"])
def get_all(
user_id: str,
service_svc: ServiceService = Depends(),
user_svc: UserService = Depends(),
uuid: str, user_svc: UserService = Depends(), service_svc: ServiceService = Depends()
):
subject = user_svc.get_user_by_uuid(user_id)
subject = user_svc.get_user_by_uuid(uuid)
return service_svc.get_service_by_user(subject)
@api.get("/{name}", response_model=Service, tags=["Service"])
def get_by_name(
name: str, uuid: str, user_svc: UserService = Depends(), service_svc: ServiceService = Depends()
):
subject = user_svc.get_user_by_uuid(uuid)
return service_svc.get_service_by_name(name, subject)
@api.put("", response_model=Service, tags=["Service"])
def update(
uuid: str, service: Service, user_svc: UserService = Depends(), service_svc: ServiceService = Depends()
):
subject = user_svc.get_user_by_uuid(uuid)
return service_svc.update(subject, service)
@api.delete("", response_model=None, tags=["Service"])
def delete(
uuid: str, service: Service, user_svc: UserService = Depends(), service_svc: ServiceService = Depends()
):
subject = user_svc.get_user_by_uuid(uuid)
service_svc.delete(subject, service)

51
backend/api/tag.py Normal file
View File

@ -0,0 +1,51 @@
from fastapi import APIRouter, Depends
from backend.models.tag_model import Tag
from backend.models.user_model import User
from backend.services.tag import TagService
from ..services import ResourceService, UserService
from ..models.resource_model import Resource
from typing import List
api = APIRouter(prefix="/api/tag")
openapi_tags = {
"name": "Tag",
"description": "Tag CRUD operations.",
}
# TODO: Add security using HTTP Bearer Tokens
# TODO: Enable authorization by passing user uuid to API
# TODO: Create custom exceptions
@api.post("", response_model=Tag, tags=["Tag"])
def create(
subject: User,
tag: Tag,
tag_service: TagService=Depends()
):
return tag_service.create(subject, tag)
@api.get("", response_model=List[Tag], tags=["Tag"])
def get_all(
subject: User,
tag_svc: TagService=Depends()
):
return tag_svc.get_all()
@api.put("", response_model=Tag, tags=["Tag"])
def update(
subject: User,
tag: Tag,
tag_svc: TagService=Depends()
):
return tag_svc.delete(subject, tag)
@api.delete("", response_model=None, tags=["Tag"])
def delete(
subject: User,
tag: Tag,
tag_svc: TagService=Depends()
):
tag_svc.delete(subject, tag)

147
backend/api/test_routes.md Normal file
View File

@ -0,0 +1,147 @@
# Synopsis
Collection of sample curl requests for api routes.
# Resources
## Get All
Given an admin UUID, gets all of the resources from ResourceEntity.
```
curl -X 'GET' \
'http://127.0.0.1:8000/api/resource?uuid=acc6e112-d296-4739-a80c-b89b2933e50b' \
-H 'accept: application/json'
```
## Get by Name
Given the name of a resource and an admin UUID, gets a resource from ResourceEntity by name.
```
curl -X 'GET' \
'http://127.0.0.1:8000/api/resource/Financial%20Empowerment%20Center?uuid=acc6e112-d296-4739-a80c-b89b2933e50b' \
-H 'accept: application/json'
```
## Create
Given an admin UUID and a new resource object, adds a resource to ResourceEntity.
```
curl -X 'POST' \
'http://127.0.0.1:8000/api/resource?uuid=acc6e112-d296-4739-a80c-b89b2933e50b' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"id": 25,
"name": "algorithms and analysis textbook",
"summary": "textbook written by kevin sun for c550",
"link": "kevinsun.org",
"program": "DOMESTIC",
"created_at": "2024-11-04T20:07:31.875166"
}'
```
## Update
Given an admin UUID and a modified resource object, updates the resource with a matching ID if it exists.
```
curl -X 'PUT' \
'http://127.0.0.1:8000/api/resource?uuid=acc6e112-d296-4739-a80c-b89b2933e50b' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"id": 25,
"name": "algorithms and analysis textbook",
"summary": "textbook written by the goat himself, kevin sun, for c550",
"link": "kevinsun.org",
"program": "DOMESTIC",
"created_at": "2024-11-04T20:07:31.875166"
}'
```
## Delete
Given an admin UUID and a resource object, deletes the resource with a matching ID if it exists.
```
curl -X 'DELETE' \
'http://127.0.0.1:8000/api/resource?uuid=acc6e112-d296-4739-a80c-b89b2933e50b' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"id": 25,
"name": "algorithms and analysis textbook",
"summary": "textbook written by the goat himself, kevin sun, for c550",
"link": "kevinsun.org",
"program": "DOMESTIC",
"created_at": "2024-11-04T20:07:31.875166"
}'
```
# Services
## Get All
Given an admin UUID, gets all of the services from ServiceEntity.
```
curl -X 'GET' \
'http://127.0.0.1:8000/api/service?uuid=acc6e112-d296-4739-a80c-b89b2933e50b' \
-H 'accept: application/json'
```
## Get by Name
Given the name of a service and an admin UUID, gets a service from ServiceEntity by name.
```
curl -X 'GET' \
'http://127.0.0.1:8000/api/service/Shelter%20Placement?uuid=acc6e112-d296-4739-a80c-b89b2933e50b' \
-H 'accept: application/json'
```
## Create
Given an admin UUID and a new service object, adds a service to ServiceEntity.
```
curl -X 'POST' \
'http://127.0.0.1:8000/api/service?uuid=acc6e112-d296-4739-a80c-b89b2933e50b' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"id": 25,
"created_at": "2024-11-04T20:07:31.890412",
"name": "c550 tutoring",
"status": "open",
"summary": "tutoring for kevin sun'\''s c550 class",
"requirements": [
"must be in c550"
],
"program": "COMMUNITY"
}'
```
## Update
Given an admin UUID and a modified service object, updates the service with a matching ID if it exists.
```
curl -X 'PUT' \
'http://127.0.0.1:8000/api/service?uuid=acc6e112-d296-4739-a80c-b89b2933e50b' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"id": 25,
"created_at": "2024-11-04T20:07:31.890412",
"name": "c550 tutoring",
"status": "closed",
"summary": "tutoring for kevin sun'\''s c550 class",
"requirements": [
"must be in c550"
],
"program": "COMMUNITY"
}'
```
## Delete
Given an admin UUID and a service object, deletes the service with a matching ID if it exists.
```
curl -X 'DELETE' \
'http://127.0.0.1:8000/api/service?uuid=acc6e112-d296-4739-a80c-b89b2933e50b' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"id": 25,
"created_at": "2024-11-04T20:07:31.890412",
"name": "c550 tutoring",
"status": "closed",
"summary": "tutoring for kevin sun'\''s c550 class",
"requirements": [
"must be in c550"
],
"program": "COMMUNITY"
}'
```

View File

@ -23,3 +23,4 @@ class ServiceTagEntity(EntityBase):
# relationships
service: Mapped["ServiceEntity"] = relationship(back_populates="serviceTags")
tag: Mapped["TagEntity"] = relationship(back_populates="serviceTags")

View File

@ -45,6 +45,7 @@ class TagEntity(EntityBase):
return cls(
id=model.id,
created_at=model.created_at,
content=model.id,
)
@ -58,8 +59,6 @@ class TagEntity(EntityBase):
return Tag(
id=self.id,
create_at=self.created_at,
content=self.content,
)

View File

@ -2,7 +2,8 @@ from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.middleware.gzip import GZipMiddleware
from .api import user, health, service, resource
from .api import user, health, service, resource, tag
description = """
Welcome to the **COMPASS** RESTful Application Programming Interface.
@ -17,12 +18,13 @@ app = FastAPI(
health.openapi_tags,
service.openapi_tags,
resource.openapi_tags,
tag.openapi_tags
],
)
app.add_middleware(GZipMiddleware)
feature_apis = [user, health, service, resource]
feature_apis = [user, health, service, resource, tag]
for feature_api in feature_apis:
app.include_router(feature_api.api)

View File

@ -12,4 +12,4 @@ class Resource(BaseModel):
summary: str = Field(..., max_length=300, description="The summary of the resource")
link: str = Field(..., max_length=150, description="link to the resource")
program: ProgramTypeEnum
created_at: Optional[datetime]
created_at: Optional[datetime] = datetime.now()

View File

@ -8,7 +8,7 @@ from .enum_for_models import ProgramTypeEnum
class Service(BaseModel):
id: int | None = None
created_at: datetime | None = None
created_at: datetime | None = datetime.now()
name: str
status: str
summary: str

View File

@ -10,4 +10,4 @@ class Tag(BaseModel):
content: str = Field(
..., max_length=600, description="content associated with the tag"
)
created_at: datetime | None = None
created_at: datetime | None = datetime.now()

View File

@ -14,5 +14,5 @@ class User(BaseModel):
group: str
program: List[ProgramTypeEnum]
role: UserTypeEnum
created_at: Optional[datetime]
created_at: Optional[datetime] = datetime.now()
uuid: str | None = None

View File

@ -20,6 +20,8 @@ class UserPermissionException(Exception):
class ServiceNotFoundException(Exception):
"""Exception for when the service being requested is not in the table."""
class TagNotFoundException(Exception):
"""Exception for when the tag being requested is not in the table."""
class ProgramNotAssignedException(Exception):
"""Exception for when the user does not have correct access for requested services."""

View File

@ -1,12 +1,12 @@
from fastapi import Depends
from ..database import db_session
from sqlalchemy.orm import Session
from sqlalchemy import select
from sqlalchemy import and_, select
from ..models.resource_model import Resource
from ..entities.resource_entity import ResourceEntity
from ..models.user_model import User, UserTypeEnum
from .exceptions import ResourceNotFoundException
from .exceptions import ProgramNotAssignedException, ResourceNotFoundException
class ResourceService:
@ -14,25 +14,40 @@ class ResourceService:
def __init__(self, session: Session = Depends(db_session)):
self._session = session
def get_resource_by_user(self, subject: User):
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)
entities = self._session.scalars(query).all()
return [resource.to_model() for resource in entities]
else:
programs = subject.program
resources = []
for program in programs:
query = select(ResourceEntity).filter(ResourceEntity.program == program)
entities = self._session.scalars(query).all()
entities = (
self._session.query(ResourceEntity)
.where(ResourceEntity.program == program)
.all()
)
for entity in entities:
resources.append(entity)
resources.append(entity.to_model())
return [resource for resource in resources]
return [resource.to_model() for resource in resources]
def get_resource_by_name(self, name: str, subject: User) -> Resource:
"""Get a resource by name."""
query = select(ResourceEntity).where(
and_(
ResourceEntity.name == name, ResourceEntity.program.in_(subject.program)
)
)
entity = self._session.scalars(query).one_or_none()
if entity is None:
raise ResourceNotFoundException(
f"Resource with name: {name} does not exist or program has not been assigned."
)
return entity.to_model()
def create(self, user: User, resource: Resource) -> Resource:
def create(self, subject: User, resource: Resource) -> Resource:
"""
Creates a resource based on the input object and adds it to the table if the user has the right permissions.
Parameters:
@ -41,43 +56,16 @@ class ResourceService:
Returns:
Resource: Object added to table
"""
if user.role != UserTypeEnum.ADMIN:
raise PermissionError(
"User does not have permission to add resources in this program."
if subject.role != UserTypeEnum.ADMIN:
raise ProgramNotAssignedException(
f"User is not {UserTypeEnum.ADMIN}, cannot update service"
)
resource_entity = ResourceEntity.from_model(resource)
self._session.add(resource_entity)
self._session.commit()
return resource_entity.to_model()
def get_by_id(self, user: User, id: int) -> Resource:
"""
Gets a resource based on the resource id that the user has access to
Parameters:
user: a valid User model representing the currently logged in User
id: int, the id of the resource
Returns:
Resource
Raises:
ResourceNotFoundException: If no resource is found with id
"""
resource = (
self._session.query(ResourceEntity)
.filter(
ResourceEntity.id == id,
ResourceEntity.program.in_(user.program),
)
.one_or_none()
)
if resource is None:
raise ResourceNotFoundException(f"No resource found with id: {id}")
return resource.to_model()
def update(self, user: User, resource: ResourceEntity) -> Resource:
def update(self, subject: User, resource: Resource) -> Resource:
"""
Update the resource if the user has access
Parameters:
@ -88,28 +76,24 @@ class ResourceService:
Raises:
ResourceNotFoundException: If no resource is found with the corresponding ID
"""
if user.role != UserTypeEnum.ADMIN:
raise PermissionError(
"User does not have permission to update this resource."
if subject.role != UserTypeEnum.ADMIN:
raise ProgramNotAssignedException(
f"User is not {UserTypeEnum.ADMIN}, cannot update service"
)
obj = self._session.get(ResourceEntity, resource.id) if resource.id else None
if obj is None:
query = select(ResourceEntity).where(ResourceEntity.id == resource.id)
entity = self._session.scalars(query).one_or_none()
if entity is None:
raise ResourceNotFoundException(
f"No resource found with matching id: {resource.id}"
)
obj.name = resource.name
obj.summary = resource.summary
obj.link = resource.link
obj.program = resource.program
entity.name = resource.name
entity.summary = resource.summary
entity.link = resource.link
entity.program = resource.program
self._session.commit()
return entity.to_model()
return obj.to_model()
def delete(self, user: User, id: int) -> None:
def delete(self, subject: User, resource: Resource) -> None:
"""
Delete resource based on id that the user has access to
Parameters:
@ -118,23 +102,17 @@ class ResourceService:
Raises:
ResourceNotFoundException: If no resource is found with the corresponding id
"""
if user.role != UserTypeEnum.ADMIN:
raise PermissionError(
"User does not have permission to delete this resource."
if subject.role != UserTypeEnum.ADMIN:
raise ProgramNotAssignedException(
f"User is not {UserTypeEnum.ADMIN}, cannot update service"
)
resource = (
self._session.query(ResourceEntity)
.filter(
ResourceEntity.id == id,
query = select(ResourceEntity).where(ResourceEntity.id == resource.id)
entity = self._session.scalars(query).one_or_none()
if entity is None:
raise ResourceNotFoundException(
f"No resource found with matching id: {resource.id}"
)
.one_or_none()
)
if resource is None:
raise ResourceNotFoundException(f"No resource found with matching id: {id}")
self._session.delete(resource)
self._session.delete(entity)
self._session.commit()
def get_by_slug(self, user: User, search_string: str) -> list[Resource]:
@ -150,7 +128,7 @@ class ResourceService:
"""
query = select(ResourceEntity).where(
ResourceEntity.name.ilike(f"%{search_string}%"),
ResourceEntity.program.in_(user.program)
ResourceEntity.program.in_(user.program),
)
entities = self._session.scalars(query).all()

View File

@ -19,62 +19,34 @@ class ServiceService:
def __init__(self, session: Session = Depends(db_session)):
self._session = session
def get_service_by_program(self, program: ProgramTypeEnum) -> list[Service]:
"""Service method getting services belonging to a particular program."""
query = select(ServiceEntity).filter(ServiceEntity.program == program)
entities = self._session.scalars(query)
return [entity.to_model() for entity in entities]
def get_service_by_id(self, id: int) -> Service:
"""Service method getting services by id."""
query = select(ServiceEntity).filter(ServiceEntity.id == id)
entity = self._session.scalars(query).one_or_none()
if entity is None:
raise ServiceNotFoundException(f"Service with id: {id} does not exist")
return entity.to_model()
def get_service_by_name(self, name: str) -> Service:
"""Service method getting services by id."""
query = select(ServiceEntity).filter(ServiceEntity.name == name)
entity = self._session.scalars(query).one_or_none()
if entity is None:
raise ServiceNotFoundException(f"Service with name: {name} does not exist")
return entity.to_model()
def get_service_by_user(self, subject: User):
"""Service method getting all of the services that a user has access to based on role"""
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)
entities = self._session.scalars(query).all()
return [service.to_model() for service in entities]
else:
programs = subject.program
services = []
for program in programs:
query = select(ServiceEntity).filter(ServiceEntity.program == program)
entities = self._session.scalars(query).all()
entities = self._session.query(ServiceEntity).where(ServiceEntity.program == program).all()
for entity in entities:
services.append(entity)
services.append(entity.to_model())
return [service for service in services]
return [service.to_model() for service in services]
def get_all(self, subject: User) -> list[Service]:
"""Service method retrieving all of the services in the table."""
if subject.role == UserTypeEnum.VOLUNTEER:
raise ProgramNotAssignedException(
f"User is not {UserTypeEnum.ADMIN} or {UserTypeEnum.VOLUNTEER}, cannot get all"
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)
)
)
entity = self._session.scalars(query).one_or_none()
query = select(ServiceEntity)
entities = self._session.scalars(query).all()
if entity is None:
raise ServiceNotFoundException(f"Service with name: {name} does not exist or program has not been assigned")
return [service.to_model() for service in entities]
return entity.to_model()
def create(self, subject: User, service: Service) -> Service:
"""Creates/adds a service to the table."""
@ -95,33 +67,35 @@ class ServiceService:
f"User is not {UserTypeEnum.ADMIN}, cannot update service"
)
service_entity = self._session.get(ServiceEntity, service.id)
query = select(ServiceEntity).where(ServiceEntity.id == service.id)
entity = self._session.scalars(query).one_or_none()
if service_entity is None:
if entity is None:
raise ServiceNotFoundException(
"The service you are searching for does not exist."
)
service_entity.name = service.name
service_entity.status = service.status
service_entity.summary = service.summary
service_entity.requirements = service.requirements
service_entity.program = service.program
entity.name = service.name
entity.status = service.status
entity.summary = service.summary
entity.requirements = service.requirements
entity.program = service.program
self._session.commit()
return service_entity.to_model()
return entity.to_model()
def delete(self, subject: User, service: Service) -> None:
"""Deletes a service from the table."""
if subject.role != UserTypeEnum.ADMIN:
raise ProgramNotAssignedException(f"User is not {UserTypeEnum.ADMIN}")
service_entity = self._session.get(ServiceEntity, service.id)
if service_entity is None:
query = select(ServiceEntity).where(ServiceEntity.id == service.id)
entity = self._session.scalars(query).one_or_none()
if entity is None:
raise ServiceNotFoundException(
"The service you are searching for does not exist."
)
self._session.delete(service_entity)
self._session.delete(entity)
self._session.commit()

View File

@ -1,20 +1,52 @@
from fastapi import Depends
from backend.models.enum_for_models import UserTypeEnum
from backend.models.user_model import User
from backend.services.exceptions import TagNotFoundException
from ..database import db_session
from sqlalchemy.orm import Session
from ..models.tag_model import Tag
from ..entities.tag_entity import TagEntity
from sqlalchemy import select
# Add in checks for user permission?
class TagService:
def __init__(self, session: Session = Depends(db_session)):
self._session = session
def all(self) -> list[Tag]:
def get_all(self) -> list[Tag]:
"""Returns a list of all Tags"""
query = select(TagEntity)
entities = self._session.scalars(query).all()
return [entity.to_model() for entity in entities]
def create(self, subject: User, tag: Tag) -> Tag:
entity = TagEntity.from_model(tag)
self._session.add(entity)
self._session.commit()
return entity.to_model()
def update(self, subject: User, tag: Tag) -> Tag:
query = select(TagEntity).where(TagEntity.id == tag.id)
entity = self._session.scalars(query).one_or_none()
if entity is None:
raise TagNotFoundException(f"Tag with id {tag.id} does not exist")
entity.content = tag.content
self._session.commit()
return entity.to_model()
def delete(self, subject: User, tag: Tag) -> None:
query = select(TagEntity).where(TagEntity.id == tag.id)
entity = self._session.scalars(query).one_or_none()
if entity is None:
raise TagNotFoundException(f"Tag with id {tag.id} does not exist")
self._session.delete(entity)
self._session.commit()

View File

@ -45,13 +45,11 @@ const Drawer: FunctionComponent<DrawerProps> = ({
const [tempRowContent, setTempRowContent] = useState(rowContent);
const onRowUpdate = (updatedRow: any) => {
setData((prevData: any) => (
prevData.map((row: any) => (
row.id === updatedRow.id
? updatedRow
: row
))
))
setData((prevData: any) =>
prevData.map((row: any) =>
row.id === updatedRow.id ? updatedRow : row
)
);
};
const handleTempRowContentChange = (e) => {

View File

@ -8,24 +8,20 @@ import TagsInput from "@/components/TagsInput/Index";
import Resource from "@/utils/models/Resource";
type ResourceTableProps = {
data: Resource[],
setData: Dispatch<SetStateAction<Resource[]>>
}
data: Resource[];
setData: Dispatch<SetStateAction<Resource[]>>;
};
/**
* Table componenet used for displaying resources
* @param props.data Stateful list of resources to be displayed by the table
* @param props.setData State setter for the list of resources
*/
export default function ResourceTable({ data, setData }: ResourceTableProps ) {
export default function ResourceTable({ data, setData }: ResourceTableProps) {
const columnHelper = createColumnHelper<Resource>();
// Set up tag handling
const programProps = useTagsHandler([
"community",
"domestic",
"economic",
])
const programProps = useTagsHandler(["community", "domestic", "economic"]);
// Define Tanstack columns
const columns: ColumnDef<Resource, any>[] = [
@ -66,10 +62,7 @@ export default function ResourceTable({ data, setData }: ResourceTableProps ) {
</>
),
cell: (info) => (
<TagsInput
presetValue={info.getValue()}
{...programProps}
/>
<TagsInput presetValue={info.getValue()} {...programProps} />
),
}),
@ -85,5 +78,5 @@ export default function ResourceTable({ data, setData }: ResourceTableProps ) {
}),
];
return <Table data={data} setData={setData} columns={columns}/>
return <Table data={data} setData={setData} columns={columns} />;
}

View File

@ -3,12 +3,16 @@ import DataPoint from "@/utils/models/DataPoint";
import { Dispatch, SetStateAction, useState } from "react";
type RowOpenActionProps<T extends DataPoint> = {
title: string,
rowData: T,
setData: Dispatch<SetStateAction<T[]>>
}
title: string;
rowData: T;
setData: Dispatch<SetStateAction<T[]>>;
};
export function RowOpenAction<T extends DataPoint>({ title, rowData, setData }: RowOpenActionProps<T>) {
export function RowOpenAction<T extends DataPoint>({
title,
rowData,
setData,
}: RowOpenActionProps<T>) {
const [pageContent, setPageContent] = useState("");
const handleDrawerContentChange = (newContent: string) => {
@ -31,4 +35,4 @@ export function RowOpenAction<T extends DataPoint>({ title, rowData, setData }:
</span>
</div>
);
};
}

View File

@ -8,35 +8,31 @@ import TagsInput from "@/components/TagsInput/Index";
import Service from "@/utils/models/Service";
type ServiceTableProps = {
data: Service[],
setData: Dispatch<SetStateAction<Service[]>>
}
data: Service[];
setData: Dispatch<SetStateAction<Service[]>>;
};
/**
* Table componenet used for displaying services
* @param props.data Stateful list of services to be displayed by the table
* @param props.setData State setter for the list of services
*/
export default function ServiceTable({ data, setData }: ServiceTableProps ) {
export default function ServiceTable({ data, setData }: ServiceTableProps) {
const columnHelper = createColumnHelper<Service>();
// Set up tag handling
const programProps = useTagsHandler([
"community",
"domestic",
"economic",
])
const programProps = useTagsHandler(["community", "domestic", "economic"]);
// TODO: Dynamically or statically get full list of preset requirement tag options
const requirementProps = useTagsHandler([
'anonymous',
'confidential',
'referral required',
'safety assessment',
'intake required',
'income eligibility',
'initial assessment',
])
"anonymous",
"confidential",
"referral required",
"safety assessment",
"intake required",
"income eligibility",
"initial assessment",
]);
// Define Tanstack columns
const columns: ColumnDef<Service, any>[] = [
@ -71,10 +67,7 @@ export default function ServiceTable({ data, setData }: ServiceTableProps ) {
</>
),
cell: (info) => (
<TagsInput
presetValue={info.getValue()}
{...programProps}
/>
<TagsInput presetValue={info.getValue()} {...programProps} />
),
}),
columnHelper.accessor("requirements", {
@ -86,7 +79,9 @@ export default function ServiceTable({ data, setData }: ServiceTableProps ) {
cell: (info) => (
// TODO: Setup different tag handler for requirements
<TagsInput
presetValue={info.getValue()[0] !== "" ? info.getValue() : ["N/A"]}
presetValue={
info.getValue()[0] !== "" ? info.getValue() : ["N/A"]
}
{...requirementProps}
/>
),
@ -104,5 +99,5 @@ export default function ServiceTable({ data, setData }: ServiceTableProps ) {
}),
];
return <Table data={data} setData={setData} columns={columns} />
};
return <Table data={data} setData={setData} columns={columns} />;
}

View File

@ -4,15 +4,15 @@ import {
useReactTable,
getCoreRowModel,
flexRender,
createColumnHelper
} from "@tanstack/react-table";
createColumnHelper,
} from "@tanstack/react-table";
import {
ChangeEvent,
useState,
useEffect,
Key,
Dispatch,
SetStateAction
SetStateAction,
} from "react";
import { TableAction } from "./TableAction";
import { PlusIcon } from "@heroicons/react/24/solid";
@ -21,9 +21,9 @@ import { RowOptionMenu } from "./RowOptionMenu";
import DataPoint from "@/utils/models/DataPoint";
type TableProps<T extends DataPoint> = {
data: T[],
setData: Dispatch<SetStateAction<T[]>>,
columns: ColumnDef<T, any>[]
data: T[];
setData: Dispatch<SetStateAction<T[]>>;
columns: ColumnDef<T, any>[];
};
/** Fuzzy search function */
@ -48,20 +48,21 @@ const fuzzyFilter = (
* @param props.data Stateful list of data to be held in the table
* @param props.setData State setter for the list of data
* @param props.columns Column definitions made with Tanstack columnHelper
*/
export default function Table<T extends DataPoint>({ data, setData, columns }: TableProps<T>) {
*/
export default function Table<T extends DataPoint>({
data,
setData,
columns,
}: TableProps<T>) {
const columnHelper = createColumnHelper<T>();
/** Sorting function based on visibility */
const visibilitySort = (a: T, b: T) => (
a.visible === b.visible
? 0
: a.visible ? -1 : 1
)
const visibilitySort = (a: T, b: T) =>
a.visible === b.visible ? 0 : a.visible ? -1 : 1;
// Sort data on load
useEffect(() => {
setData(prevData => prevData.sort(visibilitySort))
setData((prevData) => prevData.sort(visibilitySort));
}, [setData]);
// Data manipulation methods
@ -75,13 +76,13 @@ export default function Table<T extends DataPoint>({ data, setData, columns }: T
const hideData = (dataId: number) => {
console.log(`Toggling visibility for data with ID: ${dataId}`);
setData(currentData => {
setData((currentData) => {
const newData = currentData
.map(data => (
.map((data) =>
data.id === dataId
? { ...data, visible: !data.visible }
: data
))
? { ...data, visible: !data.visible }
: data
)
.sort(visibilitySort);
console.log(newData);
@ -104,7 +105,7 @@ export default function Table<T extends DataPoint>({ data, setData, columns }: T
/>
),
})
)
);
// Searching
const [query, setQuery] = useState("");
@ -221,4 +222,4 @@ export default function Table<T extends DataPoint>({ data, setData, columns }: T
</table>
</div>
);
};
}

View File

@ -1,4 +1,8 @@
import { ArrowDownCircleIcon, AtSymbolIcon, Bars2Icon } from "@heroicons/react/24/solid";
import {
ArrowDownCircleIcon,
AtSymbolIcon,
Bars2Icon,
} from "@heroicons/react/24/solid";
import { Dispatch, SetStateAction } from "react";
import useTagsHandler from "@/components/TagsInput/TagsHandler";
import { ColumnDef, createColumnHelper } from "@tanstack/react-table";
@ -8,16 +12,16 @@ import TagsInput from "@/components/TagsInput/Index";
import User from "@/utils/models/User";
type UserTableProps = {
data: User[],
setData: Dispatch<SetStateAction<User[]>>
}
data: User[];
setData: Dispatch<SetStateAction<User[]>>;
};
/**
* Table componenet used for displaying users
* @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 }: UserTableProps ) {
export default function UserTable({ data, setData }: UserTableProps) {
const columnHelper = createColumnHelper<User>();
// Set up tag handling
@ -25,13 +29,9 @@ export default function UserTable({ data, setData }: UserTableProps ) {
"administrator",
"volunteer",
"employee",
])
]);
const programProps = useTagsHandler([
"community",
"domestic",
"economic",
])
const programProps = useTagsHandler(["community", "domestic", "economic"]);
// Define Tanstack columns
const columns: ColumnDef<User, any>[] = [
@ -57,10 +57,7 @@ export default function UserTable({ data, setData }: UserTableProps ) {
</>
),
cell: (info) => (
<TagsInput
presetValue={info.getValue()}
{...roleProps}
/>
<TagsInput presetValue={info.getValue()} {...roleProps} />
),
}),
columnHelper.accessor("email", {
@ -83,13 +80,10 @@ export default function UserTable({ data, setData }: UserTableProps ) {
</>
),
cell: (info) => (
<TagsInput
presetValue={info.getValue()}
{...programProps}
/>
<TagsInput presetValue={info.getValue()} {...programProps} />
),
}),
];
return <Table<User> data={data} setData={setData} columns={columns}/>
return <Table<User> data={data} setData={setData} columns={columns} />;
}

View File

@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState } from "react";
/**
* Custom hook used to handle the state of tag options and colors
@ -31,5 +31,5 @@ export default function useTagsHandler(initialOptions: string[]) {
return tagColors.get(tag) as string;
};
return { presetOptions, setPresetOptions, getTagColor }
return { presetOptions, setPresetOptions, getTagColor };
}