[as] feat: 행정지원 페이지 useSearchParamsState 적용
This commit is contained in:
@@ -1,3 +1,11 @@
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { DesignListPage } from "@/pages/design-cnstn";
|
||||
|
||||
export default DesignListPage;
|
||||
export default function Page() {
|
||||
return (
|
||||
<Suspense>
|
||||
<DesignListPage />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { FnrsListPage } from "@/pages/fnrs-cnsltn";
|
||||
|
||||
export default FnrsListPage;
|
||||
export default function Page() {
|
||||
return (
|
||||
<Suspense>
|
||||
<FnrsListPage />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { InstlListPage } from "@/pages/instl-sncn";
|
||||
|
||||
export default InstlListPage;
|
||||
export default function Page() {
|
||||
return (
|
||||
<Suspense>
|
||||
<InstlListPage />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { SewerBscPlanListPage } from "@/pages/sewer-bsc-plan";
|
||||
|
||||
export default SewerBscPlanListPage;
|
||||
export default function Page() {
|
||||
return (
|
||||
<Suspense>
|
||||
<SewerBscPlanListPage />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useQueryClient } from "@tanstack/react-query";
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { getListHref, useAlertStore, useUserStore, withListQs } from "@/shared/lib";
|
||||
import {
|
||||
type DesignCnstnApplySaveRequestDto,
|
||||
designCnstnKeys,
|
||||
@@ -103,11 +103,11 @@ function FloatingActionButtons({
|
||||
isRegister && actionButtons.some((button) => button === DESIGN_CNSTN_VIEW_BUTTON_ACTION.strg);
|
||||
|
||||
function handleGoToList() {
|
||||
router.push("/design-cnstn");
|
||||
router.push(getListHref("/design-cnstn"));
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
router.push(`/design-cnstn/${designCnstnDocNo}/edit`);
|
||||
router.push(withListQs(`/design-cnstn/${designCnstnDocNo}/edit`));
|
||||
}
|
||||
|
||||
function shouldShowButton(action: DesignCnstnViewButtonAction) {
|
||||
@@ -177,7 +177,7 @@ function FloatingActionButtons({
|
||||
action: DESIGN_CNSTN_VIEW_BUTTON_ACTION.del,
|
||||
successMessage: "삭제되었습니다.",
|
||||
});
|
||||
router.push("/design-cnstn");
|
||||
router.push(getListHref("/design-cnstn"));
|
||||
}
|
||||
|
||||
function getReviewOpinionFormData() {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { getListHref, useAlertStore, useUserStore, withListQs } from "@/shared/lib";
|
||||
import { RhfDevTool, SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui";
|
||||
import {
|
||||
designCnstnKeys,
|
||||
@@ -91,7 +91,7 @@ function DesignFormPage({ mode, initialData, createDefaults }: DesignFormPagePro
|
||||
|
||||
alert("저장되었습니다.");
|
||||
queryClient.invalidateQueries({ queryKey: designCnstnKeys.all });
|
||||
router.push(`/design-cnstn/${designCnstnDocNo}/edit`);
|
||||
router.push(withListQs(`/design-cnstn/${designCnstnDocNo}/edit`));
|
||||
} catch (error) {
|
||||
console.error("저장 실패:", error);
|
||||
alert("저장 중 오류가 발생했습니다.");
|
||||
@@ -115,7 +115,7 @@ function DesignFormPage({ mode, initialData, createDefaults }: DesignFormPagePro
|
||||
|
||||
alert("신청 완료되었습니다.");
|
||||
queryClient.invalidateQueries({ queryKey: designCnstnKeys.all });
|
||||
router.push(`/design-cnstn/${designCnstnDocNo}`);
|
||||
router.push(withListQs(`/design-cnstn/${designCnstnDocNo}`));
|
||||
} catch (error) {
|
||||
console.error("신청 실패:", error);
|
||||
alert("신청 중 오류가 발생했습니다.");
|
||||
@@ -144,7 +144,7 @@ function DesignFormPage({ mode, initialData, createDefaults }: DesignFormPagePro
|
||||
await designCnstnMngService.delete(docNo);
|
||||
alert("삭제되었습니다.");
|
||||
queryClient.invalidateQueries({ queryKey: designCnstnKeys.list() });
|
||||
router.push("/design-cnstn");
|
||||
router.push(getListHref("/design-cnstn"));
|
||||
} catch (error) {
|
||||
console.error("삭제 실패:", error);
|
||||
alert("삭제 중 오류가 발생했습니다.");
|
||||
@@ -184,7 +184,7 @@ function DesignFormPage({ mode, initialData, createDefaults }: DesignFormPagePro
|
||||
<Button
|
||||
color="white"
|
||||
type="button"
|
||||
onClick={() => router.push("/design-cnstn")}
|
||||
onClick={() => router.push(getListHref("/design-cnstn"))}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
목록
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import type { PaginationState } from "@tanstack/react-table";
|
||||
|
||||
import { useSearchParamsState } from "@/shared/lib";
|
||||
import {
|
||||
type CmCdAdstDstrComboResponseDto,
|
||||
useFindGugunCombo,
|
||||
@@ -28,47 +29,61 @@ const DEFAULT_PAGE_INDEX = 0;
|
||||
const DEFAULT_PAGE_SIZE = 10;
|
||||
const DEFAULT_SORT = "designCnstnDocNo,desc";
|
||||
const EMPTY_ADST_LIST: CmCdAdstDstrComboResponseDto[] = [];
|
||||
|
||||
const DEFAULT_SEARCH_PARAMS: DesignCnstnMngSearchDto = {
|
||||
bizNm: "",
|
||||
taskSeComCd: "",
|
||||
admdstCd: "",
|
||||
page: DEFAULT_PAGE_INDEX,
|
||||
size: DEFAULT_PAGE_SIZE,
|
||||
sort: DEFAULT_SORT,
|
||||
};
|
||||
|
||||
const ALL_SELECT_VALUE = "__ALL__";
|
||||
|
||||
const DEFAULT_PARAMS = {
|
||||
sidoComCd: "",
|
||||
sggComCd: "",
|
||||
taskSeComCd: "",
|
||||
bizNm: "",
|
||||
page: DEFAULT_PAGE_INDEX,
|
||||
size: DEFAULT_PAGE_SIZE,
|
||||
};
|
||||
|
||||
function DesignListPage() {
|
||||
const [bizNm, setBizNm] = useState("");
|
||||
const [taskSeComCd, setTaskSeComCd] = useState("");
|
||||
const [sidoComCd, setSidoComCd] = useState("");
|
||||
const [sggComCd, setSggComCd] = useState("");
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: DEFAULT_PAGE_INDEX,
|
||||
pageSize: DEFAULT_PAGE_SIZE,
|
||||
});
|
||||
const [searchParams, setSearchParams] = useState<DesignCnstnMngSearchDto>(DEFAULT_SEARCH_PARAMS);
|
||||
const { params, setParams } = useSearchParamsState(DEFAULT_PARAMS);
|
||||
const [bizNm, setBizNm] = useState(params.bizNm);
|
||||
const [taskSeComCd, setTaskSeComCd] = useState(params.taskSeComCd);
|
||||
const [sidoComCd, setSidoComCd] = useState(params.sidoComCd);
|
||||
const [sggComCd, setSggComCd] = useState(params.sggComCd);
|
||||
|
||||
const { data: sidoData } = useFindSidoCombo();
|
||||
const { data: gugunData } = useFindGugunCombo(sidoComCd);
|
||||
const sidoOptions = sidoData ?? EMPTY_ADST_LIST;
|
||||
const gugunOptions = gugunData ?? EMPTY_ADST_LIST;
|
||||
|
||||
const pagination: PaginationState = {
|
||||
pageIndex: params.page,
|
||||
pageSize: params.size,
|
||||
};
|
||||
|
||||
const searchParams: DesignCnstnMngSearchDto = useMemo(
|
||||
() => ({
|
||||
bizNm: params.bizNm,
|
||||
taskSeComCd: params.taskSeComCd,
|
||||
admdstCd: params.sggComCd || params.sidoComCd || "",
|
||||
page: params.page,
|
||||
size: params.size,
|
||||
sort: DEFAULT_SORT,
|
||||
}),
|
||||
[params],
|
||||
);
|
||||
|
||||
const handleSearch = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
setPagination((prev) => ({ ...prev, pageIndex: DEFAULT_PAGE_INDEX }));
|
||||
setSearchParams({
|
||||
...DEFAULT_SEARCH_PARAMS,
|
||||
setParams({
|
||||
bizNm,
|
||||
taskSeComCd,
|
||||
admdstCd: sggComCd || sidoComCd || "",
|
||||
sidoComCd,
|
||||
sggComCd,
|
||||
page: DEFAULT_PAGE_INDEX,
|
||||
size: pagination.pageSize,
|
||||
});
|
||||
};
|
||||
|
||||
const handlePaginationChange = (next: PaginationState) => {
|
||||
setParams({ page: next.pageIndex, size: next.pageSize });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4 px-[1.8rem] pb-6">
|
||||
<section className="card card-border mx-0 bg-background">
|
||||
@@ -143,7 +158,7 @@ function DesignListPage() {
|
||||
<DesignList
|
||||
searchParams={searchParams}
|
||||
pagination={pagination}
|
||||
onPaginationChange={setPagination}
|
||||
onPaginationChange={handlePaginationChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useRouter } from "next/navigation";
|
||||
|
||||
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
|
||||
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
import { useDataTable, withListQs } from "@/shared/lib";
|
||||
import {
|
||||
type DesignCnstnMngResponseDto,
|
||||
type DesignCnstnMngSearchDto,
|
||||
@@ -43,7 +43,7 @@ function DesignList({ searchParams, pagination, onPaginationChange }: DesignList
|
||||
const columns = useMemo(() => createDesignCnstnColumns(), []);
|
||||
|
||||
const handleRowClick = (row: DesignCnstnMngResponseDto) => {
|
||||
router.push(`/design-cnstn/${row.designCnstnDocNo}`);
|
||||
router.push(withListQs(`/design-cnstn/${row.designCnstnDocNo}`));
|
||||
};
|
||||
|
||||
const { tableComponent } = useDataTable({
|
||||
|
||||
@@ -83,7 +83,9 @@ function DesignViewPage({ id }: DesignViewPageProps) {
|
||||
return (
|
||||
<div className="mt-4">
|
||||
<SectionCollapsibleGroup>
|
||||
<SectionCollapsibleToggle />
|
||||
<div className="sticky top-[6rem] z-30 bg-container px-4 py-2">
|
||||
<SectionCollapsibleToggle />
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<ProgressStatusSection
|
||||
columns={PROGRESS_STATUS_COLUMNS}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useQueryClient } from "@tanstack/react-query";
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { getListHref, useAlertStore, useUserStore, withListQs } from "@/shared/lib";
|
||||
import {
|
||||
AI_AUTO_SE,
|
||||
type AiAutoPrcsLatestResponseDto,
|
||||
@@ -219,11 +219,11 @@ function FloatingActionButtons({
|
||||
isRegister && actionButtons.some((button) => button === FNRS_CNSLTN_VIEW_BUTTON_ACTION.strg);
|
||||
|
||||
function handleGoToList() {
|
||||
router.push("/fnrs-cnsltn");
|
||||
router.push(getListHref("/fnrs-cnsltn"));
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
router.push(`/fnrs-cnsltn/${fnrsCnsltnDocNo}/edit`);
|
||||
router.push(withListQs(`/fnrs-cnsltn/${fnrsCnsltnDocNo}/edit`));
|
||||
}
|
||||
|
||||
function shouldShowButton(action: FnrsCnsltnViewButtonAction) {
|
||||
@@ -314,7 +314,7 @@ function FloatingActionButtons({
|
||||
action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.del,
|
||||
successMessage: "삭제되었습니다.",
|
||||
});
|
||||
router.push("/fnrs-cnsltn");
|
||||
router.push(getListHref("/fnrs-cnsltn"));
|
||||
}
|
||||
|
||||
async function handleEnvoSbmsnClick() {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { getListHref, useAlertStore, useUserStore, withListQs } from "@/shared/lib";
|
||||
import { RhfDevTool, SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui";
|
||||
import { useAcntgSeMngTree } from "@/entities/acntg";
|
||||
import { useEsntlAtchFileMngTree } from "@/entities/esntl";
|
||||
@@ -153,7 +153,7 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
|
||||
alert("보완 완료 처리되었습니다.");
|
||||
await queryClient.invalidateQueries({ queryKey: fnrsCnsltnKeys.all });
|
||||
router.push(`/fnrs-cnsltn/${docNo}`);
|
||||
router.push(withListQs(`/fnrs-cnsltn/${docNo}`));
|
||||
} catch (error) {
|
||||
console.error("보완 완료 실패:", error);
|
||||
alert("보완 완료 처리 중 오류가 발생했습니다.");
|
||||
@@ -172,7 +172,7 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
|
||||
alert("저장되었습니다.");
|
||||
await queryClient.invalidateQueries({ queryKey: fnrsCnsltnKeys.all });
|
||||
router.push(`/fnrs-cnsltn/${fnrsCnsltnDocNo}/edit`);
|
||||
router.push(withListQs(`/fnrs-cnsltn/${fnrsCnsltnDocNo}/edit`));
|
||||
} catch (error) {
|
||||
console.error("저장 실패:", error);
|
||||
alert("저장 중 오류가 발생했습니다.");
|
||||
@@ -206,7 +206,7 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
|
||||
alert("신청 완료되었습니다.");
|
||||
await queryClient.invalidateQueries({ queryKey: fnrsCnsltnKeys.all });
|
||||
router.push(`/fnrs-cnsltn/${fnrsCnsltnDocNo}`);
|
||||
router.push(withListQs(`/fnrs-cnsltn/${fnrsCnsltnDocNo}`));
|
||||
} catch (error) {
|
||||
console.error("신청 실패:", error);
|
||||
alert("신청 중 오류가 발생했습니다.");
|
||||
@@ -235,7 +235,7 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
await fnrsCnsltnMngService.remove(docNo);
|
||||
alert("삭제되었습니다.");
|
||||
await queryClient.invalidateQueries({ queryKey: fnrsCnsltnKeys.all });
|
||||
router.push("/fnrs-cnsltn");
|
||||
router.push(getListHref("/fnrs-cnsltn"));
|
||||
} catch (error) {
|
||||
console.error("삭제 실패:", error);
|
||||
alert("삭제 중 오류가 발생했습니다.");
|
||||
@@ -285,7 +285,7 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
<Button
|
||||
color="white"
|
||||
type="button"
|
||||
onClick={() => router.push("/fnrs-cnsltn")}
|
||||
onClick={() => router.push(getListHref("/fnrs-cnsltn"))}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
목록
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import type { PaginationState } from "@tanstack/react-table";
|
||||
|
||||
import { useSearchParamsState } from "@/shared/lib";
|
||||
import {
|
||||
type CmCdAdstDstrComboResponseDto,
|
||||
useFindGugunCombo,
|
||||
@@ -28,47 +29,61 @@ const DEFAULT_PAGE_INDEX = 0;
|
||||
const DEFAULT_PAGE_SIZE = 10;
|
||||
const DEFAULT_SORT = "fnrsCnsltnDocNo,desc";
|
||||
const EMPTY_ADST_LIST: CmCdAdstDstrComboResponseDto[] = [];
|
||||
|
||||
const DEFAULT_SEARCH_PARAMS: FnrsCnsltnMngSearchDto = {
|
||||
bizNm: "",
|
||||
taskSeComCd: "",
|
||||
admdstCd: "",
|
||||
page: DEFAULT_PAGE_INDEX,
|
||||
size: DEFAULT_PAGE_SIZE,
|
||||
sort: DEFAULT_SORT,
|
||||
};
|
||||
|
||||
const ALL_SELECT_VALUE = "__ALL__";
|
||||
|
||||
const DEFAULT_PARAMS = {
|
||||
sidoComCd: "",
|
||||
sggComCd: "",
|
||||
taskSeComCd: "",
|
||||
bizNm: "",
|
||||
page: DEFAULT_PAGE_INDEX,
|
||||
size: DEFAULT_PAGE_SIZE,
|
||||
};
|
||||
|
||||
function FnrsListPage() {
|
||||
const [bizNm, setBizNm] = useState("");
|
||||
const [taskSeComCd, setTaskSeComCd] = useState("");
|
||||
const [sidoComCd, setSidoComCd] = useState("");
|
||||
const [sggComCd, setSggComCd] = useState("");
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: DEFAULT_PAGE_INDEX,
|
||||
pageSize: DEFAULT_PAGE_SIZE,
|
||||
});
|
||||
const [searchParams, setSearchParams] = useState<FnrsCnsltnMngSearchDto>(DEFAULT_SEARCH_PARAMS);
|
||||
const { params, setParams } = useSearchParamsState(DEFAULT_PARAMS);
|
||||
const [bizNm, setBizNm] = useState(params.bizNm);
|
||||
const [taskSeComCd, setTaskSeComCd] = useState(params.taskSeComCd);
|
||||
const [sidoComCd, setSidoComCd] = useState(params.sidoComCd);
|
||||
const [sggComCd, setSggComCd] = useState(params.sggComCd);
|
||||
|
||||
const { data: sidoData } = useFindSidoCombo();
|
||||
const { data: gugunData } = useFindGugunCombo(sidoComCd);
|
||||
const sidoOptions = sidoData ?? EMPTY_ADST_LIST;
|
||||
const gugunOptions = gugunData ?? EMPTY_ADST_LIST;
|
||||
|
||||
const pagination: PaginationState = {
|
||||
pageIndex: params.page,
|
||||
pageSize: params.size,
|
||||
};
|
||||
|
||||
const searchParams: FnrsCnsltnMngSearchDto = useMemo(
|
||||
() => ({
|
||||
bizNm: params.bizNm,
|
||||
taskSeComCd: params.taskSeComCd,
|
||||
admdstCd: params.sggComCd || params.sidoComCd || "",
|
||||
page: params.page,
|
||||
size: params.size,
|
||||
sort: DEFAULT_SORT,
|
||||
}),
|
||||
[params],
|
||||
);
|
||||
|
||||
const handleSearch = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
setPagination((prev) => ({ ...prev, pageIndex: DEFAULT_PAGE_INDEX }));
|
||||
setSearchParams({
|
||||
...DEFAULT_SEARCH_PARAMS,
|
||||
setParams({
|
||||
bizNm,
|
||||
taskSeComCd,
|
||||
admdstCd: sggComCd || sidoComCd || "",
|
||||
sidoComCd,
|
||||
sggComCd,
|
||||
page: DEFAULT_PAGE_INDEX,
|
||||
size: pagination.pageSize,
|
||||
});
|
||||
};
|
||||
|
||||
const handlePaginationChange = (next: PaginationState) => {
|
||||
setParams({ page: next.pageIndex, size: next.pageSize });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4 px-[1.8rem] pb-6">
|
||||
<section className="card card-border mx-0 bg-background">
|
||||
@@ -143,7 +158,7 @@ function FnrsListPage() {
|
||||
<FnrsList
|
||||
searchParams={searchParams}
|
||||
pagination={pagination}
|
||||
onPaginationChange={setPagination}
|
||||
onPaginationChange={handlePaginationChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useRouter } from "next/navigation";
|
||||
|
||||
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
|
||||
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
import { useDataTable, withListQs } from "@/shared/lib";
|
||||
import {
|
||||
FnrsCnsltnMngResponseDto,
|
||||
type FnrsCnsltnMngSearchDto,
|
||||
@@ -43,7 +43,7 @@ function FnrsList({ searchParams, pagination, onPaginationChange }: FnrsListProp
|
||||
const columns = useMemo(() => createFnrsCnsltnColumns(), []);
|
||||
|
||||
const handleRowClick = (row: FnrsCnsltnMngResponseDto) => {
|
||||
router.push(`/fnrs-cnsltn/${row.fnrsCnsltnDocNo}`);
|
||||
router.push(withListQs(`/fnrs-cnsltn/${row.fnrsCnsltnDocNo}`));
|
||||
};
|
||||
|
||||
const { tableComponent } = useDataTable({
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useQueryClient } from "@tanstack/react-query";
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { getListHref, useAlertStore, useUserStore, withListQs } from "@/shared/lib";
|
||||
import {
|
||||
buildElaprPrgrsSttsListFromAplnRows,
|
||||
ElaprApprovalDialog,
|
||||
@@ -114,11 +114,11 @@ function FloatingActionButtons({
|
||||
isRegister && actionButtons.some((button) => button === INSTL_SNCN_VIEW_BUTTON_ACTION.strg);
|
||||
|
||||
function handleGoToList() {
|
||||
router.push("/instl-sncn");
|
||||
router.push(getListHref("/instl-sncn"));
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
router.push(`/instl-sncn/${instlSncnDocNo}/edit`);
|
||||
router.push(withListQs(`/instl-sncn/${instlSncnDocNo}/edit`));
|
||||
}
|
||||
|
||||
function shouldShowButton(action: InstlSncnViewButtonAction) {
|
||||
@@ -212,7 +212,7 @@ function FloatingActionButtons({
|
||||
action: INSTL_SNCN_VIEW_BUTTON_ACTION.del,
|
||||
successMessage: "삭제되었습니다.",
|
||||
});
|
||||
router.push("/instl-sncn");
|
||||
router.push(getListHref("/instl-sncn"));
|
||||
}
|
||||
|
||||
async function handleInstlSncnAncmntClick() {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { getListHref, useAlertStore, useUserStore, withListQs } from "@/shared/lib";
|
||||
import { RhfDevTool, SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui";
|
||||
import { useEsntlAtchFileMngTree } from "@/entities/esntl";
|
||||
import {
|
||||
@@ -110,7 +110,7 @@ function InstlFormPage({ mode, initialData, createDefaults }: InstlFormPageProps
|
||||
if (!result) return;
|
||||
|
||||
alert("보완완료 처리되었습니다.");
|
||||
router.push(`/instl-sncn/${docNo}`);
|
||||
router.push(withListQs(`/instl-sncn/${docNo}`));
|
||||
queryClient.invalidateQueries({ queryKey: instlSncnKeys.all });
|
||||
} catch (error) {
|
||||
console.error("보완완료 실패:", error);
|
||||
@@ -129,7 +129,7 @@ function InstlFormPage({ mode, initialData, createDefaults }: InstlFormPageProps
|
||||
if (!instlSncnDocNo) return;
|
||||
|
||||
alert("저장되었습니다.");
|
||||
router.push(`/instl-sncn/${instlSncnDocNo}/edit`);
|
||||
router.push(withListQs(`/instl-sncn/${instlSncnDocNo}/edit`));
|
||||
queryClient.invalidateQueries({ queryKey: instlSncnKeys.all });
|
||||
} catch (error) {
|
||||
console.error("저장 실패:", error);
|
||||
@@ -158,7 +158,7 @@ function InstlFormPage({ mode, initialData, createDefaults }: InstlFormPageProps
|
||||
if (!instlSncnDocNo) return;
|
||||
|
||||
alert("신청 완료되었습니다.");
|
||||
router.push(`/instl-sncn/${instlSncnDocNo}`);
|
||||
router.push(withListQs(`/instl-sncn/${instlSncnDocNo}`));
|
||||
queryClient.invalidateQueries({ queryKey: instlSncnKeys.all });
|
||||
} catch (error) {
|
||||
console.error("신청 실패:", error);
|
||||
@@ -184,7 +184,7 @@ function InstlFormPage({ mode, initialData, createDefaults }: InstlFormPageProps
|
||||
await instlSncnService.remove(docNo);
|
||||
alert("삭제되었습니다.");
|
||||
queryClient.invalidateQueries({ queryKey: instlSncnKeys.list() });
|
||||
router.push("/instl-sncn");
|
||||
router.push(getListHref("/instl-sncn"));
|
||||
} catch (error) {
|
||||
console.error("삭제 실패:", error);
|
||||
alert("삭제 중 오류가 발생했습니다.");
|
||||
@@ -227,7 +227,7 @@ function InstlFormPage({ mode, initialData, createDefaults }: InstlFormPageProps
|
||||
<Button
|
||||
color="white"
|
||||
type="button"
|
||||
onClick={() => router.push("/instl-sncn")}
|
||||
onClick={() => router.push(getListHref("/instl-sncn"))}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
목록
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import type { PaginationState } from "@tanstack/react-table";
|
||||
|
||||
import { useSearchParamsState } from "@/shared/lib";
|
||||
import type { CmCdAdstDstrComboResponseDto } from "@/entities/adstdstr";
|
||||
import { useFindGugunCombo, useFindSidoCombo } from "@/entities/adstdstr";
|
||||
import { CodeSelect } from "@/entities/code";
|
||||
@@ -26,44 +27,59 @@ const DEFAULT_PAGE_SIZE = 10;
|
||||
const DEFAULT_SORT = "instlSncnDocNo,desc";
|
||||
const EMPTY_ADST_LIST: CmCdAdstDstrComboResponseDto[] = [];
|
||||
|
||||
const DEFAULT_SEARCH_PARAMS: InstlSncnMngSearchParams = {
|
||||
bizNm: "",
|
||||
const DEFAULT_PARAMS = {
|
||||
sidoComCd: "",
|
||||
sggComCd: "",
|
||||
taskSeComCd: "",
|
||||
admdstCd: "",
|
||||
bizNm: "",
|
||||
page: DEFAULT_PAGE_INDEX,
|
||||
size: DEFAULT_PAGE_SIZE,
|
||||
sort: DEFAULT_SORT,
|
||||
};
|
||||
|
||||
function InstlListPage() {
|
||||
const [bizNm, setBizNm] = useState("");
|
||||
const [taskSeComCd, setTaskSeComCd] = useState("");
|
||||
const [sidoComCd, setSidoComCd] = useState("");
|
||||
const [sggComCd, setSggComCd] = useState("");
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: DEFAULT_PAGE_INDEX,
|
||||
pageSize: DEFAULT_PAGE_SIZE,
|
||||
});
|
||||
const [searchParams, setSearchParams] = useState<InstlSncnMngSearchParams>(DEFAULT_SEARCH_PARAMS);
|
||||
const { params, setParams } = useSearchParamsState(DEFAULT_PARAMS);
|
||||
const [bizNm, setBizNm] = useState(params.bizNm);
|
||||
const [taskSeComCd, setTaskSeComCd] = useState(params.taskSeComCd);
|
||||
const [sidoComCd, setSidoComCd] = useState(params.sidoComCd);
|
||||
const [sggComCd, setSggComCd] = useState(params.sggComCd);
|
||||
|
||||
const { data: sidoData } = useFindSidoCombo();
|
||||
const { data: gugunData } = useFindGugunCombo(sidoComCd);
|
||||
const sidoOptions = sidoData ?? EMPTY_ADST_LIST;
|
||||
const gugunOptions = gugunData ?? EMPTY_ADST_LIST;
|
||||
|
||||
const pagination: PaginationState = {
|
||||
pageIndex: params.page,
|
||||
pageSize: params.size,
|
||||
};
|
||||
|
||||
const searchParams: InstlSncnMngSearchParams = useMemo(
|
||||
() => ({
|
||||
bizNm: params.bizNm,
|
||||
taskSeComCd: params.taskSeComCd,
|
||||
admdstCd: params.sggComCd || params.sidoComCd || "",
|
||||
page: params.page,
|
||||
size: params.size,
|
||||
sort: DEFAULT_SORT,
|
||||
}),
|
||||
[params],
|
||||
);
|
||||
|
||||
const handleSearch = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
setPagination((prev) => ({ ...prev, pageIndex: DEFAULT_PAGE_INDEX }));
|
||||
setSearchParams({
|
||||
...DEFAULT_SEARCH_PARAMS,
|
||||
setParams({
|
||||
bizNm,
|
||||
taskSeComCd,
|
||||
admdstCd: sggComCd || sidoComCd || "",
|
||||
sidoComCd,
|
||||
sggComCd,
|
||||
page: DEFAULT_PAGE_INDEX,
|
||||
size: pagination.pageSize,
|
||||
});
|
||||
};
|
||||
|
||||
const handlePaginationChange = (next: PaginationState) => {
|
||||
setParams({ page: next.pageIndex, size: next.pageSize });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4 px-[1.8rem] pb-6">
|
||||
<section className="card card-border mx-0 bg-background">
|
||||
@@ -132,7 +148,7 @@ function InstlListPage() {
|
||||
<InstlList
|
||||
searchParams={searchParams}
|
||||
pagination={pagination}
|
||||
onPaginationChange={setPagination}
|
||||
onPaginationChange={handlePaginationChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useRouter } from "next/navigation";
|
||||
|
||||
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
|
||||
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
import { useDataTable, withListQs } from "@/shared/lib";
|
||||
import {
|
||||
type InstlSncnMngResponseDto,
|
||||
type InstlSncnMngSearchParams,
|
||||
@@ -43,7 +43,7 @@ function InstlList({ searchParams, pagination, onPaginationChange }: InstlListPr
|
||||
const columns = useMemo(() => createInstlSncnColumns(), []);
|
||||
|
||||
const handleRowClick = (row: InstlSncnMngResponseDto) => {
|
||||
router.push(`/instl-sncn/${row.instlSncnDocNo}`);
|
||||
router.push(withListQs(`/instl-sncn/${row.instlSncnDocNo}`));
|
||||
};
|
||||
|
||||
const { tableComponent } = useDataTable({
|
||||
|
||||
@@ -95,7 +95,9 @@ function InstlViewPage({ id }: InstlViewPageProps) {
|
||||
return (
|
||||
<div className="mt-4">
|
||||
<SectionCollapsibleGroup>
|
||||
<SectionCollapsibleToggle />
|
||||
<div className="sticky top-[6rem] z-30 bg-container px-4 py-2">
|
||||
<SectionCollapsibleToggle />
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<ProgressStatusSection
|
||||
columns={PROGRESS_STATUS_COLUMNS}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { type QueryClient, useQueryClient } from "@tanstack/react-query";
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { getListHref, useAlertStore, useUserStore, withListQs } from "@/shared/lib";
|
||||
import {
|
||||
buildElaprPrgrsSttsListFromAplnRows,
|
||||
ElaprApprovalDialog,
|
||||
@@ -116,11 +116,11 @@ function FloatingActionButtons({
|
||||
isRegister && actionButtons.some((button) => button === SEWER_BSC_PLAN_VIEW_BUTTON_ACTION.strg);
|
||||
|
||||
function handleGoToList() {
|
||||
router.push("/sewer-bsc-plan");
|
||||
router.push(getListHref("/sewer-bsc-plan"));
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
router.push(`/sewer-bsc-plan/${sewerBscPlanDocNo}/edit?chgCycl=${chgCycl}`);
|
||||
router.push(withListQs(`/sewer-bsc-plan/${sewerBscPlanDocNo}/edit?chgCycl=${chgCycl}`));
|
||||
}
|
||||
|
||||
function shouldShowButton(action: SewerBscPlanViewButtonAction) {
|
||||
@@ -201,7 +201,7 @@ function FloatingActionButtons({
|
||||
action: SEWER_BSC_PLAN_VIEW_BUTTON_ACTION.del,
|
||||
successMessage: "삭제되었습니다.",
|
||||
});
|
||||
router.push("/sewer-bsc-plan");
|
||||
router.push(getListHref("/sewer-bsc-plan"));
|
||||
}
|
||||
|
||||
function getReviewOpinionFormData() {
|
||||
@@ -502,7 +502,9 @@ async function handleDmndSplmnt(
|
||||
context.request,
|
||||
);
|
||||
await context.queryClient.invalidateQueries({ queryKey: sewerBscPlanKeys.all });
|
||||
context.router.replace(`/sewer-bsc-plan/${result.sewerBscPlanDocNo}?chgCycl=${result.chgCycl}`);
|
||||
context.router.replace(
|
||||
withListQs(`/sewer-bsc-plan/${result.sewerBscPlanDocNo}?chgCycl=${result.chgCycl}`),
|
||||
);
|
||||
context.onSuccess?.();
|
||||
} catch (error) {
|
||||
context.onError?.(error as Error);
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { useAlertStore, useUserStore } from "@/shared/lib";
|
||||
import { getListHref, useAlertStore, useUserStore, withListQs } from "@/shared/lib";
|
||||
import { RhfDevTool, SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui";
|
||||
import { useAcntgSeMngTree } from "@/entities/acntg";
|
||||
import { useEsntlAtchFileMngTree } from "@/entities/esntl";
|
||||
@@ -123,7 +123,9 @@ function SewerBscPlanFormPage({
|
||||
if (!result?.sewerBscPlanDocNo) return;
|
||||
|
||||
alert("저장되었습니다.");
|
||||
router.push(`/sewer-bsc-plan/${result.sewerBscPlanDocNo}/edit?chgCycl=${result.chgCycl}`);
|
||||
router.push(
|
||||
withListQs(`/sewer-bsc-plan/${result.sewerBscPlanDocNo}/edit?chgCycl=${result.chgCycl}`),
|
||||
);
|
||||
queryClient.invalidateQueries({ queryKey: sewerBscPlanKeys.all });
|
||||
} catch (error) {
|
||||
console.error("저장 실패:", error);
|
||||
@@ -162,7 +164,11 @@ function SewerBscPlanFormPage({
|
||||
if (!result?.sewerBscPlanDocNo) return;
|
||||
|
||||
alert("신청 완료되었습니다.");
|
||||
router.push(`/sewer-bsc-plan/${result.sewerBscPlanDocNo}?chgCycl=${result.chgCycl ?? 1}`);
|
||||
router.push(
|
||||
withListQs(
|
||||
`/sewer-bsc-plan/${result.sewerBscPlanDocNo}?chgCycl=${result.chgCycl ?? 1}`,
|
||||
),
|
||||
);
|
||||
queryClient.invalidateQueries({ queryKey: sewerBscPlanKeys.all });
|
||||
} catch (error) {
|
||||
console.error("신청 실패:", error);
|
||||
@@ -199,7 +205,7 @@ function SewerBscPlanFormPage({
|
||||
|
||||
alert("보완완료 처리되었습니다.");
|
||||
const chgCycl = data.sewerBscPlanDtl.chgCycl ?? initialData?.detDto?.chgCycl ?? 1;
|
||||
router.push(`/sewer-bsc-plan/${docNo}?chgCycl=${chgCycl}`);
|
||||
router.push(withListQs(`/sewer-bsc-plan/${docNo}?chgCycl=${chgCycl}`));
|
||||
queryClient.invalidateQueries({ queryKey: sewerBscPlanKeys.all });
|
||||
} catch (error) {
|
||||
console.error("보완완료 실패:", error);
|
||||
@@ -228,7 +234,7 @@ function SewerBscPlanFormPage({
|
||||
setIsSubmitting(true);
|
||||
await sewerBscPlanService.delete(docNo);
|
||||
alert("삭제되었습니다.");
|
||||
router.push("/sewer-bsc-plan");
|
||||
router.push(getListHref("/sewer-bsc-plan"));
|
||||
queryClient.invalidateQueries({ queryKey: sewerBscPlanKeys.list() });
|
||||
} catch (error) {
|
||||
console.error("삭제 실패:", error);
|
||||
@@ -274,7 +280,7 @@ function SewerBscPlanFormPage({
|
||||
<Button
|
||||
color="white"
|
||||
type="button"
|
||||
onClick={() => router.push("/sewer-bsc-plan")}
|
||||
onClick={() => router.push(getListHref("/sewer-bsc-plan"))}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
목록
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import type { PaginationState } from "@tanstack/react-table";
|
||||
|
||||
import { useSearchParamsState } from "@/shared/lib";
|
||||
import {
|
||||
type CmCdAdstDstrComboResponseDto,
|
||||
useFindGugunCombo,
|
||||
@@ -28,51 +29,65 @@ const DEFAULT_PAGE_INDEX = 0;
|
||||
const DEFAULT_PAGE_SIZE = 10;
|
||||
const DEFAULT_SORT = "sewerBscPlanDocNo,desc";
|
||||
const EMPTY_ADST_LIST: CmCdAdstDstrComboResponseDto[] = [];
|
||||
|
||||
const DEFAULT_SEARCH_PARAMS: SewerBscPlanSearchRequestDto = {
|
||||
sewerBscPlanNm: "",
|
||||
sewerBscPlanSeComCd: "",
|
||||
taskSeComCd: "",
|
||||
admdstCd: "",
|
||||
page: DEFAULT_PAGE_INDEX,
|
||||
size: DEFAULT_PAGE_SIZE,
|
||||
sort: DEFAULT_SORT,
|
||||
};
|
||||
|
||||
const ALL_SELECT_VALUE = "__ALL__";
|
||||
|
||||
const DEFAULT_PARAMS = {
|
||||
sidoComCd: "",
|
||||
sggComCd: "",
|
||||
sewerBscPlanSeComCd: "",
|
||||
taskSeComCd: "",
|
||||
sewerBscPlanNm: "",
|
||||
page: DEFAULT_PAGE_INDEX,
|
||||
size: DEFAULT_PAGE_SIZE,
|
||||
};
|
||||
|
||||
function SewerBscPlanListPage() {
|
||||
const [sewerBscPlanNm, setSewerBscPlanNm] = useState("");
|
||||
const [sewerBscPlanSeComCd, setSewerBscPlanSeComCd] = useState("");
|
||||
const [taskSeComCd, setTaskSeComCd] = useState("");
|
||||
const [sidoComCd, setSidoComCd] = useState("");
|
||||
const [sggComCd, setSggComCd] = useState("");
|
||||
const [pagination, setPagination] = useState<PaginationState>({
|
||||
pageIndex: DEFAULT_PAGE_INDEX,
|
||||
pageSize: DEFAULT_PAGE_SIZE,
|
||||
});
|
||||
const [searchParams, setSearchParams] =
|
||||
useState<SewerBscPlanSearchRequestDto>(DEFAULT_SEARCH_PARAMS);
|
||||
const { params, setParams } = useSearchParamsState(DEFAULT_PARAMS);
|
||||
const [sewerBscPlanNm, setSewerBscPlanNm] = useState(params.sewerBscPlanNm);
|
||||
const [sewerBscPlanSeComCd, setSewerBscPlanSeComCd] = useState(params.sewerBscPlanSeComCd);
|
||||
const [taskSeComCd, setTaskSeComCd] = useState(params.taskSeComCd);
|
||||
const [sidoComCd, setSidoComCd] = useState(params.sidoComCd);
|
||||
const [sggComCd, setSggComCd] = useState(params.sggComCd);
|
||||
|
||||
const { data: sidoData } = useFindSidoCombo();
|
||||
const { data: gugunData } = useFindGugunCombo(sidoComCd);
|
||||
const sidoOptions = sidoData ?? EMPTY_ADST_LIST;
|
||||
const gugunOptions = gugunData ?? EMPTY_ADST_LIST;
|
||||
|
||||
const pagination: PaginationState = {
|
||||
pageIndex: params.page,
|
||||
pageSize: params.size,
|
||||
};
|
||||
|
||||
const searchParams: SewerBscPlanSearchRequestDto = useMemo(
|
||||
() => ({
|
||||
sewerBscPlanNm: params.sewerBscPlanNm,
|
||||
sewerBscPlanSeComCd: params.sewerBscPlanSeComCd,
|
||||
taskSeComCd: params.taskSeComCd,
|
||||
admdstCd: params.sggComCd || params.sidoComCd || "",
|
||||
page: params.page,
|
||||
size: params.size,
|
||||
sort: DEFAULT_SORT,
|
||||
}),
|
||||
[params],
|
||||
);
|
||||
|
||||
const handleSearch = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
setPagination((prev) => ({ ...prev, pageIndex: DEFAULT_PAGE_INDEX }));
|
||||
setSearchParams({
|
||||
...DEFAULT_SEARCH_PARAMS,
|
||||
setParams({
|
||||
sewerBscPlanNm,
|
||||
sewerBscPlanSeComCd,
|
||||
taskSeComCd,
|
||||
admdstCd: sggComCd || sidoComCd || "",
|
||||
sidoComCd,
|
||||
sggComCd,
|
||||
page: DEFAULT_PAGE_INDEX,
|
||||
size: pagination.pageSize,
|
||||
});
|
||||
};
|
||||
|
||||
const handlePaginationChange = (next: PaginationState) => {
|
||||
setParams({ page: next.pageIndex, size: next.pageSize });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4 px-[1.8rem] pb-6">
|
||||
<section className="card card-border mx-0 bg-background">
|
||||
@@ -163,7 +178,7 @@ function SewerBscPlanListPage() {
|
||||
<SewerBscPlanList
|
||||
searchParams={searchParams}
|
||||
pagination={pagination}
|
||||
onPaginationChange={setPagination}
|
||||
onPaginationChange={handlePaginationChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useRouter } from "next/navigation";
|
||||
|
||||
import { getCoreRowModel, type PaginationState } from "@tanstack/react-table";
|
||||
|
||||
import { useDataTable } from "@/shared/lib";
|
||||
import { useDataTable, withListQs } from "@/shared/lib";
|
||||
import {
|
||||
type SewerBscPlanResponseDto,
|
||||
type SewerBscPlanSearchRequestDto,
|
||||
@@ -47,7 +47,7 @@ function SewerBscPlanList({ searchParams, pagination, onPaginationChange }: Sewe
|
||||
const columns = useMemo(() => createSewerBscPlanColumns(), []);
|
||||
|
||||
const handleRowClick = (row: SewerBscPlanResponseDto) => {
|
||||
router.push(`/sewer-bsc-plan/${row.sewerBscPlanDocNo}?chgCycl=${row.chgCycl}`);
|
||||
router.push(withListQs(`/sewer-bsc-plan/${row.sewerBscPlanDocNo}?chgCycl=${row.chgCycl}`));
|
||||
};
|
||||
|
||||
const { tableComponent } = useDataTable({
|
||||
|
||||
@@ -102,7 +102,9 @@ function SewerBscPlanViewPage({ id, propChgCycl }: SewerBscPlanViewPageProps) {
|
||||
return (
|
||||
<div className="mt-4">
|
||||
<SectionCollapsibleGroup>
|
||||
<SectionCollapsibleToggle />
|
||||
<div className="sticky top-[6rem] z-30 bg-container px-4 py-2">
|
||||
<SectionCollapsibleToggle />
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<ProgressStatusSection
|
||||
columns={PROGRESS_STATUS_COLUMNS}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
export { getCookie, removeCookie, setCookie } from "./cookie";
|
||||
export { dayjs } from "./dayjs";
|
||||
export { getListHref, withListQs } from "./list-return-href";
|
||||
export { matchQueryStatus } from "./match-query-status";
|
||||
export { queryClient } from "./query-client";
|
||||
export { useAlertStore } from "./use-alert-store";
|
||||
export { useDataTable } from "./use-data-table";
|
||||
export { useMenuStore } from "./use-menu-store";
|
||||
export { useMutationWrapper } from "./use-mutation-wrapper";
|
||||
export { useSearchParamsState } from "./use-search-params-state";
|
||||
export { getDefaultFileKey, useTus } from "./use-tus";
|
||||
export { useUserStore } from "./use-user-store";
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 목록 조회 조건을 sessionStorage에 저장하고, 목록 복귀 시 복원하기 위한 헬퍼.
|
||||
*
|
||||
* - 목록 → 상세: 현재 목록 쿼리를 `list-qs-{listPath}` 키로 저장
|
||||
* - 상세 → 수정·저장 후 이동: storage를 건드리지 않음 (같은 탭에서 유지)
|
||||
* - 목록/삭제 후 이동: storage에서 읽어 `/listPath?bizNm=...` 로 복원
|
||||
*/
|
||||
|
||||
function toListSlug(listPath: string) {
|
||||
return listPath.replace(/^\/+|\/+$/g, "").replaceAll("/", "-");
|
||||
}
|
||||
|
||||
function storageKey(listPath: string) {
|
||||
return `list-qs-${toListSlug(listPath)}`;
|
||||
}
|
||||
|
||||
/** 상세/수정 href에서 목록 slug를 추출한다. */
|
||||
function inferListPath(href: string) {
|
||||
const path = href.split("?")[0] ?? "";
|
||||
const segments = path.split("/").filter(Boolean);
|
||||
return segments[0] ?? "";
|
||||
}
|
||||
|
||||
function isOnListPage(slug: string) {
|
||||
if (typeof window === "undefined" || !slug) return false;
|
||||
const { pathname } = window.location;
|
||||
return pathname === `/${slug}` || pathname === `/${slug}/`;
|
||||
}
|
||||
|
||||
/** 목록에 있을 때 현재 쿼리를 저장하고, href는 그대로 반환한다. */
|
||||
export function withListQs(href: string) {
|
||||
if (typeof window === "undefined") return href;
|
||||
|
||||
const slug = inferListPath(href);
|
||||
if (!isOnListPage(slug)) return href;
|
||||
|
||||
const search = window.location.search.replace(/^\?/, "");
|
||||
if (search) sessionStorage.setItem(storageKey(slug), search);
|
||||
else sessionStorage.removeItem(storageKey(slug));
|
||||
|
||||
return href;
|
||||
}
|
||||
|
||||
/** sessionStorage에 저장된 목록 쿼리로 href를 만든다. */
|
||||
export function getListHref(listPath: string) {
|
||||
if (typeof window === "undefined") return listPath;
|
||||
|
||||
const listQs = sessionStorage.getItem(storageKey(listPath));
|
||||
return listQs ? `${listPath}?${listQs}` : listPath;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useMemo, useRef } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
|
||||
/**
|
||||
* 조회 조건을 URL searchParams와 동기화하는 훅.
|
||||
*
|
||||
* - URL이 단일 소스 오브 트루스(SSOT)이며, 새로고침·뒤로가기 시에도 조건이 유지된다.
|
||||
* - 기본값과 동일한 값은 URL에서 제거해 쿼리스트링을 깨끗하게 유지한다.
|
||||
* - `window.history.replaceState` 기반 shallow 갱신으로 서버 라운드트립 없이 동작한다.
|
||||
* - 숫자 기본값이 주어진 키는 자동으로 `Number` 파싱한다.
|
||||
*
|
||||
* @example
|
||||
* const { params, setParams, resetParams } = useSearchParamsState({
|
||||
* keyword: "",
|
||||
* page: 0,
|
||||
* size: 10,
|
||||
* });
|
||||
*/
|
||||
export function useSearchParamsState<T extends Record<string, ParamValue>>(
|
||||
defaults: T,
|
||||
): UseSearchParamsStateReturn<T> {
|
||||
const searchParams = useSearchParams();
|
||||
const defaultsRef = useRef(defaults);
|
||||
|
||||
const params = useMemo(
|
||||
() => parseParams(searchParams, defaultsRef.current),
|
||||
[searchParams],
|
||||
);
|
||||
|
||||
const setParams = useCallback((partial: Partial<T>) => {
|
||||
const next = new URLSearchParams(window.location.search);
|
||||
for (const [key, value] of Object.entries(partial)) {
|
||||
if (value === undefined || value === defaultsRef.current[key]) {
|
||||
next.delete(key);
|
||||
} else {
|
||||
next.set(key, String(value));
|
||||
}
|
||||
}
|
||||
replaceUrl(next);
|
||||
}, []);
|
||||
|
||||
const resetParams = useCallback(() => {
|
||||
const next = new URLSearchParams(window.location.search);
|
||||
for (const key of Object.keys(defaultsRef.current)) {
|
||||
next.delete(key);
|
||||
}
|
||||
replaceUrl(next);
|
||||
}, []);
|
||||
|
||||
return { params, setParams, resetParams };
|
||||
}
|
||||
|
||||
function parseParams<T extends Record<string, ParamValue>>(
|
||||
searchParams: ReturnType<typeof useSearchParams>,
|
||||
defaults: T,
|
||||
): T {
|
||||
const result = { ...defaults };
|
||||
|
||||
for (const key of Object.keys(defaults) as (keyof T)[]) {
|
||||
const raw = searchParams?.get(key as string) ?? null;
|
||||
if (raw === null) continue;
|
||||
|
||||
if (typeof defaults[key] === "number") {
|
||||
const parsed = Number(raw);
|
||||
if (!Number.isNaN(parsed)) result[key] = parsed as T[keyof T];
|
||||
} else {
|
||||
result[key] = raw as T[keyof T];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function replaceUrl(searchParams: URLSearchParams) {
|
||||
const qs = searchParams.toString();
|
||||
const url = qs
|
||||
? `${window.location.pathname}?${qs}`
|
||||
: window.location.pathname;
|
||||
window.history.replaceState(null, "", url);
|
||||
}
|
||||
|
||||
type ParamValue = string | number | null;
|
||||
|
||||
interface UseSearchParamsStateReturn<T> {
|
||||
params: T;
|
||||
setParams: (partial: Partial<T>) => void;
|
||||
resetParams: () => void;
|
||||
}
|
||||
Reference in New Issue
Block a user