diff --git a/api/app/account/models.py b/api/app/account/models.py
index 3af720a..d1cdbc0 100644
--- a/api/app/account/models.py
+++ b/api/app/account/models.py
@@ -1,11 +1,7 @@
from uuid import UUID, uuid4
from enum import Enum
-from sqlmodel import Field, SQLModel, select, Relationship
-from pydantic import Field as PydField
-
-from category.models import CategoryRead, Category
-
+from sqlmodel import Field, SQLModel, select
class AccountType(Enum):
Asset = "Asset" # < Denotes a generic asset account.
@@ -21,24 +17,21 @@ class AccountType(Enum):
MoneyMarket = "MoneyMarket" # < Money Market Account
Currency = "Currency" # < Denotes a currency trading account.
- Income = "Income" # < Denotes an income account
- Expense = "Expense" # < Denotes an expense account
AssetLoan = "AssetLoan" # < Denotes a loan (asset of the owner of this object)
Stock = "Stock" # < Denotes an security account as sub-account for an investment
Equity = "Equity" # < Denotes an equity account e.g. opening/closing balance
- Payee = "Payee"
+ Income = "Income" # < Denotes an income account
+ Expense = "Expense" # < Denotes an expense account
class AccountBase(SQLModel):
name: str = Field(index=True)
type: AccountType = Field(index=True)
- default_category_id: UUID | None = Field(default=None, foreign_key="category.id")
class AccountBaseId(AccountBase):
id: UUID | None = Field(default_factory=uuid4, primary_key=True)
class Account(AccountBaseId, table=True):
- default_category: Category | None = Relationship()
@classmethod
def create(cls, account, session):
@@ -51,7 +44,7 @@ class Account(AccountBaseId, table=True):
@classmethod
def list(cls):
- return select(Account).join(Category)
+ return select(Account)
@classmethod
def get(cls, session, account_id):
@@ -71,18 +64,10 @@ class Account(AccountBaseId, table=True):
session.commit()
class AccountRead(AccountBaseId):
- default_category: CategoryRead
+ pass
class AccountWrite(AccountBase):
- default_category_id: UUID = PydField(default=None, json_schema_extra={
- "foreign_key": {
- "reference": {
- "resource": "categories",
- "schema": "CategoryRead",
- "label": "name"
- }
- }
- })
+ pass
class AccountCreate(AccountWrite):
pass
diff --git a/api/app/category/models.py b/api/app/category/models.py
deleted file mode 100644
index 7a8edaa..0000000
--- a/api/app/category/models.py
+++ /dev/null
@@ -1,60 +0,0 @@
-from uuid import UUID, uuid4
-from enum import Enum
-from typing import Optional
-
-from fastapi_filter.contrib.sqlalchemy import Filter
-from sqlmodel import Field, SQLModel, select
-
-class CategoryBase(SQLModel):
- name: str = Field(index=True)
-
-class CategoryRead(CategoryBase):
- id: UUID | None = Field(default_factory=uuid4, primary_key=True)
-
-class Category(CategoryRead, table=True):
-
- @classmethod
- def create(cls, category, session):
- category_db = cls.model_validate(category)
- session.add(category_db)
- session.commit()
- session.refresh(category_db)
-
- return category_db
-
- @classmethod
- def list(cls, filters):
- return filters.filter(select(cls))
-
- @classmethod
- def get(cls, session, category_id):
- return session.get(Category, category_id)
-
- @classmethod
- def update(cls, session, category_db, category_data):
- category_db.sqlmodel_update(category_data)
- session.add(category_db)
- session.commit()
- session.refresh(category_db)
- return category_db
-
- @classmethod
- def delete(cls, session, category):
- session.delete(category)
- session.commit()
-
-class CategoryWrite(CategoryBase):
- pass
-
-class CategoryCreate(CategoryWrite):
- pass
-
-class CategoryUpdate(CategoryWrite):
- pass
-
-class CategoryFilters(Filter):
- name__like: Optional[str] = None
-
- class Constants(Filter.Constants):
- model = Category
- search_model_fields = ["name"]
diff --git a/api/app/category/routes.py b/api/app/category/routes.py
deleted file mode 100644
index 0129007..0000000
--- a/api/app/category/routes.py
+++ /dev/null
@@ -1,49 +0,0 @@
-from uuid import UUID
-
-from fastapi import APIRouter, HTTPException, Depends
-from fastapi_filter import FilterDepends
-from fastapi_pagination import Page
-from fastapi_pagination.ext.sqlmodel import paginate
-
-from category.models import Category, CategoryCreate, CategoryRead, CategoryUpdate, CategoryFilters
-from db import SessionDep
-from user.manager import get_current_user
-
-router = APIRouter()
-
-@router.post("")
-def create_category(category: CategoryCreate, session: SessionDep, current_user=Depends(get_current_user)) -> CategoryRead:
- Category.create(category, session)
- return category
-
-@router.get("")
-def read_categories(session: SessionDep,
- filters: CategoryFilters = FilterDepends(CategoryFilters),
- current_user=Depends(get_current_user)) -> Page[CategoryRead]:
- return paginate(session, Category.list(filters))
-
-@router.get("/{category_id}")
-def read_category(category_id: UUID, session: SessionDep, current_user=Depends(get_current_user)) -> CategoryRead:
- category = Category.get(session, category_id)
- if not category:
- raise HTTPException(status_code=404, detail="Category not found")
- return category
-
-@router.put("/{category_id}")
-def update_category(category_id: UUID, category: CategoryUpdate, session: SessionDep, current_user=Depends(get_current_user)) -> CategoryRead:
- db_category = Category.get(session, category_id)
- if not db_category:
- raise HTTPException(status_code=404, detail="Category not found")
-
- category_data = category.model_dump(exclude_unset=True)
- category = Category.update(session, db_category, category_data)
- return category
-
-@router.delete("/{category_id}")
-def delete_category(category_id: UUID, session: SessionDep, current_user=Depends(get_current_user)):
- category = Category.get(session, category_id)
- if not category:
- raise HTTPException(status_code=404, detail="Category not found")
-
- Category.delete(session, category)
- return {"ok": True}
diff --git a/api/app/main.py b/api/app/main.py
index 59c37c9..ceb480c 100644
--- a/api/app/main.py
+++ b/api/app/main.py
@@ -9,7 +9,8 @@ from fastapi_pagination import add_pagination
from db import create_db_and_tables
from user import user_router, auth_router, create_admin_account
from account.routes import router as account_router
-from category.routes import router as category_router
+from payee.routes import router as payee_router
+from transaction.routes import router as transaction_router
@asynccontextmanager
@@ -38,7 +39,8 @@ app.add_middleware(
app.include_router(auth_router, prefix="/auth", tags=["auth"], )
app.include_router(user_router, prefix="/users", tags=["users"])
app.include_router(account_router, prefix="/accounts", tags=["accounts"])
-app.include_router(category_router, prefix="/categories", tags=["categories"])
+app.include_router(payee_router, prefix="/payees", tags=["payees"])
+app.include_router(transaction_router, prefix="/transactions", tags=["transactions"])
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
diff --git a/api/app/category/__init__.py b/api/app/payee/__init__.py
similarity index 100%
rename from api/app/category/__init__.py
rename to api/app/payee/__init__.py
diff --git a/api/app/payee/models.py b/api/app/payee/models.py
new file mode 100644
index 0000000..405d450
--- /dev/null
+++ b/api/app/payee/models.py
@@ -0,0 +1,76 @@
+from uuid import UUID, uuid4
+from typing import Optional
+
+from fastapi_filter.contrib.sqlalchemy import Filter
+from sqlmodel import Field, SQLModel, select, Relationship
+from pydantic import Field as PydField
+
+from account.models import Account, AccountRead
+
+
+class PayeeBase(SQLModel):
+ name: str = Field(index=True)
+ default_account_id: UUID | None = Field(default=None, foreign_key="account.id")
+
+class PayeeBaseId(PayeeBase):
+ id: UUID | None = Field(default_factory=uuid4, primary_key=True)
+
+class Payee(PayeeBaseId, table=True):
+ default_account: Account | None = Relationship()
+
+ @classmethod
+ def create(cls, payee, session):
+ payee_db = cls.model_validate(payee)
+ session.add(payee_db)
+ session.commit()
+ session.refresh(payee_db)
+
+ return payee_db
+
+ @classmethod
+ def list(cls, filters):
+ return filters.filter(select(cls)).join(Account)
+
+ @classmethod
+ def get(cls, session, payee_id):
+ return session.get(Payee, payee_id)
+
+ @classmethod
+ def update(cls, session, payee_db, payee_data):
+ payee_db.sqlmodel_update(payee_data)
+ session.add(payee_db)
+ session.commit()
+ session.refresh(payee_db)
+ return payee_db
+
+ @classmethod
+ def delete(cls, session, payee):
+ session.delete(payee)
+ session.commit()
+
+class PayeeRead(PayeeBaseId):
+ default_account: AccountRead
+
+class PayeeWrite(PayeeBase):
+ default_account_id: UUID = PydField(default=None, json_schema_extra={
+ "foreign_key": {
+ "reference": {
+ "resource": "accounts",
+ "schema": "AccountRead",
+ "label": "name"
+ }
+ }
+ })
+
+class PayeeCreate(PayeeWrite):
+ pass
+
+class PayeeUpdate(PayeeWrite):
+ pass
+
+class PayeeFilters(Filter):
+ name__like: Optional[str] = None
+
+ class Constants(Filter.Constants):
+ model = Payee
+ search_model_fields = ["name"]
diff --git a/api/app/payee/routes.py b/api/app/payee/routes.py
new file mode 100644
index 0000000..912e02e
--- /dev/null
+++ b/api/app/payee/routes.py
@@ -0,0 +1,49 @@
+from uuid import UUID
+
+from fastapi import APIRouter, HTTPException, Depends
+from fastapi_filter import FilterDepends
+from fastapi_pagination import Page
+from fastapi_pagination.ext.sqlmodel import paginate
+
+from payee.models import Payee, PayeeCreate, PayeeRead, PayeeUpdate, PayeeFilters
+from db import SessionDep
+from user.manager import get_current_user
+
+router = APIRouter()
+
+@router.post("")
+def create_payee(payee: PayeeCreate, session: SessionDep, current_user=Depends(get_current_user)) -> PayeeRead:
+ result = Payee.create(payee, session)
+ return Payee.get(session, result.id)
+
+@router.get("")
+def read_categories(session: SessionDep,
+ filters: PayeeFilters = FilterDepends(PayeeFilters),
+ current_user=Depends(get_current_user)) -> Page[PayeeRead]:
+ return paginate(session, Payee.list(filters))
+
+@router.get("/{payee_id}")
+def read_payee(payee_id: UUID, session: SessionDep, current_user=Depends(get_current_user)) -> PayeeRead:
+ payee = Payee.get(session, payee_id)
+ if not payee:
+ raise HTTPException(status_code=404, detail="Payee not found")
+ return payee
+
+@router.put("/{payee_id}")
+def update_payee(payee_id: UUID, payee: PayeeUpdate, session: SessionDep, current_user=Depends(get_current_user)) -> PayeeRead:
+ db_payee = Payee.get(session, payee_id)
+ if not db_payee:
+ raise HTTPException(status_code=404, detail="Payee not found")
+
+ payee_data = payee.model_dump(exclude_unset=True)
+ payee = Payee.update(session, db_payee, payee_data)
+ return payee
+
+@router.delete("/{payee_id}")
+def delete_payee(payee_id: UUID, session: SessionDep, current_user=Depends(get_current_user)):
+ payee = Payee.get(session, payee_id)
+ if not payee:
+ raise HTTPException(status_code=404, detail="Payee not found")
+
+ Payee.delete(session, payee)
+ return {"ok": True}
diff --git a/api/app/transaction/__init__.py b/api/app/transaction/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/api/app/transaction/models.py b/api/app/transaction/models.py
new file mode 100644
index 0000000..5e4034e
--- /dev/null
+++ b/api/app/transaction/models.py
@@ -0,0 +1,127 @@
+from decimal import Decimal
+from uuid import UUID, uuid4
+
+from sqlmodel import Field, SQLModel, select, Relationship
+from pydantic import Field as PydField
+
+from account.models import Account, AccountRead
+from payee.models import Payee, PayeeRead
+
+
+class TransactionBase(SQLModel):
+ pass
+
+class TransactionBaseId(TransactionBase):
+ id: UUID | None = Field(default_factory=uuid4, primary_key=True)
+
+class Transaction(TransactionBaseId, table=True):
+ splits: list["Split"] = Relationship(back_populates="transaction")
+
+ @classmethod
+ def create(cls, transaction, session):
+ transaction_db = cls.model_validate(transaction)
+ session.add(transaction_db)
+ session.commit()
+ session.refresh(transaction_db)
+
+ return transaction_db
+
+ @classmethod
+ def list(cls):
+ return select(Transaction).join(Split).join(Account)
+
+ @classmethod
+ def get(cls, session, transaction_id):
+ return session.get(Transaction, transaction_id)
+
+ @classmethod
+ def update(cls, session, transaction_db, transaction_data):
+ transaction_db.sqlmodel_update(transaction_data)
+ session.add(transaction_db)
+ session.commit()
+ session.refresh(transaction_db)
+ return transaction_db
+
+ @classmethod
+ def delete(cls, session, transaction):
+ session.delete(transaction)
+ session.commit()
+
+class SplitBase(SQLModel):
+ account_id: UUID = Field(foreign_key="account.id")
+ payee_id: UUID = Field(foreign_key="payee.id")
+ amount: Decimal = Field(decimal_places=2)
+
+class SplitBaseId(SplitBase):
+ transaction_id: UUID = Field(primary_key=True, foreign_key="transaction.id")
+ id: int = Field(primary_key=True)
+
+class SplitRead(SplitBaseId):
+ account: AccountRead
+ payee: PayeeRead
+
+class TransactionRead(TransactionBaseId):
+ splits: list[SplitRead]
+
+class SplitWrite(SplitBase):
+ account_id: UUID = PydField(json_schema_extra={
+ "foreign_key": {
+ "reference": {
+ "resource": "accounts",
+ "schema": "AccountRead",
+ "label": "name"
+ }
+ }
+ })
+ payee_id: UUID = PydField(json_schema_extra={
+ "foreign_key": {
+ "reference": {
+ "resource": "payees",
+ "schema": "PayeeRead",
+ "label": "name"
+ }
+ }
+ })
+
+class TransactionWrite(TransactionBase):
+ splits: list[SplitWrite] = Field()
+
+class TransactionCreate(TransactionWrite):
+ pass
+
+class TransactionUpdate(TransactionWrite):
+ pass
+
+class Split(SplitBaseId, table=True):
+ transaction: Transaction = Relationship(back_populates="splits")
+ account: Account | None = Relationship()
+ payee: Payee | None = Relationship()
+
+ @classmethod
+ def create(cls, transaction_split, session):
+ transaction_split_db = cls.model_validate(transaction_split)
+ session.add(transaction_split_db)
+ session.commit()
+ session.refresh(transaction_split_db)
+
+ return transaction_split_db
+
+ @classmethod
+ def update(cls, session, transaction_db, transaction_data):
+ transaction_db.sqlmodel_update(transaction_data)
+ session.add(transaction_db)
+ session.commit()
+ session.refresh(transaction_db)
+ return transaction_db
+
+ @classmethod
+ def delete(cls, session, transaction):
+ session.delete(transaction)
+ session.commit()
+
+
+class SplitCreate(SplitWrite):
+ pass
+
+class SplitUpdate(SplitWrite):
+ pass
\ No newline at end of file
diff --git a/api/app/transaction/routes.py b/api/app/transaction/routes.py
new file mode 100644
index 0000000..9bb7098
--- /dev/null
+++ b/api/app/transaction/routes.py
@@ -0,0 +1,46 @@
+from uuid import UUID
+
+from fastapi import APIRouter, HTTPException, Depends
+from fastapi_pagination import Page
+from fastapi_pagination.ext.sqlmodel import paginate
+
+from transaction.models import Transaction, TransactionCreate, TransactionRead, TransactionUpdate
+from db import SessionDep
+from user.manager import get_current_user
+
+router = APIRouter()
+
+@router.post("")
+def create_transaction(transaction: TransactionCreate, session: SessionDep, current_user=Depends(get_current_user)) -> TransactionRead:
+ result = Transaction.create(transaction, session)
+ return result
+
+@router.get("")
+def read_transactions(session: SessionDep, current_user=Depends(get_current_user)) -> Page[TransactionRead]:
+ return paginate(session, Transaction.list())
+
+@router.get("/{transaction_id}")
+def read_transaction(transaction_id: UUID, session: SessionDep, current_user=Depends(get_current_user)) -> TransactionRead:
+ transaction = Transaction.get(session, transaction_id)
+ if not transaction:
+ raise HTTPException(status_code=404, detail="Transaction not found")
+ return transaction
+
+@router.put("/{transaction_id}")
+def update_transaction(transaction_id: UUID, transaction: TransactionUpdate, session: SessionDep, current_user=Depends(get_current_user)) -> TransactionRead:
+ db_transaction = Transaction.get(session, transaction_id)
+ if not db_transaction:
+ raise HTTPException(status_code=404, detail="Transaction not found")
+
+ transaction_data = transaction.model_dump(exclude_unset=True)
+ transaction = Transaction.update(session, db_transaction, transaction_data)
+ return transaction
+
+@router.delete("/{transaction_id}")
+def delete_transaction(transaction_id: UUID, session: SessionDep, current_user=Depends(get_current_user)):
+ transaction = Transaction.get(session, transaction_id)
+ if not transaction:
+ raise HTTPException(status_code=404, detail="Transaction not found")
+
+ Transaction.delete(session, transaction)
+ return {"ok": True}
diff --git a/gui/app/src/App.tsx b/gui/app/src/App.tsx
index eb9cd5d..a378d41 100644
--- a/gui/app/src/App.tsx
+++ b/gui/app/src/App.tsx
@@ -1,4 +1,4 @@
-import { Refine, type AuthProvider, Authenticated } from "@refinedev/core";
+import { Refine, Authenticated } from "@refinedev/core";
import {
ThemedLayoutV2,
ErrorComponent,
@@ -13,7 +13,6 @@ import FormControlLabel from "@mui/material/FormControlLabel";
import Checkbox from "@mui/material/Checkbox";
import { ThemeProvider } from "@mui/material/styles";
import { authProvider } from "./providers/auth-provider";
-//import dataProvider from "@refinedev/simple-rest";
import { dataProvider } from "./providers/data-provider";
import routerProvider, {
NavigateToResource,
@@ -24,9 +23,10 @@ import routerProvider, {
import { BrowserRouter, Routes, Route, Outlet } from "react-router";
import { useFormContext } from "react-hook-form";
-import { PostList, PostCreate, PostEdit } from "../src/pages/posts";
-import { AccountList, AccountCreate, AccountEdit } from "../src/pages/accounts";
-import { CategoryList, CategoryCreate, CategoryEdit } from "../src/pages/categories";
+import { PostList, PostCreate, PostEdit } from "./pages/posts";
+import { AccountList, AccountCreate, AccountEdit } from "./pages/accounts";
+import { PayeeCreate, PayeeEdit, PayeeList } from "./pages/payees";
+import { TransactionCreate, TransactionEdit, TransactionList } from "./pages/transactions";
/**
* mock auth credentials to simulate authentication
@@ -82,10 +82,16 @@ const App: React.FC = () => {
create: "/accounts/create",
},
{
- name: "categories",
- list: "/categories",
- edit: "/categories/edit/:id",
- create: "/categories/create",
+ name: "payees",
+ list: "/payees",
+ edit: "/payees/edit/:id",
+ create: "/payees/create",
+ },
+ {
+ name: "transactions",
+ list: "/transactions",
+ edit: "/transactions/edit/:id",
+ create: "/transactions/create",
},
]}
options={{
@@ -123,10 +129,15 @@ const App: React.FC = () => {
} />
} />
-
- } />
- } />
- } />
+
+ } />
+ } />
+ } />
+
+
+ } />
+ } />
+ } />
diff --git a/gui/app/src/pages/categories/create.tsx b/gui/app/src/pages/categories/create.tsx
deleted file mode 100644
index b1b577a..0000000
--- a/gui/app/src/pages/categories/create.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import {CrudForm} from "../../common/crud/crud-form";
-
-
-export const CategoryCreate: React.FC = () => {
- return (
-
- );
-};
diff --git a/gui/app/src/pages/payees/create.tsx b/gui/app/src/pages/payees/create.tsx
new file mode 100644
index 0000000..3f4dc90
--- /dev/null
+++ b/gui/app/src/pages/payees/create.tsx
@@ -0,0 +1,11 @@
+import {CrudForm} from "../../common/crud/crud-form";
+
+
+export const PayeeCreate: React.FC = () => {
+ return (
+
+ );
+};
diff --git a/gui/app/src/pages/categories/edit.tsx b/gui/app/src/pages/payees/edit.tsx
similarity index 62%
rename from gui/app/src/pages/categories/edit.tsx
rename to gui/app/src/pages/payees/edit.tsx
index 831604b..bd0f119 100644
--- a/gui/app/src/pages/categories/edit.tsx
+++ b/gui/app/src/pages/payees/edit.tsx
@@ -2,13 +2,13 @@ import { CrudForm } from "../../common/crud/crud-form";
import { useParams } from "react-router"
-export const CategoryEdit: React.FC = () => {
+export const PayeeEdit: React.FC = () => {
const { id } = useParams()
return (
);
diff --git a/gui/app/src/pages/categories/index.tsx b/gui/app/src/pages/payees/index.tsx
similarity index 100%
rename from gui/app/src/pages/categories/index.tsx
rename to gui/app/src/pages/payees/index.tsx
diff --git a/gui/app/src/pages/categories/list.tsx b/gui/app/src/pages/payees/list.tsx
similarity index 96%
rename from gui/app/src/pages/categories/list.tsx
rename to gui/app/src/pages/payees/list.tsx
index 8c014e2..c21e409 100644
--- a/gui/app/src/pages/categories/list.tsx
+++ b/gui/app/src/pages/payees/list.tsx
@@ -6,7 +6,7 @@ import { DataGrid, type GridColDef } from "@mui/x-data-grid";
import type { IAccount } from "../../interfaces";
import {ButtonGroup} from "@mui/material";
-export const CategoryList: React.FC = () => {
+export const PayeeList: React.FC = () => {
const { dataGridProps } = useDataGrid();
const columns = React.useMemo[]>(
diff --git a/gui/app/src/pages/transactions/create.tsx b/gui/app/src/pages/transactions/create.tsx
new file mode 100644
index 0000000..3d157c2
--- /dev/null
+++ b/gui/app/src/pages/transactions/create.tsx
@@ -0,0 +1,11 @@
+import {CrudForm} from "../../common/crud/crud-form";
+
+
+export const TransactionCreate: React.FC = () => {
+ return (
+
+ );
+};
diff --git a/gui/app/src/pages/transactions/edit.tsx b/gui/app/src/pages/transactions/edit.tsx
new file mode 100644
index 0000000..53ab8ec
--- /dev/null
+++ b/gui/app/src/pages/transactions/edit.tsx
@@ -0,0 +1,15 @@
+import { CrudForm } from "../../common/crud/crud-form";
+import { useParams } from "react-router"
+
+
+export const TransactionEdit: React.FC = () => {
+ const { id } = useParams()
+
+ return (
+
+ );
+};
diff --git a/gui/app/src/pages/transactions/index.tsx b/gui/app/src/pages/transactions/index.tsx
new file mode 100644
index 0000000..b9af745
--- /dev/null
+++ b/gui/app/src/pages/transactions/index.tsx
@@ -0,0 +1,3 @@
+export * from "./list";
+export * from "./create";
+export * from "./edit";
diff --git a/gui/app/src/pages/transactions/list.tsx b/gui/app/src/pages/transactions/list.tsx
new file mode 100644
index 0000000..3297a2f
--- /dev/null
+++ b/gui/app/src/pages/transactions/list.tsx
@@ -0,0 +1,48 @@
+import {DeleteButton, EditButton, List, useDataGrid} from "@refinedev/mui";
+import React from "react";
+
+import { DataGrid, type GridColDef } from "@mui/x-data-grid";
+
+import type { IAccount } from "../../interfaces";
+import {ButtonGroup} from "@mui/material";
+
+export const TransactionList: React.FC = () => {
+ const { dataGridProps } = useDataGrid();
+
+ const columns = React.useMemo[]>(
+ () => [
+ { field: "id", headerName: "ID" },
+ { field: "name", headerName: "Name", flex: 1 },
+ {
+ field: "actions",
+ headerName: "Actions",
+ display: "flex",
+ renderCell: function render({ row }) {
+ return (
+
+
+
+
+ );
+ },
+ align: "center",
+ headerAlign: "center",
+ },
+ ],
+ [],
+ );
+
+ return (
+
+
+
+
+
+ );
+};