mirror of
https://github.com/cssgunc/compass.git
synced 2025-04-23 03:29:49 -04:00
Compare commits
No commits in common. "d0a315c365cebbfc1bd2df940c66d64eb9b56ba7" and "99e43c7b309d36e64086b6bd33100c0854ff30e8" have entirely different histories.
d0a315c365
...
99e43c7b30
|
@ -1,6 +1,4 @@
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
|
|
||||||
from backend.models.user_model import User
|
|
||||||
from ..services import ResourceService, UserService
|
from ..services import ResourceService, UserService
|
||||||
from ..models.resource_model import Resource
|
from ..models.resource_model import Resource
|
||||||
|
|
||||||
|
@ -17,40 +15,12 @@ openapi_tags = {
|
||||||
# TODO: Add security using HTTP Bearer Tokens
|
# TODO: Add security using HTTP Bearer Tokens
|
||||||
# TODO: Enable authorization by passing user uuid to API
|
# TODO: Enable authorization by passing user uuid to API
|
||||||
# TODO: Create custom exceptions
|
# 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"])
|
@api.get("", response_model=List[Resource], tags=["Resource"])
|
||||||
def get_all(
|
def get_all(
|
||||||
uuid: str, user_svc: UserService = Depends(), resource_svc: ResourceService = Depends()
|
user_id: str,
|
||||||
|
resource_svc: ResourceService = Depends(),
|
||||||
|
user_svc: UserService = Depends(),
|
||||||
):
|
):
|
||||||
subject = user_svc.get_user_by_uuid(uuid)
|
subject = user_svc.get_user_by_uuid(user_id)
|
||||||
|
|
||||||
return resource_svc.get_resource_by_user(subject)
|
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)
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
|
|
||||||
from backend.models.user_model import User
|
|
||||||
from ..services import ServiceService, UserService
|
from ..services import ServiceService, UserService
|
||||||
from ..models.service_model import Service
|
from ..models.service_model import Service
|
||||||
|
|
||||||
|
@ -17,38 +15,12 @@ openapi_tags = {
|
||||||
# TODO: Add security using HTTP Bearer Tokens
|
# TODO: Add security using HTTP Bearer Tokens
|
||||||
# TODO: Enable authorization by passing user uuid to API
|
# TODO: Enable authorization by passing user uuid to API
|
||||||
# TODO: Create custom exceptions
|
# 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"])
|
@api.get("", response_model=List[Service], tags=["Service"])
|
||||||
def get_all(
|
def get_all(
|
||||||
uuid: str, user_svc: UserService = Depends(), service_svc: ServiceService = Depends()
|
user_id: str,
|
||||||
|
service_svc: ServiceService = Depends(),
|
||||||
|
user_svc: UserService = Depends(),
|
||||||
):
|
):
|
||||||
subject = user_svc.get_user_by_uuid(uuid)
|
subject = user_svc.get_user_by_uuid(user_id)
|
||||||
|
|
||||||
return service_svc.get_service_by_user(subject)
|
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)
|
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
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)
|
|
|
@ -1,147 +0,0 @@
|
||||||
# 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"
|
|
||||||
}'
|
|
||||||
```
|
|
|
@ -23,4 +23,3 @@ class ServiceTagEntity(EntityBase):
|
||||||
# relationships
|
# relationships
|
||||||
service: Mapped["ServiceEntity"] = relationship(back_populates="serviceTags")
|
service: Mapped["ServiceEntity"] = relationship(back_populates="serviceTags")
|
||||||
tag: Mapped["TagEntity"] = relationship(back_populates="serviceTags")
|
tag: Mapped["TagEntity"] = relationship(back_populates="serviceTags")
|
||||||
|
|
|
@ -45,7 +45,6 @@ class TagEntity(EntityBase):
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
id=model.id,
|
id=model.id,
|
||||||
created_at=model.created_at,
|
|
||||||
content=model.id,
|
content=model.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -59,6 +58,8 @@ class TagEntity(EntityBase):
|
||||||
|
|
||||||
return Tag(
|
return Tag(
|
||||||
id=self.id,
|
id=self.id,
|
||||||
create_at=self.created_at,
|
|
||||||
content=self.content,
|
content=self.content,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,7 @@ from fastapi import FastAPI, Request
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
from fastapi.middleware.gzip import GZipMiddleware
|
from fastapi.middleware.gzip import GZipMiddleware
|
||||||
|
|
||||||
|
from .api import user, health, service, resource
|
||||||
from .api import user, health, service, resource, tag
|
|
||||||
|
|
||||||
description = """
|
description = """
|
||||||
Welcome to the **COMPASS** RESTful Application Programming Interface.
|
Welcome to the **COMPASS** RESTful Application Programming Interface.
|
||||||
|
@ -18,13 +17,12 @@ app = FastAPI(
|
||||||
health.openapi_tags,
|
health.openapi_tags,
|
||||||
service.openapi_tags,
|
service.openapi_tags,
|
||||||
resource.openapi_tags,
|
resource.openapi_tags,
|
||||||
tag.openapi_tags
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
app.add_middleware(GZipMiddleware)
|
app.add_middleware(GZipMiddleware)
|
||||||
|
|
||||||
feature_apis = [user, health, service, resource, tag]
|
feature_apis = [user, health, service, resource]
|
||||||
|
|
||||||
for feature_api in feature_apis:
|
for feature_api in feature_apis:
|
||||||
app.include_router(feature_api.api)
|
app.include_router(feature_api.api)
|
||||||
|
|
|
@ -12,4 +12,4 @@ class Resource(BaseModel):
|
||||||
summary: str = Field(..., max_length=300, description="The summary of the resource")
|
summary: str = Field(..., max_length=300, description="The summary of the resource")
|
||||||
link: str = Field(..., max_length=150, description="link to the resource")
|
link: str = Field(..., max_length=150, description="link to the resource")
|
||||||
program: ProgramTypeEnum
|
program: ProgramTypeEnum
|
||||||
created_at: Optional[datetime] = datetime.now()
|
created_at: Optional[datetime]
|
||||||
|
|
|
@ -8,7 +8,7 @@ from .enum_for_models import ProgramTypeEnum
|
||||||
|
|
||||||
class Service(BaseModel):
|
class Service(BaseModel):
|
||||||
id: int | None = None
|
id: int | None = None
|
||||||
created_at: datetime | None = datetime.now()
|
created_at: datetime | None = None
|
||||||
name: str
|
name: str
|
||||||
status: str
|
status: str
|
||||||
summary: str
|
summary: str
|
||||||
|
|
|
@ -10,4 +10,4 @@ class Tag(BaseModel):
|
||||||
content: str = Field(
|
content: str = Field(
|
||||||
..., max_length=600, description="content associated with the tag"
|
..., max_length=600, description="content associated with the tag"
|
||||||
)
|
)
|
||||||
created_at: datetime | None = datetime.now()
|
created_at: datetime | None = None
|
||||||
|
|
|
@ -14,5 +14,5 @@ class User(BaseModel):
|
||||||
group: str
|
group: str
|
||||||
program: List[ProgramTypeEnum]
|
program: List[ProgramTypeEnum]
|
||||||
role: UserTypeEnum
|
role: UserTypeEnum
|
||||||
created_at: Optional[datetime] = datetime.now()
|
created_at: Optional[datetime]
|
||||||
uuid: str | None = None
|
uuid: str | None = None
|
||||||
|
|
|
@ -20,8 +20,6 @@ class UserPermissionException(Exception):
|
||||||
class ServiceNotFoundException(Exception):
|
class ServiceNotFoundException(Exception):
|
||||||
"""Exception for when the service being requested is not in the table."""
|
"""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):
|
class ProgramNotAssignedException(Exception):
|
||||||
"""Exception for when the user does not have correct access for requested services."""
|
"""Exception for when the user does not have correct access for requested services."""
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
from fastapi import Depends
|
from fastapi import Depends
|
||||||
from ..database import db_session
|
from ..database import db_session
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from sqlalchemy import and_, select
|
from sqlalchemy import select
|
||||||
from ..models.resource_model import Resource
|
from ..models.resource_model import Resource
|
||||||
from ..entities.resource_entity import ResourceEntity
|
from ..entities.resource_entity import ResourceEntity
|
||||||
from ..models.user_model import User, UserTypeEnum
|
from ..models.user_model import User, UserTypeEnum
|
||||||
|
|
||||||
from .exceptions import ProgramNotAssignedException, ResourceNotFoundException
|
from .exceptions import ResourceNotFoundException
|
||||||
|
|
||||||
|
|
||||||
class ResourceService:
|
class ResourceService:
|
||||||
|
@ -14,40 +14,25 @@ class ResourceService:
|
||||||
def __init__(self, session: Session = Depends(db_session)):
|
def __init__(self, session: Session = Depends(db_session)):
|
||||||
self._session = session
|
self._session = session
|
||||||
|
|
||||||
def get_resource_by_user(self, subject: User) -> list[Resource]:
|
def get_resource_by_user(self, subject: User):
|
||||||
"""Resource method getting all of the resources that a user has access to based on role"""
|
"""Resource method getting all of the resources that a user has access to based on role"""
|
||||||
if subject.role != UserTypeEnum.VOLUNTEER:
|
if subject.role != UserTypeEnum.VOLUNTEER:
|
||||||
query = select(ResourceEntity)
|
query = select(ResourceEntity)
|
||||||
entities = self._session.scalars(query).all()
|
entities = self._session.scalars(query).all()
|
||||||
|
|
||||||
return [resource.to_model() for resource in entities]
|
return [resource.to_model() for resource in entities]
|
||||||
else:
|
else:
|
||||||
programs = subject.program
|
programs = subject.program
|
||||||
resources = []
|
resources = []
|
||||||
for program in programs:
|
for program in programs:
|
||||||
entities = (
|
query = select(ResourceEntity).filter(ResourceEntity.program == program)
|
||||||
self._session.query(ResourceEntity)
|
entities = self._session.scalars(query).all()
|
||||||
.where(ResourceEntity.program == program)
|
|
||||||
.all()
|
|
||||||
)
|
|
||||||
for entity in entities:
|
for entity in entities:
|
||||||
resources.append(entity.to_model())
|
resources.append(entity)
|
||||||
return [resource for resource in resources]
|
|
||||||
|
|
||||||
def get_resource_by_name(self, name: str, subject: User) -> Resource:
|
return [resource.to_model() for resource in resources]
|
||||||
"""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, subject: User, resource: Resource) -> Resource:
|
def create(self, user: 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.
|
Creates a resource based on the input object and adds it to the table if the user has the right permissions.
|
||||||
Parameters:
|
Parameters:
|
||||||
|
@ -56,16 +41,43 @@ class ResourceService:
|
||||||
Returns:
|
Returns:
|
||||||
Resource: Object added to table
|
Resource: Object added to table
|
||||||
"""
|
"""
|
||||||
if subject.role != UserTypeEnum.ADMIN:
|
if user.role != UserTypeEnum.ADMIN:
|
||||||
raise ProgramNotAssignedException(
|
raise PermissionError(
|
||||||
f"User is not {UserTypeEnum.ADMIN}, cannot update service"
|
"User does not have permission to add resources in this program."
|
||||||
)
|
)
|
||||||
|
|
||||||
resource_entity = ResourceEntity.from_model(resource)
|
resource_entity = ResourceEntity.from_model(resource)
|
||||||
self._session.add(resource_entity)
|
self._session.add(resource_entity)
|
||||||
self._session.commit()
|
self._session.commit()
|
||||||
|
|
||||||
return resource_entity.to_model()
|
return resource_entity.to_model()
|
||||||
|
|
||||||
def update(self, subject: User, resource: Resource) -> Resource:
|
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:
|
||||||
"""
|
"""
|
||||||
Update the resource if the user has access
|
Update the resource if the user has access
|
||||||
Parameters:
|
Parameters:
|
||||||
|
@ -76,24 +88,28 @@ class ResourceService:
|
||||||
Raises:
|
Raises:
|
||||||
ResourceNotFoundException: If no resource is found with the corresponding ID
|
ResourceNotFoundException: If no resource is found with the corresponding ID
|
||||||
"""
|
"""
|
||||||
if subject.role != UserTypeEnum.ADMIN:
|
if user.role != UserTypeEnum.ADMIN:
|
||||||
raise ProgramNotAssignedException(
|
raise PermissionError(
|
||||||
f"User is not {UserTypeEnum.ADMIN}, cannot update service"
|
"User does not have permission to update this resource."
|
||||||
)
|
)
|
||||||
query = select(ResourceEntity).where(ResourceEntity.id == resource.id)
|
|
||||||
entity = self._session.scalars(query).one_or_none()
|
obj = self._session.get(ResourceEntity, resource.id) if resource.id else None
|
||||||
if entity is None:
|
|
||||||
|
if obj is None:
|
||||||
raise ResourceNotFoundException(
|
raise ResourceNotFoundException(
|
||||||
f"No resource found with matching id: {resource.id}"
|
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
|
|
||||||
self._session.commit()
|
|
||||||
return entity.to_model()
|
|
||||||
|
|
||||||
def delete(self, subject: User, resource: Resource) -> None:
|
obj.name = resource.name
|
||||||
|
obj.summary = resource.summary
|
||||||
|
obj.link = resource.link
|
||||||
|
obj.program = resource.program
|
||||||
|
|
||||||
|
self._session.commit()
|
||||||
|
|
||||||
|
return obj.to_model()
|
||||||
|
|
||||||
|
def delete(self, user: User, id: int) -> None:
|
||||||
"""
|
"""
|
||||||
Delete resource based on id that the user has access to
|
Delete resource based on id that the user has access to
|
||||||
Parameters:
|
Parameters:
|
||||||
|
@ -102,17 +118,23 @@ class ResourceService:
|
||||||
Raises:
|
Raises:
|
||||||
ResourceNotFoundException: If no resource is found with the corresponding id
|
ResourceNotFoundException: If no resource is found with the corresponding id
|
||||||
"""
|
"""
|
||||||
if subject.role != UserTypeEnum.ADMIN:
|
if user.role != UserTypeEnum.ADMIN:
|
||||||
raise ProgramNotAssignedException(
|
raise PermissionError(
|
||||||
f"User is not {UserTypeEnum.ADMIN}, cannot update service"
|
"User does not have permission to delete this resource."
|
||||||
)
|
)
|
||||||
query = select(ResourceEntity).where(ResourceEntity.id == resource.id)
|
|
||||||
entity = self._session.scalars(query).one_or_none()
|
resource = (
|
||||||
if entity is None:
|
self._session.query(ResourceEntity)
|
||||||
raise ResourceNotFoundException(
|
.filter(
|
||||||
f"No resource found with matching id: {resource.id}"
|
ResourceEntity.id == id,
|
||||||
)
|
)
|
||||||
self._session.delete(entity)
|
.one_or_none()
|
||||||
|
)
|
||||||
|
|
||||||
|
if resource is None:
|
||||||
|
raise ResourceNotFoundException(f"No resource found with matching id: {id}")
|
||||||
|
|
||||||
|
self._session.delete(resource)
|
||||||
self._session.commit()
|
self._session.commit()
|
||||||
|
|
||||||
def get_by_slug(self, user: User, search_string: str) -> list[Resource]:
|
def get_by_slug(self, user: User, search_string: str) -> list[Resource]:
|
||||||
|
@ -128,7 +150,7 @@ class ResourceService:
|
||||||
"""
|
"""
|
||||||
query = select(ResourceEntity).where(
|
query = select(ResourceEntity).where(
|
||||||
ResourceEntity.name.ilike(f"%{search_string}%"),
|
ResourceEntity.name.ilike(f"%{search_string}%"),
|
||||||
ResourceEntity.program.in_(user.program),
|
ResourceEntity.program.in_(user.program)
|
||||||
)
|
)
|
||||||
entities = self._session.scalars(query).all()
|
entities = self._session.scalars(query).all()
|
||||||
|
|
||||||
|
|
|
@ -19,34 +19,62 @@ class ServiceService:
|
||||||
def __init__(self, session: Session = Depends(db_session)):
|
def __init__(self, session: Session = Depends(db_session)):
|
||||||
self._session = session
|
self._session = session
|
||||||
|
|
||||||
def get_service_by_user(self, subject: User) -> list[Service]:
|
def get_service_by_program(self, program: ProgramTypeEnum) -> list[Service]:
|
||||||
"""Resource method getting all of the resources that a user has access to based on role"""
|
"""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"""
|
||||||
if subject.role != UserTypeEnum.VOLUNTEER:
|
if subject.role != UserTypeEnum.VOLUNTEER:
|
||||||
query = select(ServiceEntity)
|
query = select(ServiceEntity)
|
||||||
entities = self._session.scalars(query).all()
|
entities = self._session.scalars(query).all()
|
||||||
|
|
||||||
return [service.to_model() for service in entities]
|
return [service.to_model() for service in entities]
|
||||||
else:
|
else:
|
||||||
programs = subject.program
|
programs = subject.program
|
||||||
services = []
|
services = []
|
||||||
for program in programs:
|
for program in programs:
|
||||||
entities = self._session.query(ServiceEntity).where(ServiceEntity.program == program).all()
|
query = select(ServiceEntity).filter(ServiceEntity.program == program)
|
||||||
|
entities = self._session.scalars(query).all()
|
||||||
for entity in entities:
|
for entity in entities:
|
||||||
services.append(entity.to_model())
|
services.append(entity)
|
||||||
return [service for service in services]
|
|
||||||
|
|
||||||
def get_service_by_name(self, name: str, subject: User) -> Service:
|
return [service.to_model() for service in services]
|
||||||
"""Service method getting services by id."""
|
|
||||||
query = select(ServiceEntity).where(
|
def get_all(self, subject: User) -> list[Service]:
|
||||||
and_(
|
"""Service method retrieving all of the services in the table."""
|
||||||
ServiceEntity.name == name, ServiceEntity.program.in_(subject.program)
|
if subject.role == UserTypeEnum.VOLUNTEER:
|
||||||
|
raise ProgramNotAssignedException(
|
||||||
|
f"User is not {UserTypeEnum.ADMIN} or {UserTypeEnum.VOLUNTEER}, cannot get all"
|
||||||
)
|
)
|
||||||
)
|
|
||||||
entity = self._session.scalars(query).one_or_none()
|
|
||||||
|
|
||||||
if entity is None:
|
query = select(ServiceEntity)
|
||||||
raise ServiceNotFoundException(f"Service with name: {name} does not exist or program has not been assigned")
|
entities = self._session.scalars(query).all()
|
||||||
|
|
||||||
return entity.to_model()
|
return [service.to_model() for service in entities]
|
||||||
|
|
||||||
def create(self, subject: User, service: Service) -> Service:
|
def create(self, subject: User, service: Service) -> Service:
|
||||||
"""Creates/adds a service to the table."""
|
"""Creates/adds a service to the table."""
|
||||||
|
@ -67,35 +95,33 @@ class ServiceService:
|
||||||
f"User is not {UserTypeEnum.ADMIN}, cannot update service"
|
f"User is not {UserTypeEnum.ADMIN}, cannot update service"
|
||||||
)
|
)
|
||||||
|
|
||||||
query = select(ServiceEntity).where(ServiceEntity.id == service.id)
|
service_entity = self._session.get(ServiceEntity, service.id)
|
||||||
entity = self._session.scalars(query).one_or_none()
|
|
||||||
|
|
||||||
if entity is None:
|
if service_entity is None:
|
||||||
raise ServiceNotFoundException(
|
raise ServiceNotFoundException(
|
||||||
"The service you are searching for does not exist."
|
"The service you are searching for does not exist."
|
||||||
)
|
)
|
||||||
|
|
||||||
entity.name = service.name
|
service_entity.name = service.name
|
||||||
entity.status = service.status
|
service_entity.status = service.status
|
||||||
entity.summary = service.summary
|
service_entity.summary = service.summary
|
||||||
entity.requirements = service.requirements
|
service_entity.requirements = service.requirements
|
||||||
entity.program = service.program
|
service_entity.program = service.program
|
||||||
|
|
||||||
self._session.commit()
|
self._session.commit()
|
||||||
|
|
||||||
return entity.to_model()
|
return service_entity.to_model()
|
||||||
|
|
||||||
def delete(self, subject: User, service: Service) -> None:
|
def delete(self, subject: User, service: Service) -> None:
|
||||||
"""Deletes a service from the table."""
|
"""Deletes a service from the table."""
|
||||||
if subject.role != UserTypeEnum.ADMIN:
|
if subject.role != UserTypeEnum.ADMIN:
|
||||||
raise ProgramNotAssignedException(f"User is not {UserTypeEnum.ADMIN}")
|
raise ProgramNotAssignedException(f"User is not {UserTypeEnum.ADMIN}")
|
||||||
|
service_entity = self._session.get(ServiceEntity, service.id)
|
||||||
|
|
||||||
query = select(ServiceEntity).where(ServiceEntity.id == service.id)
|
if service_entity is None:
|
||||||
entity = self._session.scalars(query).one_or_none()
|
|
||||||
|
|
||||||
if entity is None:
|
|
||||||
raise ServiceNotFoundException(
|
raise ServiceNotFoundException(
|
||||||
"The service you are searching for does not exist."
|
"The service you are searching for does not exist."
|
||||||
)
|
)
|
||||||
|
|
||||||
self._session.delete(entity)
|
self._session.delete(service_entity)
|
||||||
self._session.commit()
|
self._session.commit()
|
||||||
|
|
|
@ -1,52 +1,20 @@
|
||||||
from fastapi import Depends
|
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 ..database import db_session
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from ..models.tag_model import Tag
|
from ..models.tag_model import Tag
|
||||||
from ..entities.tag_entity import TagEntity
|
from ..entities.tag_entity import TagEntity
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
|
||||||
# Add in checks for user permission?
|
|
||||||
class TagService:
|
class TagService:
|
||||||
|
|
||||||
def __init__(self, session: Session = Depends(db_session)):
|
def __init__(self, session: Session = Depends(db_session)):
|
||||||
self._session = session
|
self._session = session
|
||||||
|
|
||||||
def get_all(self) -> list[Tag]:
|
def all(self) -> list[Tag]:
|
||||||
"""Returns a list of all Tags"""
|
"""Returns a list of all Tags"""
|
||||||
|
|
||||||
query = select(TagEntity)
|
query = select(TagEntity)
|
||||||
entities = self._session.scalars(query).all()
|
entities = self._session.scalars(query).all()
|
||||||
|
|
||||||
return [entity.to_model() for entity in entities]
|
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()
|
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import Sidebar from "@/components/Sidebar/Sidebar";
|
import Sidebar from "@/components/Sidebar/Sidebar";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
import { ChevronDoubleRightIcon } from "@heroicons/react/24/outline";
|
||||||
import { createClient } from "@/utils/supabase/client";
|
import { createClient } from "@/utils/supabase/client";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
@ -13,7 +14,7 @@ export default function RootLayout({
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
|
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [user, setUser] = useState<User>();
|
const [user, setUser] = useState<User>();
|
||||||
|
|
||||||
|
@ -55,13 +56,34 @@ export default function RootLayout({
|
||||||
<div className="flex-row">
|
<div className="flex-row">
|
||||||
{user ? (
|
{user ? (
|
||||||
<div>
|
<div>
|
||||||
<Sidebar
|
{/* button to open sidebar */}
|
||||||
setIsSidebarOpen={setIsSidebarOpen}
|
<button
|
||||||
isSidebarOpen={isSidebarOpen}
|
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
|
||||||
name={user.username}
|
className={`fixed z-20 p-2 text-gray-500 hover:text-gray-800 left-0`}
|
||||||
email={user.email}
|
aria-label={"Open sidebar"}
|
||||||
isAdmin={user.role === Role.ADMIN}
|
>
|
||||||
/>
|
{
|
||||||
|
!isSidebarOpen && (
|
||||||
|
<ChevronDoubleRightIcon className="h-5 w-5" />
|
||||||
|
) // Icon for closing the sidebar
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
{/* sidebar */}
|
||||||
|
<div
|
||||||
|
className={`absolute inset-y-0 left-0 transform ${
|
||||||
|
isSidebarOpen
|
||||||
|
? "translate-x-0"
|
||||||
|
: "-translate-x-full"
|
||||||
|
} w-64 transition duration-300 ease-in-out`}
|
||||||
|
>
|
||||||
|
<Sidebar
|
||||||
|
setIsSidebarOpen={setIsSidebarOpen}
|
||||||
|
name={user.username}
|
||||||
|
email={user.email}
|
||||||
|
isAdmin={user.role === Role.ADMIN}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/* page ui */}
|
||||||
<div
|
<div
|
||||||
className={`flex-1 transition duration-300 ease-in-out ${
|
className={`flex-1 transition duration-300 ease-in-out ${
|
||||||
isSidebarOpen ? "ml-64" : "ml-0"
|
isSidebarOpen ? "ml-64" : "ml-0"
|
||||||
|
|
|
@ -9,7 +9,7 @@ export async function GET(request: Request) {
|
||||||
const { searchParams } = new URL(request.url);
|
const { searchParams } = new URL(request.url);
|
||||||
const uuid = searchParams.get("uuid");
|
const uuid = searchParams.get("uuid");
|
||||||
|
|
||||||
const data = await fetch(`${apiEndpoint}?uuid=${uuid}`);
|
const data = await fetch(`${apiEndpoint}?user_id=${uuid}`);
|
||||||
|
|
||||||
const resourceData: Resource[] = await data.json();
|
const resourceData: Resource[] = await data.json();
|
||||||
// TODO: Remove make every resource visible
|
// TODO: Remove make every resource visible
|
||||||
|
|
|
@ -9,7 +9,7 @@ export async function GET(request: Request) {
|
||||||
const { searchParams } = new URL(request.url);
|
const { searchParams } = new URL(request.url);
|
||||||
const uuid = searchParams.get("uuid");
|
const uuid = searchParams.get("uuid");
|
||||||
|
|
||||||
const data = await fetch(`${apiEndpoint}?uuid=${uuid}`);
|
const data = await fetch(`${apiEndpoint}?user_id=${uuid}`);
|
||||||
|
|
||||||
const serviceData: Service[] = await data.json();
|
const serviceData: Service[] = await data.json();
|
||||||
// TODO: Remove make every service visible
|
// TODO: Remove make every service visible
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
import Sidebar from "@/components/Sidebar/Sidebar";
|
import Sidebar from "@/components/Sidebar/Sidebar";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
import { ChevronDoubleRightIcon } from "@heroicons/react/24/outline";
|
||||||
import { createClient } from "@/utils/supabase/client";
|
import { createClient } from "@/utils/supabase/client";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
@ -12,7 +13,7 @@ export default function RootLayout({
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
|
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||||
const [user, setUser] = useState<User>();
|
const [user, setUser] = useState<User>();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
@ -44,13 +45,34 @@ export default function RootLayout({
|
||||||
<div className="flex-row">
|
<div className="flex-row">
|
||||||
{user ? (
|
{user ? (
|
||||||
<div>
|
<div>
|
||||||
<Sidebar
|
{/* button to open sidebar */}
|
||||||
name={user.username}
|
<button
|
||||||
email={user.email}
|
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
|
||||||
setIsSidebarOpen={setIsSidebarOpen}
|
className={`fixed z-20 p-2 text-gray-500 hover:text-gray-800 left-0`}
|
||||||
isSidebarOpen={isSidebarOpen}
|
aria-label={"Open sidebar"}
|
||||||
isAdmin={user.role === Role.ADMIN}
|
>
|
||||||
/>
|
{
|
||||||
|
!isSidebarOpen && (
|
||||||
|
<ChevronDoubleRightIcon className="h-5 w-5" />
|
||||||
|
) // Icon for closing the sidebar
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
{/* sidebar */}
|
||||||
|
<div
|
||||||
|
className={`absolute inset-y-0 left-0 transform ${
|
||||||
|
isSidebarOpen
|
||||||
|
? "translate-x-0"
|
||||||
|
: "-translate-x-full"
|
||||||
|
} w-64 transition duration-300 ease-in-out`}
|
||||||
|
>
|
||||||
|
<Sidebar
|
||||||
|
name={user.username}
|
||||||
|
email={user.email}
|
||||||
|
setIsSidebarOpen={setIsSidebarOpen}
|
||||||
|
isAdmin={user.role === Role.ADMIN}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/* page ui */}
|
||||||
<div
|
<div
|
||||||
className={`flex-1 transition duration-300 ease-in-out ${
|
className={`flex-1 transition duration-300 ease-in-out ${
|
||||||
isSidebarOpen ? "ml-64" : "ml-0"
|
isSidebarOpen ? "ml-64" : "ml-0"
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import Sidebar from "@/components/Sidebar/Sidebar";
|
import Sidebar from "@/components/Sidebar/Sidebar";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
import { ChevronDoubleRightIcon } from "@heroicons/react/24/outline";
|
||||||
import { createClient } from "@/utils/supabase/client";
|
import { createClient } from "@/utils/supabase/client";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
@ -13,7 +14,7 @@ export default function RootLayout({
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
|
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [user, setUser] = useState<User>();
|
const [user, setUser] = useState<User>();
|
||||||
|
|
||||||
|
@ -47,14 +48,33 @@ export default function RootLayout({
|
||||||
<div className="flex-row">
|
<div className="flex-row">
|
||||||
{user ? (
|
{user ? (
|
||||||
<div>
|
<div>
|
||||||
<Sidebar
|
{/* button to open sidebar */}
|
||||||
setIsSidebarOpen={setIsSidebarOpen}
|
<button
|
||||||
isSidebarOpen={isSidebarOpen}
|
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
|
||||||
name={user.username}
|
className={`fixed z-20 p-2 text-gray-500 hover:text-gray-800 left-0`}
|
||||||
email={user.email}
|
aria-label={"Open sidebar"}
|
||||||
isAdmin={user.role === Role.ADMIN}
|
>
|
||||||
/>
|
{
|
||||||
{/* </div>*/}
|
!isSidebarOpen && (
|
||||||
|
<ChevronDoubleRightIcon className="h-5 w-5" />
|
||||||
|
) // Icon for closing the sidebar
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
{/* sidebar */}
|
||||||
|
<div
|
||||||
|
className={`absolute inset-y-0 left-0 transform ${
|
||||||
|
isSidebarOpen
|
||||||
|
? "translate-x-0"
|
||||||
|
: "-translate-x-full"
|
||||||
|
} w-64 transition duration-300 ease-in-out`}
|
||||||
|
>
|
||||||
|
<Sidebar
|
||||||
|
setIsSidebarOpen={setIsSidebarOpen}
|
||||||
|
name={user.username}
|
||||||
|
email={user.email}
|
||||||
|
isAdmin={user.role === Role.ADMIN}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
{/* page ui */}
|
{/* page ui */}
|
||||||
<div
|
<div
|
||||||
className={`flex-1 transition duration-300 ease-in-out ${
|
className={`flex-1 transition duration-300 ease-in-out ${
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import Sidebar from "@/components/Sidebar/Sidebar";
|
import Sidebar from "@/components/Sidebar/Sidebar";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
import { ChevronDoubleRightIcon } from "@heroicons/react/24/outline";
|
||||||
import { createClient } from "@/utils/supabase/client";
|
import { createClient } from "@/utils/supabase/client";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
@ -13,7 +14,7 @@ export default function RootLayout({
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
|
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [user, setUser] = useState<User>();
|
const [user, setUser] = useState<User>();
|
||||||
|
|
||||||
|
@ -47,13 +48,34 @@ export default function RootLayout({
|
||||||
<div className="flex-row">
|
<div className="flex-row">
|
||||||
{user ? (
|
{user ? (
|
||||||
<div>
|
<div>
|
||||||
<Sidebar
|
{/* button to open sidebar */}
|
||||||
setIsSidebarOpen={setIsSidebarOpen}
|
<button
|
||||||
isSidebarOpen={isSidebarOpen}
|
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
|
||||||
name={user.username}
|
className={`fixed z-20 p-2 text-gray-500 hover:text-gray-800 left-0`}
|
||||||
email={user.email}
|
aria-label={"Open sidebar"}
|
||||||
isAdmin={user.role === Role.ADMIN}
|
>
|
||||||
/>
|
{
|
||||||
|
!isSidebarOpen && (
|
||||||
|
<ChevronDoubleRightIcon className="h-5 w-5" />
|
||||||
|
) // Icon for closing the sidebar
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
{/* sidebar */}
|
||||||
|
<div
|
||||||
|
className={`absolute inset-y-0 left-0 transform ${
|
||||||
|
isSidebarOpen
|
||||||
|
? "translate-x-0"
|
||||||
|
: "-translate-x-full"
|
||||||
|
} w-64 transition duration-300 ease-in-out`}
|
||||||
|
>
|
||||||
|
<Sidebar
|
||||||
|
setIsSidebarOpen={setIsSidebarOpen}
|
||||||
|
name={user.username}
|
||||||
|
email={user.email}
|
||||||
|
isAdmin={user.role === Role.ADMIN}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/* page ui */}
|
||||||
<div
|
<div
|
||||||
className={`flex-1 transition duration-300 ease-in-out ${
|
className={`flex-1 transition duration-300 ease-in-out ${
|
||||||
isSidebarOpen ? "ml-64" : "ml-0"
|
isSidebarOpen ? "ml-64" : "ml-0"
|
||||||
|
|
|
@ -45,11 +45,13 @@ const Drawer: FunctionComponent<DrawerProps> = ({
|
||||||
const [tempRowContent, setTempRowContent] = useState(rowContent);
|
const [tempRowContent, setTempRowContent] = useState(rowContent);
|
||||||
|
|
||||||
const onRowUpdate = (updatedRow: any) => {
|
const onRowUpdate = (updatedRow: any) => {
|
||||||
setData((prevData: any) =>
|
setData((prevData: any) => (
|
||||||
prevData.map((row: any) =>
|
prevData.map((row: any) => (
|
||||||
row.id === updatedRow.id ? updatedRow : row
|
row.id === updatedRow.id
|
||||||
)
|
? updatedRow
|
||||||
);
|
: row
|
||||||
|
))
|
||||||
|
))
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTempRowContentChange = (e) => {
|
const handleTempRowContentChange = (e) => {
|
||||||
|
|
|
@ -2,7 +2,6 @@ import React from "react";
|
||||||
import {
|
import {
|
||||||
HomeIcon,
|
HomeIcon,
|
||||||
ChevronDoubleLeftIcon,
|
ChevronDoubleLeftIcon,
|
||||||
ChevronDoubleRightIcon,
|
|
||||||
BookmarkIcon,
|
BookmarkIcon,
|
||||||
ClipboardIcon,
|
ClipboardIcon,
|
||||||
BookOpenIcon,
|
BookOpenIcon,
|
||||||
|
@ -13,7 +12,6 @@ import { UserProfile } from "../resource/UserProfile";
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
setIsSidebarOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
setIsSidebarOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
isSidebarOpen: boolean;
|
|
||||||
name: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
|
@ -21,96 +19,70 @@ interface SidebarProps {
|
||||||
|
|
||||||
const Sidebar: React.FC<SidebarProps> = ({
|
const Sidebar: React.FC<SidebarProps> = ({
|
||||||
setIsSidebarOpen,
|
setIsSidebarOpen,
|
||||||
isSidebarOpen,
|
|
||||||
name,
|
name,
|
||||||
email,
|
email,
|
||||||
isAdmin: admin,
|
isAdmin: admin,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="w-64 h-full border border-gray-200 bg-gray-50 px-4">
|
||||||
{/* Button to open the sidebar. */}
|
{/* button to close sidebar */}
|
||||||
<button
|
<div className="flex justify-end">
|
||||||
onClick={() => setIsSidebarOpen(true)}
|
<button
|
||||||
className={
|
onClick={() => setIsSidebarOpen(false)}
|
||||||
"fixed z-20 p-2 text-gray-500 hover:text-gray-800 left-0"
|
className="py-2 text-gray-500 hover:text-gray-800"
|
||||||
}
|
aria-label="Close sidebar"
|
||||||
aria-label={"Open sidebar"}
|
>
|
||||||
>
|
<ChevronDoubleLeftIcon className="h-5 w-5" />
|
||||||
{!isSidebarOpen && (
|
</button>
|
||||||
<ChevronDoubleRightIcon className="h-5 w-5" />
|
</div>
|
||||||
)}
|
<div className="flex flex-col space-y-8">
|
||||||
</button>
|
{/* user + logout button */}
|
||||||
|
<div className="flex items-center p-4 space-x-2 border border-gray-200 rounded-md ">
|
||||||
{/* The sidebar itself. */}
|
<UserProfile name={name} email={email} />
|
||||||
<div
|
|
||||||
className={
|
|
||||||
"fixed left-0 w-64 h-full border border-gray-200 bg-gray-50 px-4 " +
|
|
||||||
(isSidebarOpen
|
|
||||||
? "translate-x-0" // Open
|
|
||||||
: "-translate-x-full opacity-25") + // Closed
|
|
||||||
" transition duration-300 ease-out" // More animation properties
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{/* Button to close sidebar */}
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<button
|
|
||||||
onClick={() => setIsSidebarOpen(false)}
|
|
||||||
className="py-2 text-gray-500 hover:text-gray-800"
|
|
||||||
aria-label="Close sidebar"
|
|
||||||
>
|
|
||||||
<ChevronDoubleLeftIcon className="h-5 w-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
{/* navigation menu */}
|
||||||
|
<div className="flex flex-col space-y-2">
|
||||||
|
<h4 className="text-xs font-semibold text-gray-500">
|
||||||
|
Pages
|
||||||
|
</h4>
|
||||||
|
<nav className="flex flex-col">
|
||||||
|
{admin && (
|
||||||
|
<SidebarItem
|
||||||
|
icon={<LockClosedIcon />}
|
||||||
|
text="Admin"
|
||||||
|
active={true}
|
||||||
|
redirect="/admin"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex flex-col space-y-8">
|
<SidebarItem
|
||||||
{/* user + logout button */}
|
icon={<HomeIcon />}
|
||||||
<div className="flex items-center p-4 space-x-2 border border-gray-200 rounded-md ">
|
text="Home"
|
||||||
<UserProfile name={name} email={email} />
|
active={true}
|
||||||
</div>
|
redirect="/home"
|
||||||
{/* navigation menu */}
|
/>
|
||||||
<div className="flex flex-col space-y-2">
|
<SidebarItem
|
||||||
<h4 className="text-xs font-semibold text-gray-500">
|
icon={<BookmarkIcon />}
|
||||||
Pages
|
text="Resources"
|
||||||
</h4>
|
active={true}
|
||||||
<nav className="flex flex-col">
|
redirect="/resource"
|
||||||
{admin && (
|
/>
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
icon={<LockClosedIcon />}
|
icon={<ClipboardIcon />}
|
||||||
text="Admin"
|
text="Services"
|
||||||
active={true}
|
active={true}
|
||||||
redirect="/admin"
|
redirect="/service"
|
||||||
/>
|
/>
|
||||||
)}
|
<SidebarItem
|
||||||
|
icon={<BookOpenIcon />}
|
||||||
<SidebarItem
|
text="Training Manuals"
|
||||||
icon={<HomeIcon />}
|
active={true}
|
||||||
text="Home"
|
redirect="/training-manuals"
|
||||||
active={true}
|
/>
|
||||||
redirect="/home"
|
</nav>
|
||||||
/>
|
|
||||||
<SidebarItem
|
|
||||||
icon={<BookmarkIcon />}
|
|
||||||
text="Resources"
|
|
||||||
active={true}
|
|
||||||
redirect="/resource"
|
|
||||||
/>
|
|
||||||
<SidebarItem
|
|
||||||
icon={<ClipboardIcon />}
|
|
||||||
text="Services"
|
|
||||||
active={true}
|
|
||||||
redirect="/service"
|
|
||||||
/>
|
|
||||||
<SidebarItem
|
|
||||||
icon={<BookOpenIcon />}
|
|
||||||
text="Training Manuals"
|
|
||||||
active={true}
|
|
||||||
redirect="/training-manuals"
|
|
||||||
/>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -8,20 +8,24 @@ import TagsInput from "@/components/TagsInput/Index";
|
||||||
import Resource from "@/utils/models/Resource";
|
import Resource from "@/utils/models/Resource";
|
||||||
|
|
||||||
type ResourceTableProps = {
|
type ResourceTableProps = {
|
||||||
data: Resource[];
|
data: Resource[],
|
||||||
setData: Dispatch<SetStateAction<Resource[]>>;
|
setData: Dispatch<SetStateAction<Resource[]>>
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table componenet used for displaying resources
|
* Table componenet used for displaying resources
|
||||||
* @param props.data Stateful list of resources to be displayed by the table
|
* @param props.data Stateful list of resources to be displayed by the table
|
||||||
* @param props.setData State setter for the list of resources
|
* @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>();
|
const columnHelper = createColumnHelper<Resource>();
|
||||||
|
|
||||||
// Set up tag handling
|
// Set up tag handling
|
||||||
const programProps = useTagsHandler(["community", "domestic", "economic"]);
|
const programProps = useTagsHandler([
|
||||||
|
"community",
|
||||||
|
"domestic",
|
||||||
|
"economic",
|
||||||
|
])
|
||||||
|
|
||||||
// Define Tanstack columns
|
// Define Tanstack columns
|
||||||
const columns: ColumnDef<Resource, any>[] = [
|
const columns: ColumnDef<Resource, any>[] = [
|
||||||
|
@ -62,7 +66,10 @@ export default function ResourceTable({ data, setData }: ResourceTableProps) {
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
cell: (info) => (
|
cell: (info) => (
|
||||||
<TagsInput presetValue={info.getValue()} {...programProps} />
|
<TagsInput
|
||||||
|
presetValue={info.getValue()}
|
||||||
|
{...programProps}
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
@ -78,5 +85,5 @@ export default function ResourceTable({ data, setData }: ResourceTableProps) {
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
return <Table data={data} setData={setData} columns={columns} />;
|
return <Table data={data} setData={setData} columns={columns}/>
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,16 +3,12 @@ import DataPoint from "@/utils/models/DataPoint";
|
||||||
import { Dispatch, SetStateAction, useState } from "react";
|
import { Dispatch, SetStateAction, useState } from "react";
|
||||||
|
|
||||||
type RowOpenActionProps<T extends DataPoint> = {
|
type RowOpenActionProps<T extends DataPoint> = {
|
||||||
title: string;
|
title: string,
|
||||||
rowData: T;
|
rowData: T,
|
||||||
setData: Dispatch<SetStateAction<T[]>>;
|
setData: Dispatch<SetStateAction<T[]>>
|
||||||
};
|
}
|
||||||
|
|
||||||
export function RowOpenAction<T extends DataPoint>({
|
export function RowOpenAction<T extends DataPoint>({ title, rowData, setData }: RowOpenActionProps<T>) {
|
||||||
title,
|
|
||||||
rowData,
|
|
||||||
setData,
|
|
||||||
}: RowOpenActionProps<T>) {
|
|
||||||
const [pageContent, setPageContent] = useState("");
|
const [pageContent, setPageContent] = useState("");
|
||||||
|
|
||||||
const handleDrawerContentChange = (newContent: string) => {
|
const handleDrawerContentChange = (newContent: string) => {
|
||||||
|
@ -35,4 +31,4 @@ export function RowOpenAction<T extends DataPoint>({
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
|
@ -8,31 +8,35 @@ import TagsInput from "@/components/TagsInput/Index";
|
||||||
import Service from "@/utils/models/Service";
|
import Service from "@/utils/models/Service";
|
||||||
|
|
||||||
type ServiceTableProps = {
|
type ServiceTableProps = {
|
||||||
data: Service[];
|
data: Service[],
|
||||||
setData: Dispatch<SetStateAction<Service[]>>;
|
setData: Dispatch<SetStateAction<Service[]>>
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table componenet used for displaying services
|
* Table componenet used for displaying services
|
||||||
* @param props.data Stateful list of services to be displayed by the table
|
* @param props.data Stateful list of services to be displayed by the table
|
||||||
* @param props.setData State setter for the list of services
|
* @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>();
|
const columnHelper = createColumnHelper<Service>();
|
||||||
|
|
||||||
// Set up tag handling
|
// 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
|
// TODO: Dynamically or statically get full list of preset requirement tag options
|
||||||
const requirementProps = useTagsHandler([
|
const requirementProps = useTagsHandler([
|
||||||
"anonymous",
|
'anonymous',
|
||||||
"confidential",
|
'confidential',
|
||||||
"referral required",
|
'referral required',
|
||||||
"safety assessment",
|
'safety assessment',
|
||||||
"intake required",
|
'intake required',
|
||||||
"income eligibility",
|
'income eligibility',
|
||||||
"initial assessment",
|
'initial assessment',
|
||||||
]);
|
])
|
||||||
|
|
||||||
// Define Tanstack columns
|
// Define Tanstack columns
|
||||||
const columns: ColumnDef<Service, any>[] = [
|
const columns: ColumnDef<Service, any>[] = [
|
||||||
|
@ -67,7 +71,10 @@ export default function ServiceTable({ data, setData }: ServiceTableProps) {
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
cell: (info) => (
|
cell: (info) => (
|
||||||
<TagsInput presetValue={info.getValue()} {...programProps} />
|
<TagsInput
|
||||||
|
presetValue={info.getValue()}
|
||||||
|
{...programProps}
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor("requirements", {
|
columnHelper.accessor("requirements", {
|
||||||
|
@ -79,9 +86,7 @@ export default function ServiceTable({ data, setData }: ServiceTableProps) {
|
||||||
cell: (info) => (
|
cell: (info) => (
|
||||||
// TODO: Setup different tag handler for requirements
|
// TODO: Setup different tag handler for requirements
|
||||||
<TagsInput
|
<TagsInput
|
||||||
presetValue={
|
presetValue={info.getValue()[0] !== "" ? info.getValue() : ["N/A"]}
|
||||||
info.getValue()[0] !== "" ? info.getValue() : ["N/A"]
|
|
||||||
}
|
|
||||||
{...requirementProps}
|
{...requirementProps}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
@ -99,5 +104,5 @@ export default function ServiceTable({ data, setData }: ServiceTableProps) {
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
return <Table data={data} setData={setData} columns={columns} />;
|
return <Table data={data} setData={setData} columns={columns} />
|
||||||
}
|
};
|
||||||
|
|
|
@ -4,15 +4,15 @@ import {
|
||||||
useReactTable,
|
useReactTable,
|
||||||
getCoreRowModel,
|
getCoreRowModel,
|
||||||
flexRender,
|
flexRender,
|
||||||
createColumnHelper,
|
createColumnHelper
|
||||||
} from "@tanstack/react-table";
|
} from "@tanstack/react-table";
|
||||||
import {
|
import {
|
||||||
ChangeEvent,
|
ChangeEvent,
|
||||||
useState,
|
useState,
|
||||||
useEffect,
|
useEffect,
|
||||||
Key,
|
Key,
|
||||||
Dispatch,
|
Dispatch,
|
||||||
SetStateAction,
|
SetStateAction
|
||||||
} from "react";
|
} from "react";
|
||||||
import { TableAction } from "./TableAction";
|
import { TableAction } from "./TableAction";
|
||||||
import { PlusIcon } from "@heroicons/react/24/solid";
|
import { PlusIcon } from "@heroicons/react/24/solid";
|
||||||
|
@ -21,9 +21,9 @@ import { RowOptionMenu } from "./RowOptionMenu";
|
||||||
import DataPoint from "@/utils/models/DataPoint";
|
import DataPoint from "@/utils/models/DataPoint";
|
||||||
|
|
||||||
type TableProps<T extends DataPoint> = {
|
type TableProps<T extends DataPoint> = {
|
||||||
data: T[];
|
data: T[],
|
||||||
setData: Dispatch<SetStateAction<T[]>>;
|
setData: Dispatch<SetStateAction<T[]>>,
|
||||||
columns: ColumnDef<T, any>[];
|
columns: ColumnDef<T, any>[]
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Fuzzy search function */
|
/** Fuzzy search function */
|
||||||
|
@ -48,21 +48,20 @@ const fuzzyFilter = (
|
||||||
* @param props.data Stateful list of data to be held in the table
|
* @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.setData State setter for the list of data
|
||||||
* @param props.columns Column definitions made with Tanstack columnHelper
|
* @param props.columns Column definitions made with Tanstack columnHelper
|
||||||
*/
|
*/
|
||||||
export default function Table<T extends DataPoint>({
|
export default function Table<T extends DataPoint>({ data, setData, columns }: TableProps<T>) {
|
||||||
data,
|
|
||||||
setData,
|
|
||||||
columns,
|
|
||||||
}: TableProps<T>) {
|
|
||||||
const columnHelper = createColumnHelper<T>();
|
const columnHelper = createColumnHelper<T>();
|
||||||
|
|
||||||
/** Sorting function based on visibility */
|
/** Sorting function based on visibility */
|
||||||
const visibilitySort = (a: T, b: T) =>
|
const visibilitySort = (a: T, b: T) => (
|
||||||
a.visible === b.visible ? 0 : a.visible ? -1 : 1;
|
a.visible === b.visible
|
||||||
|
? 0
|
||||||
|
: a.visible ? -1 : 1
|
||||||
|
)
|
||||||
|
|
||||||
// Sort data on load
|
// Sort data on load
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setData((prevData) => prevData.sort(visibilitySort));
|
setData(prevData => prevData.sort(visibilitySort))
|
||||||
}, [setData]);
|
}, [setData]);
|
||||||
|
|
||||||
// Data manipulation methods
|
// Data manipulation methods
|
||||||
|
@ -76,13 +75,13 @@ export default function Table<T extends DataPoint>({
|
||||||
|
|
||||||
const hideData = (dataId: number) => {
|
const hideData = (dataId: number) => {
|
||||||
console.log(`Toggling visibility for data with ID: ${dataId}`);
|
console.log(`Toggling visibility for data with ID: ${dataId}`);
|
||||||
setData((currentData) => {
|
setData(currentData => {
|
||||||
const newData = currentData
|
const newData = currentData
|
||||||
.map((data) =>
|
.map(data => (
|
||||||
data.id === dataId
|
data.id === dataId
|
||||||
? { ...data, visible: !data.visible }
|
? { ...data, visible: !data.visible }
|
||||||
: data
|
: data
|
||||||
)
|
))
|
||||||
.sort(visibilitySort);
|
.sort(visibilitySort);
|
||||||
|
|
||||||
console.log(newData);
|
console.log(newData);
|
||||||
|
@ -105,7 +104,7 @@ export default function Table<T extends DataPoint>({
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
|
|
||||||
// Searching
|
// Searching
|
||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
|
@ -222,4 +221,4 @@ export default function Table<T extends DataPoint>({
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
import {
|
import { ArrowDownCircleIcon, AtSymbolIcon, Bars2Icon } from "@heroicons/react/24/solid";
|
||||||
ArrowDownCircleIcon,
|
|
||||||
AtSymbolIcon,
|
|
||||||
Bars2Icon,
|
|
||||||
} from "@heroicons/react/24/solid";
|
|
||||||
import { Dispatch, SetStateAction } from "react";
|
import { Dispatch, SetStateAction } from "react";
|
||||||
import useTagsHandler from "@/components/TagsInput/TagsHandler";
|
import useTagsHandler from "@/components/TagsInput/TagsHandler";
|
||||||
import { ColumnDef, createColumnHelper } from "@tanstack/react-table";
|
import { ColumnDef, createColumnHelper } from "@tanstack/react-table";
|
||||||
|
@ -12,16 +8,16 @@ import TagsInput from "@/components/TagsInput/Index";
|
||||||
import User from "@/utils/models/User";
|
import User from "@/utils/models/User";
|
||||||
|
|
||||||
type UserTableProps = {
|
type UserTableProps = {
|
||||||
data: User[];
|
data: User[],
|
||||||
setData: Dispatch<SetStateAction<User[]>>;
|
setData: Dispatch<SetStateAction<User[]>>
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table componenet used for displaying users
|
* Table componenet used for displaying users
|
||||||
* @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 }: UserTableProps) {
|
export default function UserTable({ data, setData }: UserTableProps ) {
|
||||||
const columnHelper = createColumnHelper<User>();
|
const columnHelper = createColumnHelper<User>();
|
||||||
|
|
||||||
// Set up tag handling
|
// Set up tag handling
|
||||||
|
@ -29,9 +25,13 @@ export default function UserTable({ data, setData }: UserTableProps) {
|
||||||
"administrator",
|
"administrator",
|
||||||
"volunteer",
|
"volunteer",
|
||||||
"employee",
|
"employee",
|
||||||
]);
|
])
|
||||||
|
|
||||||
const programProps = useTagsHandler(["community", "domestic", "economic"]);
|
const programProps = useTagsHandler([
|
||||||
|
"community",
|
||||||
|
"domestic",
|
||||||
|
"economic",
|
||||||
|
])
|
||||||
|
|
||||||
// Define Tanstack columns
|
// Define Tanstack columns
|
||||||
const columns: ColumnDef<User, any>[] = [
|
const columns: ColumnDef<User, any>[] = [
|
||||||
|
@ -57,7 +57,10 @@ export default function UserTable({ data, setData }: UserTableProps) {
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
cell: (info) => (
|
cell: (info) => (
|
||||||
<TagsInput presetValue={info.getValue()} {...roleProps} />
|
<TagsInput
|
||||||
|
presetValue={info.getValue()}
|
||||||
|
{...roleProps}
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor("email", {
|
columnHelper.accessor("email", {
|
||||||
|
@ -80,10 +83,13 @@ export default function UserTable({ data, setData }: UserTableProps) {
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
cell: (info) => (
|
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}/>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState } from "react";
|
import { useState } from 'react';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom hook used to handle the state of tag options and colors
|
* 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 tagColors.get(tag) as string;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { presetOptions, setPresetOptions, getTagColor };
|
return { presetOptions, setPresetOptions, getTagColor }
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user