Compare commits
4 Commits
c8514a11f1
...
52db0bb2f3
| Author | SHA1 | Date | |
|---|---|---|---|
| 52db0bb2f3 | |||
| 7ad85dfb34 | |||
| 716ba233f5 | |||
| 5ac667f200 |
@@ -17,7 +17,7 @@ def create_account(account: AccountCreate, session: SessionDep, current_user=Dep
|
|||||||
|
|
||||||
@router.get("")
|
@router.get("")
|
||||||
def read_accounts(session: SessionDep, current_user=Depends(get_current_user)) -> Page[AccountRead]:
|
def read_accounts(session: SessionDep, current_user=Depends(get_current_user)) -> Page[AccountRead]:
|
||||||
return paginate(session, Account.list())
|
return paginate(session, Account.list_accounts())
|
||||||
|
|
||||||
@router.get("/{account_id}")
|
@router.get("/{account_id}")
|
||||||
def read_account(account_id: UUID, session: SessionDep, current_user=Depends(get_current_user)) -> AccountRead:
|
def read_account(account_id: UUID, session: SessionDep, current_user=Depends(get_current_user)) -> AccountRead:
|
||||||
46
api/app/account/category_routes.py
Normal file
46
api/app/account/category_routes.py
Normal file
@@ -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 account.models import Account, AccountRead, CategoryCreate, CategoryUpdate
|
||||||
|
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)) -> AccountRead:
|
||||||
|
result = Account.create(category, session)
|
||||||
|
return result
|
||||||
|
|
||||||
|
@router.get("")
|
||||||
|
def read_categorys(session: SessionDep, current_user=Depends(get_current_user)) -> Page[AccountRead]:
|
||||||
|
return paginate(session, Account.list_categories())
|
||||||
|
|
||||||
|
@router.get("/{category_id}")
|
||||||
|
def read_category(category_id: UUID, session: SessionDep, current_user=Depends(get_current_user)) -> AccountRead:
|
||||||
|
category = Account.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)) -> AccountRead:
|
||||||
|
db_category = Account.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 = Account.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 = Account.get(session, category_id)
|
||||||
|
if not category:
|
||||||
|
raise HTTPException(status_code=404, detail="Category not found")
|
||||||
|
|
||||||
|
Account.delete(session, category)
|
||||||
|
return {"ok": True}
|
||||||
@@ -3,6 +3,61 @@ from enum import Enum
|
|||||||
|
|
||||||
from sqlmodel import Field, SQLModel, select
|
from sqlmodel import Field, SQLModel, select
|
||||||
|
|
||||||
|
|
||||||
|
class AccountBase(SQLModel):
|
||||||
|
name: str = Field(index=True)
|
||||||
|
type: str = Field(index=True)
|
||||||
|
|
||||||
|
class AccountBaseId(AccountBase):
|
||||||
|
id: UUID | None = Field(default_factory=uuid4, primary_key=True)
|
||||||
|
|
||||||
|
class Account(AccountBaseId, table=True):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, account, session):
|
||||||
|
account_db = cls.model_validate(account)
|
||||||
|
session.add(account_db)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(account_db)
|
||||||
|
|
||||||
|
return account_db
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def list(cls):
|
||||||
|
return select(Account)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def list_accounts(cls):
|
||||||
|
return cls.list().where(
|
||||||
|
Account.type.not_in([v.value for v in CategoryType])
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def list_categories(cls):
|
||||||
|
return cls.list().where(
|
||||||
|
Account.type.in_([v.value for v in CategoryType])
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls, session, account_id):
|
||||||
|
return session.get(Account, account_id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update(cls, session, account_db, account_data):
|
||||||
|
account_db.sqlmodel_update(cls.model_validate(account_data))
|
||||||
|
session.add(account_db)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(account_db)
|
||||||
|
return account_db
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete(cls, session, account):
|
||||||
|
session.delete(account)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
class AccountRead(AccountBaseId):
|
||||||
|
pass
|
||||||
|
|
||||||
class AccountType(Enum):
|
class AccountType(Enum):
|
||||||
Asset = "Asset" # < Denotes a generic asset account.
|
Asset = "Asset" # < Denotes a generic asset account.
|
||||||
Checkings = "Checkings" # < Standard checking account
|
Checkings = "Checkings" # < Standard checking account
|
||||||
@@ -24,53 +79,36 @@ class AccountType(Enum):
|
|||||||
Income = "Income" # < Denotes an income account
|
Income = "Income" # < Denotes an income account
|
||||||
Expense = "Expense" # < Denotes an expense account
|
Expense = "Expense" # < Denotes an expense account
|
||||||
|
|
||||||
class AccountBase(SQLModel):
|
class Asset(Enum):
|
||||||
name: str = Field(index=True)
|
Asset = "Asset"
|
||||||
type: AccountType = Field(index=True)
|
Checkings = "Checkings"
|
||||||
|
Savings = "Savings"
|
||||||
|
Cash = "Cash"
|
||||||
|
|
||||||
class AccountBaseId(AccountBase):
|
class Liability(Enum):
|
||||||
id: UUID | None = Field(default_factory=uuid4, primary_key=True)
|
Liability = "Liability"
|
||||||
|
CreditCard = "CreditCard"
|
||||||
class Account(AccountBaseId, table=True):
|
Loan = "Loan"
|
||||||
|
Investment = "Investment"
|
||||||
@classmethod
|
|
||||||
def create(cls, account, session):
|
|
||||||
account_db = cls.model_validate(account)
|
|
||||||
session.add(account_db)
|
|
||||||
session.commit()
|
|
||||||
session.refresh(account_db)
|
|
||||||
|
|
||||||
return account_db
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def list(cls):
|
|
||||||
return select(Account)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get(cls, session, account_id):
|
|
||||||
return session.get(Account, account_id)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def update(cls, session, account_db, account_data):
|
|
||||||
account_db.sqlmodel_update(account_data)
|
|
||||||
session.add(account_db)
|
|
||||||
session.commit()
|
|
||||||
session.refresh(account_db)
|
|
||||||
return account_db
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def delete(cls, session, account):
|
|
||||||
session.delete(account)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
class AccountRead(AccountBaseId):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class AccountWrite(AccountBase):
|
class AccountWrite(AccountBase):
|
||||||
pass
|
type: Asset | Liability = Field()
|
||||||
|
|
||||||
class AccountCreate(AccountWrite):
|
class AccountCreate(AccountWrite):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class AccountUpdate(AccountWrite):
|
class AccountUpdate(AccountWrite):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class CategoryType(Enum):
|
||||||
|
Income = "Income"
|
||||||
|
Expense = "Expense"
|
||||||
|
|
||||||
|
class CategoryWrite(AccountBase):
|
||||||
|
type: CategoryType = Field()
|
||||||
|
|
||||||
|
class CategoryCreate(CategoryWrite):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class CategoryUpdate(CategoryWrite):
|
||||||
|
pass
|
||||||
@@ -8,7 +8,8 @@ from fastapi_pagination import add_pagination
|
|||||||
|
|
||||||
from db import create_db_and_tables
|
from db import create_db_and_tables
|
||||||
from user import user_router, auth_router, create_admin_account
|
from user import user_router, auth_router, create_admin_account
|
||||||
from account.routes import router as account_router
|
from account.account_routes import router as account_router
|
||||||
|
from account.category_routes import router as category_router
|
||||||
from payee.routes import router as payee_router
|
from payee.routes import router as payee_router
|
||||||
from transaction.routes import router as transaction_router
|
from transaction.routes import router as transaction_router
|
||||||
|
|
||||||
@@ -39,6 +40,7 @@ app.add_middleware(
|
|||||||
app.include_router(auth_router, prefix="/auth", tags=["auth"], )
|
app.include_router(auth_router, prefix="/auth", tags=["auth"], )
|
||||||
app.include_router(user_router, prefix="/users", tags=["users"])
|
app.include_router(user_router, prefix="/users", tags=["users"])
|
||||||
app.include_router(account_router, prefix="/accounts", tags=["accounts"])
|
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(payee_router, prefix="/payees", tags=["payees"])
|
||||||
app.include_router(transaction_router, prefix="/transactions", tags=["transactions"])
|
app.include_router(transaction_router, prefix="/transactions", tags=["transactions"])
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class Payee(PayeeBaseId, table=True):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def update(cls, session, payee_db, payee_data):
|
def update(cls, session, payee_db, payee_data):
|
||||||
payee_db.sqlmodel_update(payee_data)
|
payee_db.sqlmodel_update(cls.model_validate(payee_data))
|
||||||
session.add(payee_db)
|
session.add(payee_db)
|
||||||
session.commit()
|
session.commit()
|
||||||
session.refresh(payee_db)
|
session.refresh(payee_db)
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class Transaction(TransactionBaseId, table=True):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def update(cls, session, transaction_db, transaction_data):
|
def update(cls, session, transaction_db, transaction_data):
|
||||||
transaction_db.sqlmodel_update(transaction_data)
|
transaction_db.sqlmodel_update(cls.model_validate(transaction_data))
|
||||||
session.add(transaction_db)
|
session.add(transaction_db)
|
||||||
session.commit()
|
session.commit()
|
||||||
session.refresh(transaction_db)
|
session.refresh(transaction_db)
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import { useFormContext } from "react-hook-form";
|
|||||||
|
|
||||||
import { PostList, PostCreate, PostEdit } from "./pages/posts";
|
import { PostList, PostCreate, PostEdit } from "./pages/posts";
|
||||||
import { AccountList, AccountCreate, AccountEdit } from "./pages/accounts";
|
import { AccountList, AccountCreate, AccountEdit } from "./pages/accounts";
|
||||||
|
import { CategoryList, CategoryCreate, CategoryEdit } from "./pages/categories";
|
||||||
import { PayeeCreate, PayeeEdit, PayeeList } from "./pages/payees";
|
import { PayeeCreate, PayeeEdit, PayeeList } from "./pages/payees";
|
||||||
import { TransactionCreate, TransactionEdit, TransactionList } from "./pages/transactions";
|
import { TransactionCreate, TransactionEdit, TransactionList } from "./pages/transactions";
|
||||||
|
|
||||||
@@ -81,6 +82,12 @@ const App: React.FC = () => {
|
|||||||
edit: "/accounts/edit/:id",
|
edit: "/accounts/edit/:id",
|
||||||
create: "/accounts/create",
|
create: "/accounts/create",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "categories",
|
||||||
|
list: "/categories",
|
||||||
|
edit: "/categories/edit/:id",
|
||||||
|
create: "/categories/create",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "payees",
|
name: "payees",
|
||||||
list: "/payees",
|
list: "/payees",
|
||||||
@@ -129,6 +136,11 @@ const App: React.FC = () => {
|
|||||||
<Route path="create" element={<AccountCreate />} />
|
<Route path="create" element={<AccountCreate />} />
|
||||||
<Route path="edit/:id" element={<AccountEdit />} />
|
<Route path="edit/:id" element={<AccountEdit />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route path="/categories">
|
||||||
|
<Route index element={<CategoryList />} />
|
||||||
|
<Route path="create" element={<CategoryCreate />} />
|
||||||
|
<Route path="edit/:id" element={<CategoryEdit />} />
|
||||||
|
</Route>
|
||||||
<Route path="/payees">
|
<Route path="/payees">
|
||||||
<Route index element={<PayeeList />} />
|
<Route index element={<PayeeList />} />
|
||||||
<Route path="create" element={<PayeeCreate />} />
|
<Route path="create" element={<PayeeCreate />} />
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import validator from "@rjsf/validator-ajv8";
|
import validator from "@rjsf/validator-ajv8";
|
||||||
import Form from "@rjsf/mui";
|
import Form from "@rjsf/mui";
|
||||||
import { RegistryWidgetsType } from "@rjsf/utils";
|
import {RegistryFieldsType, RegistryWidgetsType} from "@rjsf/utils";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { jsonschemaProvider } from "../../providers/jsonschema-provider";
|
import { jsonschemaProvider } from "../../providers/jsonschema-provider";
|
||||||
import { useForm } from "@refinedev/core";
|
import { useForm } from "@refinedev/core";
|
||||||
//import TextWidget from "@rjsf/core/src/components/widgets/TextWidget";
|
|
||||||
import CrudTextWidget from "./widgets/crud-text-widget";
|
import CrudTextWidget from "./widgets/crud-text-widget";
|
||||||
|
import UnionEnumField from "./fields/union-enum";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
schemaName: string,
|
schemaName: string,
|
||||||
@@ -14,7 +14,13 @@ type Props = {
|
|||||||
//onSubmit: (data: IChangeEvent, event: FormEvent<any>) => void
|
//onSubmit: (data: IChangeEvent, event: FormEvent<any>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const customWidgets: RegistryWidgetsType = { TextWidget: CrudTextWidget };
|
const customWidgets: RegistryWidgetsType = {
|
||||||
|
TextWidget: CrudTextWidget
|
||||||
|
};
|
||||||
|
|
||||||
|
const customFields: RegistryFieldsType = {
|
||||||
|
AnyOfField: UnionEnumField
|
||||||
|
}
|
||||||
|
|
||||||
export const CrudForm: React.FC<Props> = ({schemaName, resource, id}) => {
|
export const CrudForm: React.FC<Props> = ({schemaName, resource, id}) => {
|
||||||
const { onFinish, query, formLoading } = useForm({
|
const { onFinish, query, formLoading } = useForm({
|
||||||
@@ -53,6 +59,7 @@ export const CrudForm: React.FC<Props> = ({schemaName, resource, id}) => {
|
|||||||
validator={validator}
|
validator={validator}
|
||||||
omitExtraData={true}
|
omitExtraData={true}
|
||||||
widgets={customWidgets}
|
widgets={customWidgets}
|
||||||
|
fields={customFields}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
78
gui/app/src/common/crud/fields/union-enum.tsx
Normal file
78
gui/app/src/common/crud/fields/union-enum.tsx
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { ERRORS_KEY, FieldProps, getUiOptions } from "@rjsf/utils";
|
||||||
|
import AnyOfField from "@rjsf/core/lib/components/fields/MultiSchemaField";
|
||||||
|
import { UnionEnumWidget } from "../widgets/union-enum";
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
import omit from 'lodash/omit';
|
||||||
|
|
||||||
|
const UnionEnumField = (props: FieldProps) => {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
disabled = false,
|
||||||
|
errorSchema = {},
|
||||||
|
formContext,
|
||||||
|
formData,
|
||||||
|
onChange,
|
||||||
|
onBlur,
|
||||||
|
onFocus,
|
||||||
|
registry,
|
||||||
|
schema,
|
||||||
|
uiSchema,
|
||||||
|
options,
|
||||||
|
idSchema,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const enumOptions: any[] = []
|
||||||
|
for (let opt of options) {
|
||||||
|
if (!opt.hasOwnProperty('enum')) {
|
||||||
|
return (<AnyOfField {...props} />)
|
||||||
|
}
|
||||||
|
for (let val of opt.enum) {
|
||||||
|
enumOptions.push({
|
||||||
|
title: val,
|
||||||
|
value: val,
|
||||||
|
type: opt.title
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { globalUiOptions, schemaUtils } = registry;
|
||||||
|
const {
|
||||||
|
placeholder,
|
||||||
|
autofocus,
|
||||||
|
autocomplete,
|
||||||
|
title = schema.title,
|
||||||
|
...uiOptions
|
||||||
|
} = getUiOptions(uiSchema, globalUiOptions);
|
||||||
|
|
||||||
|
const rawErrors = get(errorSchema, ERRORS_KEY, []);
|
||||||
|
const fieldErrorSchema = omit(errorSchema, [ERRORS_KEY]);
|
||||||
|
const displayLabel = schemaUtils.getDisplayLabel(schema, uiSchema, globalUiOptions);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UnionEnumWidget
|
||||||
|
id={`${idSchema.$id}${schema.oneOf ? '__oneof_select' : '__anyof_select'}`}
|
||||||
|
name={`${name}${schema.oneOf ? '__oneof_select' : '__anyof_select'}`}
|
||||||
|
schema={schema}
|
||||||
|
uiSchema={uiSchema}
|
||||||
|
onChange={onChange}
|
||||||
|
onBlur={onBlur}
|
||||||
|
onFocus={onFocus}
|
||||||
|
disabled={disabled || isEmpty(options)}
|
||||||
|
multiple={false}
|
||||||
|
rawErrors={rawErrors}
|
||||||
|
errorSchema={fieldErrorSchema}
|
||||||
|
value={formData}
|
||||||
|
options={{enumOptions}}
|
||||||
|
registry={registry}
|
||||||
|
formContext={formContext}
|
||||||
|
placeholder={placeholder}
|
||||||
|
autocomplete={autocomplete}
|
||||||
|
autofocus={autofocus}
|
||||||
|
label={title ?? name}
|
||||||
|
hideLabel={!displayLabel}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UnionEnumField;
|
||||||
@@ -8,6 +8,7 @@ export const BgfcPriceWidget = (props: WidgetProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<NumericFormat
|
<NumericFormat
|
||||||
|
{...props}
|
||||||
customInput={TextField}
|
customInput={TextField}
|
||||||
getInputRef={inputRef}
|
getInputRef={inputRef}
|
||||||
onValueChange={values => {
|
onValueChange={values => {
|
||||||
@@ -19,15 +20,15 @@ export const BgfcPriceWidget = (props: WidgetProps) => {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
input: {
|
input: {
|
||||||
startAdornment: <InputAdornment position="end">$</InputAdornment>,
|
//startAdornment: <InputAdornment position="end">$</InputAdornment>,
|
||||||
endAdornment: <InputAdornment position="start">€</InputAdornment>
|
endAdornment: <InputAdornment position="start">€</InputAdornment>
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
valueIsNumericString={true}
|
valueIsNumericString={true}
|
||||||
fixedDecimalScale={true}
|
fixedDecimalScale={true}
|
||||||
decimalScale={2}
|
decimalScale={2}
|
||||||
defaultValue={0}
|
//defaultValue={0}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
40
gui/app/src/common/crud/widgets/union-enum.tsx
Normal file
40
gui/app/src/common/crud/widgets/union-enum.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import {FieldProps, WidgetProps} from "@rjsf/utils";
|
||||||
|
import AnyOfField from "@rjsf/core/lib/components/fields/MultiSchemaField";
|
||||||
|
import {ListSubheader, MenuItem, Select} from "@mui/material";
|
||||||
|
import {useState} from "react";
|
||||||
|
import Autocomplete from "@mui/material/Autocomplete";
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
|
||||||
|
export const UnionEnumWidget = (props: WidgetProps) => {
|
||||||
|
const {
|
||||||
|
options,
|
||||||
|
value,
|
||||||
|
} = props;
|
||||||
|
const [selectedValue, setSelectedValue] = useState(null);
|
||||||
|
|
||||||
|
|
||||||
|
if (! selectedValue && value && options.enumOptions) {
|
||||||
|
for (const opt of options.enumOptions){
|
||||||
|
if (opt.value == value) {
|
||||||
|
setSelectedValue(opt);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Autocomplete
|
||||||
|
value={selectedValue}
|
||||||
|
onChange={(event, newValue) => {
|
||||||
|
setSelectedValue(newValue);
|
||||||
|
props.onChange(newValue.value);
|
||||||
|
}}
|
||||||
|
options={options.enumOptions}
|
||||||
|
groupBy={(option) => option.type}
|
||||||
|
getOptionLabel={(option) => option.title}
|
||||||
|
renderInput={(params) => (
|
||||||
|
<TextField {...params} label={ props.label } variant="outlined" />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
12
gui/app/src/pages/categories/create.tsx
Normal file
12
gui/app/src/pages/categories/create.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import {CrudForm} from "../../common/crud/crud-form";
|
||||||
|
import {CategoryEdit} from "./edit";
|
||||||
|
|
||||||
|
|
||||||
|
export const CategoryCreate: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<CrudForm
|
||||||
|
schemaName={"CategoryCreate"}
|
||||||
|
resource={"categories"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
15
gui/app/src/pages/categories/edit.tsx
Normal file
15
gui/app/src/pages/categories/edit.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { CrudForm } from "../../common/crud/crud-form";
|
||||||
|
import { useParams } from "react-router"
|
||||||
|
|
||||||
|
|
||||||
|
export const CategoryEdit: React.FC = () => {
|
||||||
|
const { id } = useParams()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CrudForm
|
||||||
|
schemaName={"CategoryUpdate"}
|
||||||
|
resource={"categories"}
|
||||||
|
id={id}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
3
gui/app/src/pages/categories/index.tsx
Normal file
3
gui/app/src/pages/categories/index.tsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./list";
|
||||||
|
export * from "./create";
|
||||||
|
export * from "./edit";
|
||||||
49
gui/app/src/pages/categories/list.tsx
Normal file
49
gui/app/src/pages/categories/list.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
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 CategoryList: React.FC = () => {
|
||||||
|
const { dataGridProps } = useDataGrid<IAccount>();
|
||||||
|
|
||||||
|
const columns = React.useMemo<GridColDef<IAccount>[]>(
|
||||||
|
() => [
|
||||||
|
{ field: "id", headerName: "ID" },
|
||||||
|
{ field: "name", headerName: "Name", flex: 1 },
|
||||||
|
{ field: "type", headerName: "Type", flex: 0.3 },
|
||||||
|
{
|
||||||
|
field: "actions",
|
||||||
|
headerName: "Actions",
|
||||||
|
display: "flex",
|
||||||
|
renderCell: function render({ row }) {
|
||||||
|
return (
|
||||||
|
<ButtonGroup>
|
||||||
|
<EditButton hideText recordItemId={row.id} />
|
||||||
|
<DeleteButton hideText recordItemId={row.id} />
|
||||||
|
</ButtonGroup>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
align: "center",
|
||||||
|
headerAlign: "center",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
maxHeight: "calc(100vh - 320px)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DataGrid {...dataGridProps} columns={columns} />
|
||||||
|
</div>
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { DataProvider } from "@refinedev/core";
|
import type { DataProvider } from "@refinedev/core";
|
||||||
|
|
||||||
//const API_URL = "https://api.fake-rest.refine.dev";
|
|
||||||
const API_URL = "http://localhost:8000";
|
const API_URL = "http://localhost:8000";
|
||||||
|
|
||||||
const fetcher = async (url: string, options?: RequestInit) => {
|
const fetcher = async (url: string, options?: RequestInit) => {
|
||||||
|
|||||||
@@ -31,8 +31,9 @@ function buildResource(rawSchemas: RJSFSchema, resourceName: string) {
|
|||||||
if (is_reference(prop)) {
|
if (is_reference(prop)) {
|
||||||
resolveReference(rawSchemas, resource, prop);
|
resolveReference(rawSchemas, resource, prop);
|
||||||
} else if (is_union(prop)) {
|
} else if (is_union(prop)) {
|
||||||
for (let i in prop.oneOf) {
|
const union = prop.hasOwnProperty("oneOf") ? prop.oneOf : prop.anyOf;
|
||||||
resolveReference(rawSchemas, resource, prop.oneOf[i]);
|
for (let i in union) {
|
||||||
|
resolveReference(rawSchemas, resource, union[i]);
|
||||||
}
|
}
|
||||||
} else if (is_enum(prop)) {
|
} else if (is_enum(prop)) {
|
||||||
for (let i in prop.allOf) {
|
for (let i in prop.allOf) {
|
||||||
@@ -92,11 +93,11 @@ function is_array(prop: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function is_union(prop: any) {
|
function is_union(prop: any) {
|
||||||
return prop.hasOwnProperty('oneOf');
|
return prop.hasOwnProperty('oneOf') || prop.hasOwnProperty('anyOf');
|
||||||
}
|
}
|
||||||
|
|
||||||
function is_enum(prop: any) {
|
function is_enum(prop: any) {
|
||||||
return prop.hasOwnProperty('allOf');
|
return prop.hasOwnProperty('enum');
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_reference_name(prop: any) {
|
function get_reference_name(prop: any) {
|
||||||
@@ -112,7 +113,8 @@ function has_descendant(rawSchemas:RJSFSchema, resource: RJSFSchema, property_na
|
|||||||
let subresourceName = get_reference_name(resource);
|
let subresourceName = get_reference_name(resource);
|
||||||
return has_descendant(rawSchemas, buildResource(rawSchemas, subresourceName), property_name);
|
return has_descendant(rawSchemas, buildResource(rawSchemas, subresourceName), property_name);
|
||||||
} else if (is_union(resource)) {
|
} else if (is_union(resource)) {
|
||||||
for (const ref of resource.oneOf!) {
|
const union = resource.hasOwnProperty("oneOf") ? resource.oneOf : resource.anyOf;
|
||||||
|
for (const ref of union) {
|
||||||
return has_descendant(rawSchemas, ref, property_name)
|
return has_descendant(rawSchemas, ref, property_name)
|
||||||
}
|
}
|
||||||
} else if (is_enum(resource)) {
|
} else if (is_enum(resource)) {
|
||||||
@@ -183,4 +185,3 @@ function get_property_by_path(rawSchemas: RJSFSchema, resource: RJSFSchema, path
|
|||||||
path.substring(pointFirstPosition + 1)
|
path.substring(pointFirstPosition + 1)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user