Merge commit '5505791b6e3ef04480731b123512e90e9e035c43'
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
@tailwind components;
|
||||
|
||||
@layer components {
|
||||
.fileup_root {
|
||||
@apply relative rounded-lg bg-background p-2;
|
||||
}
|
||||
|
||||
.fileup_dropzone {
|
||||
@apply border border-border outline-dashed outline-1 outline-border;
|
||||
}
|
||||
|
||||
.fileup_panel {
|
||||
@apply flex w-full flex-col items-center justify-center p-1;
|
||||
}
|
||||
|
||||
.fileup_icon {
|
||||
@apply h-4 w-5 text-label;
|
||||
}
|
||||
|
||||
.fileup_hint {
|
||||
@apply mb-1 text-sm text-label;
|
||||
}
|
||||
|
||||
.fileup_hint_strong {
|
||||
@apply font-semibold text-foreground;
|
||||
}
|
||||
|
||||
.fileup_meta {
|
||||
@apply text-xs text-label;
|
||||
}
|
||||
|
||||
.fileup_item {
|
||||
@apply text-foreground;
|
||||
}
|
||||
|
||||
.fileup_item_active {
|
||||
@apply bg-muted;
|
||||
}
|
||||
|
||||
.fileup_item_name {
|
||||
@apply flex h-full w-4/5 items-center gap-1.5 overflow-hidden font-medium leading-normal tracking-tight;
|
||||
}
|
||||
|
||||
.fileup_item_remove {
|
||||
@apply text-label;
|
||||
}
|
||||
|
||||
.fileup_item_remove_icon {
|
||||
@apply h-4 w-4 transition-colors duration-200 ease-in-out hover:stroke-destructive;
|
||||
}
|
||||
|
||||
.fileup_input {
|
||||
@apply border;
|
||||
}
|
||||
|
||||
.fileup_input_idle {
|
||||
@apply border-border;
|
||||
}
|
||||
|
||||
.fileup_input_accept {
|
||||
@apply border-primary;
|
||||
}
|
||||
|
||||
.fileup_input_reject {
|
||||
@apply border-destructive;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
"use client";
|
||||
import "./file-upload.css";
|
||||
|
||||
import { CloudUpload, Paperclip } from "lucide-react";
|
||||
import { FileInput, FileUploader, FileUploaderContent, FileUploaderItem } from "./lib/file_upload";
|
||||
|
||||
@@ -36,19 +38,19 @@ export const FileUpload =
|
||||
onValueChange={setFiles}
|
||||
dropzoneOptions={dropZoneConfig}
|
||||
acceptName={fileTypes}
|
||||
className="relative bg-background rounded-lg p-2"
|
||||
className="fileup_root"
|
||||
>
|
||||
<FileInput
|
||||
id="fileInput"
|
||||
className="outline-dashed outline-1 outline-slate-500"
|
||||
className="fileup_dropzone"
|
||||
>
|
||||
<div className="flex items-center justify-center flex-col p-1 w-full ">
|
||||
<CloudUpload className='text-gray-500 w-5 h-4' />
|
||||
<p className="mb-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<span className="font-semibold">클릭 후 업로드 </span>
|
||||
<div className="fileup_panel">
|
||||
<CloudUpload className='fileup_icon' />
|
||||
<p className="fileup_hint">
|
||||
<span className="fileup_hint_strong">클릭 후 업로드 </span>
|
||||
혹은 파일을 드래그 해주세요
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
<p className="fileup_meta">
|
||||
{!fileTypes?"SVG, PNG, JPG, GIF":fileTypes}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -344,14 +344,14 @@ export const FileUploaderItem = forwardRef<
|
||||
ref={ref}
|
||||
className={cn(
|
||||
buttonVariants({ variant: "ghost" }),
|
||||
"h-9 justify-between cursor-pointer relative w-full",
|
||||
"fileup_item h-9 justify-between cursor-pointer relative w-full",
|
||||
className,
|
||||
isSelected ? "bg-muted" : "",
|
||||
isSelected ? "fileup_item_active" : "",
|
||||
)}
|
||||
{...props}
|
||||
|
||||
>
|
||||
<div className="font-medium leading-normal tracking-tight flex items-center gap-1.5 h-full !w-4/5 overflow-hidden "
|
||||
<div className="fileup_item_name"
|
||||
onClick={() =>{onFileClick?.(index)
|
||||
}}>
|
||||
{children}
|
||||
@@ -359,7 +359,7 @@ export const FileUploaderItem = forwardRef<
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
"absolute",
|
||||
"fileup_item_remove absolute",
|
||||
"!w-1/5 z-50",
|
||||
direction === "rtl"
|
||||
? "left-1 top-1/2 -translate-y-1/2"
|
||||
@@ -372,7 +372,7 @@ export const FileUploaderItem = forwardRef<
|
||||
removeFileFromSet(index)}}
|
||||
>
|
||||
<span className="sr-only">remove item {index}</span>
|
||||
<RemoveIcon className="w-4 h-4 hover:stroke-destructive duration-200 ease-in-out" />
|
||||
<RemoveIcon className="fileup_item_remove_icon" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
@@ -396,14 +396,12 @@ export const FileInput = forwardRef<
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
`w-full rounded-lg duration-300 ease-in-out
|
||||
${
|
||||
dropzoneState.isDragAccept
|
||||
? "border-green-500"
|
||||
: dropzoneState.isDragReject || isFileTooBig
|
||||
? "border-red-500"
|
||||
: "border-gray-300"
|
||||
}`,
|
||||
"fileup_input w-full rounded-lg duration-300 ease-in-out",
|
||||
dropzoneState.isDragAccept && "fileup_input_accept",
|
||||
(dropzoneState.isDragReject || isFileTooBig) && "fileup_input_reject",
|
||||
!dropzoneState.isDragAccept &&
|
||||
!(dropzoneState.isDragReject || isFileTooBig) &&
|
||||
"fileup_input_idle",
|
||||
className,
|
||||
)}
|
||||
{...rootProps}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
@tailwind components;
|
||||
|
||||
@layer components {
|
||||
.formui_label {
|
||||
@apply text-xl;
|
||||
}
|
||||
|
||||
.formui_option_wrap {
|
||||
@apply float-right;
|
||||
}
|
||||
|
||||
.formui_option {
|
||||
@apply flex items-center gap-2;
|
||||
}
|
||||
|
||||
.formui_option_label {
|
||||
@apply w-28 text-xl text-label;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import "./form-ui.css";
|
||||
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { FormControl, FormItem, FormLabel, FormMessage } from "../form";
|
||||
import { Button, Checkbox } from "@common/components/ui";
|
||||
import { Checkbox } from "@common/components/ui";
|
||||
|
||||
export const BZNM_TOT_NO = '종사업자등록번호';
|
||||
export const CANDIDATE = '공급처 후보지 선택(필수아님)';
|
||||
export const BZNM_TOT_NO = "종사업자등록번호";
|
||||
export const CANDIDATE = "공급처 후보지 선택(필수아님)";
|
||||
|
||||
interface FormUiProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
label?: string;
|
||||
@@ -29,17 +31,19 @@ const FormUi = React.forwardRef<HTMLDivElement, FormUiProps>(
|
||||
}, [onCheckboxChange]);
|
||||
return (
|
||||
<FormItem ref={ref}>
|
||||
{label && <FormLabel className="text-xl">{label}</FormLabel>}
|
||||
{label && <FormLabel className="formui_label">{label}</FormLabel>}
|
||||
{label === BZNM_TOT_NO && (
|
||||
<>
|
||||
<div className="float-right">
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="check"
|
||||
<div className="formui_option_wrap">
|
||||
<div className="formui_option">
|
||||
<Checkbox
|
||||
id="check"
|
||||
checked={isChecked}
|
||||
onCheckedChange={handleCheckboxChange}
|
||||
/>
|
||||
<label className="w-28 text-xl text-gray-600" htmlFor="check">해당없음</label>
|
||||
<label className="formui_option_label" htmlFor="check">
|
||||
해당없음
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
@@ -48,14 +52,14 @@ const FormUi = React.forwardRef<HTMLDivElement, FormUiProps>(
|
||||
{label === CANDIDATE && (
|
||||
<FormControl>
|
||||
{children}
|
||||
</FormControl>
|
||||
</FormControl>
|
||||
)}
|
||||
{label !== BZNM_TOT_NO && label !== CANDIDATE && (
|
||||
<FormControl>
|
||||
{React.isValidElement(children) && isDisabled
|
||||
? React.cloneElement(children, {
|
||||
? React.cloneElement(children, {
|
||||
disabled: isDisabled,
|
||||
...(children.props as FormElementProps)
|
||||
...(children.props as FormElementProps),
|
||||
})
|
||||
: children}
|
||||
</FormControl>
|
||||
@@ -66,5 +70,5 @@ const FormUi = React.forwardRef<HTMLDivElement, FormUiProps>(
|
||||
}
|
||||
);
|
||||
|
||||
FormUi.displayName = 'FormUi';
|
||||
export { FormUi };
|
||||
FormUi.displayName = "FormUi";
|
||||
export { FormUi };
|
||||
|
||||
@@ -49,12 +49,14 @@ function Gis({
|
||||
searchView: SearchView,
|
||||
detailView: DetailView,
|
||||
onFeatureSelect,
|
||||
canSelectFeature,
|
||||
setIsSearchModalOpen,
|
||||
onCheckedLayersChange,
|
||||
searchBar: SearchBar,
|
||||
waterDepth: WaterDepth = null,
|
||||
layerSettingView: LayerSettingView = null,
|
||||
inlineLayerSettingView,
|
||||
treeSectionClassName = "",
|
||||
layerIconVisible,
|
||||
isPlaying = false,
|
||||
setIsPlaying = () => { },
|
||||
@@ -73,6 +75,7 @@ function Gis({
|
||||
zoomGuide,
|
||||
showFloatingSearchButton = true,
|
||||
treeTogglePosition = "center",
|
||||
detachedTreeToggleButton = false,
|
||||
showZoomScaleInfo = false,
|
||||
}) {
|
||||
const mapRef = useRef();
|
||||
@@ -111,7 +114,7 @@ function Gis({
|
||||
(data) => {
|
||||
onFeatureSelect?.(data); // 선택된 피쳐 데이터 콜백 호출
|
||||
},
|
||||
[mapInstanceRef],
|
||||
[onFeatureSelect],
|
||||
);
|
||||
|
||||
// 지도 생성 및 초기화
|
||||
@@ -315,7 +318,9 @@ function Gis({
|
||||
onProgressChange={handleProgressChange}
|
||||
layerSettingView={LayerSettingView}
|
||||
inlineLayerSettingView={inlineLayerSettingView}
|
||||
treeSectionClassName={treeSectionClassName}
|
||||
toggleButtonPosition={treeTogglePosition}
|
||||
detachedToggleButton={detachedTreeToggleButton}
|
||||
/>
|
||||
|
||||
{typeof WaterDepth === "function" && (
|
||||
@@ -354,7 +359,7 @@ function Gis({
|
||||
<GisZoomLevel zoomGuide={zoomGuide} showZoomScaleInfo={showZoomScaleInfo} />
|
||||
)}
|
||||
<GisMapToggle />
|
||||
<GisSelect onFeatureSelect={handleFeatureSelect} />
|
||||
<GisSelect onFeatureSelect={handleFeatureSelect} canSelectFeature={canSelectFeature} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,25 +1,43 @@
|
||||
import React, { useContext, useEffect } from "react";
|
||||
import React, { useContext, useEffect, useRef } from "react";
|
||||
import { GisContext } from "./Gis";
|
||||
import GisApp from "./lib/gis.core";
|
||||
import GisOlSelect from "./lib/gis.select";
|
||||
|
||||
export default React.memo(function GisSelect({ onFeatureSelect }) {
|
||||
export default React.memo(function GisSelect({ onFeatureSelect, canSelectFeature }) {
|
||||
const { gisMap } = useContext(GisContext);
|
||||
const onFeatureSelectRef = useRef(onFeatureSelect);
|
||||
const canSelectFeatureRef = useRef(canSelectFeature);
|
||||
|
||||
useEffect(() => {
|
||||
onFeatureSelectRef.current = onFeatureSelect;
|
||||
}, [onFeatureSelect]);
|
||||
|
||||
useEffect(() => {
|
||||
canSelectFeatureRef.current = canSelectFeature;
|
||||
}, [canSelectFeature]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!gisMap) return; // 아직 초기화 안 됐으면 아무것도 안 함
|
||||
|
||||
// 이미 select가 있으면 재생성하지 않음
|
||||
const select = new GisOlSelect(gisMap);
|
||||
GisApp.Module.select = select;
|
||||
|
||||
// 피처 선택 시 콜백 등록
|
||||
select.getSelectedInfo(function (features) {
|
||||
if (features?.[0]) {
|
||||
onFeatureSelect(features[0]);
|
||||
const isSelectable = canSelectFeatureRef.current?.(features[0]);
|
||||
if (isSelectable === false) {
|
||||
select.resetSelectedFeature?.();
|
||||
return;
|
||||
}
|
||||
onFeatureSelectRef.current?.(features[0]);
|
||||
}
|
||||
});
|
||||
}, [gisMap, onFeatureSelect]);
|
||||
|
||||
return () => {
|
||||
select.removeInteration?.();
|
||||
};
|
||||
}, [gisMap]);
|
||||
|
||||
// const select = new GisOlSelect(gisMap);
|
||||
// GisApp.Module.select = select;
|
||||
|
||||
@@ -29,6 +29,7 @@ import GisApp from "./lib/gis.core";
|
||||
* - onProgressChange(total, current): 애니메이션 진행상태 콜백
|
||||
* - layerSettingView: (선택) 좌측 레이어 트리 하단에 표시할 설정 영역
|
||||
* - layerIconVisible: (선택) 레이어 행 우측 액션 아이콘 노출 여부를 결정하는 함수
|
||||
* - detachedToggleButton: (선택) 접기/펼치기 버튼을 패널 밖 고정 핸들로 노출할지 여부
|
||||
*/
|
||||
export default React.memo(function GisTree({
|
||||
checkedNodes = [],
|
||||
@@ -46,7 +47,10 @@ export default React.memo(function GisTree({
|
||||
layerSettingView: LayerSettingView = null,
|
||||
layerIconVisible,
|
||||
inlineLayerSettingView,
|
||||
treeSectionClassName = "",
|
||||
toggleButtonPosition = "center",
|
||||
showLayerPanelTitle = true,
|
||||
detachedToggleButton = false,
|
||||
}) {
|
||||
const { gisCore, treeData } = useGisContext();
|
||||
|
||||
@@ -442,10 +446,11 @@ export default React.memo(function GisTree({
|
||||
* 레이어 정보로부터 범례 이미지 경로 또는 동적 생성 이미지를 반환합니다.
|
||||
*/
|
||||
const makeLegendSrc = (layerinfo) => {
|
||||
if (!layerinfo.gobjTy) return null;
|
||||
if (layerinfo.iconNm) {
|
||||
return GisApp.CONTEXT_LAYER_PATH + layerinfo.iconNm;
|
||||
} else {
|
||||
}
|
||||
if (!layerinfo.gobjTy) return null;
|
||||
else {
|
||||
return makeLegendImage(layerinfo).src;
|
||||
}
|
||||
};
|
||||
@@ -512,7 +517,10 @@ export default React.memo(function GisTree({
|
||||
if (!treeData || treeData.length === 0) return null;
|
||||
|
||||
const toggleButtonPositionClass =
|
||||
toggleButtonPosition === "top" ? "top-[5.5rem]" : "top-1/2 -translate-y-1/2";
|
||||
toggleButtonPosition === "top" ? "top-4" : "top-1/2 -translate-y-1/2";
|
||||
const thinScrollbarClass =
|
||||
"[scrollbar-width:thin] [&::-webkit-scrollbar]:w-[4px]";
|
||||
const treeSectionClassNames = [treeSectionClassName, thinScrollbarClass].filter(Boolean).join(" ");
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -524,13 +532,18 @@ export default React.memo(function GisTree({
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`grow overflow-y-auto border-none p-2 pt-0 shadow-none ${activeTab === "layers" ? "block" : "hidden"
|
||||
className={`grow overflow-y-auto border-none p-2 ${showLayerPanelTitle ? "pt-0" : "pt-2"} shadow-none ${activeTab === "layers" ? "block" : "hidden"
|
||||
}`}
|
||||
>
|
||||
<h3 className="gis-layer-panel-title mx-2 my-4 text-3xl font-bold text-gray-600 text-center">시설물</h3>
|
||||
<hr className="mb-4" />
|
||||
{showLayerPanelTitle && (
|
||||
<>
|
||||
<h3 className="gis-layer-panel-title mx-2 my-4 text-3xl font-bold text-gray-600 text-center">시설물</h3>
|
||||
<hr className="mb-4" />
|
||||
</>
|
||||
)}
|
||||
{/* DnD Tree */}
|
||||
<DndProvider backend={MultiBackend} options={getBackendOptions()}>
|
||||
<div className={treeSectionClassNames || undefined}>
|
||||
<DndProvider backend={MultiBackend} options={getBackendOptions()}>
|
||||
<Tree
|
||||
tree={nodes}
|
||||
rootId={null}
|
||||
@@ -542,6 +555,13 @@ export default React.memo(function GisTree({
|
||||
(child) =>
|
||||
child.parent === node.id && child.data.exprsYn === "Y"
|
||||
);
|
||||
const nodeLabel = String(node?.data?.lyrNm ?? "").trim();
|
||||
const isSinkholeRootTreeNode =
|
||||
String(node.id) === "SN_SINKHOLE" ||
|
||||
nodeLabel === "지반침하정보";
|
||||
const isFloodHistoryRootTreeNode = nodeLabel === "침수흔적도";
|
||||
const hideTreeLegendIcon =
|
||||
isSinkholeRootTreeNode || isFloodHistoryRootTreeNode;
|
||||
const showLayerActionButton = layerIconVisible?.(node);
|
||||
const showLayerActionIcon = depth > 0 && imageVisible;
|
||||
|
||||
@@ -550,9 +570,10 @@ export default React.memo(function GisTree({
|
||||
return (
|
||||
<div style={{ display: isVisible ? "block" : "none" }}>
|
||||
<div
|
||||
className="gis-layer-row flex items-center gap-2 rounded px-2 py-1 hover:bg-gray-100"
|
||||
className="gis-layer-row flex w-full items-center gap-2 rounded px-2 py-1 hover:bg-gray-100"
|
||||
style={{
|
||||
marginInlineStart: depth * 16,
|
||||
width: `calc(100% - ${depth * 16}px)`,
|
||||
}}
|
||||
>
|
||||
{hasVisibleChildren ? (
|
||||
@@ -563,7 +584,7 @@ export default React.memo(function GisTree({
|
||||
title={isOpen ? "닫기" : "열기"}
|
||||
/>
|
||||
) : (
|
||||
<span style={{ display: "inline-block", width: 16 }} />
|
||||
<span className="shrink-0" style={{ display: "inline-block", width: 16 }} />
|
||||
)}
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -571,14 +592,17 @@ export default React.memo(function GisTree({
|
||||
onChange={() => handleCheck(node.id)}
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
{node.icon && <span>{node.icon}</span>}
|
||||
<span className="text-[1.3rem] font-medium text-label">
|
||||
{!hideTreeLegendIcon && node.icon && <span>{node.icon}</span>}
|
||||
<span
|
||||
className="min-w-0 flex-1 truncate whitespace-nowrap text-[1.3rem] font-medium text-label"
|
||||
title={String(node.text ?? "")}
|
||||
>
|
||||
{node.text}
|
||||
</span>
|
||||
{showLayerActionButton && (
|
||||
<button
|
||||
type="button"
|
||||
className="gis-layer-action-button ml-1 shrink-0 rounded-sm border border-[#9cc6f0] bg-white px-2 py-[1px] text-[1.1rem] font-medium leading-5 text-[#2c6fb7] transition-colors hover:bg-[#eef6ff]"
|
||||
className="gis-layer-action-button ml-auto shrink-0 rounded-sm border border-[#9cc6f0] bg-white px-2 py-[1px] text-[1.1rem] font-medium leading-5 text-[#2c6fb7] transition-colors hover:bg-[#eef6ff]"
|
||||
onClick={() =>
|
||||
handleLayerImgClick(
|
||||
node.data.lyrTyNm,
|
||||
@@ -624,26 +648,41 @@ export default React.memo(function GisTree({
|
||||
return aOrder - bOrder;
|
||||
}}
|
||||
/>
|
||||
</DndProvider>
|
||||
</DndProvider>
|
||||
</div>
|
||||
</div>
|
||||
{LayerSettingView && (
|
||||
<div className="gis-layer-setting shrink-0 bg-white px-2 pb-3">
|
||||
<LayerSettingView />
|
||||
</div>
|
||||
)}
|
||||
{!detachedToggleButton && (
|
||||
<button
|
||||
onClick={() => setVisible(false)}
|
||||
className={`gis-layer-toggle absolute right-[-3px] z-50 transform rounded-l-none rounded-r border border-l border-gray-300 bg-white px-1 py-2 shadow-sm ${toggleButtonPositionClass}`}
|
||||
>
|
||||
<ChevronLeft size={16} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{detachedToggleButton && visible && (
|
||||
<button
|
||||
onClick={() => setVisible(false)}
|
||||
className={`gis-layer-toggle absolute right-[-3px] z-50 transform rounded-l-none rounded-r border border-l border-gray-300 bg-white px-1 py-2 shadow-sm ${toggleButtonPositionClass}`}
|
||||
className={`gis-layer-toggle absolute left-[250px] z-50 flex h-[36px] w-[20px] transform items-center justify-center rounded-[4px] border border-[#d8dee8] bg-[rgba(255,255,255,0.96)] text-[#1e88e5] shadow-[0_8px_18px_rgba(15,23,42,0.14)] transition hover:bg-[#eaf4ff] ${toggleButtonPositionClass}`}
|
||||
title="접기"
|
||||
>
|
||||
<ChevronLeft size={16} />
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{!visible && (
|
||||
<button
|
||||
onClick={() => setVisible(true)}
|
||||
className={`gis-layer-toggle absolute left-[-3px] z-50 transform rounded-r border border-r border-gray-300 bg-white px-1 py-2 shadow-sm ${toggleButtonPositionClass}`}
|
||||
className={detachedToggleButton
|
||||
? `gis-layer-toggle absolute left-0 z-50 flex h-[36px] w-[20px] transform items-center justify-center rounded-[4px] border border-[#d8dee8] bg-[rgba(255,255,255,0.96)] text-[#1e88e5] shadow-[0_8px_18px_rgba(15,23,42,0.14)] transition hover:bg-[#eaf4ff] ${toggleButtonPositionClass}`
|
||||
: `gis-layer-toggle absolute left-[-3px] z-50 transform rounded-r border border-r border-gray-300 bg-white px-1 py-2 shadow-sm ${toggleButtonPositionClass}`}
|
||||
title="열기"
|
||||
>
|
||||
<ChevronRight size={16} />
|
||||
{detachedToggleButton ? <ChevronRight className="h-4 w-4" /> : <ChevronRight size={16} />}
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -45,7 +45,7 @@ GisApp.GOOGLE_3D_URL = `https://tile.googleapis.com/v1/3dtiles/root.json?key=${G
|
||||
|
||||
// NGII 정보(배경지도)
|
||||
//GisApp.NGII_WMTS_URL = process.env.NEXT_PUBLIC_NGII_URL;
|
||||
GisApp.NGII_WMTS_URL = `/ngii-server/openapi/Gettile.do?apikey=${process.env.NEXT_PUBLIC_NGII_PARAM}`;
|
||||
GisApp.NGII_WMTS_PARAM = process.env.NEXT_PUBLIC_NGII_PARAM ?? '';
|
||||
|
||||
GisApp.EMAP_URL = '/emap/';
|
||||
GisApp.EMAP_SATELLITE_URL = '/emap/satellite/';
|
||||
@@ -79,6 +79,17 @@ GisApp.setLayerInfos = function (layerInfos) {
|
||||
GisApp.layerInfos = layerInfos;
|
||||
};
|
||||
|
||||
GisApp.getNgiiWmtsUrl = function (layerName, matrix, x, y) {
|
||||
const layerAwareParams = /(^|&)layer=[^&]*/i.test(GisApp.NGII_WMTS_PARAM)
|
||||
? GisApp.NGII_WMTS_PARAM.replace(/(^|&)layer=[^&]*/i, `$1layer=${layerName}`)
|
||||
: `${GisApp.NGII_WMTS_PARAM}&layer=${layerName}`;
|
||||
|
||||
return `/ngii-server/openapi/Gettile.do?apikey=${layerAwareParams}`
|
||||
.replace("{z}", matrix)
|
||||
.replace("{x}", x)
|
||||
.replace("{y}", y);
|
||||
};
|
||||
|
||||
/**
|
||||
* GisApp core
|
||||
* @requires gis.layer.js
|
||||
@@ -161,7 +172,7 @@ GisApp.core.prototype = {
|
||||
});
|
||||
|
||||
const wmtsSource = new TileWMTS({
|
||||
//url: GisApp.NGII_WMTS_URL,
|
||||
// url은 setTileUrlFunction에서 layer별로 동적으로 만든다.
|
||||
projection: projection,
|
||||
tileGrid: tileGrid,
|
||||
crossOrigin: "anonymous",
|
||||
@@ -175,11 +186,7 @@ GisApp.core.prototype = {
|
||||
wmtsSource.setTileUrlFunction((tileCoord) => {
|
||||
const [z, x, y] = tileCoord;
|
||||
const matrix = matrixIds[z];
|
||||
// console.log(GisApp.NGII_WMTS_URL.replace("{z}", matrix).replace("{x}", x).replace("{y}", y));
|
||||
return GisApp.NGII_WMTS_URL
|
||||
.replace("{z}", matrix)
|
||||
.replace("{x}", x)
|
||||
.replace("{y}", y);
|
||||
return GisApp.getNgiiWmtsUrl(_layer, matrix, x, y);
|
||||
});
|
||||
|
||||
return new TileLayer({
|
||||
|
||||
@@ -742,10 +742,13 @@ GisOlLayer.prototype = {
|
||||
const extent = this.combineExtents(geometries.map(g => g?.getExtent()));
|
||||
|
||||
let map = GisApp.Module.core.map;
|
||||
const targetZoom = 16;
|
||||
const currentZoom = Number(map.getView().getZoom());
|
||||
const maxZoom = Number.isFinite(currentZoom) && currentZoom > targetZoom ? currentZoom : targetZoom;
|
||||
map.getView().fit(extent, {
|
||||
duration: 300,
|
||||
padding: [50, 50, 50, 50],
|
||||
maxZoom: 16
|
||||
maxZoom: maxZoom
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -132,13 +132,15 @@ GisOlSelect.prototype = {
|
||||
const selectedLayer = e.target?.getLayer(feature) || feature.getLayer?.();
|
||||
const clckZoom = Number(selectedLayer?.get('clckZoom'));
|
||||
const targetZoom = Number.isFinite(clckZoom) && clckZoom > 0 ? clckZoom : 16;
|
||||
const currentZoom = Number(map.getView().getZoom());
|
||||
const maxZoom = Number.isFinite(currentZoom) && currentZoom > targetZoom ? currentZoom : targetZoom;
|
||||
const geometry = feature.getGeometry();
|
||||
if (geometry) {
|
||||
const extent = geometry.getExtent();
|
||||
map.getView().fit(extent, {
|
||||
duration: 300,
|
||||
padding: [50, 50, 50, 50],
|
||||
maxZoom: targetZoom
|
||||
maxZoom: maxZoom
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,12 +343,13 @@ GisOlStyle.prototype = {
|
||||
//레이어 스타일 반환
|
||||
getLayerStyle: function (layer, options) {
|
||||
let gobjTy = layer.get('gobjTy');
|
||||
if (!gobjTy) return;
|
||||
if (!gobjTy || typeof this[gobjTy] !== 'function') return;
|
||||
let style = this[gobjTy](layer, options);
|
||||
return style;
|
||||
},
|
||||
getLayerStyleFunction: function (layer, options) {
|
||||
let gobjTy = layer.get('gobjTy');
|
||||
if (!gobjTy || typeof this[gobjTy] !== 'function') return;
|
||||
let styleFunction = function (feature, resolution) {
|
||||
return this[gobjTy](layer, options, feature, resolution);
|
||||
}.bind(this);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import "./search-icon.css";
|
||||
|
||||
export default function SearchIcon() {
|
||||
return (
|
||||
<svg
|
||||
@@ -6,7 +8,7 @@ export default function SearchIcon() {
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="w-6 h-6 text-gray-700"
|
||||
className="searchicon_svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
@@ -15,4 +17,4 @@ export default function SearchIcon() {
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
@tailwind components;
|
||||
|
||||
@layer components {
|
||||
.searchicon_svg {
|
||||
@apply h-6 w-6 text-label;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
import "./search-input.css";
|
||||
|
||||
import { Input, Label } from "@common/components";
|
||||
import React from "react";
|
||||
import { cn } from "../../lib/utils";
|
||||
|
||||
const SearchInput = React.forwardRef<HTMLInputElement, {className:string, label:string, textValue:string, setText:(val:string)=>void}>(({className, label, textValue, setText}, ref) => {
|
||||
const changeText=(e:React.ChangeEvent<HTMLInputElement>)=>{
|
||||
@@ -20,9 +23,9 @@ const SearchInput = React.forwardRef<HTMLInputElement, {className:string, label:
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className+" flex items-center"} >
|
||||
{label && <Label className="w-20 text-xl text-gray-600">{label}</Label>}
|
||||
<Input className="!text-lg flex-1 w-3/4 h-13 border rounded" value={textValue} onChange={changeText} onKeyUp={keyUp}></Input>
|
||||
<div className={cn("searchinput_root", className)} >
|
||||
{label && <Label className="searchinput_label">{label}</Label>}
|
||||
<Input ref={ref} className="searchinput_field" value={textValue} onChange={changeText} onKeyUp={keyUp}></Input>
|
||||
</div>
|
||||
)
|
||||
});
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
@tailwind components;
|
||||
|
||||
@layer components {
|
||||
.searchinput_root {
|
||||
@apply flex items-center;
|
||||
}
|
||||
|
||||
.searchinput_label {
|
||||
@apply w-20 text-xl;
|
||||
}
|
||||
|
||||
.searchinput_field {
|
||||
@apply h-14 w-3/4 flex-1 rounded-md border border-input text-lg;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import "./search-input-select.css";
|
||||
|
||||
import { Input, Label } from "@common/components";
|
||||
import React from "react";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../select/lib/selectBox";
|
||||
import { cn } from "../../lib/utils";
|
||||
export type SelectDataType = {
|
||||
text: string;
|
||||
val: string;
|
||||
@@ -23,12 +26,12 @@ const SearchInputSelect = React.forwardRef<HTMLInputElement, {className:string,
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className+" flex items-center"} >
|
||||
{label && <Label className="w-20 text-xl text-gray-600">{label}</Label>}
|
||||
<div className="flex w-full items-center">
|
||||
<Input className="w-2/3 text-xl h-14 p-2 border rounded" value={textValue} onChange={changeText} />
|
||||
<div className={cn("searchinputselect_root", className)} >
|
||||
{label && <Label className="searchinputselect_label">{label}</Label>}
|
||||
<div className="searchinputselect_controls">
|
||||
<Input ref={ref} className="searchinputselect_input" value={textValue} onChange={changeText} />
|
||||
<Select onValueChange={onSelectValue} value={selectValue}>
|
||||
<SelectTrigger className="ml-2 w-1/3" >
|
||||
<SelectTrigger className="searchinputselect_select" >
|
||||
<SelectValue placeholder={selectData?.[0]?.text ?? ""} />
|
||||
</SelectTrigger>
|
||||
{/* <SelectContent ref={refs ? (refs as unknown as RefType)?.selectValue : null}> */}
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
@tailwind components;
|
||||
|
||||
@layer components {
|
||||
.searchinputselect_root {
|
||||
@apply flex items-center;
|
||||
}
|
||||
|
||||
.searchinputselect_label {
|
||||
@apply w-20 text-xl;
|
||||
}
|
||||
|
||||
.searchinputselect_controls {
|
||||
@apply flex w-full items-center;
|
||||
}
|
||||
|
||||
.searchinputselect_input {
|
||||
@apply h-14 w-2/3 rounded-md border border-input p-2 text-xl;
|
||||
}
|
||||
|
||||
.searchinputselect_select {
|
||||
@apply ml-2 w-1/3;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import "./select.css";
|
||||
|
||||
// selectBox
|
||||
|
||||
import { Label } from "@common/components";
|
||||
@@ -10,6 +12,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "./lib/selectBox";
|
||||
import { cn } from "../../lib/utils";
|
||||
|
||||
/** 차트 기본 스타일 */
|
||||
interface SelectBox1 {
|
||||
@@ -43,7 +46,7 @@ const SelectBox = React.forwardRef<
|
||||
value,
|
||||
readOnly = false,
|
||||
selectContentClass,
|
||||
labelClass = "w-20 whitespace-nowrap text-xl text-gray-600",
|
||||
labelClass = "w-20 whitespace-nowrap text-xl",
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
@@ -72,9 +75,9 @@ const SelectBox = React.forwardRef<
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<div className={className + " flex items-center"} ref={ref}>
|
||||
<div className={cn("selectbox_root", className)} ref={ref}>
|
||||
{label && (
|
||||
<Label className={labelClass}>
|
||||
<Label className={cn("selectbox_label", labelClass)}>
|
||||
{label}
|
||||
</Label>
|
||||
)}
|
||||
@@ -92,9 +95,7 @@ const SelectBox = React.forwardRef<
|
||||
value={currentValue}
|
||||
// disabled={readOnly} //물재이용 감리 지적사항으로 일단 주석처리
|
||||
>
|
||||
<SelectTrigger
|
||||
className={selectClass ? selectClass : "flex-1 rounded border p-2"}
|
||||
>
|
||||
<SelectTrigger className={cn("selectbox_trigger", selectClass)}>
|
||||
<SelectValue placeholder={placeHolder} />
|
||||
</SelectTrigger>
|
||||
<SelectContent className={selectContentClass}>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
@tailwind components;
|
||||
|
||||
@layer components {
|
||||
.selectbox_root {
|
||||
@apply flex items-center;
|
||||
}
|
||||
|
||||
.selectbox_label {
|
||||
@apply text-label;
|
||||
}
|
||||
|
||||
.selectbox_trigger {
|
||||
@apply flex-1 rounded-md border border-input bg-form px-3 py-2;
|
||||
}
|
||||
}
|
||||
@@ -1,46 +1,61 @@
|
||||
import { Tabs, TabsList, TabsTrigger } from '@common/components/ui';
|
||||
import React from 'react';
|
||||
import "./tab.css";
|
||||
|
||||
import { Tabs, TabsList, TabsTrigger } from "@common/components/ui";
|
||||
import React from "react";
|
||||
import { cn } from "../../lib/utils";
|
||||
|
||||
interface TabItem {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface UITabProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'dir'> {
|
||||
interface UITabProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "dir"> {
|
||||
defaultValue?: string;
|
||||
tabList: TabItem[];
|
||||
children?: React.ReactNode;
|
||||
onValueChange?: (value: string) => void;
|
||||
variant?: "default" | "underline" | "outline";
|
||||
}
|
||||
|
||||
const UITab = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
UITabProps
|
||||
>(({defaultValue, tabList, children, onValueChange, ...props}, ref) => {
|
||||
return (
|
||||
<Tabs
|
||||
defaultValue={defaultValue || tabList[0]?.value}
|
||||
className={"w-full space-x-1 border-gray-200"}
|
||||
onValueChange={onValueChange}
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
<TabsList className="flex space-x-1">
|
||||
{tabList.map((tab) => (
|
||||
<TabsTrigger
|
||||
key={tab.value}
|
||||
value={tab.value}
|
||||
className="py-2 px-4 text-sm font-medium border-b-2 focus:outline-none data-[state=active]:border-blue-500 data-[state=active]:text-blue-600 data-[state=inactive]:border-transparent data-[state=inactive]:text-gray-500 hover:text-gray-700 hover:border-gray-300"
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
{children}
|
||||
</Tabs>
|
||||
);
|
||||
});
|
||||
const UITab = React.forwardRef<HTMLDivElement, UITabProps>(
|
||||
(
|
||||
{
|
||||
defaultValue,
|
||||
tabList,
|
||||
children,
|
||||
onValueChange,
|
||||
className,
|
||||
variant = "default",
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
return (
|
||||
<Tabs
|
||||
variant={variant}
|
||||
defaultValue={defaultValue || tabList[0]?.value}
|
||||
className={cn("w-full", variant === "underline" && "btab_root", className)}
|
||||
onValueChange={onValueChange}
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
<TabsList className={cn(variant === "underline" && "btab_list")}>
|
||||
{tabList.map((tab) => (
|
||||
<TabsTrigger
|
||||
key={tab.value}
|
||||
value={tab.value}
|
||||
className={cn(variant === "underline" && "btab_trigger")}
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
{children}
|
||||
</Tabs>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
UITab.displayName = 'UITab';
|
||||
UITab.displayName = "UITab";
|
||||
|
||||
export { UITab };
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
@tailwind components;
|
||||
|
||||
@layer components {
|
||||
.btab_root {
|
||||
@apply w-full;
|
||||
}
|
||||
|
||||
.btab_list {
|
||||
@apply flex space-x-1 border-b border-border bg-transparent p-0;
|
||||
}
|
||||
|
||||
.btab_trigger {
|
||||
@apply rounded-none border-b-2 border-transparent bg-transparent px-4 py-2 text-sm font-medium text-label;
|
||||
}
|
||||
|
||||
.btab_root .btab_trigger[data-state="active"] {
|
||||
@apply border-primary text-primary;
|
||||
}
|
||||
|
||||
.btab_root .btab_trigger[data-state="inactive"] {
|
||||
@apply text-label;
|
||||
}
|
||||
|
||||
.btab_root .btab_trigger:hover {
|
||||
@apply border-border text-foreground;
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ const HeadMakeColSpan = ({ upHeadList }: { upHeadList: TableUpperProps[] }) => {
|
||||
colSpan={head.colSpan || 1}
|
||||
rowSpan={head.rowSpan || 1}
|
||||
style={{ whiteSpace: 'pre-line' }}
|
||||
className="bg-gray-50"
|
||||
className=""
|
||||
>
|
||||
{head.title}
|
||||
</TableHead>
|
||||
|
||||
@@ -22,4 +22,6 @@
|
||||
.tabs[data-variant="outline"] .tabs-trigger {
|
||||
@apply relative rounded-none rounded-t-sm border-border bg-background text-muted-foreground after:absolute after:bottom-0 after:left-0 after:h-[0.1rem] after:w-full after:bg-border after:content-[''] data-[state=active]:border data-[state=active]:border-b-0 data-[state=active]:border-border data-[state=active]:text-foreground data-[state=active]:after:bg-transparent;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user