Last Commit for solidjs
This commit is contained in:
parent
5eb89d3b99
commit
953edb3d0c
@ -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'
|
||||
|
@ -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}>"
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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": {
|
||||
|
9
frontend/pnpm-lock.yaml
generated
9
frontend/pnpm-lock.yaml
generated
@ -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)):
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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'
|
||||
|
@ -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 }}
|
||||
>
|
||||
<div class="p-3 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<Show when={data().length > 0} fallback={language.ui.empty}>
|
||||
<For each={data()}>
|
||||
<Show when={dataArea().length > 0} fallback={language.ui.empty}>
|
||||
<For each={dataArea()}>
|
||||
{(item, index) => (
|
||||
<AreaItem
|
||||
{...item}
|
||||
data={item}
|
||||
formName={props.name}
|
||||
key={index()}
|
||||
num={index()}
|
||||
onDelete={onClickDeleteItem}
|
||||
onEdit={onClickEditItem}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
@ -112,8 +168,14 @@ export default function AreaAdd(props) {
|
||||
</div>
|
||||
</div>
|
||||
<Popup
|
||||
icon={<IconCirclePlus size={20} class="text-green-500" />}
|
||||
title={language.ui.addArea}
|
||||
icon={
|
||||
editMode() ? (
|
||||
<IconEditCircle size={20} class="text-blue-500" />
|
||||
) : (
|
||||
<IconCirclePlus size={20} class="text-green-500" />
|
||||
)
|
||||
}
|
||||
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')}
|
||||
/>
|
||||
<Textarea
|
||||
icon={IconFileDescription}
|
||||
name="description"
|
||||
name="desc"
|
||||
label={language.ui.areaDesc}
|
||||
placeholder={language.ui.areaDesc}
|
||||
error={errors('description')}
|
||||
value={data('desc')}
|
||||
error={errors('desc')}
|
||||
/>
|
||||
</div>
|
||||
<div class="modal-action">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{language.ui.save}
|
||||
</button>
|
||||
<Show
|
||||
when={editMode()}
|
||||
fallback={
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{language.ui.save}
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary"
|
||||
onClick={onClickUpdateItem}
|
||||
>
|
||||
{language.ui.update}
|
||||
</button>
|
||||
</Show>
|
||||
<button type="button" class="btn btn-ghost" onClick={onModalClose}>
|
||||
{language.ui.cancel}
|
||||
</button>
|
||||
|
@ -1,32 +1,54 @@
|
||||
import { IconTrash } from '@tabler/icons-solidjs'
|
||||
import { IconPencil, IconTrash } from '@tabler/icons-solidjs'
|
||||
import { Show } from 'solid-js'
|
||||
|
||||
export default function AreaItem(props) {
|
||||
const data = () => props.data
|
||||
|
||||
return (
|
||||
<div class="flex justify-between items-center shadow rounded-lg p-3 border border-gray-300">
|
||||
<div class="">
|
||||
<span class="text-md font-bold">{props.name}</span>
|
||||
<p class="text-xs">{props.description}</p>
|
||||
<div class="flex justify-between shadow rounded-lg p-3 border border-gray-300">
|
||||
<div class="item-body">
|
||||
<span class="text-md font-bold">{data().name}</span>
|
||||
<p class="text-xs">{data().desc}</p>
|
||||
<Show when={!data().isCreate}>
|
||||
<input
|
||||
type="hidden"
|
||||
name={`${props.formName}.${props.num}.id`}
|
||||
value={data().id}
|
||||
/>
|
||||
</Show>
|
||||
<input
|
||||
type="hidden"
|
||||
name={`${props.formName}.${props.key}.name`}
|
||||
value={props.name}
|
||||
name={`${props.formName}.${props.num}.name`}
|
||||
value={data().name}
|
||||
data-value={data().name}
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
name={`${props.formName}.${props.key}.desc`}
|
||||
value={props.description}
|
||||
name={`${props.formName}.${props.num}.desc`}
|
||||
value={data().desc}
|
||||
data-value={data().desc}
|
||||
/>
|
||||
</div>
|
||||
<Show when={props.onDelete}>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-circle btn-ghost btn-sm text-red-500 hover:bg-red-100"
|
||||
onClick={() => props.onDelete(props.key)}
|
||||
>
|
||||
<IconTrash size={20} />
|
||||
</button>
|
||||
</Show>
|
||||
<div class="flex">
|
||||
<Show when={props.onEdit}>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-circle btn-ghost btn-sm text-blue-500 hover:bg-blue-100"
|
||||
onClick={() => props.onEdit(data().id)}
|
||||
>
|
||||
<IconPencil size={20} />
|
||||
</button>
|
||||
</Show>
|
||||
<Show when={props.onDelete}>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-circle btn-ghost btn-sm text-red-500 hover:bg-red-100"
|
||||
onClick={() => props.onDelete(data().id)}
|
||||
>
|
||||
<IconTrash size={20} />
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
"newPassword": "Mật khẩu mới",
|
||||
"confirmNewPassword": "Nhập lại mật khẩu",
|
||||
"newHouse": "Tạo địa điểm mới",
|
||||
"editHouse": "Sửa địa điểm",
|
||||
"houseName": "Tên địa điểm",
|
||||
"houseIcon": "Ký tự",
|
||||
"houseAddress": "Địa chỉ",
|
||||
@ -24,6 +25,7 @@
|
||||
"areaName": "Tên khu vực",
|
||||
"areaDesc": "Mô tả",
|
||||
"addArea": "Thêm khu vực",
|
||||
"editArea": "Sửa khu vực",
|
||||
"create": "Tạo",
|
||||
"update": "Cập nhật",
|
||||
"delete": "Xóa",
|
||||
@ -60,6 +62,7 @@
|
||||
"CREATE_HOUSE_FAIL": "Tạo địa điểm mới thất bại!",
|
||||
"CREATE_HOUSE_SUCCESS": "Tạo địa điểm mới thành công!",
|
||||
"CONFIRM_DELETE": "Bạn có chắc là muốn xóa mục này?",
|
||||
"CONFIRM_DELETE_NOTE": "Chú Ý ‼: Một khi đã XÓA thì không thể nào khôi phục lại được."
|
||||
"CONFIRM_DELETE_NOTE": "Chú Ý ‼: Một khi đã XÓA thì không thể nào khôi phục lại được.",
|
||||
"MIN_THREE_CHAR": "Ít nhất phải có 3 ký tự"
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,25 @@
|
||||
import ViewSwitch, { VIEWDATA } from '@components/ViewSwitch'
|
||||
import useLanguage from '@hooks/useLanguage'
|
||||
import * as icons from '@tabler/icons-solidjs'
|
||||
import { For, createEffect, createResource, createSignal } from 'solid-js'
|
||||
import {
|
||||
For,
|
||||
createComponent,
|
||||
createEffect,
|
||||
createResource,
|
||||
createSignal,
|
||||
} from 'solid-js'
|
||||
|
||||
import { getAllHouse } from '@api/house'
|
||||
import ConfirmPopup from '@components/common/ConfirmPopup'
|
||||
import Pagination from '@components/common/Pagination'
|
||||
import { A } from '@solidjs/router'
|
||||
import { A, useNavigate } from '@solidjs/router'
|
||||
import {
|
||||
IconHome,
|
||||
IconPencil,
|
||||
IconSquareRoundedPlus,
|
||||
IconTrash,
|
||||
} from '@tabler/icons-solidjs'
|
||||
import { Helpers } from '@utils/helper'
|
||||
import { Dynamic } from 'solid-js/web'
|
||||
import './house.scss'
|
||||
|
||||
@ -31,6 +39,7 @@ const fetchHouses = async ({ page, pageSize }) => {
|
||||
|
||||
export default function House() {
|
||||
const { language } = useLanguage()
|
||||
const navigate = useNavigate()
|
||||
const [pageSize, setPageSize] = createSignal(PAGE_SIZE[0])
|
||||
const [currentPage, setCurrentPage] = createSignal(1)
|
||||
const [view, setView] = createSignal(VIEWDATA['list'])
|
||||
@ -48,12 +57,23 @@ export default function House() {
|
||||
}
|
||||
})
|
||||
|
||||
const onEdit = () => {
|
||||
console.log('edit')
|
||||
const onClickEdit = (editId) => {
|
||||
navigate(Helpers.getRoutePath('edit-location').replace(':id', editId), {
|
||||
replace: true,
|
||||
})
|
||||
}
|
||||
|
||||
const onDelete = () => {
|
||||
console.log('delete')
|
||||
const onConfirmDelete = (deleteId) => {
|
||||
console.log(deleteId)
|
||||
}
|
||||
|
||||
const onClickDelete = (deleteId) => {
|
||||
createComponent(ConfirmPopup, {
|
||||
title: language?.message['CONFIRM_DELETE'],
|
||||
children: language?.message['CONFIRM_DELETE_NOTE'],
|
||||
deleteId,
|
||||
onConfirm: onConfirmDelete,
|
||||
})
|
||||
}
|
||||
|
||||
const onSetPageSize = (pageSize) => {
|
||||
@ -100,21 +120,21 @@ export default function House() {
|
||||
<div class="col hide w-1/12">
|
||||
{pageSize() * currentPage() - (pageSize() - idx() - 1)}
|
||||
</div>
|
||||
<div class="col w-1/12">
|
||||
<Dynamic component={icons[item?.icon]} size={21} />
|
||||
<div class="col symbol w-1/12">
|
||||
<Dynamic component={icons[item?.icon]} class="w-7 h-7" />
|
||||
</div>
|
||||
<div class="col w-4/12">{item?.name}</div>
|
||||
<div class="col w-4/12">{item?.address}</div>
|
||||
<div class="col actionbar w-2/12">
|
||||
<button
|
||||
class="btn btn-ghost btn-sm px-1 text-blue-500 mr-2"
|
||||
onClick={onEdit}
|
||||
class="btn btn-ghost btn-sm px-1 text-blue-500"
|
||||
onClick={[onClickEdit, item?.id]}
|
||||
>
|
||||
<IconPencil size={20} />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-ghost btn-sm px-1 text-red-500"
|
||||
onClick={onDelete}
|
||||
onClick={[onClickDelete, item?.id]}
|
||||
>
|
||||
<IconTrash size={20} />
|
||||
</button>
|
||||
@ -144,7 +164,7 @@ export default function House() {
|
||||
tabindex="0"
|
||||
class="dropdown-content menu bg-base-100 rounded-box z-[1] w-32 p-2 shadow mb-2"
|
||||
>
|
||||
<For each={PAGE_SIZE.reverse()}>
|
||||
<For each={PAGE_SIZE}>
|
||||
{(pageSize) => (
|
||||
<li>
|
||||
<a onClick={[onSetPageSize, pageSize]}>{pageSize}</a>
|
||||
|
@ -12,6 +12,10 @@
|
||||
&.view-head {
|
||||
@apply bg-base-200 font-bold;
|
||||
}
|
||||
|
||||
& > .actionbar {
|
||||
@apply flex gap-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,10 +28,11 @@
|
||||
|
||||
& .row {
|
||||
&.view-item {
|
||||
@apply card bg-base-100 shadow-md border border-gray-200 p-3 relative pb-8;
|
||||
@apply card bg-base-100 shadow-md border border-gray-200 relative
|
||||
grid grid-rows-2 grid-flow-col p-2 pr-4 gap-1 auto-cols-max overflow-hidden;
|
||||
|
||||
& .col {
|
||||
@apply w-auto
|
||||
@apply w-auto;
|
||||
}
|
||||
|
||||
& > .hide {
|
||||
@ -35,10 +40,23 @@
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
& .col.symbol {
|
||||
@apply row-span-2 border-r border-gray-300 bg-white flex items-center pr-2 mr-2;
|
||||
|
||||
& > svg {
|
||||
@apply w-14 h-14;
|
||||
}
|
||||
}
|
||||
|
||||
& > .actionbar {
|
||||
@apply absolute right-0 bottom-0 flex px-3 py-1 w-auto border-t border-l rounded-tl-xl;
|
||||
@apply absolute flex flex-col justify-center px-3 w-auto h-full border-l rounded-xl bg-white z-10 transition-all;
|
||||
right: -45px;
|
||||
box-shadow: -1px -1px 10px 0px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
&:hover > .actionbar {
|
||||
@apply right-0;
|
||||
}
|
||||
}
|
||||
|
||||
&.view-head {
|
||||
|
@ -1,40 +1,77 @@
|
||||
import { postCreateHouse } from '@api/house'
|
||||
import { getHouseDetail, postCreateHouse, putUpdateHouse } from '@api/house'
|
||||
import AreaAdd from '@components/AreaAdd'
|
||||
import TextInput from '@components/common/TextInput'
|
||||
import { createForm } from '@felte/solid'
|
||||
import { validator } from '@felte/validator-yup'
|
||||
import useLanguage from '@hooks/useLanguage'
|
||||
import useToast from '@hooks/useToast'
|
||||
import { A, useNavigate } from '@solidjs/router'
|
||||
import { A, useNavigate, useParams } from '@solidjs/router'
|
||||
import {
|
||||
IconAddressBook,
|
||||
IconIcons,
|
||||
IconLocationBolt,
|
||||
IconMapPinPlus,
|
||||
IconTag,
|
||||
} from '@tabler/icons-solidjs'
|
||||
import { Helpers } from '@utils/helper'
|
||||
import { createEffect, createResource, Show } from 'solid-js'
|
||||
import * as yup from 'yup'
|
||||
|
||||
const houseSchema = (language, isRequired) =>
|
||||
yup.object({
|
||||
icon: yup.string().required(isRequired(language.ui.houseIcon)),
|
||||
name: yup.string().required(isRequired(language.ui.houseName)),
|
||||
address: yup.string().required(isRequired(language.ui.houseAddress)),
|
||||
icon: yup
|
||||
.string()
|
||||
.min(3, language.message['MIN_THREE_CHAR'])
|
||||
.required(isRequired(language.ui.houseIcon)),
|
||||
name: yup
|
||||
.string()
|
||||
.min(3, language.message['MIN_THREE_CHAR'])
|
||||
.required(isRequired(language.ui.houseName)),
|
||||
address: yup
|
||||
.string()
|
||||
.min(3, language.message['MIN_THREE_CHAR'])
|
||||
.required(isRequired(language.ui.houseAddress)),
|
||||
areas: yup
|
||||
.array()
|
||||
.min(1, isRequired(language.ui.areas))
|
||||
.required(isRequired(language.ui.areas)),
|
||||
})
|
||||
|
||||
const fetchHouses = async (id) => {
|
||||
if (id) {
|
||||
const response = await getHouseDetail(id)
|
||||
if (response.status === 200) {
|
||||
return response.data
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export default function HouseCreate() {
|
||||
const { language, isRequired } = useLanguage()
|
||||
const notify = useToast()
|
||||
const navigate = useNavigate()
|
||||
const { form, errors } = createForm({
|
||||
const { id } = useParams()
|
||||
const [house] = createResource(id, fetchHouses)
|
||||
const { form, data, setData, errors } = createForm({
|
||||
extend: [validator({ schema: houseSchema(language, isRequired) })],
|
||||
onSubmit: async (values) => {
|
||||
console.log(values)
|
||||
const resp = await postCreateHouse(values)
|
||||
const call = id ? putUpdateHouse : postCreateHouse
|
||||
const result = {
|
||||
...values,
|
||||
areas: values.areas.map((item) =>
|
||||
id
|
||||
? { ...item }
|
||||
: {
|
||||
name: item.name,
|
||||
desc: item.desc,
|
||||
},
|
||||
),
|
||||
}
|
||||
console.log(values.areas)
|
||||
const resp = await call(result)
|
||||
return resp
|
||||
},
|
||||
onSuccess: (resp) => {
|
||||
@ -45,7 +82,7 @@ export default function HouseCreate() {
|
||||
language.message[resp.data] ||
|
||||
language.message['CREATE_HOUSE_SUCCESS'],
|
||||
})
|
||||
navigate(Helpers.getRoutePath('location'), { replace: true })
|
||||
// navigate(Helpers.getRoutePath('location'), { replace: true })
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
@ -58,6 +95,12 @@ export default function HouseCreate() {
|
||||
},
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (id && house()) {
|
||||
setData(house())
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div class="house-create">
|
||||
<div class="text-sm breadcrumbs mb-2">
|
||||
@ -65,18 +108,21 @@ export default function HouseCreate() {
|
||||
<li>
|
||||
<A href={Helpers.getRoutePath('location')}>{language.ui.house}</A>
|
||||
</li>
|
||||
<li>{language.ui.newHouse}</li>
|
||||
<li>{id ? language.ui.editHouse : language.ui.newHouse}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 mb-5 text-xl">
|
||||
<span class="text-secondary">
|
||||
<IconMapPinPlus size={30} />
|
||||
{id ? <IconLocationBolt size={30} /> : <IconMapPinPlus size={30} />}
|
||||
</span>
|
||||
{language.ui.newHouse}
|
||||
{id ? language.ui.editHouse : language.ui.newHouse}
|
||||
</div>
|
||||
<div class="card w-full bg-base-100 shadow-lg border border-gray-200">
|
||||
<div class="card-body">
|
||||
<form autoComplete="off" use:form>
|
||||
<Show when={id}>
|
||||
<input type="hidden" name="id" value={id} />
|
||||
</Show>
|
||||
<div class="grid gap-4 grid-cols-1 lg:grid-cols-2">
|
||||
<div class="col-auto">
|
||||
<TextInput
|
||||
@ -85,6 +131,7 @@ export default function HouseCreate() {
|
||||
label={language.ui.houseIcon}
|
||||
labelClass="md:w-40"
|
||||
placeholder={language.ui.houseIcon}
|
||||
value={data('icon')}
|
||||
error={errors('icon')}
|
||||
>
|
||||
<div class="label">
|
||||
@ -105,6 +152,7 @@ export default function HouseCreate() {
|
||||
label={language.ui.houseName}
|
||||
labelClass="md:w-40"
|
||||
placeholder={language.ui.houseName}
|
||||
value={data('name')}
|
||||
error={errors('name')}
|
||||
/>
|
||||
</div>
|
||||
@ -115,12 +163,15 @@ export default function HouseCreate() {
|
||||
label={language.ui.houseAddress}
|
||||
labelClass="md:w-40"
|
||||
placeholder={language.ui.houseAddress}
|
||||
value={data('address')}
|
||||
error={errors('address')}
|
||||
/>
|
||||
</div>
|
||||
<div class="col-auto lg:col-span-2">
|
||||
<AreaAdd
|
||||
name="areas"
|
||||
value={data('areas')}
|
||||
setData={setData}
|
||||
error={
|
||||
errors('areas') &&
|
||||
Helpers.clearArrayWithNullObject(errors('areas'))
|
||||
@ -130,8 +181,11 @@ export default function HouseCreate() {
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{language.ui.create}
|
||||
{id ? language.ui.update : language.ui.create}
|
||||
</button>
|
||||
<A href={Helpers.getRoutePath('location')} class="btn btn-ghost">
|
||||
{language.ui.cancel}
|
||||
</A>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -36,6 +36,15 @@ export const ROUTES = [
|
||||
filter: {},
|
||||
show: true,
|
||||
},
|
||||
{
|
||||
name: 'edit-location',
|
||||
path: '/location/edit/:id',
|
||||
components: lazy(() => import('@pages/HouseCreate')),
|
||||
filter: {
|
||||
id: /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/,
|
||||
},
|
||||
show: true,
|
||||
},
|
||||
{
|
||||
name: 'warehouse',
|
||||
path: '/warehouse',
|
||||
|
@ -31,7 +31,7 @@ export default defineConfig(({ mode }) => {
|
||||
plugins: [solid()],
|
||||
server: {
|
||||
https: false,
|
||||
host: true,
|
||||
host: false,
|
||||
port: 5001,
|
||||
strictPort: true,
|
||||
watch: {
|
||||
@ -59,6 +59,7 @@ export default defineConfig(({ mode }) => {
|
||||
// plugins: [solid(), mkcert()],
|
||||
plugins: [solid()],
|
||||
server: {
|
||||
open: true,
|
||||
https: false,
|
||||
host: true,
|
||||
port: 5001,
|
||||
|
Loading…
x
Reference in New Issue
Block a user