+
{/* 설치년도 */}
setYearFilter({ ...yearFilter, 시작: range.start, 종료: range.end })}
+ onChange={(range) =>
+ setYearFilter({
+ ...yearFilter,
+ 시작: range.start,
+ 종료: range.end,
+ })
+ }
startLabel="설치년도"
endLabel=""
/>
{/* 경과년도 */}
-
+
{
// 숫자만 입력 허용
- const val = e.target.value.replace(/[^0-9]/g, '');
+ const val = e.target.value.replace(/[^0-9]/g, "");
setYearFilter({ ...yearFilter, 경과년도: val });
}}
/>
{/* 용도코드 */}
-
+
{
// 숫자만 입력 허용
- const val = e.target.value.replace(/[^0-9]/g, '');
+ const val = e.target.value.replace(/[^0-9]/g, "");
set변수1(val);
}}
/>
{/* 하수맨홀 구경 */}
-
+
{
// 숫자만 입력 허용
- const val = e.target.value.replace(/[^0-9]/g, '');
+ const val = e.target.value.replace(/[^0-9]/g, "");
set시작1(val);
}}
/>
- ~
+ ~
{
// 숫자만 입력 허용
- const val = e.target.value.replace(/[^0-9]/g, '');
+ const val = e.target.value.replace(/[^0-9]/g, "");
set종료1(val);
}}
/>
@@ -667,7 +809,8 @@ const SearchModal: React.FC = ({ open, onOpenChange, onFeature
)}
{/* 분기처리: 하수펌프장 */}
- {(basicType === 'tb_ms_swage_psmtn_1' || basicType === 'tb_ms_swage_psmtn_2') && (
+ {(basicType === "tb_ms_swage_psmtn_1" ||
+ basicType === "tb_ms_swage_psmtn_2") && (
{/* 설치년도 */}
@@ -693,48 +836,52 @@ const SearchModal: React.FC = ({ open, onOpenChange, onFeature
{/* 일일처리용량 - 오수맨홀 */}
-
+
{
// 숫자만 입력 허용
- const val = e.target.value.replace(/[^0-9]/g, '');
+ const val = e.target.value.replace(/[^0-9]/g, "");
set시작1(val);
}}
/>
- ~
+ ~
{
// 숫자만 입력 허용
- const val = e.target.value.replace(/[^0-9]/g, '');
+ const val = e.target.value.replace(/[^0-9]/g, "");
set종료1(val);
}}
/>
{/* 우수양수능력 - 우수맨홀 */}
-
+
{
// 숫자만 입력 허용
- const val = e.target.value.replace(/[^0-9]/g, '');
+ const val = e.target.value.replace(/[^0-9]/g, "");
set시작2(val);
}}
/>
- ~
+ ~
{
// 숫자만 입력 허용
- const val = e.target.value.replace(/[^0-9]/g, '');
+ const val = e.target.value.replace(/[^0-9]/g, "");
set종료2(val);
}}
/>
@@ -742,7 +889,7 @@ const SearchModal: React.FC = ({ open, onOpenChange, onFeature
)}
{/* 분기처리: 우수토실? 토구? */}
- {basicType === 'tb_ms_otfl' && (
+ {basicType === "tb_ms_otfl" && (
{/* 설치년도 */}
@@ -768,9 +915,11 @@ const SearchModal: React.FC = ({ open, onOpenChange, onFeature
{/* 하천명 */}
-
+
{
@@ -778,25 +927,27 @@ const SearchModal: React.FC
= ({ open, onOpenChange, onFeature
}}
/>
{/* 계획방류량 */}
-
+
{
// 숫자만 입력 허용
- const val = e.target.value.replace(/[^0-9]/g, '');
+ const val = e.target.value.replace(/[^0-9]/g, "");
set시작1(val);
}}
/>
- ~
+ ~
{
// 숫자만 입력 허용
- const val = e.target.value.replace(/[^0-9]/g, '');
+ const val = e.target.value.replace(/[^0-9]/g, "");
set종료1(val);
}}
/>
@@ -818,8 +969,11 @@ const SearchModal: React.FC = ({ open, onOpenChange, onFeature
className="w-60 mr-8"
selectClass="rounded-lg bg-white text-xl"
label="업무구분"
- selectArray={WORK_TYPES.map(w => ({ val: w.val, text: w.text }))}
- onSelectValue={val => setWorkType(String(val))}
+ selectArray={WORK_TYPES.map((w) => ({
+ val: w.val,
+ text: w.text,
+ }))}
+ onSelectValue={(val) => setWorkType(String(val))}
value={workType}
/>
@@ -827,20 +981,28 @@ const SearchModal: React.FC = ({ open, onOpenChange, onFeature
setYearFilter({ ...yearFilter, 시작: range.start, 종료: range.end })}
+ onChange={(range) =>
+ setYearFilter({
+ ...yearFilter,
+ 시작: range.start,
+ 종료: range.end,
+ })
+ }
startLabel="설치년도"
endLabel=""
/>
{/* 경과년도 */}
-
+
{
// 숫자만 입력 허용
- const val = e.target.value.replace(/[^0-9]/g, '');
+ const val = e.target.value.replace(/[^0-9]/g, "");
setYearFilter({ ...yearFilter, 경과년도: val });
}}
/>
@@ -856,7 +1018,7 @@ const SearchModal: React.FC
= ({ open, onOpenChange, onFeature
{activeTab === 'report' && (
{/* 시도, 시군구, 하수처리구역 한 줄 */}
-
+
= ({ open, onOpenChange, onFeature
-
)}
-
-
{/* 📋 결과 리스트 */}
- < div
- className="border rounded-lg space-y-2 w-full h-[60vh] overflow-auto"
- onWheel={e => {
- // 세로 스크롤이 최상단/최하단에 도달했을 때 가로 스크롤이 막히는 현상을 해결
- const container = e.currentTarget;
- const isShift = e.shiftKey;
- if (!isShift) return;
-
- // 세로 스크롤 위치
- const atTop = container.scrollTop === 0;
- const atBottom = Math.ceil(container.scrollTop + container.clientHeight) >= container.scrollHeight;
-
- // 세로 스크롤이 최상단/최하단에 도달한 경우에만 가로 스크롤 수동 처리
- if ((atTop && e.deltaY < 0) || (atBottom && e.deltaY > 0)) {
- container.scrollLeft += e.deltaY;
- e.preventDefault();
- }
- }}
- >
- {/* 선택된 탭의 label */}
- < div className="m-2 text-2xl font-bold mb-2" >
- {TABS.find(tab => tab.val === activeTab)?.text}
- {/* 조회 결과 라벨 및 건수 표시 */}
- {
+
+ {/* 테이블 영역 (가로/세로 스크롤 가능) */}
+
+
{
- // 조회 건수 변수 선언
const resultCount = tableData.length;
- // 탭별 라벨
const label =
- activeTab === 'basic'
- ? BASIC_TYPES.find(type => type.val === basicType)?.text
- : activeTab === 'operation'
- ? WORK_TYPES.find(type => type.val === workType)?.text
- : activeTab === 'report'
- ? '하수도정비기본계획보고서'
- : TABS.find(tab => tab.val === activeTab)?.text;
+ activeTab === "basic"
+ ? BASIC_TYPES.find((type) => type.val === basicType)?.text
+ : activeTab === "operation"
+ ? WORK_TYPES.find((type) => type.val === workType)?.text
+ : activeTab === "report"
+ ? "하수도정비기본계획보고서"
+ : TABS.find((tab) => tab.val === activeTab)?.text;
return (
-
- ▶ {label} 조회 결과
- ({tableCount}건)
-
+ <>
+ {label} 조회 결과 ({tableCount})건
+ >
);
})()
- }
-
-
+ } // 중복 방지
+ tableData={tableData}
+ headlist={dynamicHeadlist}
+ pageSize={tableData.length < 10 ? tableData.length : 10}
+ total={tableCount}
+ children={undefined}
+ cellClick={handleCellClick}
+ onPageChange={setCurPage}
+ resetPageNo={false}
+ cur={curPage}
+ height="51vh"
+ />
+
>
);
@@ -986,6 +1122,6 @@ const SearchModal: React.FC = ({ open, onOpenChange, onFeature
// 필요시 footerContent 등 추가
/>
);
-}
+};
export default React.memo(SearchModal);
diff --git a/packages/common/business_components/src/ui/map/GisSearch.js b/packages/common/business_components/src/ui/map/GisSearch.js
deleted file mode 100644
index c321d8efa..000000000
--- a/packages/common/business_components/src/ui/map/GisSearch.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import React from 'react';
-
-export default function GisSearch() {
- return (
-
-
범례 영역입니다 (향후 컴포넌트로 교체 가능)
-
- );
-}
\ No newline at end of file
diff --git a/packages/common/business_components/src/ui/tab/index-2.tsx b/packages/common/business_components/src/ui/tab/index-2.tsx
index c8f6e75dd..71c103835 100644
--- a/packages/common/business_components/src/ui/tab/index-2.tsx
+++ b/packages/common/business_components/src/ui/tab/index-2.tsx
@@ -18,8 +18,8 @@ const UITab2 = React.forwardRef<
UITabProps
>(({defaultValue, tabList, children}, ref)=>{
return (
-
-
+
+
{tabList.map((tab) => (
(-1);
-
const [curState, setCurState] = useState(cur ?? 1);
+
useEffect(() => {
setCurState(cur ?? 1);
}, [cur]);
@@ -40,7 +54,7 @@ export function UiTable2({ tableData, headlist, pageSize, total, children, headN
const handlePageChange = (page: number) => {
setCurState(page);
onPageChange(page);
- }
+ };
const handleCellClick = (index: number, headId: string) => {
if (cellClick) {
@@ -50,68 +64,91 @@ export function UiTable2({ tableData, headlist, pageSize, total, children, headN
};
const startIdx = (curState - 1) * pageSize;
- const endIdx = startIdx + pageSize;
return (
<>
- {headName ? (
+ {headName && (
-
{headName}
+
+
{headName}
+
- ) : null}
- 0 ? 'auto' : 'calc(100% - 2.4rem)' }} // 데이터 있을 땐 auto, 없을 땐 부모 높이 채움
- >
- {/* 테이블 또는 "데이터 없음" 메시지를 담는 컨테이너, flex-grow로 공간 차지 및 스크롤 처리 */}
-
- {(!tableData || tableData.length === 0) ? (
- // "데이터 없음" 메시지를 flex를 이용해 수직/수평 중앙 정렬
-
- 조회된 데이터가 없습니다.
-
- ) : (
-
- {/* 스크롤 관련 div 제거 */}
-
- {children ? (
- <>{children}>
- ) : (
-
- {headlist?.map((head) => (
-
- {head.title}
-
- ))}
-
- )}
-
-
- {tableData.map((el: { [key: string]: string | number | boolean | object }, index) => (
+ )}
+
+
+ {/* ✅ 외부에서 조절 가능한 높이 적용 */}
+
+
+
+ {children ? (
+ <>{children}>
+ ) : (
+
+ {headlist.map((head) => (
+
+ {head.title}
+
+ ))}
+
+ )}
+
+
+
+ {tableData.length > 0 ? (
+ tableData.map((el, index) => (
- {headlist?.map((head) => (
+ {headlist.map((head) => (
handleCellClick(startIdx + index, head.id)}
key={head.id + index}
+ className={`
+ ${head.align === 'right'
+ ? 'text-right'
+ : head.align === 'center'
+ ? 'text-center'
+ : typeof el[head.id] === 'number'
+ ? 'text-right'
+ : 'text-center'
+ }
+ ${cellClick && idx === index ? '!font-[1000] cursor-pointer' :
+ cellClick ? 'cursor-pointer' : ''}
+ `}
+ onClick={() => handleCellClick(startIdx + index, head.id)}
>
- {typeof el[head.id] === 'number' ? String(el[head.id]).replace(/\B(?=(\d{3})+(?!\d))/g, ",") : String(el[head.id] ?? '')}
+ {typeof el[head.id] === 'number'
+ ? String(el[head.id]).replace(/\B(?=(\d{3})+(?!\d))/g, ",")
+ : String(el[head.id] ?? '')}
))}
- ))}
-
-
- )}
+ ))
+ ) : (
+
+
+ {/* ✅ 높이 보장 + 가운데 정렬용 div */}
+
+ 🔍 검색 결과가 없습니다.
+
+
+
+ )}
+
+
- {/* 페이지네이션을 sticky 하단에 고정 */}
- {total > 0 && (
-
-
+
+ {/* 페이지네이션 하단 고정 */}
+ { (
+
+
)}
diff --git a/packages/common/components/src/api/user.ts b/packages/common/components/src/api/user.ts
index 78b833325..e3c7e31df 100644
--- a/packages/common/components/src/api/user.ts
+++ b/packages/common/components/src/api/user.ts
@@ -37,9 +37,9 @@ export interface IUser {
const client = getApiClient();
-export const getUserInfo = async (userUniqId: string) => {
+export const getUserInfo = async () => {
const response = await client.get
(
- `${ENDPOINT.USER_SERVICE.USERS}/${userUniqId}`,
+ `${ENDPOINT.USER_SERVICE.USERS}/info/me`,
);
return response.data;
};
diff --git a/packages/common/components/src/ui/button-group/button-group.stories.tsx b/packages/common/components/src/ui/button-group/button-group.stories.tsx
index 17e2f3664..fa52df4be 100644
--- a/packages/common/components/src/ui/button-group/button-group.stories.tsx
+++ b/packages/common/components/src/ui/button-group/button-group.stories.tsx
@@ -2,11 +2,11 @@ import "./button-group.css";
import type { Meta, StoryObj } from "@storybook/react";
-import { RadioGroup, RadioGroupItem } from "../radio-group";
+import { ButtonGroup, ButtonGroupItem } from "../button-group";
-const meta: Meta = {
+const meta: Meta = {
title: "Elements/ButtonGroup",
- component: RadioGroup,
+ component: ButtonGroup,
parameters: {
docs: {
description: {
@@ -19,49 +19,49 @@ ButtonGroup 컴포넌트는 RadioGroup 컴포넌트를 기반으로 버튼 모
};
export default meta;
-type Story = StoryObj;
+type Story = StoryObj;
export const Default: Story = {
render: () => (
-
- Default
- Comfortable
- Compact
-
+
+ Default
+ Comfortable
+ Compact
+
),
};
export const Secondary: Story = {
render: () => (
-
- Default
- Comfortable
- Compact
-
+
+ Default
+ Comfortable
+ Compact
+
),
};
export const Rounded: Story = {
render: () => (
-
- Default
- Comfortable
- Compact
-
+
+ Default
+ Comfortable
+ Compact
+
),
};
export const SecondaryRounded: Story = {
render: () => (
-
- Default
- Comfortable
- Compact
-
+ Default
+ Comfortable
+ Compact
+
),
};
diff --git a/packages/common/components/src/ui/button-group/index.tsx b/packages/common/components/src/ui/button-group/index.tsx
new file mode 100644
index 000000000..86d44b9c9
--- /dev/null
+++ b/packages/common/components/src/ui/button-group/index.tsx
@@ -0,0 +1,39 @@
+import "./button-group.css";
+
+import * as React from "react";
+import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
+
+const ButtonGroup = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ variant?: "radio" | "button";
+ color?: "primary" | "secondary";
+ shape?: "square" | "rounded";
+ }
+>(
+ (
+ { variant = "radio", color = "primary", shape = "square", ...props },
+ ref,
+ ) => {
+ return (
+
+ );
+ },
+);
+ButtonGroup.displayName = RadioGroupPrimitive.Root.displayName;
+
+const ButtonGroupItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ ...props }, ref) => {
+ return ;
+});
+ButtonGroupItem.displayName = RadioGroupPrimitive.Item.displayName;
+
+export { ButtonGroup, ButtonGroupItem };
diff --git a/packages/common/components/src/ui/data-table/index.tsx b/packages/common/components/src/ui/data-table/index.tsx
index bcb072bb1..2b9879879 100644
--- a/packages/common/components/src/ui/data-table/index.tsx
+++ b/packages/common/components/src/ui/data-table/index.tsx
@@ -16,6 +16,7 @@ import type { HTMLAttributes } from "react";
declare module "@tanstack/react-table" {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface ColumnMeta {
+ width?: number;
align?: "left" | "center" | "right";
rowSpan?: number;
}
diff --git a/packages/common/components/src/ui/date-range-picker/index.tsx b/packages/common/components/src/ui/date-range-picker/index.tsx
index 2c99347a2..3572ffb7b 100644
--- a/packages/common/components/src/ui/date-range-picker/index.tsx
+++ b/packages/common/components/src/ui/date-range-picker/index.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useState } from "react";
+import { useEffect, useState } from "react";
import { format } from "date-fns";
import { CalendarIcon } from "lucide-react";
import type { DateRange } from "react-day-picker";
@@ -11,20 +11,38 @@ import { Calendar } from "../calendar";
import { Popover, PopoverContent, PopoverTrigger } from "../popover";
type DateRangePickerProps = React.HTMLAttributes & {
+ value?: DateRange;
+ defaultValue?: DateRange;
disabled?: boolean;
- handleDateChange?: (date: DateRange | undefined) => void;
+ onValueChange?: (date: DateRange | undefined) => void;
};
export function DateRangePicker({
+ value,
+ defaultValue,
disabled,
className,
- handleDateChange,
+ children,
+ onValueChange,
}: DateRangePickerProps) {
- const [date, setDate] = useState();
+ const [dateState, setDateState] = useState(
+ defaultValue,
+ );
+
+ const isControlled = value !== undefined;
+ const dateValue = isControlled ? value : dateState;
+
+ useEffect(() => {
+ if (isControlled) {
+ setDateState(value);
+ }
+ }, [isControlled, value]);
const handleSelect = (date: DateRange | undefined) => {
- setDate(date);
- handleDateChange?.(date);
+ if (!isControlled) {
+ setDateState(date);
+ }
+ onValueChange?.(date);
};
return (
@@ -36,19 +54,19 @@ export function DateRangePicker({
variant="outline"
className={cn(
"h-[3.2rem] min-w-[12rem] justify-start border-input !bg-background px-4 py-2 text-left text-[1.3rem] font-normal text-muted-foreground hover:!bg-accent",
- !date && "text-muted-foreground",
+ !dateValue && "text-muted-foreground",
)}
disabled={disabled}
>
- {date?.from ? (
- date.to ? (
+ {dateValue?.from ? (
+ dateValue.to ? (
<>
- {format(date.from, "yyyy-MM-dd")} ~{" "}
- {format(date.to, "yyyy-MM-dd")}
+ {format(dateValue.from, "yyyy-MM-dd")} ~{" "}
+ {format(dateValue.to, "yyyy-MM-dd")}
>
) : (
- format(date.from, "yyyy-MM-dd")
+ format(dateValue.from, "yyyy-MM-dd")
)
) : (
기간 선택
@@ -56,14 +74,17 @@ export function DateRangePicker({
-
+
+ {children}
+
+
diff --git a/packages/common/components/src/ui/header/index.tsx b/packages/common/components/src/ui/header/index.tsx
index 9fb310408..285fad049 100644
--- a/packages/common/components/src/ui/header/index.tsx
+++ b/packages/common/components/src/ui/header/index.tsx
@@ -2,7 +2,7 @@ import { useQuery } from "@tanstack/react-query";
import { images } from "@common/assets";
import { getConfig } from "@common/config";
import { ENDPOINT } from "../../config";
-import { getCookie, setCookie } from "../../lib";
+import { setCookie } from "../../lib";
import { getSiteInfo, getUserInfo } from "../../api";
import {
DropdownMenu,
@@ -12,14 +12,12 @@ import {
} from "../dropdown-menu";
import { Button } from "../button";
-const { AUTH_USER_ID, SITE_ID } = getConfig();
+const { SITE_ID } = getConfig();
export function Header({ viewOnly }: { viewOnly?: boolean }) {
- const userUniqId = getCookie(AUTH_USER_ID ?? "") ?? "";
-
const { data: userInfo } = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.USERS],
- queryFn: () => getUserInfo(userUniqId),
+ queryFn: () => getUserInfo(),
enabled: !viewOnly,
});
diff --git a/packages/common/components/src/ui/index.ts b/packages/common/components/src/ui/index.ts
index 134b76d21..2e2cea547 100644
--- a/packages/common/components/src/ui/index.ts
+++ b/packages/common/components/src/ui/index.ts
@@ -1,6 +1,7 @@
export * from "./accordion";
export * from "./badge";
export * from "./button";
+export * from "./button-group";
export * from "./calendar";
export * from "./carousel";
export * from "./checkbox";
diff --git a/packages/common/components/src/ui/input/index.tsx b/packages/common/components/src/ui/input/index.tsx
index 11cbca71d..1922ca859 100644
--- a/packages/common/components/src/ui/input/index.tsx
+++ b/packages/common/components/src/ui/input/index.tsx
@@ -7,7 +7,7 @@ const Input = React.forwardRef>(
return (
>>>>>> origin/main
'@frontend-opensource/use-react-hooks':
specifier: ^1.1.4
version: 1.1.4
@@ -172,6 +187,9 @@ importers:
'@minoru/react-dnd-treeview':
specifier: 3.5.1-alpha.1
version: 3.5.1-alpha.1(dnd-core@16.0.1)(react-dnd@16.0.1)(react-dom@18.3.1)(react@18.3.1)
+ '@radix-ui/react-icons':
+ specifier: ^1.3.2
+ version: 1.3.2(react@18.3.1)
'@radix-ui/react-label':
specifier: ^2.1.2
version: 2.1.2(@types/react-dom@18.3.5)(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1)
@@ -1850,6 +1868,10 @@ packages:
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
dev: false
+ /@fontsource-variable/noto-sans-kr@5.2.5:
+ resolution: {integrity: sha512-dIJyVPIBRJXtvaFmQ6GKCV93J9Fx3O2Ym5C3gDRcz5zLpEigrnlEfBnsXBUv4aDBHEF/gm/Q07d40dddGxFytg==}
+ dev: false
+
/@frontend-opensource/use-react-hooks@1.1.4:
resolution: {integrity: sha512-otCSyciL5MVCNNYmdDdg2lEL6rbZ3XX1meYQCzapUSC4V6qx2YxMk1kUbhwEM/+vdxhYzBHsVBGD0rZWl5cgrw==}
dependencies:
@@ -4085,6 +4107,21 @@ packages:
resolution: {integrity: sha512-AkFmuukVejyqVIjEQoFhLb3q+xHl7JG8G9cANWTMe3s8iKzD9j1VBSYXgCjy6vm6xM8cUCR9zP2yqWxY9pTWOA==}
dev: false
+ /@tanstack/query-devtools@5.74.7:
+ resolution: {integrity: sha512-nSNlfuGdnHf4yB0S+BoNYOE1o3oAH093weAYZolIHfS2stulyA/gWfSk/9H4ZFk5mAAHb5vNqAeJOmbdcGPEQw==}
+ dev: false
+
+ /@tanstack/react-query-devtools@5.75.5(@tanstack/react-query@5.67.1)(react@18.3.1):
+ resolution: {integrity: sha512-S31U00nJOQIbxydRH1kOwdLRaLBrda8O5QjzmgkRg60UZzPGdbI6+873Qa0YGUfPeILDbR2ukgWyg7CJQPy4iA==}
+ peerDependencies:
+ '@tanstack/react-query': ^5.75.5
+ react: ^18.3.1
+ dependencies:
+ '@tanstack/query-devtools': 5.74.7
+ '@tanstack/react-query': 5.67.1(react@18.3.1)
+ react: 18.3.1
+ dev: false
+
/@tanstack/react-query@5.67.1(react@18.3.1):
resolution: {integrity: sha512-fH5u4JLwB6A+wLFdi8wWBWAYoJV5deYif2OveJ26ktAWjU499uvVFS1wPWnyEyq5LvZX1MZInvv9QRaIZANRaQ==}
peerDependencies: