Skip to content

Commit

Permalink
feat(frontend): Table fetch data from API
Browse files Browse the repository at this point in the history
  • Loading branch information
berdal84 committed Jan 18, 2024
1 parent 5e8d25b commit a8c17b2
Show file tree
Hide file tree
Showing 11 changed files with 236 additions and 50 deletions.
15 changes: 15 additions & 0 deletions backend/main.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from src import routes
from src.database import models
from src.database.database import engine
Expand All @@ -7,6 +8,20 @@

models.Base.metadata.create_all(bind=engine)

origins = [
"http://localhost",
"http://localhost:8080",
"http://localhost:3000",
]

app.add_middleware(
CORSMiddleware,
allow_origins=origins,
# allow_credentials=True, TODO: user accounts
allow_methods=["*"],
allow_headers=["*"],
)

app.include_router(routes.sample)

@app.get("/")
Expand Down
4 changes: 2 additions & 2 deletions backend/src/database/sample_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
def get_sample_by_id(db: Session, sample_id: int) -> models.Sample | None:
return db.query(models.Sample).filter(models.Sample.id == sample_id).first()

def get_sample_page(db: Session, skip: int = 0, limit: int = 100) -> list[models.Sample]:
return db.query(models.Sample).offset(skip).limit(limit).all()
def get_sample_page(db: Session, index: int = 0, limit: int = 100) -> list[models.Sample]:
return db.query(models.Sample).offset(index * limit).limit(limit).all()

def get_count(db: Session) -> int:
return db.query(models.Sample).count()
Expand Down
11 changes: 8 additions & 3 deletions backend/src/routes/sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,19 @@ async def read_sample(sample_id: int, db: Session = Depends(get_session)) -> sch
return schemas.Sample.model_validate(db_sample, from_attributes=True)

@router.get("/", description="Read a list of N samples with a given offset")
async def read_samples(skip: int = 0, limit: int = 100, db: Session = Depends(get_session)) -> Page[schemas.Sample]:
db_samples = sample_crud.get_sample_page(db, skip, limit)
async def read_samples(index: int = 0, limit: int = 100, db: Session = Depends(get_session)) -> Page[schemas.Sample]:
db_samples = sample_crud.get_sample_page(db, index, limit)

# TODO: get the count and the list in a single request
count = sample_crud.get_count(db)
items = list( map( lambda each: schemas.Sample.model_validate(each, from_attributes=True), db_samples ))

return Page[schemas.Sample](items=items, total_item_count=count)
return Page[schemas.Sample](
items=items,
total_item_count=count,
limit=limit,
index=index
)


@router.post("/", description="Create a new sample")
Expand Down
12 changes: 9 additions & 3 deletions backend/src/schemas/page.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,14 @@ class Page[ItemType](BaseModel):
return page
"""

items: list[ItemType] = []
items: list[ItemType]
"Page items (size is limited, depends on the client's query)"

total_item_count: int = 0
"The total item count (not limited to data's content)"
total_item_count: int
"The total item count (not limited to data's content)"

limit: int
"The item max count for a page (client needs it when using defaults)"

index: int
"Page index (zero-based)"
91 changes: 91 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@mui/base": "^5.0.0-beta.32",
"@mui/icons-material": "^5.15.5",
"@mui/material": "^5.15.5",
"axios": "^1.6.5",
"next": "14.0.4",
"react": "^18",
"react-dom": "^18"
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/app/components/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export default function Table({ rows, page, setPage, rowsPerPage, setRowsPerPage
<tr>
<TablePagination
className="pt-3"
rowsPerPageOptions={[5, 10, 25, { label: 'All', value: -1 }]}
rowsPerPageOptions={[5, 10, 25, 50, 100]}
colSpan={3}
count={count}
rowsPerPage={rowsPerPage}
Expand Down
57 changes: 28 additions & 29 deletions frontend/src/app/contexts/AppContext.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
"use client"
import { Dispatch, PropsWithChildren, createContext, useContext, useReducer } from 'react';
import { Sample, Page } from '@/app/types';
import { MOCK_SAMPLES } from '@/app/mocks/samples';

type Status = "loading" | "error" | "pending";

/** The various action types to dispatch */
type Action = { type: 'fetch', payload: Partial<{ page: number, limit: number }> }
type Action =
{ type: 'setStatus', payload: { status: Status, message?: string } } |
{ type: 'setPage', payload: { page: Page<Sample> } } |
{ type: string, payload: never }


/** State of the AppContext */
type AppState = {
/** True when a new page is being fetched */
isLoading: boolean;
/** Page returned by the last fetch */
status: Status;
statusMessage?: string;
/** Cached page (last fetch result) */
page: Page<Sample>;
/** Current page index (zero-based) */
pageIndex: number;
/** Current page limit */
pageLimit: number;
}

const AppContext = createContext<AppState | null>(null);
Expand Down Expand Up @@ -45,26 +46,24 @@ export function useAppDispatchContext(): Dispatch<Action> | never {

function appReducer(state: AppState, action: Action): AppState {
switch (action.type) {
case 'fetch': {
case 'setPage': {

// Compute new page index, offset and limit
const newPageIndex = action.payload.page ?? state.pageIndex;
const newPageLimit = action.payload.limit ?? state.pageLimit
const offset = newPageIndex * newPageLimit;
// TODO:
// Instead of overriding fully state.page, we cache multiple pages.
// That would be usefull to limit requests.
// Should also provide a "refresh" action.

// Fetch
// TODO: use API, not from here though
const newPage = {
items: MOCK_SAMPLES.slice(offset, offset + newPageLimit),
total_item_count: MOCK_SAMPLES.length
return {
...state,
status: "pending", // We consider one request at a time for now
page: action.payload.page,
}

// Update state
}
case 'setStatus': {
return {
...state,
pageIndex: newPageIndex,
pageLimit: newPageLimit,
page: newPage,
statusMessage: action.payload.message,
status: action.payload.status
}
}
default: {
Expand All @@ -79,13 +78,13 @@ function appReducer(state: AppState, action: Action): AppState {
export function AppContextProvider({ children }: PropsWithChildren) {

const [state, dispatch] = useReducer(appReducer, {
isLoading: false,
status: "pending",
page: {
items: [],
total_item_count: 0
},
pageIndex: 0,
pageLimit: 5,
total_item_count: 0,
index: 0,
limit: 5
}
} as AppState);

return (
Expand Down
Loading

0 comments on commit a8c17b2

Please sign in to comment.