diff --git a/packages/common/business_components/package.json b/packages/common/business_components/package.json
index e7aae9fc5..da6f8c344 100644
--- a/packages/common/business_components/package.json
+++ b/packages/common/business_components/package.json
@@ -35,6 +35,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^3.6.0",
+ "libphonenumber-js": "^1.12.9",
"lucide-react": "^0.474.0",
"path": "^0.12.7",
"react-day-picker": "^8.10.1",
diff --git a/packages/common/business_components/src/ui/chart/BarChart.tsx b/packages/common/business_components/src/ui/chart/BarChart.tsx
index 0802a72e4..a7282830d 100644
--- a/packages/common/business_components/src/ui/chart/BarChart.tsx
+++ b/packages/common/business_components/src/ui/chart/BarChart.tsx
@@ -88,12 +88,12 @@ export default function Chart({chartConfig=undefined, chartData=[], chartTitle='
return (
<>
{chartTitle && chartTitle !== "" && chartTitle !== "undefined" ? (
-
+
{chartTitle}
): null}
-
-
-
+
+
+
}/>
{chartData && chartData.length > 0 && (
diff --git a/packages/common/business_components/src/ui/chart/DoubleBar.tsx b/packages/common/business_components/src/ui/chart/DoubleBar.tsx
index 9627485aa..b201b3bf9 100644
--- a/packages/common/business_components/src/ui/chart/DoubleBar.tsx
+++ b/packages/common/business_components/src/ui/chart/DoubleBar.tsx
@@ -7,6 +7,7 @@ import {
} from "./lib/chart";
import type { CSSProperties } from "react";
+/*
const customStyles = {
legendContainer: {
display: "flex",
@@ -17,6 +18,22 @@ const customStyles = {
marginTop: "10px",
} as CSSProperties,
};
+*/
+
+const customStyles = {
+ legendContainer: {
+ display: "flex",
+ flexWrap: "wrap",
+ justifyContent: "center",
+ alignItems: "center",
+ gap: "12px",
+ fontSize: "13px",
+ fontWeight: "normal",
+ marginTop: "16px",
+ marginBottom: "8px",
+ minHeight: "32px",
+ } as CSSProperties,
+};
type BarChartProps = {
chartConfig: ChartConfig | undefined;
@@ -60,7 +77,7 @@ const CustomTooltip = ({
};
// 커스텀 범례 (글씨 크기 키우기 + 스타일 추가)
-const CustomLegend = (props: any) => {
+/*const CustomLegend = (props: any) => {
const { payload } = props;
return (
@@ -72,6 +89,38 @@ const CustomLegend = (props: any) => {
))}
);
+};*/
+const CustomLegend = (props: any) => {
+ const { payload } = props;
+ if (!payload) return null;
+
+ return (
+
+ {payload.map((entry: any, index: number) => (
+
+ ))}
+
+ );
};
export default function DoubleBarChart({chartConfig=undefined, chartData=[], chartTitle='차트', chartClassName='w-full h-full', graphSize=40, yFieldText='', showLegend=false} : BarChartProps) {
@@ -83,7 +132,7 @@ export default function DoubleBarChart({chartConfig=undefined, chartData=[], cha
.map(key => item[key])
).flat());
};
- console.log(chartData);
+
const barMaxValue = getMaxValue(chartData, 'barVal');
return (
<>
diff --git a/packages/common/business_components/src/ui/chart/DoughnutChart.tsx b/packages/common/business_components/src/ui/chart/DoughnutChart.tsx
index 39dee3d9e..99395267a 100644
--- a/packages/common/business_components/src/ui/chart/DoughnutChart.tsx
+++ b/packages/common/business_components/src/ui/chart/DoughnutChart.tsx
@@ -48,6 +48,7 @@ type LegendProps = {
};
/** 차트 기본 스타일 */
+/*
const customStyles = {
legendContainer: {
display: "flex",
@@ -58,6 +59,23 @@ const customStyles = {
marginTop: "10px",
} as CSSProperties,
};
+*/
+
+/** 차트 기본 스타일 (라인차트와 동일하게) */
+const customStyles = {
+ legendContainer: {
+ display: "flex",
+ flexWrap: "wrap", // 여러 줄로 자동 줄바꿈
+ justifyContent: "center",
+ alignItems: "center",
+ gap: "12px", // 아이템 간 간격
+ fontSize: "13px",
+ fontWeight: "normal",
+ marginTop: "16px",
+ marginBottom: "8px",
+ minHeight: "32px", // 범례 영역 높이 통일
+ } as CSSProperties,
+};
// 커스텀 툴팁
const CustomTooltip = ({ active, payload, label }: {
@@ -82,6 +100,7 @@ const CustomTooltip = ({ active, payload, label }: {
};
// 커스텀 범례
+/*
const CustomLegend = (props: LegendProps) => {
const { payload } = props;
if (!payload) return null;
@@ -97,12 +116,52 @@ const CustomLegend = (props: LegendProps) => {
);
};
+*/
+
+const CustomLegend = (props: LegendProps) => {
+ const { payload } = props;
+ if (!payload) return null;
+
+ return (
+
+ {payload.map((entry: LegendEntry, index: number) => (
+
+
+
+ {entry.payload?.label || entry.value}
+
+
+ ))}
+
+ );
+};
export default function DoughnutChart(props: {
chartConfig: any;
chartData: ChartDataItem[] | undefined;
chartTitle: string
}) {
+ const legendData = props.chartData?.map(item => ({
+ value: item.label,
+ fill: props.chartConfig?.[item.label]?.fill || 'blue',
+ })) ?? [];
return (
@@ -130,9 +189,9 @@ export default function DoughnutChart(props: {
nameKey="label"
innerRadius={60}
/>
-
+
diff --git a/packages/common/business_components/src/ui/chart/LineChart.tsx b/packages/common/business_components/src/ui/chart/LineChart.tsx
index 4014702b6..28b4ceabf 100644
--- a/packages/common/business_components/src/ui/chart/LineChart.tsx
+++ b/packages/common/business_components/src/ui/chart/LineChart.tsx
@@ -18,6 +18,7 @@ type LineChartProps = {
}
/** 차트 기본 스타일 */
+/*
const customStyles = {
legendContainer: {
display: "flex",
@@ -28,6 +29,23 @@ const customStyles = {
marginTop: "10px",
} as CSSProperties,
};
+*/
+
+/** 차트 기본 스타일 */
+const customStyles = {
+ legendContainer: {
+ display: "flex",
+ flexWrap: "wrap",
+ justifyContent: "center",
+ alignItems: "center",
+ gap: "12px",
+ fontSize: "13px",
+ fontWeight: "normal",
+ marginTop: "16px",
+ marginBottom: "8px",
+ minHeight: "32px",
+ } as CSSProperties,
+};
type LegendEntry = {
value: string;
@@ -60,7 +78,7 @@ const CustomTooltip = ({ active, payload, label }: { active?: boolean; payload?:
};
// 커스텀 범례 (글씨 크기 키우기 + 스타일 추가)
-const CustomLegend = (props: LegendProps) => {
+/*const CustomLegend = (props: LegendProps) => {
const { payload } = props;
if (!payload) return null;
@@ -74,6 +92,40 @@ const CustomLegend = (props: LegendProps) => {
))}
);
+};*/
+
+// 커스텀 범례 (도넛차트와 동일하게)
+const CustomLegend = (props: LegendProps) => {
+ const { payload } = props;
+ if (!payload) return null;
+
+ return (
+
+ {payload.map((entry: LegendEntry, index: number) => (
+
+ ))}
+
+ );
};
type DataType = {label: string; val: string};
diff --git a/packages/common/business_components/src/ui/date_month_picker/index.tsx b/packages/common/business_components/src/ui/date_month_picker/index.tsx
index d318b561f..1b97d9ce1 100644
--- a/packages/common/business_components/src/ui/date_month_picker/index.tsx
+++ b/packages/common/business_components/src/ui/date_month_picker/index.tsx
@@ -17,6 +17,7 @@ type MonthPickerProps = {
field: { value: string; onChange: (date: string) => void }; // yyyyMM 문자열
selectClass?: string;
readOnly?: boolean;
+ startYear?: number; // 시작 연도 props 추가
};
// yyyyMM → Date 객체
@@ -37,6 +38,7 @@ export const DateMonthPicker = ({
field,
selectClass,
readOnly = false,
+ startYear,
}: MonthPickerProps) => {
const [open, setOpen] = useState(false);
@@ -75,6 +77,7 @@ export const DateMonthPicker = ({
diff --git a/packages/common/business_components/src/ui/date_month_picker/mothPicker.tsx b/packages/common/business_components/src/ui/date_month_picker/mothPicker.tsx
index acf17fba4..2b64ab439 100644
--- a/packages/common/business_components/src/ui/date_month_picker/mothPicker.tsx
+++ b/packages/common/business_components/src/ui/date_month_picker/mothPicker.tsx
@@ -28,35 +28,41 @@ export type MonthPickerProps = {
newMonth: Date | undefined,
shouldClosePopover?: boolean
) => void;
+ startYear?: number;
};
-export function MonthPicker({ currentMonth, onMonthChange }: MonthPickerProps) {
+export function MonthPicker({ currentMonth, onMonthChange, startYear }: MonthPickerProps) {
const today = startOfToday();
-const currentYear = today.getFullYear();
+ const currentYear = today.getFullYear();
const yearFromProp = format(currentMonth, "yyyy");
-const selectedYear = parseInt(yearFromProp);
+ const selectedYear = parseInt(yearFromProp);
// 현재 월의 시작 날짜
const fullYearStart = parse(yearFromProp, "yyyy", new Date());
const yearEnd =
selectedYear === currentYear ? today : endOfYear(fullYearStart);
-
-
- const yearRange = Array.from({ length: 21 }, (_, i) => currentYear - 20 + i);
+
+
+ // startYear가 없으면 기존과 동일하게 (현재년도 - 20년부터 현재년도까지)
+ // startYear가 있으면 해당 년도부터 현재년도까지, 내림차순으로 정렬
+ const yearRange = startYear
+ ? Array.from({ length: currentYear - startYear + 1 }, (_, i) => currentYear - i)
+ : Array.from({ length: 21 }, (_, i) => currentYear - 20 + i);
+
const months = eachMonthOfInterval({ start: fullYearStart, end: yearEnd });
const getMonthButtonClass = (month: Date) =>
cn(
"inline-flex h-9 w-16 items-center justify-center rounded-md p-0 text-sm font-normal ring-offset-white transition-colors hover:bg-slate-100 hover:text-slate-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 aria-selected:opacity-100 dark:ring-offset-slate-950 dark:hover:bg-slate-800 dark:hover:text-slate-50 dark:focus-visible:ring-slate-800",
isEqual(month, currentMonth) &&
- "bg-slate-900 text-slate-50 hover:bg-slate-900 hover:text-slate-50 focus:bg-slate-900 focus:text-slate-50 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-50 dark:hover:text-slate-900 dark:focus:bg-slate-50 dark:focus:text-slate-900",
+ "bg-slate-900 text-slate-50 hover:bg-slate-900 hover:text-slate-50 focus:bg-slate-900 focus:text-slate-50 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-50 dark:hover:text-slate-900 dark:focus:bg-slate-50 dark:focus:text-slate-900",
!isEqual(month, currentMonth) &&
- isEqual(month, getStartOfCurrentMonth()) &&
- "bg-slate-100 text-slate-900 dark:bg-slate-800 dark:text-slate-50"
+ isEqual(month, getStartOfCurrentMonth()) &&
+ "bg-slate-100 text-slate-900 dark:bg-slate-800 dark:text-slate-50"
);
return (
@@ -77,7 +83,7 @@ const selectedYear = parseInt(yearFromProp);
onMonthChange(newDate, false);
}}
>
-
+
@@ -112,7 +118,7 @@ const selectedYear = parseInt(yearFromProp);
onClick={() => onMonthChange(month, true)}
>
diff --git a/packages/common/business_components/src/ui/dialog_ui/index.tsx b/packages/common/business_components/src/ui/dialog_ui/index.tsx
index bbb27d327..df7fffd3a 100644
--- a/packages/common/business_components/src/ui/dialog_ui/index.tsx
+++ b/packages/common/business_components/src/ui/dialog_ui/index.tsx
@@ -1,7 +1,7 @@
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader } from "@common/components";
import { cn } from "../../lib/utils";
-type ModalType = {
+type ModalType = {
bodyClass: string;
body: React.ReactNode;
open?: boolean;
@@ -10,43 +10,45 @@ type ModalType = {
noFooterYn?: 'N' | 'Y';
headerTitle?: string;
footerContent?: React.ReactNode;
+ useDirectRender?: boolean;
}
-export function UiModal({
- bodyClass,
- body,
- open,
+export function UiModal({
+ bodyClass,
+ body,
+ open,
onOpenChange,
noHeaderYn = 'N',
noFooterYn = 'N',
headerTitle,
- footerContent
+ footerContent,
+ useDirectRender = false
}: ModalType) {
-
+
return (
<>
-
>
);
}
diff --git a/packages/common/business_components/src/ui/loadingBar/index.tsx b/packages/common/business_components/src/ui/loadingBar/index.tsx
new file mode 100644
index 000000000..37991d1d2
--- /dev/null
+++ b/packages/common/business_components/src/ui/loadingBar/index.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import { LoadingWithAnimation } from '@common/components/ui/loading';
+
+interface LoadingOverlayProps {
+ isLoading: boolean;
+}
+
+const LoadingOverlay: React.FC = ({ isLoading }) => {
+ if (!isLoading) return null;
+
+ return (
+
+ );
+};
+
+export default LoadingOverlay;
\ No newline at end of file
diff --git a/packages/common/business_components/src/ui/map/Gis.js b/packages/common/business_components/src/ui/map/Gis.js
index d0dde20d8..977f21b2b 100644
--- a/packages/common/business_components/src/ui/map/Gis.js
+++ b/packages/common/business_components/src/ui/map/Gis.js
@@ -149,7 +149,7 @@ function Gis({
projection: GisApp.TO_PROJ,
zoom: gisCore.defaults.zoom,
center: olProj.transform(
- [126.6757, 37.5585],
+ [127.4875, 37.4916],
GisApp.FROM_PROJ,
GisApp.TO_PROJ
),
diff --git a/packages/common/business_components/src/ui/map/GisTree.js b/packages/common/business_components/src/ui/map/GisTree.js
index 3c2cb2819..200e9d988 100644
--- a/packages/common/business_components/src/ui/map/GisTree.js
+++ b/packages/common/business_components/src/ui/map/GisTree.js
@@ -187,7 +187,7 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes
image.src = canvas.toDataURL('image/png');
return image;
};
-
+
if (!treeData || treeData.length === 0) return null;
return (
@@ -220,6 +220,7 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes
handleCheck(node.id)} className="cursor-pointer" />
{node.icon && {node.icon}}
{node.text}
+ {depth > 0 &&
{}} alt={node.text}>}
)}
sort={(a, b) => {
diff --git a/packages/common/business_components/src/ui/map/lib/gis.core.js b/packages/common/business_components/src/ui/map/lib/gis.core.js
index 72a584970..7488e4b72 100644
--- a/packages/common/business_components/src/ui/map/lib/gis.core.js
+++ b/packages/common/business_components/src/ui/map/lib/gis.core.js
@@ -92,7 +92,7 @@ GisApp.core.prototype = {
target: '',
centerX: 37.2806094,
centerY: 127.0032502,
- zoom: 12,
+ zoom: 15,
minZoom: 6,
maxZoom: 20,
},
diff --git a/packages/common/business_components/src/ui/sdmInsertForm/form.tsx b/packages/common/business_components/src/ui/sdmInsertForm/form.tsx
index 6ccaac946..2d7d2bf90 100644
--- a/packages/common/business_components/src/ui/sdmInsertForm/form.tsx
+++ b/packages/common/business_components/src/ui/sdmInsertForm/form.tsx
@@ -3,9 +3,9 @@ import { SelectBox } from "../select";
import { Input } from "@common/components";
import { FormUi } from "../form_ui";
import { useCallback, useEffect, useState } from "react";
+import { AsYouType } from "libphonenumber-js";
import type { ControllerRenderProps, UseFormReturn } from "react-hook-form";
import { SdmSearchForm } from "./sdmNameBox";
-import { set } from "date-fns";
// 수요처 정보
export type DemandCompType = {
@@ -79,6 +79,19 @@ export function SdmRegForm({ form, onCheckboxChange, selectSidoData, selectSigun
const [updateCoNm, setUpdateNm] = useState('')
+
+ const formatPhoneNumber = (value:string) => {
+ const input = value;
+ const formatter = new AsYouType("KR");
+ const formatted = formatter.input(input);
+
+ // 저장할 값 처리: 숫자만 추출
+ const digitsOnly = input.replace(/\D/g, "");
+
+ return formatted
+ };
+
+
const initializeSigunData = useCallback((sidoCd: string) => {
if (sidoCd === '0') {
setSigunValue('0');
@@ -325,14 +338,23 @@ export function SdmRegForm({ form, onCheckboxChange, selectSidoData, selectSigun
{
name: "telno",
label: "전화번호",
- component: (field) => (
+ component: (field) =>
+
+ {
+ const formattedValue = formatPhoneNumber(field.value??''); // 초기 렌더링 시에도 포맷 적용
+
+ return (
{
+ const value = e.target.value.replace(/\D/g, ''); // 숫자만 저장
+ field.onChange(value);
+ }}
/>
- )
+ )}
},
{
diff --git a/packages/common/business_components/src/ui/sdmInsertForm/index.tsx b/packages/common/business_components/src/ui/sdmInsertForm/index.tsx
index 13e461910..4219602e6 100644
--- a/packages/common/business_components/src/ui/sdmInsertForm/index.tsx
+++ b/packages/common/business_components/src/ui/sdmInsertForm/index.tsx
@@ -41,8 +41,8 @@ const createFormSchema = (isBznmTotNoDisabled: boolean, isDuplicate: boolean ,up
telno: z.string()
.min(8, "전화번호는 8자 이상 입력해주세요")
.max(11, "전화번호는 11자 이내로 입력해주세요")
- .regex(/^[0-9-]+$/, "전화번호는 숫자와 하이픈(-)만 입력 가능합니다"),
- rgtr: z.string().optional(),
+ .regex(/^[0-9]+$/, "전화번호는 숫자만 입력 가능합니다"),
+ rgtr: z.string().optional(),
dmdAplyPswd : updateList?.dmdInfoPk // 업데이트일경우
? z.string().optional() // 패스워드는 선택 사항이며 길이 제한 없음
: z.string().optional()
diff --git a/packages/common/business_components/src/ui/search_form_left/index.tsx b/packages/common/business_components/src/ui/search_form_left/index.tsx
index e7858df2a..d0da2cd1a 100644
--- a/packages/common/business_components/src/ui/search_form_left/index.tsx
+++ b/packages/common/business_components/src/ui/search_form_left/index.tsx
@@ -301,7 +301,7 @@ function SearchFormLeft({ isShowPart = false, children, selectSidoData = [], sel
resetAllValues();
// 리셋 후 트리거를 false로 설정
if (setRtnPartValue) {
- setRtnPartValue('0');
+ setRtnPartValue('A');
}
}
}, [resetTrigger]);
diff --git a/packages/common/business_components/src/ui/table/index-2.tsx b/packages/common/business_components/src/ui/table/index-2.tsx
index b6a9750b0..5961ea396 100644
--- a/packages/common/business_components/src/ui/table/index-2.tsx
+++ b/packages/common/business_components/src/ui/table/index-2.tsx
@@ -110,9 +110,7 @@ export function UiTable2({
? 'text-right'
: head.align === 'center'
? 'text-center'
- : typeof el[head.id] === 'number'
- ? 'text-right'
- : 'text-center'
+ : 'text-center'
}
${cellClick && idx === index ? '!font-[1000] cursor-pointer' :
cellClick ? 'cursor-pointer' : ''}
diff --git a/packages/common/business_components/src/ui/table/index.tsx b/packages/common/business_components/src/ui/table/index.tsx
index cd25bc8db..bb90153c2 100644
--- a/packages/common/business_components/src/ui/table/index.tsx
+++ b/packages/common/business_components/src/ui/table/index.tsx
@@ -82,7 +82,7 @@ export function UiTable({ tableData, headlist, pageSize, total, headName, childr
boldArray.includes(index)
?
:
{
+ onClick={(e) => {
+ e.preventDefault();
if (startPage > 1) handlePageChange(startPage - 1);
}}
/>
@@ -53,7 +54,7 @@ export default function UiPageNations(commonPageNationsProps: CommonPageNationsP
handlePageChange(pageNum)}
+ onClick={(e) => {e.preventDefault(); handlePageChange(pageNum)}}
>
{pageNum}
@@ -65,7 +66,8 @@ export default function UiPageNations(commonPageNationsProps: CommonPageNationsP
{
+ onClick={(e) => {
+ e.preventDefault();
if (endPage < totalPages) handlePageChange(endPage + 1);
}}
/>