From 953edb3d0c2c3fa7572d04bb7c9078e84f3096cf Mon Sep 17 00:00:00 2001 From: Sam Liu Date: Wed, 24 Jul 2024 02:39:55 +0000 Subject: [PATCH] Last Commit for solidjs --- backend/core/message_code.py | 1 + backend/db/models/houses/houses.py | 2 +- backend/repos/repository_houses.py | 44 +++++- backend/routes/house/house.py | 16 ++- backend/schemas/house/house.py | 34 +++-- backend/services/house/house_service.py | 10 +- frontend/package.json | 1 + frontend/pnpm-lock.yaml | 9 ++ frontend/src/api/house.js | 15 +- frontend/src/api/url.js | 6 + frontend/src/components/AreaAdd/AreaAdd.jsx | 129 ++++++++++++++---- frontend/src/components/AreaAdd/AreaItem.jsx | 58 +++++--- frontend/src/lang/vi.json | 5 +- frontend/src/pages/House/House.jsx | 44 ++++-- frontend/src/pages/House/house.scss | 24 +++- .../src/pages/HouseCreate/HouseCreate.jsx | 80 +++++++++-- frontend/src/routes/routes.js | 9 ++ frontend/vite.config.js | 3 +- 18 files changed, 397 insertions(+), 93 deletions(-) diff --git a/backend/core/message_code.py b/backend/core/message_code.py index 1f0eb8c..05bb044 100644 --- a/backend/core/message_code.py +++ b/backend/core/message_code.py @@ -7,3 +7,4 @@ class MessageCode(): REFRESH_TOKEN_EXPIRED: str = 'REFRESH_TOKEN_EXPIRED' CREATE_HOUSE_FAIL: str = 'CREATE_HOUSE_FAIL' CREATE_HOUSE_SUCCESS: str = 'CREATE_HOUSE_SUCCESS' + HOUSE_NOT_FOUND: str = 'HOUSE_NOT_FOUND' diff --git a/backend/db/models/houses/houses.py b/backend/db/models/houses/houses.py index 4f3b099..017e0f5 100644 --- a/backend/db/models/houses/houses.py +++ b/backend/db/models/houses/houses.py @@ -30,4 +30,4 @@ class Areas(SqlAlchemyBase, DeleteMixin): house: Mapped['Houses'] = relationship(back_populates="areas") def __repr__(self): - return f"{self.__class__.__name__}, name: {self.name}" + return f"<{self.__class__.__name__} id={self.id} name={self.name} house_id={self.house_id}, desc={self.desc}>" diff --git a/backend/repos/repository_houses.py b/backend/repos/repository_houses.py index 4225e7c..1999bc4 100644 --- a/backend/repos/repository_houses.py +++ b/backend/repos/repository_houses.py @@ -1,7 +1,7 @@ from backend.db.models.houses import Houses, Areas from sqlalchemy.orm import Session -from backend.schemas.house import HouseCreate +from backend.schemas.house import HouseCreate, HouseUpdate, AreaUpdate class RepositoryHouses: def __init__(self): @@ -34,3 +34,45 @@ class RepositoryHouses: db.refresh(db_house) return db_house + + def update_area(self, db: Session, areas: list[AreaUpdate], house_id: str): + db_house = self.get_by_id(house_id) + if not db_house: + return None + try: + db_areaId = [area.id for area in db_house.areas] + area_update_id = [area.id for area in areas if area.id is not None] + area_delete_id = [area_id for area_id in db_areaId if area_id not in area_update_id] + if area_delete_id: + self.areas.query.where(Areas.id.in_(area_delete_id)).delete() + + for area in areas: + area_obj = Areas(**area.dict(), house_id=house_id) + db.merge(area_obj) + + # areaList = [dict(**area.dict(), house_id=house_id) for area in areas] + # db.execute(update(Areas), areaList) + # self.areas.query.update(areaList) + db.commit() + except Exception: + db.rollback() + raise + + + def update(self, db: Session, house: HouseUpdate): + db_house = self.get_by_id(house.id) + if not db_house: + return None + try: + self.houses.query.where(Houses.id == house.id).update(house.dict(exclude={"id", 'areas'}, exclude_unset=True, exclude_none=True)) + db.commit() + self.update_area(db, house.areas, house.id) + except Exception: + db.rollback() + raise + + db.refresh(db_house) + return db_house + + def delete(self, db: Session, house_id: str): + pass diff --git a/backend/routes/house/house.py b/backend/routes/house/house.py index 88163ef..0a6d033 100644 --- a/backend/routes/house/house.py +++ b/backend/routes/house/house.py @@ -7,7 +7,7 @@ from backend.core.message_code import MessageCode from backend.db.db_setup import generate_session from backend.schemas.common import ReturnValue from backend.schemas.house import HouseCreate -from backend.schemas.house.house import HousesListResponse +from backend.schemas.house.house import HouseUpdate, HousesListResponse, HouseResponse from backend.schemas.user import ProfileResponse from backend.services.house import HouseService @@ -27,7 +27,19 @@ def create_house(house: HouseCreate, db: db_dependency, current_user: current_us return ReturnValue(status=200, data=MessageCode.CREATE_HOUSE_SUCCESS) @public_router.get("/all", response_model=ReturnValue[HousesListResponse]) -async def get_all_house(page: int, pageSize: int, current_user: current_user_token) -> ReturnValue[HousesListResponse]: +def get_all_house(page: int, pageSize: int, current_user: current_user_token) -> ReturnValue[HousesListResponse]: housesCount = house_service.get_all_count() houses = house_service.get_all(skip=page-1, limit=pageSize) return ReturnValue(status=200, data={'total': housesCount, 'list': list(houses)}) + +@public_router.get("/{house_id}", response_model=ReturnValue[HouseResponse]) +def get_house_by_id(house_id: str, current_user: current_user_token) -> ReturnValue[HouseCreate]: + house = house_service.get_by_id(id=house_id) + if not house: + raise HTTPException(status_code=404, detail=MessageCode.HOUSE_NOT_FOUND) + return ReturnValue(status=200, data=house) + +@public_router.put("/update", response_model=ReturnValue[HouseCreate]) +def update_house(house: HouseUpdate, current_user: current_user_token, db: db_dependency) -> ReturnValue[Any]: + db_house = house_service.update(db=db, house=house) + return ReturnValue(status=200, data=db_house) diff --git a/backend/schemas/house/house.py b/backend/schemas/house/house.py index 68e71a1..98162d7 100644 --- a/backend/schemas/house/house.py +++ b/backend/schemas/house/house.py @@ -5,23 +5,33 @@ from pydantic import ConfigDict from backend.schemas.main_model import MainModel class HouseBase(MainModel): - pass - -class AreaBase(MainModel): - pass - -class AreaCreate(AreaBase): - name: str - desc: str - -class HouseCreate(HouseBase): icon: str name: str address: str - areas: list[AreaCreate] + +class AreaBase(MainModel): + name: str + desc: str + +class AreaUpdate(AreaBase): + id: UUID | None = None model_config = ConfigDict(from_attributes=True) -class HousesList(HouseCreate): +class HouseCreate(HouseBase): + areas: list[AreaBase] + model_config = ConfigDict(from_attributes=True) + +class HouseUpdate(HouseBase): + id: UUID + areas: list[AreaUpdate] + model_config = ConfigDict(from_attributes=True) + +class HouseResponse(HouseBase): + id: UUID + areas: list[AreaUpdate] + model_config = ConfigDict(from_attributes=True) + +class HousesList(HouseBase): id: UUID created_at: datetime updated_at: datetime diff --git a/backend/services/house/house_service.py b/backend/services/house/house_service.py index 4f2a6c8..cc45487 100644 --- a/backend/services/house/house_service.py +++ b/backend/services/house/house_service.py @@ -1,6 +1,6 @@ from sqlalchemy.orm import Session from backend.repos import RepositoryHouses -from backend.schemas import HouseCreate +from backend.schemas import HouseCreate, HouseUpdate from backend.services._base_service import BaseService class HouseService(BaseService): @@ -13,5 +13,11 @@ class HouseService(BaseService): def get_all(self, skip: int = 0, limit: int = 100): return self.repos.get_all(skip=skip, limit=limit) - def get_all_count(self, skip: int = 0, limit: int = 100): + def get_all_count(self): return self.repos.get_count_all() + + def get_by_id(self, id: str): + return self.repos.get_by_id(house_id=id) + + def update(self, db: Session, house: HouseUpdate): + return self.repos.update(db=db, house=house) diff --git a/frontend/package.json b/frontend/package.json index 23847f5..999ff77 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,6 +26,7 @@ "crypto-js": "^4.2.0", "solid-js": "^1.8.15", "solid-toast": "^0.5.0", + "uuid": "^10.0.0", "yup": "^1.4.0" }, "devDependencies": { diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index c87676b..c10481a 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -41,6 +41,9 @@ importers: solid-toast: specifier: ^0.5.0 version: 0.5.0(solid-js@1.8.17) + uuid: + specifier: ^10.0.0 + version: 10.0.0 yup: specifier: ^1.4.0 version: 1.4.0 @@ -1668,6 +1671,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + hasBin: true + validate-html-nesting@1.2.2: resolution: {integrity: sha512-hGdgQozCsQJMyfK5urgFcWEqsSSrK63Awe0t/IMR0bZ0QMtnuaiHzThW81guu3qx9abLi99NEuiaN6P9gVYsNg==} @@ -3302,6 +3309,8 @@ snapshots: util-deprecate@1.0.2: {} + uuid@10.0.0: {} + validate-html-nesting@1.2.2: {} vite-plugin-mkcert@1.17.5(vite@5.2.11(sass@1.77.4)): diff --git a/frontend/src/api/house.js b/frontend/src/api/house.js index 8bcf2f7..0743288 100644 --- a/frontend/src/api/house.js +++ b/frontend/src/api/house.js @@ -1,5 +1,10 @@ import { protocol } from './index' -import { GET_HOUSES_LIST, POST_HOUSE_CREATE } from './url' +import { + GET_HOUSE_DETAIL, + GET_HOUSES_LIST, + POST_HOUSE_CREATE, + PUT_UPDATE_HOUSE, +} from './url' export const postCreateHouse = (payload) => { return protocol.post(POST_HOUSE_CREATE, payload) @@ -14,3 +19,11 @@ export const getAllHouse = ({ page, pageSize }) => { {}, ) } + +export const getHouseDetail = (id) => { + return protocol.get(GET_HOUSE_DETAIL(id), {}) +} + +export const putUpdateHouse = (payload) => { + return protocol.put(PUT_UPDATE_HOUSE, payload) +} diff --git a/frontend/src/api/url.js b/frontend/src/api/url.js index cbded8a..5cd733b 100644 --- a/frontend/src/api/url.js +++ b/frontend/src/api/url.js @@ -3,6 +3,12 @@ export const POST_LOGOUT = '/api/auth/logout' export const POST_REFRESH = '/api/auth/refresh' export const GET_USER_PROFILE = '/api/user/me' export const PUT_UPDATE_USER_PROFILE = '/api/user/update-profile' + +/** + * House API + */ export const POST_HOUSE_CREATE = '/api/house/create' export const GET_HOUSES_LIST = ({ page, pageSize }) => `/api/house/all?page=${page}&pageSize=${pageSize}` +export const GET_HOUSE_DETAIL = (id) => `/api/house/${id}` +export const PUT_UPDATE_HOUSE = '/api/house/update' diff --git a/frontend/src/components/AreaAdd/AreaAdd.jsx b/frontend/src/components/AreaAdd/AreaAdd.jsx index 161225a..e6a0263 100644 --- a/frontend/src/components/AreaAdd/AreaAdd.jsx +++ b/frontend/src/components/AreaAdd/AreaAdd.jsx @@ -8,11 +8,20 @@ import useLanguage from '@hooks/useLanguage' import { IconAddressBook, IconCirclePlus, + IconEditCircle, IconFileDescription, IconInfoCircle, IconVector, } from '@tabler/icons-solidjs' -import { For, Show, createComponent, createSignal } from 'solid-js' +import { + createComponent, + createEffect, + createSignal, + For, + Show, + untrack, +} from 'solid-js' +import { v4 as uuidv4 } from 'uuid' import * as yup from 'yup' import AreaItem from './AreaItem' @@ -25,39 +34,85 @@ import AreaItem from './AreaItem' */ const areaSchema = (language, isRequired) => yup.object({ - name: yup.string().required(isRequired(language.ui.areaName)), - description: yup.string().required(isRequired(language.ui.areaName)), + name: yup + .string() + .min(3, language.message['MIN_THREE_CHAR']) + .required(isRequired(language.ui.areaName)), + desc: yup + .string() + .min(3, language.message['MIN_THREE_CHAR']) + .required(isRequired(language.ui.areaName)), }) export default function AreaAdd(props) { const [openModal, setOpenModal] = createSignal(false) - const [data, setData] = createSignal([]) + const [dataArea, setDataArea] = createSignal([]) + const [editMode, setEditMode] = createSignal(false) const { language, isRequired } = useLanguage() - const { form, reset, errors } = createForm({ - extend: [validator({ schema: areaSchema(language, isRequired) })], - onSubmit: async (values) => { - setData((prev) => [...prev, values]) - onModalClose() - }, + const { form, reset, data, setData, errors, createSubmitHandler } = + createForm({ + extend: [validator({ schema: areaSchema(language, isRequired) })], + onSubmit: async (values) => { + values.id = uuidv4() + values.isCreate = true + setDataArea((prev) => [...prev, values]) + onModalClose() + }, + }) + + createEffect(() => { + if (props.value && dataArea().length === 0) { + console.log(props.value) + untrack(() => setDataArea(props.value)) + } }) const onModalClose = () => { - setOpenModal(false), reset() + setOpenModal(false), reset(), setEditMode(false) } const onOpenModal = () => { setOpenModal(true) } - const onConfirmDelete = (index) => { - setData((prev) => [...prev.slice(0, index), ...prev.slice(index + 1)]) + const onConfirmDelete = (delId) => { + setDataArea((prev) => { + prev = prev.filter((item) => item.id !== delId) + return [...prev] + }) + props.setData('areas', dataArea()) } - const onClickDeleteItem = (index) => { + const onClickEditItem = (id) => { + const editItem = dataArea().find((item) => item.id === id) + setData(editItem) + setEditMode(true) + setOpenModal(true) + } + + const onClickUpdateItem = createSubmitHandler({ + onSubmit: (values) => { + setDataArea((prev) => { + prev = prev.map((item) => { + if (item.id === values.id) { + return values + } + return item + }) + return [...prev] + }) + props.setData('areas', dataArea()) + reset() + setEditMode(false) + onModalClose() + }, + }) + + const onClickDeleteItem = (delId) => { createComponent(ConfirmPopup, { title: language?.message['CONFIRM_DELETE'], children: language?.message['CONFIRM_DELETE_NOTE'], - deleteId: index, + deleteId: delId, onConfirm: onConfirmDelete, }) } @@ -96,14 +151,15 @@ export default function AreaAdd(props) { classList={{ 'border-red-500': props.error }} >
- 0} fallback={language.ui.empty}> - + 0} fallback={language.ui.empty}> + {(item, index) => ( )} @@ -112,8 +168,14 @@ export default function AreaAdd(props) {
} - title={language.ui.addArea} + icon={ + editMode() ? ( + + ) : ( + + ) + } + title={editMode() ? language.ui.editArea : language.ui.addArea} titleClass="text-lg" openModal={openModal()} onModalClose={onModalClose} @@ -126,20 +188,35 @@ export default function AreaAdd(props) { name="name" label={language.ui.areaName} placeholder={language.ui.areaName} + value={data('name')} error={errors('name')} />