Merge branch 'iipbs/common/main' into iipbs/sn/ii
This commit is contained in:
@@ -74,17 +74,25 @@ const CustomLegend = (props: any) => {
|
||||
);
|
||||
};
|
||||
|
||||
// chartData의 모든 값 중 최대값 구하기
|
||||
const getGlobalMax = (data: Record<string, number>[]) => {
|
||||
if (!data || data.length === 0) return 0;
|
||||
return Math.max(
|
||||
...data.flatMap(item =>
|
||||
Object.entries(item)
|
||||
.filter(([key]) => key !== 'label')
|
||||
.map(([, value]) => value as number)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default function Chart({chartConfig=undefined, chartData=[], chartTitle='차트', chartClassName='w-full h-full', graphSize=40, yFieldText='', showLegend=false} : BarChartProps) {
|
||||
const colors = ["#FF6B6B", "#4ECDC4", "#45B7D1", "#FED16A", "#FFEEAD", "#D4A5A5"];
|
||||
const getMaxValue = (data: any[], keyPrefix: string) => {
|
||||
return Math.max(...data.map(item =>
|
||||
Object.keys(item)
|
||||
.filter(key => key.includes(keyPrefix))
|
||||
.map(key => item[key])
|
||||
).flat());
|
||||
};
|
||||
|
||||
const barMaxValue = getMaxValue(chartData, 'barVal');
|
||||
const globalMax = getGlobalMax(chartData);
|
||||
const tickCount = 5;
|
||||
// interval을 globalMax / tickCount로 계산
|
||||
const interval = globalMax / tickCount;
|
||||
const ticks = Array.from({ length: tickCount + 1 }, (_, i) => Math.round(interval * i));
|
||||
return (
|
||||
<>
|
||||
{chartTitle && chartTitle !== "" && chartTitle !== "undefined" ? (
|
||||
@@ -93,8 +101,8 @@ export default function Chart({chartConfig=undefined, chartData=[], chartTitle='
|
||||
</div>): null}
|
||||
<div className="flex justify-center">
|
||||
<ChartContainer config={chartConfig ?? {}} className={chartClassName + " !w-[90%]"}>
|
||||
<BarChart accessibilityLayer data={chartData}>
|
||||
<CartesianGrid vertical={false} />
|
||||
<BarChart accessibilityLayer data={chartData} className="!h-[88%]">
|
||||
<CartesianGrid vertical={false} className="!h-[85%]" />
|
||||
<XAxis
|
||||
dataKey="label"
|
||||
tickLine={true}
|
||||
@@ -106,12 +114,12 @@ export default function Chart({chartConfig=undefined, chartData=[], chartTitle='
|
||||
<YAxis
|
||||
tickLine={true}
|
||||
tickMargin={10}
|
||||
tickCount={6}
|
||||
axisLine={true}
|
||||
fontSize={12}
|
||||
fontWeight={500}
|
||||
domain={[0, Math.ceil(barMaxValue * 1)]}
|
||||
label={yFieldText ? { value: yFieldText, className:"text-[13px] font-bold", position: 'insideLeft', angle: -90, offset: 20 } : undefined}
|
||||
domain={[0, globalMax]}
|
||||
ticks={ticks}
|
||||
label={yFieldText ? { value: yFieldText, className:"text-[15px] font-bold", position: 'insideLeft', angle: -90, offset: -30 } : undefined}
|
||||
/>
|
||||
<ChartTooltip cursor={false} content={<CustomTooltip active={undefined} payload={undefined} label={undefined} />}/>
|
||||
{chartData && chartData.length > 0 && (
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import "../date_picker/date-picker.css";
|
||||
|
||||
import { format } from "date-fns";
|
||||
import { CalendarIcon } from "lucide-react";
|
||||
import type { DayPickerSingleProps } from "react-day-picker";
|
||||
|
||||
import { cn } from "../../lib/utils";
|
||||
import { Button } from "@common/components/ui";
|
||||
import { Calendar } from "@common/components/ui";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@common/components/ui";
|
||||
|
||||
type DatePickerRangeProps = {
|
||||
placeholder?: string;
|
||||
disabled?: boolean | ((date: Date) => boolean);
|
||||
} & DayPickerSingleProps;
|
||||
|
||||
export function DatePickerRange({
|
||||
className,
|
||||
placeholder = "날짜 선택",
|
||||
disabled,
|
||||
...props
|
||||
}: DatePickerRangeProps) {
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
className={cn("date-picker-trigger", className)}
|
||||
disabled={typeof disabled === "boolean" ? disabled : false}
|
||||
>
|
||||
<CalendarIcon className="!size-[1.6rem]" />
|
||||
{props.selected ? (
|
||||
format(props.selected, "yyyy-MM-dd")
|
||||
) : (
|
||||
<span>{placeholder}</span>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0">
|
||||
<Calendar initialFocus classNames={{ cell: "rounded-md" }} {...props}
|
||||
disabled={typeof disabled === "function" ? disabled : undefined}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@@ -21,4 +21,5 @@ export * from "./table_div";
|
||||
export * from "./title";
|
||||
export * from "./sdmInsertForm";
|
||||
export * from "./popover_ui";
|
||||
export * from "./date_month_picker";
|
||||
export * from "./date_month_picker";
|
||||
export * from "./date_picker_range"
|
||||
@@ -30,6 +30,7 @@ function Gis({
|
||||
setIsOpen,
|
||||
setFeatureData,
|
||||
featureData,
|
||||
imgVisble = false,
|
||||
searchView: SearchView,
|
||||
detailView: DetailView,
|
||||
onFeatureSelect,
|
||||
@@ -46,7 +47,7 @@ function Gis({
|
||||
const gisCore = useMemo(() => new GisApp.core({ targetId: mapRef.current }), []);
|
||||
const mapInstanceRef = useRef(null);
|
||||
const checkedLayerIds = useRef([]);
|
||||
|
||||
const layerNmRef = useRef('')
|
||||
|
||||
// 팝업 데이터를 저장할 상태와 오버레이 참조를 저장할 ref 추가
|
||||
// const [facilityInfoPopupData, setFacilityInfoPopupData] = useState([]);
|
||||
@@ -84,12 +85,32 @@ function Gis({
|
||||
foundFeature = data;
|
||||
} else {
|
||||
// 아니면 featureId로 맵에서 Feature 인스턴스를 찾아서 사용
|
||||
const featureId = data.fid || data.ftrIdn || data.id;
|
||||
let featureId
|
||||
const layerName = layerNmRef.current
|
||||
if (layerName === '하수재이용시설') {
|
||||
featureId = data.searchPk;
|
||||
} else if (layerName === '빗물이용시설'|| layerName == '중수도시설') {
|
||||
featureId = data.bdstInfoPk;
|
||||
} else if (layerName === '하수처리시설') {
|
||||
featureId = data.bscFctCd;
|
||||
} else if (layerName === '온배수시설' ) {
|
||||
featureId = data.bdstRuseFcltPk;
|
||||
} else {
|
||||
console.warn('해당 레이어에 대한 featureId 매핑 없음', layerNm);
|
||||
featureId = data.fid || data.id || data.ftrIdn;
|
||||
}
|
||||
gisMap.getLayers().getArray().forEach(layer => {
|
||||
const source = layer.getSource?.();
|
||||
if (source?.getFeatureById) {
|
||||
const feature = source.getFeatureById(String(featureId).trim());
|
||||
if (feature) foundFeature = feature;
|
||||
if (source?.getFeatures) {
|
||||
const allFeatures = source.getFeatures();
|
||||
for (const f of allFeatures) {
|
||||
const id = String(f.getId());
|
||||
// console.log('id', id, featureId)
|
||||
if (id && id.split('.')[1] === String(featureId)) {
|
||||
foundFeature = f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -360,11 +381,13 @@ function Gis({
|
||||
setVisible={setTabVisible}
|
||||
onCheckedChange={handleCheckedLayersChange}
|
||||
onLayerIconClick={(nodeNm) => {
|
||||
layerNmRef.current = nodeNm
|
||||
setLayerNm(nodeNm); setIsOpen(true); const dummyFeature = {
|
||||
getProperties: () => ({}), // 최소 구조
|
||||
};
|
||||
onFeatureSelect(dummyFeature);
|
||||
}}
|
||||
imageVisible={imgVisble}
|
||||
/>
|
||||
|
||||
{/* <GisContent onFeatureSelect={handleFeatureSelect} isSearch={isSearch} isSearchModalOpen={isSearchModalOpen} setIsSearchModalOpen={setIsSearchModalOpen} /> */}
|
||||
@@ -375,6 +398,7 @@ function Gis({
|
||||
featureData={featureData || { getProperties: () => ({}) }}
|
||||
seletectedLayerNm={layerNm}
|
||||
onClose={closePopup} // 닫기 콜백 전달
|
||||
onFeatureSelect={handleFeatureSelect}
|
||||
/>
|
||||
)}
|
||||
{/* SearchView가 존재할 때만 렌더링 */}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { DndProvider } from "react-dnd";
|
||||
import { useGisContext } from './Gis';
|
||||
import GisApp from './lib/gis.core';
|
||||
|
||||
export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes = () => { }, visible, setVisible, onCheckedChange, onLayerIconClick }) {
|
||||
export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes = () => { }, visible, setVisible, onCheckedChange, onLayerIconClick,imageVisible=false }) {
|
||||
const { gisCore, treeData } = useGisContext();
|
||||
const [checked, setChecked] = useState([]);
|
||||
const [expanded, setExpanded] = useState([]);
|
||||
@@ -188,9 +188,9 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes
|
||||
image.src = canvas.toDataURL('image/png');
|
||||
return image;
|
||||
};
|
||||
const handleLayerImgClick = (nodeNm) => {
|
||||
console.log('click!', nodeNm)
|
||||
onLayerIconClick(nodeNm)
|
||||
const handleLayerImgClick = (nodeNm, nodeId) => {
|
||||
if(!checked.includes(nodeId)) alert('시설을 선택해주세요')
|
||||
else onLayerIconClick(nodeNm)
|
||||
}
|
||||
if (!treeData || treeData.length === 0) return null;
|
||||
|
||||
@@ -224,7 +224,7 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes
|
||||
<input type="checkbox" checked={checked.includes(node.id)} onChange={() => handleCheck(node.id)} className="cursor-pointer" />
|
||||
{node.icon && <span>{node.icon}</span>}
|
||||
<span className="text-[1.3rem] font-medium text-label">{node.text}</span>
|
||||
{depth > 0 && <span><img className="cursor-pointer w-5 h-5" src='/images/map/layericon/Layer_Select_BTN.png' onClick={()=>{handleLayerImgClick(node.text)}} alt={node.text}></img></span>}
|
||||
{imageVisible ? depth > 0 && <span><img className="cursor-pointer w-5 h-5" src='/images/map/layericon/Layer_Select_BTN.png' onClick={()=>{handleLayerImgClick(node.text, node.id)}} alt={node.text}></img></span> : null}
|
||||
</div>
|
||||
)}
|
||||
sort={(a, b) => {
|
||||
|
||||
@@ -234,6 +234,7 @@ export function SdmRegForm({ form, onCheckboxChange, selectSidoData, selectSigun
|
||||
}, [updateList,form]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log( 'form.formState.errors ', form.formState.errors)
|
||||
},[form.formState.errors])
|
||||
|
||||
const formFields: Array<{
|
||||
|
||||
@@ -42,12 +42,14 @@ const createFormSchema = (isBznmTotNoDisabled: boolean, isDuplicate: boolean ,up
|
||||
.min(8, "전화번호는 8자 이상 입력해주세요")
|
||||
.max(11, "전화번호는 11자 이내로 입력해주세요")
|
||||
.regex(/^[0-9]+$/, "전화번호는 숫자만 입력 가능합니다"),
|
||||
rgtr: z.string().optional(),
|
||||
rgtr: z.string().nullish(),
|
||||
dmdAplyPswd : updateList?.dmdInfoPk // 업데이트일경우
|
||||
? z.string().optional() // 패스워드는 선택 사항이며 길이 제한 없음
|
||||
: z.string().optional()
|
||||
.refine((val) => !val || (val.length >= 8 && val.length <= 16), "패스워드는 8자 이상 16자 이내로 입력해주세요"),
|
||||
dmdAplyPswdConfirm: z.string()
|
||||
dmdAplyPswdConfirm: updateList?.dmdInfoPk // 업데이트일경우
|
||||
? z.string().optional() // 패스워드는 선택 사항이며 길이 제한 없음
|
||||
:z.string()
|
||||
.min(1, "패스워드확인을 입력해주세요"),
|
||||
}).refine((data) => {
|
||||
// 패스워드 확인 검증
|
||||
|
||||
@@ -301,7 +301,7 @@ function SearchFormLeft({ isShowPart = false, children, selectSidoData = [], sel
|
||||
resetAllValues();
|
||||
// 리셋 후 트리거를 false로 설정
|
||||
if (setRtnPartValue) {
|
||||
setRtnPartValue('0');
|
||||
setRtnPartValue('A');
|
||||
}
|
||||
}
|
||||
}, [resetTrigger]);
|
||||
|
||||
@@ -2,7 +2,7 @@ import "./date-picker.css";
|
||||
|
||||
import { format } from "date-fns";
|
||||
import { CalendarIcon } from "lucide-react";
|
||||
import type { DayPicker } from "react-day-picker";
|
||||
import type { DayPickerSingleProps } from "react-day-picker";
|
||||
|
||||
import { cn } from "../../lib/utils";
|
||||
import { Button } from "../button";
|
||||
@@ -12,15 +12,12 @@ import { Popover, PopoverContent, PopoverTrigger } from "../popover";
|
||||
type DatePickerProps = {
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
selected?: Date;
|
||||
zIndex?: number; // z-index prop 추가
|
||||
} & React.ComponentProps<typeof DayPicker>;
|
||||
} & DayPickerSingleProps;
|
||||
|
||||
export function DatePicker({
|
||||
className,
|
||||
placeholder = "날짜 선택",
|
||||
disabled,
|
||||
zIndex, // zIndex prop 받기
|
||||
...props
|
||||
}: DatePickerProps) {
|
||||
return (
|
||||
@@ -39,11 +36,8 @@ export function DatePicker({
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="w-auto p-0"
|
||||
style={{ zIndex: zIndex || 'auto' }}
|
||||
>
|
||||
<Calendar classNames={{ cell: "rounded-md" }} {...props} />
|
||||
<PopoverContent className="w-auto p-0">
|
||||
<Calendar initialFocus classNames={{ cell: "rounded-md" }} {...props} />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user