Merge commit '5505791b6e3ef04480731b123512e90e9e035c43'

This commit is contained in:
2026-06-29 12:56:12 +09:00
135 changed files with 3155 additions and 938 deletions
@@ -237,7 +237,7 @@ export default function FacilityTermModalHeader<T extends { msrDt: string }>({
onSelectValue={(val) => setFlowPanel(val.toString())}
className="mx-3 w-[150px]"
label=""
selectClass="w-full p-1 border rounded bg-white"
selectClass="w-full rounded border border-input bg-form p-1"
/>
</div>
</TableCell>
@@ -251,7 +251,7 @@ export default function FacilityTermModalHeader<T extends { msrDt: string }>({
mode="single"
selected={startDate}
onSelect={(date) => handleStartDateSelect(date as Date)}
className="w-full p-1 border rounded bg-white"
className="w-full rounded border border-input bg-form p-1 text-foreground"
disabled={(date) => !isDateInRanges(date)}
defaultMonth={startDate}
/>
@@ -262,7 +262,7 @@ export default function FacilityTermModalHeader<T extends { msrDt: string }>({
mode="single"
selected={endDate}
onSelect={(date) => handleEndDateSelect(date as Date)}
className="w-full p-1 border rounded bg-white"
className="w-full rounded border border-input bg-form p-1 text-foreground"
disabled={(date) => !isDateInRanges(date)}
defaultMonth={endDate}
/>
@@ -275,4 +275,4 @@ export default function FacilityTermModalHeader<T extends { msrDt: string }>({
</div>
</ListDiv>
);
}
}
@@ -219,7 +219,7 @@ export default function FacilityTermModalHeaderMonth<T extends { msrDt: string }
return (
<ListDiv className="!h-[100px]">
<div className="card !m-[1.2rem] max-h-[40rem] overflow-auto !p-0r">
<div className="card !m-[1.2rem] max-h-[40rem] overflow-auto !p-0">
<Table>
<thead>
<TableRow>
@@ -237,7 +237,7 @@ export default function FacilityTermModalHeaderMonth<T extends { msrDt: string }
onSelectValue={(val) => setFlowPanel(val.toString())}
className="mx-3 w-[150px]"
label=""
selectClass="w-full p-1 border rounded bg-white"
selectClass="w-full rounded border border-input bg-form p-1"
/>
</div>
</TableCell>
@@ -251,7 +251,7 @@ export default function FacilityTermModalHeaderMonth<T extends { msrDt: string }
mode="single"
selected={startDate}
onSelect={(date) => handleStartDateSelect(date as Date)}
className="w-full p-1 border rounded bg-white"
className="w-full rounded border border-input bg-form p-1 text-foreground"
defaultMonth={startDate}
/>
</div>
@@ -261,7 +261,7 @@ export default function FacilityTermModalHeaderMonth<T extends { msrDt: string }
mode="single"
selected={endDate}
onSelect={(date) => handleEndDateSelect(date as Date)}
className="w-full p-1 border rounded bg-white"
className="w-full rounded border border-input bg-form p-1 text-foreground"
defaultMonth={endDate}
/>
</div>
@@ -273,4 +273,4 @@ export default function FacilityTermModalHeaderMonth<T extends { msrDt: string }
</div>
</ListDiv>
);
}
}
@@ -5,55 +5,55 @@ const HeadMakeColSpan = ({ upHeadList, layoutNum }: { upHeadList: TableUpperProp
return (
layoutNum === 'A' ? (
<>
<TableRow className="bg-gray-50">{upHeadList.map((head: TableUpperProps) =>
<TableRow className="bg-secondary">{upHeadList.map((head: TableUpperProps) =>
(head.id === 'trtFrfNm' ? <TableHead className="w-1/12" rowSpan={2} key={head.id}>{head.title}</TableHead> :
head.id === 'laeNm' ? <TableHead className="w-1/12" rowSpan={2} key={head.id}>{head.title}</TableHead> :
head.id == 'fctCpc' || head.id == 'planInputWaterQlty' || head.id == 'designInputWaterQlty' ? <TableHead rowSpan={2} className="w-40" style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead> :
head.id === 'trstOprEtpNm' ? <TableHead className="w-1/15" rowSpan={2} key={head.id}>{head.title}</TableHead> :
head.upName === 'upChangeRe' && head.upSequnce === 1 ? <TableHead className="bg-gray-50" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={2}>{'행정구역'}</TableHead> :
!head.upName ? <TableHead rowSpan={2} className="w-1/20 bg-gray-20" style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead> : null))}
head.upName === 'upChangeRe' && head.upSequnce === 1 ? <TableHead className="bg-secondary" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={2}>{'행정구역'}</TableHead> :
!head.upName ? <TableHead rowSpan={2} className="w-1/20 bg-accent text-accent-foreground" style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead> : null))}
</TableRow>
<TableRow className="bg-gray-50">
<TableRow className="bg-secondary">
{upHeadList.map((head: TableUpperProps) => {
if (head.upName === 'upChangeRe') {
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-40 bg-gray-50">{head.title}</TableHead>;
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-40 bg-secondary">{head.title}</TableHead>;
}
})}
</TableRow>
</>
) : layoutNum === 'R' ? (
<>
<TableRow className="bg-gray-50">{upHeadList.map((head: TableUpperProps) =>
<TableRow className="bg-secondary">{upHeadList.map((head: TableUpperProps) =>
(head.id === 'trtFrfNm' ? <TableHead className="w-1/12" rowSpan={2} key={head.id}>{head.title}</TableHead> :
head.id === 'laeNm' ? <TableHead className="w-1/12" rowSpan={2} key={head.id}>{head.title}</TableHead> :
head.id == 'fctCpc' || head.id == 'planInputWaterQlty' || head.id == 'designInputWaterQlty' ? <TableHead rowSpan={2} className="w-40" style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead> :
head.id === 'trstOprEtpNm' ? <TableHead className="w-1/15" rowSpan={2} key={head.id}>{head.title}</TableHead> :
head.upName === 'upChangeRe' && head.upSequnce === 1 ? <TableHead className="w-75 bg-gray-50" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={3}>{'유역'}</TableHead> :
!head.upName ? <TableHead rowSpan={2} className="w-1/20 bg-gray-20" style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead> : null))}
head.upName === 'upChangeRe' && head.upSequnce === 1 ? <TableHead className="w-75 bg-secondary" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={3}>{'유역'}</TableHead> :
!head.upName ? <TableHead rowSpan={2} className="w-1/20 bg-accent text-accent-foreground" style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead> : null))}
</TableRow>
<TableRow className="bg-gray-50">
<TableRow className="bg-secondary">
{upHeadList.map((head: TableUpperProps) => {
if (head.upName === 'upChangeRe') {
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-40 bg-gray-50">{head.title}</TableHead>;
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-40 bg-secondary">{head.title}</TableHead>;
}
})}
</TableRow>
</>
) : (
<>
<TableRow className="bg-gray-50">{upHeadList.map((head: TableUpperProps) =>
<TableRow className="bg-secondary">{upHeadList.map((head: TableUpperProps) =>
(head.id === 'trtFrfNm' ? <TableHead className="w-1/12" rowSpan={2} key={head.id}>{head.title}</TableHead> :
head.id === 'laeNm' ? <TableHead className="w-1/12" rowSpan={2} key={head.id}>{head.title}</TableHead> :
head.id == 'fctCpc' || head.id == 'planInputWaterQlty' || head.id == 'designInputWaterQlty' ? <TableHead rowSpan={2} className="w-40" style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead> :
head.id === 'trstOprEtpNm' ? <TableHead className="w-1/15" rowSpan={2} key={head.id}>{head.title}</TableHead> :
head.upName === 'upChangeRe1' && head.upSequnce === 1 ? <TableHead className="w-75 bg-gray-50" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={3}>{'권역'}</TableHead> :
head.upName === 'upChangeRe2' && head.upSequnce === 1 ? <TableHead className="w-75 bg-gray-50" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={2}>{'행정구역'}</TableHead> :
!head.upName ? <TableHead rowSpan={2} className="w-1/20 bg-gray-20" style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead> : null))}
head.upName === 'upChangeRe1' && head.upSequnce === 1 ? <TableHead className="w-75 bg-secondary" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={3}>{'권역'}</TableHead> :
head.upName === 'upChangeRe2' && head.upSequnce === 1 ? <TableHead className="w-75 bg-secondary" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={2}>{'행정구역'}</TableHead> :
!head.upName ? <TableHead rowSpan={2} className="w-1/20 bg-accent text-accent-foreground" style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead> : null))}
</TableRow>
<TableRow className="bg-gray-50">
<TableRow className="bg-secondary">
{upHeadList.map((head: TableUpperProps) => {
if (head.upName === 'upChangeRe1' || head.upName === 'upChangeRe2') {
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-40 bg-gray-50">{head.title}</TableHead>;
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-40 bg-secondary">{head.title}</TableHead>;
}
})}
</TableRow>
@@ -99,22 +99,22 @@ export default function SearchInputText({label=undefined, setValue=()=>{}, sidoS
return (
<div className={'relative'} ref={wrapperRef}>
{label && <Label className="w-20 text-xl text-gray-600 justify-self-center">{label}</Label>}
{label && <Label className="w-20 justify-self-center text-xl text-label">{label}</Label>}
<input
type="text"
value={textValue}
onChange={handleInputChange}
onFocus={onInputFocus}
className="w-[200px] ml-6 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
className="ml-6 w-[200px] rounded-md border border-input bg-form px-3 py-2 text-foreground focus:outline-none focus:ring-2 focus:ring-ring"
placeholder={`${label}을(를) 입력하세요`}
/>
{isOpen && processFacilityList.length > 0 && (
<div className="absolute z-10 w-[300px] px-2 py-1 !mt-5 ml-20 bg-white border border-gray-300 rounded-md shadow-lg h-90 overflow-auto">
<div className="absolute z-10 ml-20 !mt-5 h-[90px] w-[300px] overflow-auto rounded-md border border-border bg-popover px-2 py-1 text-popover-foreground shadow-lg">
<div className="max-h-72">
{processFacilityList.map((item : facilityListProps, index : number) => (
<div
key={index}
className="w-full text-left pl-1 pr-10 py-2 hover:bg-gray-100 cursor-pointer overflow-auto"
className="w-full cursor-pointer overflow-auto py-2 pl-1 pr-10 text-left hover:bg-accent hover:text-accent-foreground"
onClick={() => handleSuggestionClick(item.bscFctCd, item.bscFctNm, Number(item.fctCpc), item.trtFrfNm, item.efflWqltStndrdNm)}
role="button"
tabIndex={0}
@@ -17,7 +17,7 @@ function UnderComment({commentStartPoint="☞", commentArray=[], divHeight="14"}
<div className={"mt-"+divHeight}>
{commentArray.map((item, index) => (
<React.Fragment key={"comment" + index}>
<span className="text-2xl font-bold text-gray-500">
<span className="text-2xl font-bold text-muted-foreground">
{commentStartPoint} {item}
</span>
<br/>
@@ -28,4 +28,4 @@ function UnderComment({commentStartPoint="☞", commentArray=[], divHeight="14"}
}
}
export default UnderComment;
export default UnderComment;
+7 -4
View File
@@ -127,15 +127,18 @@ const Map = () => {
}
const el = overlayContainerRef.current;
el.className =
"ol-custom-overlay shadow-xl rounded-2xl bg-white/95 backdrop-blur p-3 border border-gray-200 min-w-[220px] max-w-[350px] ";
"ol-custom-overlay shadow-xl rounded-2xl backdrop-blur p-3 min-w-[220px] max-w-[350px]";
el.style.backgroundColor = "color-mix(in srgb, var(--popover) 95%, transparent)";
el.style.color = "var(--popover-foreground)";
el.style.border = "1px solid var(--border)";
el.innerHTML = `
<div class="font-semibold text-lg text-gray-800 mb-2 text-center bg-green-400">${title}</div>
<table class="w-full text-base text-gray-800">
<div class="mb-2 rounded-md px-2 py-1 text-center text-lg font-semibold" style="background-color: var(--secondary); color: var(--secondary-foreground);">${title}</div>
<table class="w-full text-base" style="color: var(--foreground);">
${Object.entries(props)
.map(
([k, v]) => `
<tr>
<td class="py-1 pr-2 text-gray-500 whitespace-nowrap">${k}</td>
<td class="whitespace-nowrap py-1 pr-2" style="color: var(--muted-foreground);">${k}</td>
<td class="py-1 font-medium">${safe(v)}</td>
</tr>`
)
@@ -54,23 +54,23 @@ export default function DrainageEquipment({searchYear = '', fcltyList = []}: Dra
const HeadMakeColSpan = ({ upHeadList }: { upHeadList: TableUpperProps[] }) => {
return (
<>
<TableRow className="bg-gray-50">{upHeadList.map((head: TableUpperProps) =>
<TableRow className="bg-secondary">{upHeadList.map((head: TableUpperProps) =>
(head.id === 'part1' ? <TableHead rowSpan={3} className='w-60' key={head.id}>구분</TableHead> :
head.id === 'part2' ? <TableHead rowSpan={3} className='w-60' key={head.id}>구분</TableHead> :
head.id == 'nowSum' ? <TableHead rowSpan={2} colSpan={3} key={head.id} style={{ whiteSpace: 'pre-line' }}>{'현재\n(기준년도)'}</TableHead> :
head.id == 'stepOneSum' ? <TableHead rowSpan={1} colSpan={12} key={head.id} style={{ whiteSpace: 'pre-line' }}>{'변경(금회계획)'}</TableHead> :
head.id == 'etc' ? <TableHead rowSpan={3} key={head.id}>비고</TableHead> : null))}
</TableRow>
<TableRow className="bg-gray-50">{upHeadList.map((head: TableUpperProps) =>
<TableRow className="bg-secondary">{upHeadList.map((head: TableUpperProps) =>
(head.id === 'stepOneSum' ? <TableHead rowSpan={1} colSpan={3} key={head.id}>1단계</TableHead> :
head.id === 'stepTwoSum' ? <TableHead rowSpan={1} colSpan={3} key={head.id}>2단계</TableHead> :
head.id === 'stepThreeSum' ? <TableHead rowSpan={1} colSpan={3} key={head.id}>3단계</TableHead> :
head.id === 'stepFourSum' ? <TableHead rowSpan={1} colSpan={3} key={head.id}>4단계</TableHead> : null))}
</TableRow>
<TableRow className="bg-gray-50">
<TableRow className="bg-secondary">
{upHeadList.map((head: TableUpperProps) => {
if (head.upName === 'upChangeRe2' || head.upName === 'upChangeRe3' || head.upName === 'upChangeRe4' || head.upName === 'upChangeRe5' || head.upName === 'upChangeRe6') {
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-40 bg-gray-50">{head.title}</TableHead>;
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-40 bg-secondary">{head.title}</TableHead>;
}
})}
</TableRow>
@@ -147,7 +147,7 @@ export default function DashBoardChartModal({item='', period='', title='',modalD
if (label) {
config[label] = {
label: label,
color : "hsl(var(--chart-1))"
color : "var(--chart-1)"
};
}
return config;
@@ -32,12 +32,12 @@ const headMakeColSpanPlanFclty = (upHeadList: TableUpperProps[], planYearList: s
return ( <>
<TableRow>{
upHeadList.map((head: TableUpperProps) =>
(!head.upSequnce?<TableHead rowSpan={2}key={head.id}>{head.title}</TableHead>:
head.upName === 'upChangeRe'&& head.upSequnce=== 1?<TableHead key={head.id} rowSpan={1} colSpan={7} style={{ whiteSpace: 'pre-line' }}>{'시설계획\n(금회)'}</TableHead>:null))}
(!head.upSequnce?<TableHead className="fo-facility-info-header-surface" rowSpan={2}key={head.id}>{head.title}</TableHead>:
head.upName === 'upChangeRe'&& head.upSequnce=== 1?<TableHead className="fo-facility-info-header-surface" key={head.id} rowSpan={1} colSpan={7} style={{ whiteSpace: 'pre-line' }}>{'시설계획\n(금회)'}</TableHead>:null))}
</TableRow>
<TableRow>
{upHeadList.map((head: TableUpperProps, index: number) =>
(head.upName === 'upChangeRe'?<TableHead key={head.id} style={{ whiteSpace: 'pre-line' }}>{index===0?head.title:head.title+'\n('+planYearList[index-1]+')'}</TableHead>:null))}
(head.upName === 'upChangeRe'?<TableHead className="fo-facility-info-header-surface" key={head.id} style={{ whiteSpace: 'pre-line' }}>{index===0?head.title:head.title+'\n('+planYearList[index-1]+')'}</TableHead>:null))}
</TableRow>
</> )
}
@@ -52,20 +52,20 @@ const headMakeColSpanPlanFclty = (upHeadList: TableUpperProps[], planYearList: s
const headMakeColSpanOperationStatus = (upHeadList: TableUpperProps[], searchYear: string) => {
return ( <>
<TableRow>
<TableHead rowSpan={1} colSpan={17} style={{ whiteSpace: 'pre-line' }}>{searchYear+'년도 운영실태'}</TableHead>
<TableHead className="fo-facility-info-header-surface" rowSpan={1} colSpan={17} style={{ whiteSpace: 'pre-line' }}>{searchYear+'년도 운영실태'}</TableHead>
</TableRow>
<TableRow>{
upHeadList.map((head: TableUpperProps) =>
(head.upName === 'upChangeRe1'&&head.upSequnce===1?<TableHead key={head.id} rowSpan={2}colSpan={2} style={{ whiteSpace: 'pre-line' }}>{head.title}</TableHead>:
!head.upSequnce?<TableHead style={{ whiteSpace: 'pre-line' }} rowSpan={2}key={head.id}>{head.title}</TableHead>:
head.upName === 'upChangeRe2'&&head.upSequnce===1?<TableHead key={head.id} colSpan={3} style={{ whiteSpace: 'pre-line' }}>{'연간 운영실태'}</TableHead>:
head.upName === 'upChangeRe3'&&head.upSequnce===1?<TableHead key={head.id} colSpan={3} style={{ whiteSpace: 'pre-line' }}>{'하절기 운영실태\n(6~8월)'}</TableHead>:
head.upName === 'upChangeRe4'&&head.upSequnce===1?<TableHead key={head.id} colSpan={4} style={{ whiteSpace: 'pre-line' }}>{'동절기 운영실태\n(1~2월)'}</TableHead>:
head.upName === 'upChangeRe5'&&head.upSequnce===1?<TableHead key={head.id} colSpan={3} style={{ whiteSpace: 'pre-line' }}>{'지하수 유입량 추정'}</TableHead>:null))}
(head.upName === 'upChangeRe1'&&head.upSequnce===1?<TableHead className="fo-facility-info-header-surface" key={head.id} rowSpan={2}colSpan={2} style={{ whiteSpace: 'pre-line' }}>{head.title}</TableHead>:
!head.upSequnce?<TableHead className="fo-facility-info-header-surface" style={{ whiteSpace: 'pre-line' }} rowSpan={2}key={head.id}>{head.title}</TableHead>:
head.upName === 'upChangeRe2'&&head.upSequnce===1?<TableHead className="fo-facility-info-header-surface" key={head.id} colSpan={3} style={{ whiteSpace: 'pre-line' }}>{'연간 운영실태'}</TableHead>:
head.upName === 'upChangeRe3'&&head.upSequnce===1?<TableHead className="fo-facility-info-header-surface" key={head.id} colSpan={3} style={{ whiteSpace: 'pre-line' }}>{'하절기 운영실태\n(6~8월)'}</TableHead>:
head.upName === 'upChangeRe4'&&head.upSequnce===1?<TableHead className="fo-facility-info-header-surface" key={head.id} colSpan={4} style={{ whiteSpace: 'pre-line' }}>{'동절기 운영실태\n(1~2월)'}</TableHead>:
head.upName === 'upChangeRe5'&&head.upSequnce===1?<TableHead className="fo-facility-info-header-surface" key={head.id} colSpan={3} style={{ whiteSpace: 'pre-line' }}>{'지하수 유입량 추정'}</TableHead>:null))}
</TableRow>
<TableRow>
{upHeadList.map((head: TableUpperProps) =>
(head.upName === 'upChangeRe2'||head.upName === 'upChangeRe3'||head.upName === 'upChangeRe4'||head.upName === 'upChangeRe5'?<TableHead style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead>:null))}
(head.upName === 'upChangeRe2'||head.upName === 'upChangeRe3'||head.upName === 'upChangeRe4'||head.upName === 'upChangeRe5'?<TableHead className="fo-facility-info-header-surface" style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead>:null))}
</TableRow>
</> )
}
@@ -239,18 +239,18 @@ export default function ProcessFacilitySearch({searchYear = '', fcltyList = []}:
:((index === 6 || index === 7 || index === 8) && (head.id === 'now' || head.id === 'stepOne' || head.id === 'stepThree' || head.id === 'stepFour'))?
null
:((index === 5) && (head.id === 'provisionalApproval' || head.id === 'stepTwo'))?
<TableCell colSpan={3} className="text-center border border-[var(--border)] bg-[rgb(234,243,251)] text-[rgb(100,119,132)]" style={{ whiteSpace: 'pre-line' }} key={head.id+index}>{el[head.id]}</TableCell>
<TableCell colSpan={3} className="fo-facility-info-header-surface text-center" style={{ whiteSpace: 'pre-line' }} key={head.id+index}>{el[head.id]}</TableCell>
:((index === 5) && (head.id === 'now' || head.id === 'stepOne' || head.id === 'stepThree'|| head.id === 'stepFour'))?
null
:(index < 5 || index > 5)&& (index!= 4) && (head.id ==='provisionalApproval'||head.id ==='now'||head.id ==='stepOne'||head.id ==='stepTwo'||head.id ==='stepThree'||head.id ==='stepFour')
?<TableCell className="text-right" style={{ whiteSpace: 'pre-line' }} key={head.id+index}>{el[head.id]}</TableCell>
:(head.id === "provisionalApproval" && index === 4)
?<TableCell colSpan={6} className="text-right border border-[var(--border)] text-[rgb(100,119,132)]" style={{ whiteSpace: 'pre-line' }} key={head.id+index}>{roundNumber(String((Number(waterUsage1)+Number(waterUsage2)+Number(waterUsage3))/3), 0)}</TableCell>
?<TableCell colSpan={6} className="text-right border border-border text-label" style={{ whiteSpace: 'pre-line' }} key={head.id+index}>{roundNumber(String((Number(waterUsage1)+Number(waterUsage2)+Number(waterUsage3))/3), 0)}</TableCell>
:((head.id ==='now'||head.id ==='stepOne'||head.id ==='stepTwo'||head.id ==='stepThree'||head.id ==='stepFour') )
?null
:(head.id === 'item' && index != 5)?
<TableCell className="text-center text-[rgb(100,119,132)]" style={{ whiteSpace: 'pre-line' }} key={head.id+index}>{el[head.id]}</TableCell>
:<TableCell className="text-center border border-[var(--border)] bg-[rgb(234,243,251)] text-[rgb(100,119,132)]" style={{ whiteSpace: 'pre-line' }} key={head.id+index}>{el[head.id]}</TableCell>
<TableCell className="text-center text-label" style={{ whiteSpace: 'pre-line' }} key={head.id+index}>{el[head.id]}</TableCell>
:<TableCell className="fo-facility-info-header-surface text-center" style={{ whiteSpace: 'pre-line' }} key={head.id+index}>{el[head.id]}</TableCell>
))}
</TableRow>
))}
@@ -278,15 +278,15 @@ export default function ProcessFacilitySearch({searchYear = '', fcltyList = []}:
(head.id === 'rdiRain'&&index === 0)?<TableCell rowSpan={7} className="text-right" style={{ whiteSpace: 'pre-line' }} key={head.id+index}>{el['rdiRain']}</TableCell>:
(head.id === 'rdiRain'&&index === 7)?<TableCell rowSpan={6} className="text-right" style={{ whiteSpace: 'pre-line' }} key={head.id+index}></TableCell>:
(head.id === 'waterQlty')?<TableCell className="text-center" style={{ whiteSpace: 'pre-line' }} key={head.id+index}>{el[head.id]}</TableCell>:
((head.id === 'yearAvg' || head.id === 'yearMax' || head.id === 'yearMin') && (index>=0 && index<=4))?<TableCell className="bg-yellow-50 text-right cursor-pointer" style={{ whiteSpace: 'pre-line' }} key={head.id+index} onClick={()=>{fncLoadModal('flowRate', 'year', el['waterQlty'] as string);}}>{el[head.id]}</TableCell>:
((head.id === 'yearAvg' || head.id === 'yearMax' || head.id === 'yearMin') && (index>=5 && index<=10))?<TableCell className="bg-green-50 text-right cursor-pointer" style={{ whiteSpace: 'pre-line' }} key={head.id+index} onClick={()=>{fncLoadModal('waterQlty', 'year', el['waterQlty'] as string);}}>{el[head.id]}</TableCell>:
((head.id === 'yearAvg' || head.id === 'yearMax' || head.id === 'yearMin') && (index>=11 && index<=12))?<TableCell className="bg-blue-50 text-right cursor-pointer" style={{ whiteSpace: 'pre-line' }} key={head.id+index} onClick={()=>{fncLoadModal('chongin', 'year', el['waterQlty'] as string);}}>{el[head.id]}</TableCell>:
((head.id === 'summerAll' || head.id === 'summerThree' || head.id === 'summerRain') && (index>=0 && index<=4))?<TableCell className="bg-yellow-100 text-right cursor-pointer" style={{ whiteSpace: 'pre-line' }} key={head.id+index} onClick={()=>{fncLoadModal('flowRate', 'summer', el['waterQlty'] as string);}}>{el[head.id]}</TableCell>:
((head.id === 'summerAll' || head.id === 'summerThree' || head.id === 'summerRain') && (index>=5 && index<=10))?<TableCell className="bg-green-100 text-right cursor-pointer" style={{ whiteSpace: 'pre-line' }} key={head.id+index} onClick={()=>{fncLoadModal('waterQlty', 'summer', el['waterQlty'] as string);}}>{el[head.id]}</TableCell>:
((head.id === 'summerAll' || head.id === 'summerThree' || head.id === 'summerRain') && (index>=11 && index<=12))?<TableCell className="bg-blue-100 text-right cursor-pointer" style={{ whiteSpace: 'pre-line' }} key={head.id+index} onClick={()=>{fncLoadModal('chongin', 'summer', el['waterQlty'] as string);}}>{el[head.id]}</TableCell>:
((head.id === 'winterAll' || head.id === 'winterSunnyAvg' || head.id === 'winterSunnyMax' || head.id === 'winterSunnyMin') && (index>=0 && index<=4))?<TableCell className="bg-yellow-200 text-right cursor-pointer" style={{ whiteSpace: 'pre-line' }} key={head.id+index} onClick={()=>{fncLoadModal('flowRate', 'winter', el['waterQlty'] as string);}}>{el[head.id]}</TableCell>:
((head.id === 'winterAll' || head.id === 'winterSunnyAvg' || head.id === 'winterSunnyMax' || head.id === 'winterSunnyMin') && (index>=5 && index<=10))?<TableCell className="bg-green-200 text-right cursor-pointer" style={{ whiteSpace: 'pre-line' }} key={head.id+index} onClick={()=>{fncLoadModal('waterQlty', 'winter', el['waterQlty'] as string);}}>{el[head.id]}</TableCell>:
((head.id === 'winterAll' || head.id === 'winterSunnyAvg' || head.id === 'winterSunnyMax' || head.id === 'winterSunnyMin') && (index>=11 && index<=12))?<TableCell className="bg-blue-200 text-right cursor-pointer" style={{ whiteSpace: 'pre-line' }} key={head.id+index} onClick={()=>{fncLoadModal('chongin', 'winter', el['waterQlty'] as string);}}>{el[head.id]}</TableCell>:
((head.id === 'yearAvg' || head.id === 'yearMax' || head.id === 'yearMin') && (index>=0 && index<=4))?<TableCell className="fo-facility-info-flow-year text-right cursor-pointer" style={{ whiteSpace: 'pre-line' }} key={head.id+index} onClick={()=>{fncLoadModal('flowRate', 'year', el['waterQlty'] as string);}}>{el[head.id]}</TableCell>:
((head.id === 'yearAvg' || head.id === 'yearMax' || head.id === 'yearMin') && (index>=5 && index<=10))?<TableCell className="fo-facility-info-water-year text-right cursor-pointer" style={{ whiteSpace: 'pre-line' }} key={head.id+index} onClick={()=>{fncLoadModal('waterQlty', 'year', el['waterQlty'] as string);}}>{el[head.id]}</TableCell>:
((head.id === 'yearAvg' || head.id === 'yearMax' || head.id === 'yearMin') && (index>=11 && index<=12))?<TableCell className="fo-facility-info-phosphorus-year text-right cursor-pointer" style={{ whiteSpace: 'pre-line' }} key={head.id+index} onClick={()=>{fncLoadModal('chongin', 'year', el['waterQlty'] as string);}}>{el[head.id]}</TableCell>:
((head.id === 'summerAll' || head.id === 'summerThree' || head.id === 'summerRain') && (index>=0 && index<=4))?<TableCell className="fo-facility-info-flow-summer text-right cursor-pointer" style={{ whiteSpace: 'pre-line' }} key={head.id+index} onClick={()=>{fncLoadModal('flowRate', 'summer', el['waterQlty'] as string);}}>{el[head.id]}</TableCell>:
((head.id === 'summerAll' || head.id === 'summerThree' || head.id === 'summerRain') && (index>=5 && index<=10))?<TableCell className="fo-facility-info-water-summer text-right cursor-pointer" style={{ whiteSpace: 'pre-line' }} key={head.id+index} onClick={()=>{fncLoadModal('waterQlty', 'summer', el['waterQlty'] as string);}}>{el[head.id]}</TableCell>:
((head.id === 'summerAll' || head.id === 'summerThree' || head.id === 'summerRain') && (index>=11 && index<=12))?<TableCell className="fo-facility-info-phosphorus-summer text-right cursor-pointer" style={{ whiteSpace: 'pre-line' }} key={head.id+index} onClick={()=>{fncLoadModal('chongin', 'summer', el['waterQlty'] as string);}}>{el[head.id]}</TableCell>:
((head.id === 'winterAll' || head.id === 'winterSunnyAvg' || head.id === 'winterSunnyMax' || head.id === 'winterSunnyMin') && (index>=0 && index<=4))?<TableCell className="fo-facility-info-flow-winter text-right cursor-pointer" style={{ whiteSpace: 'pre-line' }} key={head.id+index} onClick={()=>{fncLoadModal('flowRate', 'winter', el['waterQlty'] as string);}}>{el[head.id]}</TableCell>:
((head.id === 'winterAll' || head.id === 'winterSunnyAvg' || head.id === 'winterSunnyMax' || head.id === 'winterSunnyMin') && (index>=5 && index<=10))?<TableCell className="fo-facility-info-water-winter text-right cursor-pointer" style={{ whiteSpace: 'pre-line' }} key={head.id+index} onClick={()=>{fncLoadModal('waterQlty', 'winter', el['waterQlty'] as string);}}>{el[head.id]}</TableCell>:
((head.id === 'winterAll' || head.id === 'winterSunnyAvg' || head.id === 'winterSunnyMax' || head.id === 'winterSunnyMin') && (index>=11 && index<=12))?<TableCell className="fo-facility-info-phosphorus-winter text-right cursor-pointer" style={{ whiteSpace: 'pre-line' }} key={head.id+index} onClick={()=>{fncLoadModal('chongin', 'winter', el['waterQlty'] as string);}}>{el[head.id]}</TableCell>:
null
))}
</TableRow>
@@ -60,7 +60,14 @@ export default function DashBoardChart({searchYear = '', fcltyList = []}: facili
/**
* 차트 Config
*/
const colors = ["#FF6B6B", "#4ECDC4", "#45B7D1", "#96CEB4", "#00b50c", "#D4A5A5"];
const colors = [
"var(--chart-5)",
"var(--chart-2)",
"var(--chart-1)",
"var(--chart-3)",
"var(--primary)",
"var(--chart-6)",
];
//유입하수량-강수량
const chartConfig_inflowRain = {
@@ -50,7 +50,7 @@ export default function RainManageTab({ searchYear = '', fcltyList = [] }: facil
widthFixed={true}
>
</UiTable>
<span className="text-2xl font-bold text-gray-500">☞ 일수에 강우영향일 포함됨</span>
<span className="text-2xl font-bold text-muted-foreground">☞ 일수에 강우영향일 포함됨</span>
</div>
)
}
@@ -1,5 +1,5 @@
/**
* 대시보드 - 표로보기 상단구역 페에지
* 대시보드 - 표로보기 상단구역 페이지
*
* - 대시보드 - 표로보기의 3개의 탭 상단에 조회년도, 행정정보, 시설명의 구역을 컴포넌트
*/
@@ -198,7 +198,7 @@ export default function PrecipitationModal(props: PrecipitationModalProps) {
mode="single"
selected={startDate}
onSelect={(date) => handleStartDateSelect(date as Date)}
className="w-full p-1 border rounded bg-white"
className="w-full rounded border border-input bg-form p-1 text-foreground"
disabled={(date) => !isDateInRanges(date)}
defaultMonth={startDate}
/>
@@ -209,7 +209,7 @@ export default function PrecipitationModal(props: PrecipitationModalProps) {
mode="single"
selected={endDate}
onSelect={(date) => handleEndDateSelect(date as Date)}
className="w-full p-1 border rounded bg-white"
className="w-full rounded border border-input bg-form p-1 text-foreground"
disabled={(date) => !isDateInRanges(date)}
defaultMonth={endDate}
/>
@@ -239,4 +239,4 @@ export default function PrecipitationModal(props: PrecipitationModalProps) {
</ListDiv>
</>
);
}
}
@@ -17,27 +17,27 @@ const HeadMakeColSpan = ({ upHeadList, layoutNum }: { upHeadList: TableUpperProp
return (
layoutNum === 0 ? (
<>
<TableRow className="bg-gray-50">
<TableRow className="bg-secondary">
{upHeadList.map((head: TableUpperProps) => {
if(!head.upName) {
return <TableHead className="bg-gray-50" rowSpan={2} style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead>
return <TableHead className="bg-secondary" rowSpan={2} style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead>
}else if(head.upName==="upChangeRe1"&&head.upSequnce===1) {
return <TableHead className="bg-gray-50" colSpan={2} style={{ whiteSpace: 'pre-line' }} key={head.id}>행정구역</TableHead>
return <TableHead className="bg-secondary" colSpan={2} style={{ whiteSpace: 'pre-line' }} key={head.id}>행정구역</TableHead>
}else if(head.upName==="upChangeRe3"&&head.upSequnce===1) {
return <TableHead className="bg-gray-50" colSpan={5} style={{ whiteSpace: 'pre-line' }} key={head.id}>유입</TableHead>
return <TableHead className="bg-secondary" colSpan={5} style={{ whiteSpace: 'pre-line' }} key={head.id}>유입</TableHead>
}else if(head.upName==="upChangeRe4"&&head.upSequnce===1) {
return <TableHead className="bg-gray-50" colSpan={5} style={{ whiteSpace: 'pre-line' }} key={head.id}>유출</TableHead>
return <TableHead className="bg-secondary" colSpan={5} style={{ whiteSpace: 'pre-line' }} key={head.id}>유출</TableHead>
}
})}
</TableRow>
<TableRow className="bg-gray-50">
<TableRow className="bg-secondary">
{upHeadList.map((head: TableUpperProps, idx: number) => {
if (head.upName === 'upChangeRe1' || head.upName === 'upChangeRe3' || head.upName === 'upChangeRe4') {
if (idx == 1 || idx == 2 || idx == 3) {
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-1/8 bg-gray-50">{head.title}</TableHead>;
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-1/8 bg-secondary">{head.title}</TableHead>;
}
else {
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-1/8 bg-gray-50">{head.title}</TableHead>;
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-1/8 bg-secondary">{head.title}</TableHead>;
}
}
})}
@@ -45,27 +45,27 @@ const HeadMakeColSpan = ({ upHeadList, layoutNum }: { upHeadList: TableUpperProp
</>
) : layoutNum === 1 ? (
<>
<TableRow className="bg-gray-50">
<TableRow className="bg-secondary">
{upHeadList.map((head: TableUpperProps) => {
if(!head.upName) {
return <TableHead className="bg-gray-50" rowSpan={2} style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead>
return <TableHead className="bg-secondary" rowSpan={2} style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead>
}else if(head.upName==="upChangeRe1"&&head.upSequnce===1) {
return <TableHead className="bg-gray-50" colSpan={3} style={{ whiteSpace: 'pre-line' }} key={head.id}>권역</TableHead>
return <TableHead className="bg-secondary" colSpan={3} style={{ whiteSpace: 'pre-line' }} key={head.id}>권역</TableHead>
}else if(head.upName==="upChangeRe3"&&head.upSequnce===1) {
return <TableHead className="bg-gray-50" colSpan={5} style={{ whiteSpace: 'pre-line' }} key={head.id}>유입</TableHead>
return <TableHead className="bg-secondary" colSpan={5} style={{ whiteSpace: 'pre-line' }} key={head.id}>유입</TableHead>
}else if(head.upName==="upChangeRe4"&&head.upSequnce===1) {
return <TableHead className="bg-gray-50" colSpan={5} style={{ whiteSpace: 'pre-line' }} key={head.id}>유출</TableHead>
return <TableHead className="bg-secondary" colSpan={5} style={{ whiteSpace: 'pre-line' }} key={head.id}>유출</TableHead>
}
})}
</TableRow>
<TableRow className="bg-gray-50">
<TableRow className="bg-secondary">
{upHeadList.map((head: TableUpperProps, idx: number) => {
if (head.upName === 'upChangeRe1' || head.upName === 'upChangeRe3' || head.upName === 'upChangeRe4') {
if (idx == 1 || idx == 2 || idx == 3) {
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-1/8 bg-gray-50">{head.title}</TableHead>;
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-1/8 bg-secondary">{head.title}</TableHead>;
}
else {
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-1/8 bg-gray-50">{head.title}</TableHead>;
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-1/8 bg-secondary">{head.title}</TableHead>;
}
}
})}
@@ -73,29 +73,29 @@ const HeadMakeColSpan = ({ upHeadList, layoutNum }: { upHeadList: TableUpperProp
</>
) : layoutNum === 2 ? (
<>
<TableRow className="bg-gray-50">
<TableRow className="bg-secondary">
{upHeadList.map((head: TableUpperProps) => {
if(!head.upName) {
return <TableHead className="bg-gray-50" rowSpan={2} style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead>
return <TableHead className="bg-secondary" rowSpan={2} style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead>
}else if(head.upName==="upChangeRe1"&&head.upSequnce===1) {
return <TableHead className="bg-gray-50" colSpan={3} style={{ whiteSpace: 'pre-line' }} key={head.id}>권역</TableHead>
return <TableHead className="bg-secondary" colSpan={3} style={{ whiteSpace: 'pre-line' }} key={head.id}>권역</TableHead>
}else if(head.upName==="upChangeRe2"&&head.upSequnce===1) {
return <TableHead className="bg-gray-50" colSpan={2} style={{ whiteSpace: 'pre-line' }} key={head.id}>행정구역</TableHead>
return <TableHead className="bg-secondary" colSpan={2} style={{ whiteSpace: 'pre-line' }} key={head.id}>행정구역</TableHead>
}else if(head.upName==="upChangeRe3"&&head.upSequnce===1) {
return <TableHead className="bg-gray-50" colSpan={5} style={{ whiteSpace: 'pre-line' }} key={head.id}>유입</TableHead>
return <TableHead className="bg-secondary" colSpan={5} style={{ whiteSpace: 'pre-line' }} key={head.id}>유입</TableHead>
}else if(head.upName==="upChangeRe4"&&head.upSequnce===1) {
return <TableHead className="bg-gray-50" colSpan={5} style={{ whiteSpace: 'pre-line' }} key={head.id}>유출</TableHead>
return <TableHead className="bg-secondary" colSpan={5} style={{ whiteSpace: 'pre-line' }} key={head.id}>유출</TableHead>
}
})}
</TableRow>
<TableRow className="bg-gray-50">
<TableRow className="bg-secondary">
{upHeadList.map((head: TableUpperProps, idx: number) => {
if (head.upName === 'upChangeRe1' || head.upName === 'upChangeRe2' || head.upName === 'upChangeRe3' || head.upName === 'upChangeRe4') {
if (idx == 1 || idx == 2 || idx == 3) {
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-1/8 bg-gray-50">{head.title}</TableHead>;
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-1/8 bg-secondary">{head.title}</TableHead>;
}
else {
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-1/8 bg-gray-50">{head.title}</TableHead>;
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-1/8 bg-secondary">{head.title}</TableHead>;
}
}
})}
@@ -103,25 +103,25 @@ const HeadMakeColSpan = ({ upHeadList, layoutNum }: { upHeadList: TableUpperProp
</>
) : layoutNum === 3 ? (
<>
<TableRow className="bg-gray-50">
<TableRow className="bg-secondary">
{upHeadList.map((head: TableUpperProps) => {
if(!head.upName) {
return <TableHead className="bg-gray-50" rowSpan={2} style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead>
return <TableHead className="bg-secondary" rowSpan={2} style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead>
}else if(head.upName==="upChangeRe1"&&head.upSequnce===1) {
return <TableHead className="bg-gray-50" colSpan={5} style={{ whiteSpace: 'pre-line' }} key={head.id}>유입</TableHead>
return <TableHead className="bg-secondary" colSpan={5} style={{ whiteSpace: 'pre-line' }} key={head.id}>유입</TableHead>
}else if(head.upName==="upChangeRe2"&&head.upSequnce===1) {
return <TableHead className="bg-gray-50" colSpan={5} style={{ whiteSpace: 'pre-line' }} key={head.id}>방류</TableHead>
return <TableHead className="bg-secondary" colSpan={5} style={{ whiteSpace: 'pre-line' }} key={head.id}>방류</TableHead>
}
})}
</TableRow>
<TableRow className="bg-gray-50">
<TableRow className="bg-secondary">
{upHeadList.map((head: TableUpperProps, idx: number) => {
if (head.upName === 'upChangeRe1' || head.upName === 'upChangeRe2') {
if (idx == 1 || idx == 2 || idx == 3) {
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-1/8 bg-gray-50">{head.title}</TableHead>;
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-1/8 bg-secondary">{head.title}</TableHead>;
}
else {
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-1/8 bg-gray-50">{head.title}</TableHead>;
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-1/8 bg-secondary">{head.title}</TableHead>;
}
}
})}
@@ -129,23 +129,23 @@ const HeadMakeColSpan = ({ upHeadList, layoutNum }: { upHeadList: TableUpperProp
</>
) : layoutNum === 4 ? (
<>
<TableRow className="bg-gray-50">
<TableRow className="bg-secondary">
{upHeadList.map((head: TableUpperProps) => {
if(!head.upName) {
return <TableHead className="bg-gray-50" rowSpan={2} style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead>
return <TableHead className="bg-secondary" rowSpan={2} style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead>
}else if(head.upName==="upChangeRe"&&head.upSequnce===1) {
return <TableHead className="bg-gray-50" colSpan={4} style={{ whiteSpace: 'pre-line' }} key={head.id}>방류</TableHead>
return <TableHead className="bg-secondary" colSpan={4} style={{ whiteSpace: 'pre-line' }} key={head.id}>방류</TableHead>
}
})}
</TableRow>
<TableRow className="bg-gray-50">
<TableRow className="bg-secondary">
{upHeadList.map((head: TableUpperProps, idx: number) => {
if (head.upName === 'upChangeRe') {
if (idx == 1 || idx == 2 || idx == 3) {
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-1/8 bg-gray-50">{head.title}</TableHead>;
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-1/8 bg-secondary">{head.title}</TableHead>;
}
else {
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-1/8 bg-gray-50">{head.title}</TableHead>;
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-1/8 bg-secondary">{head.title}</TableHead>;
}
}
})}
@@ -153,21 +153,21 @@ const HeadMakeColSpan = ({ upHeadList, layoutNum }: { upHeadList: TableUpperProp
</>
) : layoutNum === 5 ? ( // 총인설비 수질현황 헤더
<>
<TableRow className="bg-gray-50">
<TableRow className="bg-secondary">
{upHeadList.map((head: TableUpperProps) => {
if(!head.upName) {
return <TableHead className="w-[180px] bg-gray-50" rowSpan={2} style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead>
return <TableHead className="w-[180px] bg-secondary" rowSpan={2} style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead>
}else if(head.upName==="upChangeRe1" && head.upSequnce===1) {
return <TableHead className="bg-gray-50" colSpan={2} style={{ whiteSpace: 'pre-line' }} key={head.id}>유입</TableHead>
return <TableHead className="bg-secondary" colSpan={2} style={{ whiteSpace: 'pre-line' }} key={head.id}>유입</TableHead>
}else if(head.upName==="upChangeRe2" && head.upSequnce===1) {
return <TableHead className="bg-gray-50" colSpan={2} style={{ whiteSpace: 'pre-line' }} key={head.id}>방류</TableHead>
return <TableHead className="bg-secondary" colSpan={2} style={{ whiteSpace: 'pre-line' }} key={head.id}>방류</TableHead>
}
})}
</TableRow>
<TableRow className="bg-gray-50">
<TableRow className="bg-secondary">
{upHeadList.map((head: TableUpperProps) => {
if (head.upName === 'upChangeRe1' || head.upName === 'upChangeRe2') {
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-1/8 bg-gray-50">{head.title}</TableHead>;
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-1/8 bg-secondary">{head.title}</TableHead>;
}
})}
</TableRow>
@@ -237,7 +237,7 @@ export default function FlowRateModal(props: { fcltyName: string; facilityCd: st
const raw = row.infoRnDay;
const value = typeof raw === "string" ? Number(raw.replace(/,/g, "").trim()) : Number(raw);
if (!Number.isFinite(value)) return "";
return value > 3 ? "bg-slate-50" : "";
return value > 3 ? "fo-rain-impact-row" : "";
},
[activeTab],
);
@@ -5,77 +5,77 @@ const HeadMakeColSpan = ({ upHeadList, layoutNum }: { upHeadList: TableUpperProp
return (
layoutNum === 0 ? (
<>
<TableRow className="bg-gray-50">{upHeadList.map((head: TableUpperProps) =>
<TableRow className="bg-secondary">{upHeadList.map((head: TableUpperProps) =>
(head.id === 'trtFrfNm' ? <TableHead className="w-1/12" rowSpan={3} key={head.id}>{head.title}</TableHead> :
head.id === 'laeNm' ? <TableHead className="w-1/12" rowSpan={3} key={head.id}>{head.title}</TableHead> :
head.id == 'fctCpc' || head.id == 'planInputWaterQlty' || head.id == 'designInputWaterQlty' ? <TableHead rowSpan={3} className="w-40" style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead> :
head.id === 'trstOprEtpNm' ? <TableHead className="w-1/15" rowSpan={3} key={head.id}>{head.title}</TableHead> :
head.upName === 'upChangeRe' && head.upSequnce === 1 ? <TableHead className="bg-gray-50" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={2} colSpan={2}>{'행정구역'}</TableHead> :
head.upName === 'upChangeRe3' && head.upSequnce === 1 ? <TableHead className="bg-gray-50" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={2} colSpan={6}>{'증설계획'}</TableHead> :
head.upName === 'upChangeRe4' && head.upSequnce === 1 ? <TableHead className="bg-gray-50" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={19}>{'시설계획'}</TableHead> :
!head.upName ? <TableHead rowSpan={3} className="w-1/20 bg-gray-20" style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead> : null))}
head.upName === 'upChangeRe' && head.upSequnce === 1 ? <TableHead className="bg-secondary" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={2} colSpan={2}>{'행정구역'}</TableHead> :
head.upName === 'upChangeRe3' && head.upSequnce === 1 ? <TableHead className="bg-secondary" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={2} colSpan={6}>{'증설계획'}</TableHead> :
head.upName === 'upChangeRe4' && head.upSequnce === 1 ? <TableHead className="bg-secondary" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={19}>{'시설계획'}</TableHead> :
!head.upName ? <TableHead rowSpan={3} className="w-1/20 bg-accent text-accent-foreground" style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead> : null))}
</TableRow>
<TableRow className="bg-gray-50">
<TableRow className="bg-secondary">
{upHeadList.map((head: TableUpperProps) => (
head.upName === 'upChangeRe4' && head.upSequnce === 1 ? <TableHead className="bg-gray-50" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={6}>{'증설계획(m³/일)'}</TableHead> :
head.upName === 'upChangeRe5' && head.upSequnce === 1 ? <TableHead className="bg-gray-50" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={6}>{'처리인구(인)'}</TableHead> :
head.upName === 'upChangeRe6' && head.upSequnce === 1 ? <TableHead className="bg-gray-50" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={7}>{'처리구역(k㎡)'}</TableHead> :
head.upName === 'upChangeRe4' && head.upSequnce === 1 ? <TableHead className="bg-secondary" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={6}>{'증설계획(m³/일)'}</TableHead> :
head.upName === 'upChangeRe5' && head.upSequnce === 1 ? <TableHead className="bg-secondary" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={6}>{'처리인구(인)'}</TableHead> :
head.upName === 'upChangeRe6' && head.upSequnce === 1 ? <TableHead className="bg-secondary" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={7}>{'처리구역(k㎡)'}</TableHead> :
null
))}
</TableRow>
<TableRow className="bg-gray-50">
<TableRow className="bg-secondary">
{upHeadList.map((head: TableUpperProps) => {
if (head.upName === 'upChangeRe' || head.upName === 'upChangeRe1' || head.upName === 'upChangeRe2' || head.upName === 'upChangeRe3' || head.upName === 'upChangeRe4' || head.upName === 'upChangeRe5' || head.upName === 'upChangeRe6') {
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-40 bg-gray-50">{head.title}</TableHead>;
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-40 bg-secondary">{head.title}</TableHead>;
}
})}
</TableRow>
</>
) : layoutNum === 1 ? (
<>
<TableRow className="bg-gray-50">{upHeadList.map((head: TableUpperProps) =>
<TableRow className="bg-secondary">{upHeadList.map((head: TableUpperProps) =>
(head.id === 'trtFrfNm' ? <TableHead className="w-1/12" rowSpan={3} key={head.id}>{head.title}</TableHead> :
head.id === 'laeNm' ? <TableHead className="w-1/12" rowSpan={3} key={head.id}>{head.title}</TableHead> :
head.id == 'fctCpc' || head.id == 'planInputWaterQlty' || head.id == 'designInputWaterQlty' ? <TableHead rowSpan={3} className="w-40" style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead> :
head.id === 'trstOprEtpNm' ? <TableHead className="w-1/15" rowSpan={3} key={head.id}>{head.title}</TableHead> :
head.upName === 'upChangeRe' && head.upSequnce === 1 ? <TableHead className="w-75 bg-gray-50" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={2} colSpan={3}>{'유역'}</TableHead> :
head.upName === 'upChangeRe3' && head.upSequnce === 1 ? <TableHead className="bg-gray-50" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={2} colSpan={6}>{'증설계획'}</TableHead> :
!head.upName ? <TableHead rowSpan={3} className="w-1/20 bg-gray-20" style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead> : null))}
head.upName === 'upChangeRe' && head.upSequnce === 1 ? <TableHead className="w-75 bg-secondary" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={2} colSpan={3}>{'유역'}</TableHead> :
head.upName === 'upChangeRe3' && head.upSequnce === 1 ? <TableHead className="bg-secondary" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={2} colSpan={6}>{'증설계획'}</TableHead> :
!head.upName ? <TableHead rowSpan={3} className="w-1/20 bg-accent text-accent-foreground" style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead> : null))}
</TableRow>
<TableRow className="bg-gray-50">
<TableRow className="bg-secondary">
{upHeadList.map((head: TableUpperProps) => (
head.upName === 'upChangeRe4' && head.upSequnce === 1 ? <TableHead className="bg-gray-50" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={6}>{'증설계획(m³/일)'}</TableHead> :
head.upName === 'upChangeRe5' && head.upSequnce === 1 ? <TableHead className="bg-gray-50" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={6}>{'처리인구(인)'}</TableHead> :
head.upName === 'upChangeRe6' && head.upSequnce === 1 ? <TableHead className="bg-gray-50" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={7}>{'처리구역(k㎡)'}</TableHead> :
head.upName === 'upChangeRe4' && head.upSequnce === 1 ? <TableHead className="bg-secondary" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={6}>{'증설계획(m³/일)'}</TableHead> :
head.upName === 'upChangeRe5' && head.upSequnce === 1 ? <TableHead className="bg-secondary" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={6}>{'처리인구(인)'}</TableHead> :
head.upName === 'upChangeRe6' && head.upSequnce === 1 ? <TableHead className="bg-secondary" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={7}>{'처리구역(k㎡)'}</TableHead> :
null
))}
</TableRow>
<TableRow className="bg-gray-50">
<TableRow className="bg-secondary">
{upHeadList.map((head: TableUpperProps) => {
if (head.upName === 'upChangeRe' || head.upName === 'upChangeRe1' || head.upName === 'upChangeRe2' || head.upName === 'upChangeRe3' || head.upName === 'upChangeRe4' || head.upName === 'upChangeRe5' || head.upName === 'upChangeRe6') {
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-40 bg-gray-50">{head.title}</TableHead>;
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-40 bg-secondary">{head.title}</TableHead>;
}
})}
</TableRow>
</>
) : (
<>
<TableRow className="bg-gray-50">{upHeadList.map((head: TableUpperProps) =>
<TableRow className="bg-secondary">{upHeadList.map((head: TableUpperProps) =>
(head.id === 'trtFrfNm' ? <TableHead className="w-1/12" rowSpan={2} key={head.id}>{head.title}</TableHead> :
head.id === 'laeNm' ? <TableHead className="w-1/12" rowSpan={2} key={head.id}>{head.title}</TableHead> :
head.id == 'fctCpc' || head.id == 'planInputWaterQlty' || head.id == 'designInputWaterQlty' ? <TableHead rowSpan={2} className="w-40" style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead> :
head.id === 'trstOprEtpNm' ? <TableHead className="w-1/15" rowSpan={2} key={head.id}>{head.title}</TableHead> :
head.upName === 'upChangeRe1' && head.upSequnce === 1 ? <TableHead className="w-75 bg-gray-50" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={3}>{'권역'}</TableHead> :
head.upName === 'upChangeRe2' && head.upSequnce === 1 ? <TableHead className="w-75 bg-gray-50" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={2}>{'환경청'}</TableHead> :
head.upName === 'upChangeRe3' && head.upSequnce === 1 ? <TableHead className="w-75 bg-gray-50" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={6}>{'증설계획(m³/일)'}</TableHead> :
head.upName === 'upChangeRe5' && head.upSequnce === 1 ? <TableHead className="w-75 bg-gray-50" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={6}>{'처리인구(인)'}</TableHead> :
head.upName === 'upChangeRe6' && head.upSequnce === 1 ? <TableHead className="w-75 bg-gray-50" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={7}>{'처리구역(k㎡)'}</TableHead> :
!head.upName ? <TableHead rowSpan={2} className="w-1/20 bg-gray-20" style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead> : null))}
head.upName === 'upChangeRe1' && head.upSequnce === 1 ? <TableHead className="w-75 bg-secondary" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={3}>{'권역'}</TableHead> :
head.upName === 'upChangeRe2' && head.upSequnce === 1 ? <TableHead className="w-75 bg-secondary" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={2}>{'환경청'}</TableHead> :
head.upName === 'upChangeRe3' && head.upSequnce === 1 ? <TableHead className="w-75 bg-secondary" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={6}>{'증설계획(m³/일)'}</TableHead> :
head.upName === 'upChangeRe5' && head.upSequnce === 1 ? <TableHead className="w-75 bg-secondary" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={6}>{'처리인구(인)'}</TableHead> :
head.upName === 'upChangeRe6' && head.upSequnce === 1 ? <TableHead className="w-75 bg-secondary" style={{ whiteSpace: 'pre-line' }} key={head.id} rowSpan={1} colSpan={7}>{'처리구역(k㎡)'}</TableHead> :
!head.upName ? <TableHead rowSpan={2} className="w-1/20 bg-accent text-accent-foreground" style={{ whiteSpace: 'pre-line' }} key={head.id}>{head.title}</TableHead> : null))}
</TableRow>
<TableRow className="bg-gray-50">
<TableRow className="bg-secondary">
{upHeadList.map((head: TableUpperProps) => {
if (head.upName === 'upChangeRe1' || head.upName === 'upChangeRe2' ||head.upName === 'upChangeRe3' || head.upName === 'upChangeRe4' || head.upName === 'upChangeRe5' || head.upName === 'upChangeRe6') {
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-40 bg-gray-50">{head.title}</TableHead>;
return <TableHead key={head.id} style={{ whiteSpace: 'pre-line' }} className="w-40 bg-secondary">{head.title}</TableHead>;
}
})}
</TableRow>
@@ -84,4 +84,4 @@ const HeadMakeColSpan = ({ upHeadList, layoutNum }: { upHeadList: TableUpperProp
);
}
export { HeadMakeColSpan };
export { HeadMakeColSpan };
@@ -116,7 +116,7 @@ export function SearchBar({ onFeatureSelect, gisMaps }: SearchBarProps) {
<SelectBox
labelClass="w-[40px]"
className="mr-10 w-[200px]"
selectClass="rounded-lg bg-white"
selectClass="rounded-lg"
label="시도"
selectArray={sidoData}
onSelectValue={(val) => onSelectValue(val.toString(), "ctpvCd")}
@@ -125,7 +125,7 @@ export function SearchBar({ onFeatureSelect, gisMaps }: SearchBarProps) {
<SelectBox
labelClass="w-[65px]"
className="mr-10 w-[200px]"
selectClass="rounded-lg bg-white"
selectClass="rounded-lg"
label="시군구"
selectArray={filteredSigunData}
onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")}
@@ -134,7 +134,7 @@ export function SearchBar({ onFeatureSelect, gisMaps }: SearchBarProps) {
<SelectBox
labelClass="w-[140px]"
className="mr-3 w-[250px]"
selectClass="rounded-lg bg-white"
selectClass="rounded-lg"
label="하수처리시설"
selectArray={facilityData}
onSelectValue={(val) => onSelectValue(val.toString(), "fcltCd")}
@@ -0,0 +1,164 @@
# apps/fo 테마 적용 작업계획
작성일: 2026-06-25
대상 앱: `D:\iipbs\frontend\apps\fo`
## 1. 목적
- `apps/fo` 프로그램 페이지 내부에 직접 박혀 있는 색상 하드코딩을 식별한다.
- 가능한 범위는 `packages/common/assets/src/styles/base.css`의 CSS 변수 영향권으로 옮긴다.
- 즉 `bg-white`, `text-gray-*`, `border-gray-*`, `bg-blue-*`, `bg-green-*`, `bg-red-*` 같은 직접 색상 표현을 `background`, `foreground`, `label`, `border`, `secondary`, `accent`, `primary`, `destructive` 계열 토큰으로 치환한다.
## 2. 전제
- `apps/fo`는 [tailwind.config.ts](D:/iipbs/frontend/apps/fo/tailwind.config.ts:1)에서 공통 preset을 사용한다.
- 공통 토큰은 [packages/common/assets/src/styles/base.css](D:/iipbs/frontend/packages/common/assets/src/styles/base.css:1)에 정의되어 있다.
- `packages/common/components`, `packages/common/assets`는 기준 레이어이며, 앱 작업은 이 기준을 따르는 방향으로 진행한다.
- 단, 이번 요청의 1차 목표는 `apps/fo` 내부 코드에 박혀 있는 색상을 줄이는 것이다.
## 3. 확인된 하드코딩 유형
현재 `apps/fo`에서 확인된 대표 패턴은 아래와 같다.
- 검색 입력/선택 UI
- `apps/fo/app/components/common/SearchInput.tsx`
- `apps/fo/app/pages/map/searchBar/searchBar.tsx`
- `apps/fo/app/components/common/FacilityTermModalHeader.tsx`
- `apps/fo/app/components/common/FacilityTermModalHeaderMonth.tsx`
- 대시보드/안내문구
- `apps/fo/app/components/common/UnderComment.tsx`
- `apps/fo/app/pages/(with-layout)/dashboard/rainManage/page.tsx`
- 대시보드 표 셀 상태색
- `apps/fo/app/pages/(with-layout)/dashboard/facilityInfo/page.tsx`
이 페이지는 특별한 페이지로, 연간, 하절기, 동절기 색을 구분하여 보여주어야 한다. 따라서 text-foreground 가 보이도록 하되 연간, 하절기, 동절기 색을 apps\fo\src\app\styles 아래 테마별 css에 정의해서 보여주어야 한다.
- 지도 오버레이/팝업
- `apps/fo/app/pages/(map-layout)/map/page.tsx`
- 테스트/샘플성 버튼
- `apps/fo/app/pages/(with-layout)/test/page.tsx`
- 모달 입력창
- `apps/fo/app/pages/(with-layout)/facilityPrecipitation/modal/page.tsx`
## 4. 기본 원칙
### 4.1 바로 토큰 치환 가능한 경우
아래는 우선 토큰으로 바로 바꾼다.
- `bg-white` -> `bg-background` 또는 `bg-card` 또는 `bg-popover`
- `text-gray-900`, `text-gray-800` -> `text-foreground`
- `text-gray-700`, `text-gray-600` -> `text-label` 또는 `text-foreground`
- `text-gray-500` -> `text-muted-foreground` 또는 `text-placeholder`
- `border-gray-200`, `border-gray-300` -> `border-border`
- `focus:ring-blue-500` -> `focus:ring-ring`
- `text-blue-500`, `text-blue-600` -> `text-primary`
- `bg-blue-500`, `bg-blue-600` -> `bg-primary`
- `text-red-500`, `text-red-600` -> `text-destructive`
- `bg-red-50` 등 경고 계열 -> `bg-accent` 또는 별도 위험 상태 규칙 검토
### 4.2 바로 토큰 치환하면 안 되는 경우
아래는 의미색일 가능성이 높아 바로 토큰화하지 않는다.
- 범례 색상
- 데이터 구분용 heatmap/구간 색상
- 수질/계절/유형별 비교 색상
- 지도 오버레이에서 정보 카테고리 의미를 나타내는 색
이 경우는 다음 중 하나로 처리한다.
- 유지
- 별도 의미 클래스 부여
- 앱 전용 CSS로 상태 클래스를 만든 뒤 theme-safe surface만 토큰화
## 5. 우선순위
### 5.1 1순위: 일반 UI surface
가장 먼저 처리한다.
- 검색 입력창 배경/보더/텍스트
- 검색 결과 dropdown 배경/hover
- 팝업/모달 wrapper 배경/보더
- 제목/라벨/안내문구 색
- 버튼의 직접 blue/red/white 색상
이 영역은 theme 적용 체감이 가장 크고, 토큰 치환도 안전하다.
### 5.2 2순위: 테이블/리스트
- `selectedFcltList`
- `facilityInfo`
- modal 내부 표
- overlay 내 표 구조
이 영역은 `bg-secondary`, `bg-accent`, `text-secondary-foreground`, `border-border` 계열로 정리한다.
### 5.3 3순위: 의미색 데이터 표현
- `facilityInfo`의 계절/항목별 `bg-green-*`, `bg-blue-*`
- 지도 범례 색상
- 지도 feature overlay 강조색
- 대시보드 표 셀 상태색
- `apps/fo/app/pages/(with-layout)/dashboard/facilityInfo/page.tsx`
이 페이지는 특별한 페이지로, 연간, 하절기, 동절기 색을 구분하여 보여주어야 한다. 따라서 text-foreground 가 보이도록 하되 연간, 하절기, 동절기 색을 apps\fo\src\app\styles 아래 테마별 css에 정의해서 보여주어야 한다.
이 영역은 “없애는 것”보다 “역할을 분리하는 것”이 중요하다.
## 6. 실제 작업 순서
1. `apps/fo` 내부 하드코딩 색상 파일 목록 확정
2. 공통 UI와 데이터 의미색을 분리
3. 일반 UI 색상부터 `base.css` 토큰으로 치환
4. 필요 시 `apps/fo/app/styles/*.css`에 앱 전용 의미 클래스 추가
5. `dashboard`, `map`, `modal`, `search` 대표 화면 점검
6. 남은 의미색 영역은 별도 상태색 정책으로 정리
## 7. 권장 치환 전략
### 7.1 JSX 안에서 바로 치환
아래처럼 className을 직접 토큰 기반으로 변경한다.
- `bg-white` -> `bg-background`
- `text-gray-600` -> `text-label`
- `border-gray-300` -> `border-border`
이 방식은 빠르지만 반복이 많으면 유지보수성이 떨어진다.
### 7.2 반복되는 UI는 앱 전용 클래스 도입
예:
- `fo-search-panel`
- `fo-search-input`
- `fo-popup-card`
- `fo-empty-text`
반복되는 구조는 `apps/fo/app/styles/components*.css`에 올리고, JSX에서는 의미 클래스만 사용한다.
이 방식이 장기적으로 더 좋다.
## 8. 예상 주의사항
- `h-90` 같은 Tailwind v3 미지원 클래스가 섞여 있을 수 있다.
- `bg-white`를 `bg-background`로 바꾸면 black theme에서 contrast가 크게 달라질 수 있으므로 텍스트 색도 같이 바꿔야 한다.
- `bg-blue-*`, `bg-green-*`는 실제 status color인지 단순 강조색인지 구분하고 바꿔야 한다.
- 지도 오버레이는 HTML string으로 만드는 부분이 있으면 className 치환만으로 끝나지 않을 수 있다.
## 9. 1차 적용 추천 대상
우선 아래 파일부터 적용하는 것이 효율적이다.
1. `apps/fo/app/components/common/SearchInput.tsx`
2. `apps/fo/app/pages/map/searchBar/searchBar.tsx`
3. `apps/fo/app/components/common/UnderComment.tsx`
4. `apps/fo/app/pages/(with-layout)/dashboard/rainManage/page.tsx`
5. `apps/fo/app/components/common/FacilityTermModalHeader.tsx`
6. `apps/fo/app/components/common/FacilityTermModalHeaderMonth.tsx`
7. `apps/fo/app/pages/(with-layout)/facilityPrecipitation/modal/page.tsx`
## 10. 결론
- `apps/fo`도 `apps/wr`와 동일하게 `base.css` 토큰 중심으로 정리 가능하다.
- 1차는 일반 UI 하드코딩 제거에 집중하고, 데이터 의미색은 유지 또는 별도 정책으로 분리하는 것이 맞다.
- 가장 안전한 방향은 “반복 UI는 앱 전용 의미 클래스”, “일회성은 JSX에서 직접 토큰 치환”의 혼합 방식이다.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

+4
View File
@@ -1,5 +1,9 @@
import "./styles/base.css";
import "./styles/components.css";
import "./styles/components-black.css";
import "./styles/components-blue.css";
import "./styles/components-green.css";
import "./styles/components-red.css";
import "@fontsource-variable/noto-sans-kr";
import { AppProvider } from "./providers";
@@ -0,0 +1,43 @@
@tailwind components;
@layer components {
.card {
@apply relative m-[0.6rem] box-border min-h-[2rem] rounded-[0.5rem] p-[0.6rem] text-[1.3rem] text-[#666];
}
.card-border {
@apply border border-[#dbe2e6] bg-[rgba(255,255,255,0.6)] shadow-blur;
}
.card-header {
@apply -mx-[0.6rem] flex items-center justify-between border-b border-b-[#e1e7eb] p-4;
}
.card-footer {
@apply -mx-[0.6rem] mt-2 flex items-center justify-center border-t border-t-[#e1e7eb] py-4;
}
[data-theme="black"] {
--fo-facility-info-flow-year-bg: color-mix(in srgb, var(--chart-4) 20%, var(--secondary));
--fo-facility-info-water-year-bg: color-mix(in srgb, var(--chart-3) 20%, var(--secondary));
--fo-facility-info-phosphorus-year-bg: color-mix(in srgb, var(--chart-1) 20%, var(--secondary));
--fo-facility-info-flow-summer-bg: color-mix(in srgb, var(--chart-4) 28%, var(--secondary));
--fo-facility-info-water-summer-bg: color-mix(in srgb, var(--chart-3) 28%, var(--secondary));
--fo-facility-info-phosphorus-summer-bg: color-mix(in srgb, var(--chart-1) 28%, var(--secondary));
--fo-facility-info-flow-winter-bg: color-mix(in srgb, var(--chart-4) 36%, var(--secondary));
--fo-facility-info-water-winter-bg: color-mix(in srgb, var(--chart-3) 36%, var(--secondary));
--fo-facility-info-phosphorus-winter-bg: color-mix(in srgb, var(--chart-1) 36%, var(--secondary));
--fo-rain-impact-row-bg: color-mix(in srgb, var(--chart-1) 16%, var(--secondary));
--fo-facility-info-header-bg: #5e7b9c;
--fo-facility-info-header-fg: #ffffff;
--fo-facility-info-header-border: var(--border);
.card-border {
@apply border-[#3f4042] bg-[rgba(255,255,255,0.065)];
}
.card-header {
@apply border-b-[#3f4042];
}
}
input[type="file"] {
@apply w-[200px] h-[40px] px-3 py-2 border border-[#d1d5db] rounded-md bg-white text-sm cursor-pointer;
}
}
@@ -0,0 +1,95 @@
@tailwind components;
@layer components {
.card {
@apply relative m-[0.6rem] box-border min-h-[2rem] rounded-[0.5rem] p-[0.6rem] text-[1.3rem] text-[#666];
}
.card-border {
@apply border border-[#dbe2e6] bg-[rgba(255,255,255,0.6)] shadow-blur;
}
.card-header {
@apply -mx-[0.6rem] flex items-center justify-between border-b border-b-[#e1e7eb] p-4;
}
.card-footer {
@apply -mx-[0.6rem] mt-2 flex items-center justify-center border-t border-t-[#e1e7eb] py-4;
}
[data-theme="blue"] {
--fo-facility-info-flow-year-bg: color-mix(in srgb, var(--chart-4) 16%, var(--background));
--fo-facility-info-water-year-bg: color-mix(in srgb, var(--chart-3) 16%, var(--background));
--fo-facility-info-phosphorus-year-bg: color-mix(in srgb, var(--chart-1) 16%, var(--background));
--fo-facility-info-flow-summer-bg: color-mix(in srgb, var(--chart-4) 24%, var(--background));
--fo-facility-info-water-summer-bg: color-mix(in srgb, var(--chart-3) 24%, var(--background));
--fo-facility-info-phosphorus-summer-bg: color-mix(in srgb, var(--chart-1) 24%, var(--background));
--fo-facility-info-flow-winter-bg: color-mix(in srgb, var(--chart-4) 32%, var(--background));
--fo-facility-info-water-winter-bg: color-mix(in srgb, var(--chart-3) 32%, var(--background));
--fo-facility-info-phosphorus-winter-bg: color-mix(in srgb, var(--chart-1) 32%, var(--background));
--fo-rain-impact-row-bg: color-mix(in srgb, var(--chart-1) 12%, var(--background));
--fo-facility-info-header-bg: #eaf3fb;
--fo-facility-info-header-fg: #647784;
--fo-facility-info-header-border: #e5e5e5;
.card-border {
@apply border-[#d8e3eb] bg-white shadow-blur;
}
.card-header {
@apply border-b-[#d8e3eb];
}
.card {
@apply text-[#4b5563];
}
.card.card-border {
box-shadow: 0 1px 8px rgba(15, 23, 42, 0.06);
}
.card.card-border .card.card-border {
@apply border-[#dde6ed] bg-white;
box-shadow: none;
}
.card.card-border input,
.card.card-border button[role="combobox"] {
@apply border-[#d8e3eb] bg-white text-[#737373];
}
.card.card-border label {
@apply text-[#666666];
}
nav[aria-label="pagination"] li a {
border-color: transparent !important;
background-color: transparent !important;
color: #0a0a0a !important;
}
nav[aria-label="pagination"] li a:hover {
background-color: #f6f6f6 !important;
color: #0a0a0a !important;
}
nav[aria-label="pagination"] li a[aria-current="page"] {
border-color: #2094fa !important;
background-color: #2094fa !important;
color: #fafafa !important;
}
nav[aria-label="pagination"] li a[aria-label="Go to previous page"],
nav[aria-label="pagination"] li a[aria-label="Go to next page"] {
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
border: 1px solid #e5e7eb !important;
color: #666666 !important;
}
}
input[type="file"] {
@apply w-[200px] h-[40px] px-3 py-2 border border-[#d1d5db] rounded-md bg-white text-sm cursor-pointer;
}
}
@@ -0,0 +1,43 @@
@tailwind components;
@layer components {
.card {
@apply relative m-[0.6rem] box-border min-h-[2rem] rounded-[0.5rem] p-[0.6rem] text-[1.3rem] text-[#666];
}
.card-border {
@apply border border-[#dbe2e6] bg-[rgba(255,255,255,0.6)] shadow-blur;
}
.card-header {
@apply -mx-[0.6rem] flex items-center justify-between border-b border-b-[#e1e7eb] p-4;
}
.card-footer {
@apply -mx-[0.6rem] mt-2 flex items-center justify-center border-t border-t-[#e1e7eb] py-4;
}
[data-theme="green"] {
--fo-facility-info-flow-year-bg: color-mix(in srgb, var(--chart-4) 15%, var(--background));
--fo-facility-info-water-year-bg: color-mix(in srgb, var(--chart-3) 15%, var(--background));
--fo-facility-info-phosphorus-year-bg: color-mix(in srgb, var(--chart-1) 15%, var(--background));
--fo-facility-info-flow-summer-bg: color-mix(in srgb, var(--chart-4) 23%, var(--background));
--fo-facility-info-water-summer-bg: color-mix(in srgb, var(--chart-3) 23%, var(--background));
--fo-facility-info-phosphorus-summer-bg: color-mix(in srgb, var(--chart-1) 23%, var(--background));
--fo-facility-info-flow-winter-bg: color-mix(in srgb, var(--chart-4) 31%, var(--background));
--fo-facility-info-water-winter-bg: color-mix(in srgb, var(--chart-3) 31%, var(--background));
--fo-facility-info-phosphorus-winter-bg: color-mix(in srgb, var(--chart-1) 31%, var(--background));
--fo-rain-impact-row-bg: color-mix(in srgb, var(--chart-3) 12%, var(--background));
--fo-facility-info-header-bg: #ebf7f7;
--fo-facility-info-header-fg: #64908f;
--fo-facility-info-header-border: var(--border);
.card-border {
@apply border-[#3f4042] bg-[rgba(255,255,255,0.065)];
}
.card-header {
@apply border-b-[#3f4042];
}
}
input[type="file"] {
@apply w-[200px] h-[40px] px-3 py-2 border border-[#d1d5db] rounded-md bg-white text-sm cursor-pointer;
}
}
+36
View File
@@ -0,0 +1,36 @@
@tailwind components;
@layer components {
.card {
@apply relative m-[0.6rem] box-border min-h-[2rem] rounded-[0.5rem] p-[0.6rem] text-[1.3rem] text-[#666];
}
.card-border {
@apply border border-[#dbe2e6] bg-[rgba(255,255,255,0.6)] shadow-blur;
}
.card-header {
@apply -mx-[0.6rem] flex items-center justify-between border-b border-b-[#e1e7eb] p-4;
}
.card-footer {
@apply -mx-[0.6rem] mt-2 flex items-center justify-center border-t border-t-[#e1e7eb] py-4;
}
[data-theme="red"] {
--fo-facility-info-flow-year-bg: color-mix(in srgb, var(--chart-4) 14%, var(--background));
--fo-facility-info-water-year-bg: color-mix(in srgb, var(--chart-3) 14%, var(--background));
--fo-facility-info-phosphorus-year-bg: color-mix(in srgb, var(--chart-1) 14%, var(--background));
--fo-facility-info-flow-summer-bg: color-mix(in srgb, var(--chart-4) 22%, var(--background));
--fo-facility-info-water-summer-bg: color-mix(in srgb, var(--chart-3) 22%, var(--background));
--fo-facility-info-phosphorus-summer-bg: color-mix(in srgb, var(--chart-1) 22%, var(--background));
--fo-facility-info-flow-winter-bg: color-mix(in srgb, var(--chart-4) 30%, var(--background));
--fo-facility-info-water-winter-bg: color-mix(in srgb, var(--chart-3) 30%, var(--background));
--fo-facility-info-phosphorus-winter-bg: color-mix(in srgb, var(--chart-1) 30%, var(--background));
--fo-rain-impact-row-bg: color-mix(in srgb, var(--destructive) 12%, var(--background));
--fo-facility-info-header-bg: #f6f6f9;
--fo-facility-info-header-fg: #808080;
--fo-facility-info-header-border: var(--border);
}
input[type="file"] {
@apply w-[200px] h-[40px] px-3 py-2 border border-[#d1d5db] rounded-md bg-white text-sm cursor-pointer;
}
}
+73 -7
View File
@@ -1,6 +1,22 @@
@tailwind components;
@layer components {
:root {
--fo-facility-info-flow-year-bg: color-mix(in srgb, var(--chart-4) 18%, var(--background));
--fo-facility-info-water-year-bg: color-mix(in srgb, var(--chart-3) 18%, var(--background));
--fo-facility-info-phosphorus-year-bg: color-mix(in srgb, var(--chart-1) 18%, var(--background));
--fo-facility-info-flow-summer-bg: color-mix(in srgb, var(--chart-4) 28%, var(--background));
--fo-facility-info-water-summer-bg: color-mix(in srgb, var(--chart-3) 28%, var(--background));
--fo-facility-info-phosphorus-summer-bg: color-mix(in srgb, var(--chart-1) 28%, var(--background));
--fo-facility-info-flow-winter-bg: color-mix(in srgb, var(--chart-4) 38%, var(--background));
--fo-facility-info-water-winter-bg: color-mix(in srgb, var(--chart-3) 38%, var(--background));
--fo-facility-info-phosphorus-winter-bg: color-mix(in srgb, var(--chart-1) 38%, var(--background));
--fo-rain-impact-row-bg: color-mix(in srgb, var(--chart-1) 10%, var(--background));
--fo-facility-info-header-bg: #eaf3fb;
--fo-facility-info-header-fg: #647784;
--fo-facility-info-header-border: var(--border);
}
.card {
@apply relative m-[0.6rem] box-border min-h-[2rem] rounded-[0.5rem] p-[0.6rem] text-[1.3rem] text-[#666];
}
@@ -14,13 +30,63 @@
@apply -mx-[0.6rem] mt-2 flex items-center justify-center border-t border-t-[#e1e7eb] py-4;
}
[data-theme="black"] {
.card-border {
@apply border-[#3f4042] bg-[rgba(255,255,255,0.065)];
}
.card-header {
@apply border-b-[#3f4042];
}
.fo-facility-info-flow-year,
.fo-facility-info-water-year,
.fo-facility-info-phosphorus-year,
.fo-facility-info-flow-summer,
.fo-facility-info-water-summer,
.fo-facility-info-phosphorus-summer,
.fo-facility-info-flow-winter,
.fo-facility-info-water-winter,
.fo-facility-info-phosphorus-winter {
color: var(--foreground);
}
.fo-facility-info-flow-year {
background-color: var(--fo-facility-info-flow-year-bg);
}
.fo-facility-info-water-year {
background-color: var(--fo-facility-info-water-year-bg);
}
.fo-facility-info-phosphorus-year {
background-color: var(--fo-facility-info-phosphorus-year-bg);
}
.fo-facility-info-flow-summer {
background-color: var(--fo-facility-info-flow-summer-bg);
}
.fo-facility-info-water-summer {
background-color: var(--fo-facility-info-water-summer-bg);
}
.fo-facility-info-phosphorus-summer {
background-color: var(--fo-facility-info-phosphorus-summer-bg);
}
.fo-facility-info-flow-winter {
background-color: var(--fo-facility-info-flow-winter-bg);
}
.fo-facility-info-water-winter {
background-color: var(--fo-facility-info-water-winter-bg);
}
.fo-facility-info-phosphorus-winter {
background-color: var(--fo-facility-info-phosphorus-winter-bg);
}
.fo-rain-impact-row td {
background-color: var(--fo-rain-impact-row-bg);
}
.fo-facility-info-header-surface {
border: 1px solid var(--fo-facility-info-header-border);
background-color: var(--fo-facility-info-header-bg);
color: var(--fo-facility-info-header-fg);
}
input[type="file"] {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

+6
View File
@@ -1,3 +1,9 @@
import "./styles/base.css";
import "./styles/components.css";
import "./styles/components-black.css";
import "./styles/components-blue.css";
import "./styles/components-green.css";
import "./styles/components-red.css";
import { AppProvider } from "./providers";
import { cookies } from "next/headers";
@@ -0,0 +1,29 @@
@tailwind components;
@layer components {
.card {
@apply relative m-[0.6rem] box-border min-h-[2rem] rounded-[0.5rem] p-[0.6rem] text-[1.3rem] text-[#666];
}
.card-border {
@apply border border-[#dbe2e6] bg-[rgba(255,255,255,0.6)] shadow-blur;
}
.card-header {
@apply -mx-[0.6rem] flex items-center justify-between border-b border-b-[#e1e7eb] p-4;
}
.card-footer {
@apply -mx-[0.6rem] mt-2 flex items-center justify-center border-t border-t-[#e1e7eb] py-4;
}
[data-theme="black"] {
.card-border {
@apply border-[#3f4042] bg-[rgba(255,255,255,0.065)];
}
.card-header {
@apply border-b-[#3f4042];
}
}
input[type="file"] {
@apply w-[200px] h-[40px] px-3 py-2 border border-[#d1d5db] rounded-md bg-white text-sm cursor-pointer;
}
}
@@ -0,0 +1,29 @@
@tailwind components;
@layer components {
.card {
@apply relative m-[0.6rem] box-border min-h-[2rem] rounded-[0.5rem] p-[0.6rem] text-[1.3rem] text-[#666];
}
.card-border {
@apply border border-[#dbe2e6] bg-[rgba(255,255,255,0.6)] shadow-blur;
}
.card-header {
@apply -mx-[0.6rem] flex items-center justify-between border-b border-b-[#e1e7eb] p-4;
}
.card-footer {
@apply -mx-[0.6rem] mt-2 flex items-center justify-center border-t border-t-[#e1e7eb] py-4;
}
[data-theme="blue"] {
.card-border {
@apply border-[#3f4042] bg-[rgba(255,255,255,0.065)];
}
.card-header {
@apply border-b-[#3f4042];
}
}
input[type="file"] {
@apply w-[200px] h-[40px] px-3 py-2 border border-[#d1d5db] rounded-md bg-white text-sm cursor-pointer;
}
}
@@ -0,0 +1,29 @@
@tailwind components;
@layer components {
.card {
@apply relative m-[0.6rem] box-border min-h-[2rem] rounded-[0.5rem] p-[0.6rem] text-[1.3rem] text-[#666];
}
.card-border {
@apply border border-[#dbe2e6] bg-[rgba(255,255,255,0.6)] shadow-blur;
}
.card-header {
@apply -mx-[0.6rem] flex items-center justify-between border-b border-b-[#e1e7eb] p-4;
}
.card-footer {
@apply -mx-[0.6rem] mt-2 flex items-center justify-center border-t border-t-[#e1e7eb] py-4;
}
[data-theme="green"] {
.card-border {
@apply border-[#3f4042] bg-[rgba(255,255,255,0.065)];
}
.card-header {
@apply border-b-[#3f4042];
}
}
input[type="file"] {
@apply w-[200px] h-[40px] px-3 py-2 border border-[#d1d5db] rounded-md bg-white text-sm cursor-pointer;
}
}
+29
View File
@@ -0,0 +1,29 @@
@tailwind components;
@layer components {
.card {
@apply relative m-[0.6rem] box-border min-h-[2rem] rounded-[0.5rem] p-[0.6rem] text-[1.3rem] text-[#666];
}
.card-border {
@apply border border-[#dbe2e6] bg-[rgba(255,255,255,0.6)] shadow-blur;
}
.card-header {
@apply -mx-[0.6rem] flex items-center justify-between border-b border-b-[#e1e7eb] p-4;
}
.card-footer {
@apply -mx-[0.6rem] mt-2 flex items-center justify-center border-t border-t-[#e1e7eb] py-4;
}
[data-theme="red"] {
.card-border {
@apply border-[#3f4042] bg-[rgba(255,255,255,0.065)];
}
.card-header {
@apply border-b-[#3f4042];
}
}
input[type="file"] {
@apply w-[200px] h-[40px] px-3 py-2 border border-[#d1d5db] rounded-md bg-white text-sm cursor-pointer;
}
}
-9
View File
@@ -14,15 +14,6 @@
@apply -mx-[0.6rem] mt-2 flex items-center justify-center border-t border-t-[#e1e7eb] py-4;
}
[data-theme="black"] {
.card-border {
@apply border-[#3f4042] bg-[rgba(255,255,255,0.065)];
}
.card-header {
@apply border-b-[#3f4042];
}
}
input[type="file"] {
@apply w-[200px] h-[40px] px-3 py-2 border border-[#d1d5db] rounded-md bg-white text-sm cursor-pointer;
}
@@ -15,34 +15,34 @@ export function DrgtLegend() {
<div className="flex items-center gap-2 w-full">
<span
className="inline-block w-16 h-6"
style={{ backgroundColor: color, border: "1px solid #ccc" }}
style={{ backgroundColor: color, border: "1px solid var(--border)" }}
/>
<span className="text-xl text-gray-900 font-bold">{label}</span>
<span className="text-xl font-bold text-foreground">{label}</span>
</div>
);
}
return (
<div className="bg-white rounded-lg shadow-lg border border-gray-200 w-[160px] px-4 py-3">
<div className="w-[160px] rounded-lg border border-border bg-popover px-4 py-3 text-popover-foreground shadow-lg">
<div className="flex items-center justify-between mx-2 my-4">
<Label className="text-3xl text-gray-900 font-bold">가뭄정보 범례</Label>
<Label className="text-3xl font-bold text-foreground">가뭄정보 범례</Label>
</div>
<div className="mb-3">
<div className="w-full bg-gray-200 rounded-full h-1">
<div className="h-1 w-full rounded-full bg-secondary">
<div className="bg-blue-900 h-1 rounded-full transition-all duration-300 ease-in-out" />
</div>
</div>
<div className="w-[140px] mx-auto flex flex-col items-center">
<div className="bg-blue-900 text-white text-2xl text-center py-1 mb-2 rounded w-full">가 뭄 정 보</div>
<div className="space-y-1 w-full">
<Label className="text-xl text-gray-900 font-bold">관심</Label>
<Label className="text-xl font-bold text-foreground">관심</Label>
<LegendItem color="rgba(222,235,247, 1)" label="1회 미만" /> {/* 관심 - 1회 미만 */}
<LegendItem color="rgba(46, 117, 182, 1)" label="1회 이상" /> {/* 관심 - 1회 이상 */}
<Label className="text-xl text-gray-900 font-bold">주의</Label>
<Label className="text-xl font-bold text-foreground">주의</Label>
<LegendItem color="rgba(255, 242, 204, 1)" label="1회 미만" /> {/* 주의 - 1회 미만 */}
<LegendItem color="rgba(255, 217, 102, 1)" label="1회 이상" /> {/* 주의 - 1회 이상 */}
<Label className="text-xl text-gray-900 font-bold">관심 + 주의</Label>
<Label className="text-xl font-bold text-foreground">관심 + 주의</Label>
<LegendItem color="rgba(251, 229, 214, 1)" label="1회 미만" /> {/* 관심+주의 - 1회 미만 */}
<LegendItem color="rgba(237, 125, 49, 1)" label="1회 이상" /> {/* 관심+주의 - 1회 이상 */}
</div>
@@ -50,4 +50,4 @@ export function DrgtLegend() {
</div>
</div>
);
}
}
@@ -172,12 +172,12 @@ export function SearchBar({ gisMaps, ctpvCode, sggCode }: CtpvSggPrcsFcltSelectB
return (
<div className="flex items-center gap-4 bg-white min-h-[48px] min-w-[430px] px-5 py-3 rounded-lg shadow">
<div className="flex min-h-[48px] min-w-[430px] items-center gap-4 rounded-lg bg-card px-5 py-3 text-card-foreground shadow">
<div className={`flex items-center justify-center mr-8`}>
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">시도</Label>
<Label className="mr-2 whitespace-nowrap text-xl font-medium text-label">시도</Label>
<SelectBox
className="w-60 mr-8"
selectClass="rounded-lg bg-white"
selectClass="rounded-lg"
label=""
selectArray={[DEFAULT_OPTION, ...ctpv]}
value={ctpvCd}
@@ -187,10 +187,10 @@ export function SearchBar({ gisMaps, ctpvCode, sggCode }: CtpvSggPrcsFcltSelectB
}}
selectContentClass="z-[101]"
/>
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">시군구</Label>
<Label className="mr-2 whitespace-nowrap text-xl font-medium text-label">시군구</Label>
<SelectBox
className="w-48"
selectClass="w-48 rounded-lg bg-white truncate"
selectClass="w-48 truncate rounded-lg"
label=""
selectArray={
[DEFAULT_OPTION,
@@ -207,4 +207,4 @@ export function SearchBar({ gisMaps, ctpvCode, sggCode }: CtpvSggPrcsFcltSelectB
</div>
</div>
);
}
}
@@ -1,3 +1,6 @@
/**
* 지도에서 feature 선택시 팝업
*/
"use client";
import { useWrStore } from "@/app/store";
import CoSttnDetailPopup from "@/components/ui/map/detailPopup/coSttnDetailPopup/coSttnDetailPopup";
@@ -20,7 +23,7 @@ export default function MapFeaturePopup() {
coSttnMapDetail
} = useWrStore((state) => state);
const { pbnMapHeadList } = pbnMap;
const [isDetail, setIsDetail] = useState<any>(undefined);
const [isDetail, setIsDetail] = useState<any>(undefined); // isFeatureCd && layerNm
const [layerNm, setLayerNm] = useState<string>();
const searchParams = useSearchParams();
@@ -57,32 +60,32 @@ export default function MapFeaturePopup() {
useEffect(() => {
console.log(' layerNm ' , layerNm)
if (isFeatureCd && layerNm) {
if (layerNm === "tb_wr_bdst_ruse_fclt") {
if (layerNm === "tb_wr_bdst_ruse_fclt") { // 하수재이용시설
setIsDetail(isFeatureCd);
} else if (layerNm === "tb_il_swage_prcs_fclt") {
} else if (layerNm === "tb_il_swage_prcs_fclt") { // 하수처리시설
pbnMapDetaile(isFeatureCd).then((res) => {
console.log('res', res)
setIsDetail([res]);
});
} else if (layerNm === "tb_wr_rnwtr_utztn_fclt") {
} else if (layerNm === "tb_wr_rnwtr_utztn_fclt") { // 빗물이용시설
rainMapDetile(isFeatureCd).then((res) => {
setIsDetail(res);
});
} else if (layerNm === "tb_wr_rswtr_fclt") {
} else if (layerNm === "tb_wr_rswtr_fclt") { // 중수도시설
rswtMapDetile(isFeatureCd).then((res) => {
setIsDetail(res);
});
} else if (layerNm === "tb_wr_htdfl_fclt") {
} else if (layerNm === "tb_wr_htdfl_fclt") { // 온배수시설
htdfMapDetile(isFeatureCd).then((res) => {
setIsDetail(res);
});
}else if(layerNm ==='tb_wr_ruse_fclt_dmd_co'){
}else if(layerNm ==='tb_wr_ruse_fclt_dmd_co'){ // 수요처
coSttnMapDetail(isFeatureCd).then((res) => {
console.log('tb_wr_ruse_fclt_dmd_co ', res)
setIsDetail(res);
});
} else if (layerNm === "tb_wr_tmpr_dmd_co") {
} else if (layerNm === "tb_wr_tmpr_dmd_co") { // 잠재수요처
ldrmntMapDetile(isFeatureCd).then((res) => {
setIsDetail(res);
});
@@ -94,7 +97,7 @@ export default function MapFeaturePopup() {
return (
<>
{isDetail ? (
<div className="m-0 bg-white p-4 md:p-6 pb-24 box-border">
<div className="box-border m-0 bg-background p-4 pb-24 text-foreground md:p-6">
{layerNm === "tb_wr_bdst_ruse_fclt" ? (
<RuseDetailPopup bscFctCd={isDetail} />
) : layerNm === "tb_il_swage_prcs_fclt" ? (
@@ -94,7 +94,7 @@ export default function SearchInputText({
value={textValue || ""}
onChange={handleInputChange}
onKeyDown={handleKeyDown}
className="w-full h-[3.2rem] !text-xl pl-3 text-gray-600 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
className="h-[3.2rem] w-full rounded-md border border-input pl-3 !text-xl text-foreground focus:outline-none focus:ring-2 focus:ring-ring"
placeholder={`${label}을(를) 입력하세요`}
onClick={() => {
setTextValue("");
@@ -103,7 +103,7 @@ export default function SearchInputText({
}}
/>
{isOpen && (
<div className="absolute top-full left-0 z-50 mb-2 max-h-72 w-full overflow-auto rounded-md border border-gray-300 bg-white shadow-lg">
<div className="absolute left-0 top-full z-50 mb-2 max-h-72 w-full overflow-auto rounded-md border border-border bg-popover text-popover-foreground shadow-lg">
<div className="max-h-72">
{searchList.length > 0 ? (
searchList.map((item, index) => (
@@ -111,8 +111,8 @@ export default function SearchInputText({
key={index}
className={`w-full cursor-pointer overflow-auto px-5 py-3 text-left !text-xl ${
highlightIndex === index
? "bg-blue-100"
: "hover:bg-gray-100"
? "bg-accent text-accent-foreground"
: "hover:bg-accent hover:text-accent-foreground"
}`}
onClick={() => handleSuggestionClick(item.bscFctNm)}
role="button"
@@ -127,7 +127,7 @@ export default function SearchInputText({
</div>
))
) : (
<div className="!text-xl w-full px-5 py-3 hover:bg-gray-100 cursor-pointer overflow-auto">
<div className="w-full cursor-pointer overflow-auto px-5 py-3 !text-xl text-muted-foreground hover:bg-accent hover:text-accent-foreground">
검색 결과가 없습니다.
</div>
)}
@@ -200,17 +200,17 @@ const Map = () => {
// HTML 또는 React 오버레이 컴포넌트 구성
const overlayContent = `
<div class="rounded-md border border-gray-300 bg-white shadow-md text-sm w-[120px]">
<div class="w-[120px] rounded-md border border-border bg-popover text-sm text-popover-foreground shadow-md">
<!-- 헤더 -->
<div class="bg-cyan-700 text-white px-3 py-2 text-[11px] font-semibold text-center rounded-t-md">
<div class="rounded-t-md bg-primary px-3 py-2 text-center text-[11px] font-semibold text-primary-foreground">
${feature.get("swtrmt_fclt_nm") || "-"}
</div>
<!-- 테이블 -->
<table class="w-full text-sm text-gray-800 border-t border-gray-200">
<table class="w-full border-t border-border text-sm text-foreground">
<tbody>
<tr class="border-b border-gray-200">
<th class="bg-gray-100 text-gray-600 font-semibold text-[10px] text-left">용량(m³/일)</th>
<tr class="border-b border-border">
<th class="bg-secondary text-secondary-foreground font-semibold text-[10px] text-left">용량(m³/일)</th>
<td class="text-right text-[10px] font-bold">${formatNumber(feature.get("fclt_cpct"))}</td>
</tr>
</tbody>
@@ -60,12 +60,12 @@ export function SearchBar({
return (
<div className="flex justify-center w-full px-4 py-3">
<div className="flex items-center w-full max-w-screen-lg rounded-xl bg-white shadow-md px-6 py-4 space-x-3">
<div className="flex w-full max-w-screen-lg items-center space-x-3 rounded-xl bg-card px-6 py-4 text-card-foreground shadow-md">
<SelectBox
labelClass="w-[40px]"
className="w-[200px]"
label="시도"
selectClass="rounded-lg bg-white"
selectClass="rounded-lg"
selectArray={code.cityCd || []}
onSelectValue={(val) => onSelectValue(val.toString(), "ctpvCd")}
value={sidoValue}
@@ -74,12 +74,12 @@ export function SearchBar({
labelClass="w-[65px]"
className="w-[200px]"
label="시군구"
selectClass="rounded-lg bg-white"
selectClass="rounded-lg"
selectArray={filteredSigunData || []}
onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")}
value={sigunValue}
/>
<Label className="text-gray-600">하수재이용시설</Label>
<Label className="text-label">하수재이용시설</Label>
<SearchInputText
label="시설명"
initValue={undefined}
@@ -101,7 +101,7 @@ export function SearchBar({
/>
<Button
color="sky"
className="ml-2 text-white"
className="ml-2"
size="sm"
onClick={() => {
onApply();
@@ -24,13 +24,13 @@ export default function SelectedFcltList({
};
return (
<div className="fixed right-40 top-[8rem] w-[40rem] max-h-[130rem] overflow-auto bg-white shadow-lg rounded p-4 border border-gray-300 z-50">
<h3 className="bg-cyan-700 text-white text-xl font-bold mb-2 p-2">
<div className="fixed right-40 top-[8rem] z-50 max-h-[130rem] w-[40rem] overflow-auto rounded border border-border bg-popover p-4 text-popover-foreground shadow-lg">
<h3 className="mb-2 bg-primary p-2 text-xl font-bold text-primary-foreground">
선택된 시설
</h3>
<div className="overflow-x-auto">
<table className="w-full text-sm text-gray-800 border border-gray-200">
<thead className="bg-gray-100 text-gray-700 text-[13px]">
<table className="w-full border border-border text-sm text-foreground">
<thead className="bg-secondary text-[13px] text-secondary-foreground">
<tr>
<th className="px-3 py-2 text-left border-b">시설명</th>
<th className="px-3 py-2 text-right border-b">
@@ -60,7 +60,7 @@ export default function SelectedFcltList({
<td className="px-3 py-2 text-center">
<button
onClick={() => handleDelete(item.pbnstRuseFcltId)}
className="text-red-500 hover:text-red-700 text-sm font-bold"
className="text-sm font-bold text-destructive hover:opacity-80"
aria-label="삭제"
title="삭제"
>
@@ -71,7 +71,7 @@ export default function SelectedFcltList({
))
) : (
<tr>
<td colSpan={4} className="px-3 py-6 text-center text-[12px] text-gray-500">
<td colSpan={4} className="px-3 py-6 text-center text-[12px] text-muted-foreground">
선택된 시설이 없습니다.
</td>
</tr>
@@ -93,7 +93,7 @@ const EMPTY_DETAIL_FORM: Partial<DmdCoReqType> = {
};
export default function CoMatRvw() {
const readOnlyInputClass = "bg-slate-50 text-gray-700";
const readOnlyInputClass = "bg-secondary text-secondary-foreground";
const { auth, code, coRvw, coMatRvwInit, coMatRvwSearch, coMatRvwInqua } =
useWrStore((state) => state);
const { coMatRvwRes, coMatRvwCount } = coRvw;
@@ -305,7 +305,7 @@ export default function CoMatRvw() {
/>
<div className="col-span-7 w-full">
<div className="flex flex-row items-center">
<Label className="text-xl text-gray-600 mx-6">
<Label className="mx-6 text-xl text-label">
수요신청년도
</Label>
<SelectBox
@@ -522,7 +522,7 @@ export default function CoMatRvw() {
/>
{/* TODO: 총대장균군수 확인 필요*/}
{field.value != -1 ? (
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
<span className="pointer-events-none absolute inset-y-0 right-3 flex items-center text-xl text-placeholder">
이하
</span>
) : (
@@ -549,7 +549,7 @@ export default function CoMatRvw() {
value={formatNumberWithComma(field.value)}
readOnly
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
<span className="pointer-events-none absolute inset-y-0 right-3 flex items-center text-xl text-placeholder">
이상
</span>
</div>
@@ -573,7 +573,7 @@ export default function CoMatRvw() {
value={formatNumberWithComma(field.value)}
readOnly
/>
<span className=" text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
<span className="pointer-events-none absolute inset-y-0 right-3 flex items-center text-xl text-placeholder">
이하
</span>
</div>
@@ -601,7 +601,7 @@ export default function CoMatRvw() {
value={formatNumberWithComma(field.value)}
readOnly
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
<span className="pointer-events-none absolute inset-y-0 right-3 flex items-center text-xl text-placeholder">
이하
</span>
</div>
@@ -643,7 +643,7 @@ export default function CoMatRvw() {
value={formatNumberWithComma(field.value)}
readOnly
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
<span className="pointer-events-none absolute inset-y-0 right-3 flex items-center text-xl text-placeholder">
이하
</span>
</div>
@@ -670,7 +670,7 @@ export default function CoMatRvw() {
value={formatNumberWithComma(field.value)}
readOnly
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
<span className="pointer-events-none absolute inset-y-0 right-3 flex items-center text-xl text-placeholder">
이하
</span>
</div>
@@ -697,7 +697,7 @@ export default function CoMatRvw() {
value={formatNumberWithComma(field.value)}
readOnly
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
<span className="pointer-events-none absolute inset-y-0 right-3 flex items-center text-xl text-placeholder">
이하
</span>
</div>
@@ -748,7 +748,7 @@ export default function CoMatRvw() {
value={formatNumberWithComma(field.value)}
readOnly
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
<span className="pointer-events-none absolute inset-y-0 right-3 flex items-center text-xl text-placeholder">
이하
</span>
</div>
@@ -775,7 +775,7 @@ export default function CoMatRvw() {
value={formatNumberWithComma(field.value)}
readOnly
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
<span className="pointer-events-none absolute inset-y-0 right-3 flex items-center text-xl text-placeholder">
이하
</span>
</div>
@@ -347,7 +347,7 @@ export default function RuseCaseMng() {
}}
resetPageNo={resetPage}
/>
<span className="text-2xl font-bold text-gray-500">
<span className="text-2xl font-bold text-muted-foreground">
☞ 본 사례의 사업비는 재원협의 단계의 정보이므로, 최종확정된 비용이 아닙니다.
</span>
</div>
@@ -250,7 +250,7 @@ export default function RuseRvwHstry() {
</SelectBox>
<div className="col-span-4 w-full">
<div className="flex flex-row items-center">
<Label className="text-xl text-gray-600 ml-4 mr-4">
<Label className="ml-4 mr-4 text-xl text-label">
수요신청년도
</Label>
<div className="flex flex-row mt-2">
@@ -363,7 +363,7 @@ export default function CoSttn() {
getDemandCompList(searchText, pageCnt);
}}
rtnInputData={handleRtnInputData}
bodyClassNm={"w-full mx-auto bg-white rounded-lg shadow-md p-6"}
bodyClassNm={"w-full mx-auto bg-background rounded-lg shadow-md p-6"}
updateList={updateData}
openNetwork={true}
></SdmInsertForm>
@@ -171,7 +171,7 @@ export default function DrgtSttn() {
/>
<div className="col-span-5 w-auto">
<div className="flex flex-row items-center">
<Label className="text-xl text-gray-600 ml-8">조회기간</Label>
<Label className="ml-8 text-xl text-label">조회기간</Label>
<div className="flex flex-row w-1/4">
<DateMonthPicker
field={{
@@ -432,7 +432,7 @@ export default function PublicReuseFaciltyInsert() {
<div className="card-header text-2xl">
등록된 재이용시설 정보가 없습니다.
</div>
<div className="!mx-[1.6rem] !p-0r flex flex-col items-center justify-center h-64 border rounded-md">
<div className="!mx-[1.6rem] !p-0 flex flex-col items-center justify-center h-64 border rounded-md">
<p className="text-3xl mb-4">
신규등록 버튼을 클릭하여 재이용시설을 등록해주세요.
</p>
@@ -40,11 +40,10 @@ import { useTranslation } from "react-i18next";
const defaultSigunOptions: SelectDataType[] = [{ text: "전체", val: "0" }];
export default function HtdflSttn() {
const { auth, code, htdflSttn, htdflSttnSearch, initHtdfl, getLaeSeCdCheck } = useWrStore((state) => state);
const { auth, code, htdflSttn, htdflSttnSearch, getLaeSeCdCheck } = useWrStore((state) => state);
const { laeSeCd } = auth; // 지자체구분코드
const { htdflSttnRes, htdflSttnResCount } = htdflSttn;
const isDownloadTriggered = useRef(false);
const skipNextSearchRef = useRef(true);
const initializedSearchKeyRef = useRef<string | null>(null);
const { t } = useTranslation('base');
const [pageNation, setPageNation] = useState(1);
const [isChHead, setIsChHead] = useState(false);
@@ -61,6 +60,10 @@ export default function HtdflSttn() {
// chBuilding: chgBuilding[];
// }>({ isOpen: false, chBuilding: [] });
const [fcltNm, setFcltNm] = useState<string>("");
const [appliedSearch, setAppliedSearch] = useState<{
conValue: string;
fcltNm: string;
} | null>(null);
const alert = useAlertStore((state) => state.setMessage);
const isAllAreaUser = laeSeCd === "0";
const isLaeSeCdReady =
@@ -89,7 +92,7 @@ export default function HtdflSttn() {
setObjSearch({ ...objSearch, sgg: String(val) })
};
const getConValue = (): string => {
const getConValue = useCallback((): string => {
if (!objSearch.city || objSearch.city === "0") {
return "0";
}
@@ -97,7 +100,7 @@ export default function HtdflSttn() {
return String(objSearch.sgg);
}
return String(objSearch.city);
};
}, [objSearch]);
const getFixedConValue = useCallback((): string => {
if (laeSeCd === undefined || laeSeCd === "0") {
@@ -105,7 +108,7 @@ export default function HtdflSttn() {
}
return laeSeCd.length === 5 ? laeSeCd : laeSeCd.slice(0, 2);
}, [laeSeCd, objSearch]);
}, [laeSeCd, getConValue]);
const cellClick = (index: number | undefined, headId: string) => {
if (
@@ -117,14 +120,39 @@ export default function HtdflSttn() {
}
};
const excelClick = useCallback(() => {
if (!isLaeSeCdReady) {
const excelClick = useCallback(async () => {
if (!isLaeSeCdReady || !appliedSearch) {
return;
}
isDownloadTriggered.current = true;
htdflSttnSearch(true, 1, htdflSttnResCount, String(getFixedConValue()), fcltNm, code.cityCd, code.sggCd);
}, [htdflSttnSearch, fcltNm, htdflSttnResCount, code.cityCd, code.sggCd, getFixedConValue, isLaeSeCdReady]);
const excelData = await htdflSttnSearch(
true,
1,
htdflSttnResCount,
appliedSearch.conValue,
appliedSearch.fcltNm,
code.cityCd,
code.sggCd
);
if (excelData && excelData.length > 0) {
const sheetArray: SheetParameter[] = [{
data: excelData,
headerNum: 3,
headerRange: "A1:R3",
sheetName: "sheet1",
startCol: "A",
decimalArray0: [6, 11, 13, 15],
}];
excelDownloadNumStyle(
"온배수시설 현황",
"sample_htdflSttn.xlsx",
sheetArray
);
return;
}
alert(t("excelDownErrMsg"));
}, [htdflSttnSearch, htdflSttnResCount, appliedSearch, code.cityCd, code.sggCd, isLaeSeCdReady, alert, t]);
useEffect(() => {
getLaeSeCdCheck();
@@ -150,52 +178,39 @@ export default function HtdflSttn() {
});
}, [laeSeCd, code.sggCd, isLaeSeCdReady]);
useEffect(() => {
if (!isDownloadTriggered.current) {
return;
}
if (htdflSttn.excelList.length > 0) {
const sheetArray: SheetParameter[] = [{
data: htdflSttn.excelList,
headerNum: 3,
headerRange: "A1:R3",
sheetName: 'sheet1',
startCol: 'A',
decimalArray0: [6,11,13,15], // 0부터 시작
}];
excelDownloadNumStyle(
"온배수시설 현황",
"sample_htdflSttn.xlsx",
sheetArray
);
} else {
alert(t('excelDownErrMsg'));
}
isDownloadTriggered.current = false;
}, [htdflSttn.excelList, code]);
useEffect(() => {
if (!isLaeSeCdReady || htdflSttn.isInit) {
return;
}
skipNextSearchRef.current = true;
initHtdfl(false, 1, htdflSttnSize, String(getFixedConValue()), fcltNm, code.cityCd, code.sggCd);
}, [htdflSttn.isInit, initHtdfl, fcltNm, code.cityCd, code.sggCd, getFixedConValue, isLaeSeCdReady]);
useEffect(() => {
if (!isLaeSeCdReady) {
return;
}
if (skipNextSearchRef.current) {
skipNextSearchRef.current = false;
const initialSearchKey = laeSeCd ?? "0";
if (initializedSearchKeyRef.current === initialSearchKey) {
return;
}
htdflSttnSearch(false, pageNation, htdflSttnSize, String(getFixedConValue()), fcltNm, code.cityCd, code.sggCd);
const initialConValue =
laeSeCd === undefined || laeSeCd === "0"
? "0"
: laeSeCd.length === 5
? laeSeCd
: laeSeCd.slice(0, 2);
initializedSearchKeyRef.current = initialSearchKey;
setAppliedSearch({
conValue: initialConValue,
fcltNm: "",
});
setPageNation(1);
}, [isLaeSeCdReady, laeSeCd]);
useEffect(() => {
if (!isLaeSeCdReady || !appliedSearch) {
return;
}
htdflSttnSearch(false, pageNation, htdflSttnSize, appliedSearch.conValue, appliedSearch.fcltNm, code.cityCd, code.sggCd);
isResetPage(false)
}, [pageNation, htdflSttnSearch, fcltNm, code.cityCd, code.sggCd, getFixedConValue, isLaeSeCdReady]);
}, [pageNation, htdflSttnSearch, appliedSearch, code.cityCd, code.sggCd, isLaeSeCdReady]);
useEffect(() => {
const data = tableData(htdflSttnRes, { code });
@@ -215,6 +230,19 @@ export default function HtdflSttn() {
setBdstTable(chData);
}, [code, htdflSttnRes, htdflSttn.htdflSttHeadList]);
const handleSearch = useCallback(() => {
if (!isLaeSeCdReady) {
return;
}
setAppliedSearch({
conValue: String(getFixedConValue()),
fcltNm,
});
isResetPage(true);
setPageNation(1);
}, [fcltNm, getFixedConValue, isLaeSeCdReady]);
return (
<>
<ContentsDiv>
@@ -266,13 +294,7 @@ export default function HtdflSttn() {
<div className="flex flex-row space-x-2 items-center">
<Button
className={btnClass}
onClick={() => {
if (!isLaeSeCdReady) {
return;
}
htdflSttnSearch(false, 1,htdflSttnSize, String(getFixedConValue()), fcltNm, code.cityCd, code.sggCd);
isResetPage(true)
}}
onClick={handleSearch}
>
조회
</Button>
@@ -244,7 +244,7 @@ export default function PbnstSttn() {
setSearchYear(String(val))
}
/>
<Label className="w-full ml-4 text-xl text-gray-600">
<Label className="ml-4 w-full text-xl text-label">
{"하수처리시설명"}
</Label>
<SearchInput
@@ -1,5 +1,5 @@
/**
* 빗물이용시설 현황
* 재이용시설 현황 > 빗물이용시설 현황
*/
"use client";
import { useWrStore } from "@/app/store";
@@ -38,7 +38,7 @@ import { useTranslation } from "react-i18next";
const defaultSigunOptions: SelectDataType[] = [{ text: "전체", val: "0" }];
export default function RainSttn() {
const { auth, code, rainSttn, rainSttnSearch, rainSttnInit, getLaeSeCdCheck } = useWrStore(
const { auth, code, rainSttn, rainSttnSearch, getLaeSeCdCheck } = useWrStore(
(state) => state
);
const { rainSttnRes, rainSttnResCount } = rainSttn;
@@ -49,7 +49,6 @@ export default function RainSttn() {
const [bdstTable, setBdstTable] = useState<customRainBdst[]>();
const [selectSgg, setSelectSgg] = useState<SelectDataType[] | []>(defaultSigunOptions);
const [resetPage, isResetPage] = useState(false);
const [isDownloadTriggered, setIsDownloadTriggered] = useState(false);
const [objSearch, setObjSearch] = useState<objSearchType>({
sgg: "",
city: "",
@@ -60,14 +59,13 @@ export default function RainSttn() {
}>({ data: undefined, isOpen: false });
const [fcltNm, setFcltNm] = useState<string>("");
const [appliedSearch, setAppliedSearch] = useState({
conValue: "0",
fcltNm: "",
});
const [searchTrigger, setSearchTrigger] = useState(0);
const [appliedSearch, setAppliedSearch] = useState<{
conValue: string;
fcltNm: string;
} | null>(null);
const { t } = useTranslation("base");
const alert = useAlertStore((state) => state.setMessage);
const skipNextSearchRef = useRef(true);
const initializedSearchKeyRef = useRef<string | null>(null);
const isAllAreaUser = laeSeCd === "0";
const isLaeSeCdReady =
laeSeCd === "0" || laeSeCd.length === 2 || laeSeCd.length === 5;
@@ -100,7 +98,7 @@ export default function RainSttn() {
setObjSearch({ ...objSearch, sgg: String(val) });
};
const getConValue = (): string => {
const getConValue = useCallback((): string => {
if (!objSearch.city || objSearch.city === "0") {
return "0";
}
@@ -108,7 +106,7 @@ export default function RainSttn() {
return String(objSearch.sgg);
}
return String(objSearch.city);
};
}, [objSearch]);
const getFixedConValue = useCallback((): string => {
if (laeSeCd === undefined || laeSeCd === "0") {
@@ -116,7 +114,7 @@ export default function RainSttn() {
}
return laeSeCd.length === 5 ? laeSeCd : laeSeCd.slice(0, 2);
}, [laeSeCd, objSearch]);
}, [laeSeCd, getConValue]);
useEffect(() => {
getLaeSeCdCheck();
@@ -143,7 +141,12 @@ export default function RainSttn() {
}, [laeSeCd, code.sggCd, isLaeSeCdReady]);
useEffect(() => {
if (!isLaeSeCdReady || rainSttn.isInit) {
if (!isLaeSeCdReady) {
return;
}
const initialSearchKey = laeSeCd ?? "0";
if (initializedSearchKeyRef.current === initialSearchKey) {
return;
}
@@ -154,32 +157,16 @@ export default function RainSttn() {
? laeSeCd
: laeSeCd.slice(0, 2);
skipNextSearchRef.current = true;
initializedSearchKeyRef.current = initialSearchKey;
setAppliedSearch({
conValue: initialConValue,
fcltNm: "",
});
rainSttnInit(
false,
1,
rainSttnSize,
initialConValue,
"",
code as unknown as Record<string, SelectDataType[]>
);
}, [rainSttn.isInit, rainSttnInit, code, isLaeSeCdReady, laeSeCd]);
setPageNation(1);
}, [isLaeSeCdReady, laeSeCd]);
useEffect(() => {
if (!isLaeSeCdReady) {
return;
}
if (skipNextSearchRef.current) {
skipNextSearchRef.current = false;
return;
}
if (searchTrigger === 0) {
if (!isLaeSeCdReady || !appliedSearch) {
return;
}
@@ -194,49 +181,18 @@ export default function RainSttn() {
isResetPage(false);
}, [
pageNation,
searchTrigger,
rainSttnSearch,
code,
appliedSearch.conValue,
appliedSearch.fcltNm,
appliedSearch,
isLaeSeCdReady,
]);
useEffect(() => {
if (!isLaeSeCdReady) {
const excelClick = useCallback(async () => {
if (!isLaeSeCdReady || !appliedSearch) {
return;
}
if (!isDownloadTriggered) {
return;
}
if (rainSttn.excelList.length > 0) {
const sheetArray: SheetParameter[] = [{
data: rainSttn.excelList,
headerNum: 3,
headerRange: "A1:W3",
sheetName: 'sheet1',
startCol: 'A',
decimalArray0: [6,10,12,14,16] // 0부터 시작
}];
excelDownloadNumStyle(
"빗물이용시설 현황",
"sample_rainSttn.xlsx", // public 폴더에 있는 template 파일 경로
sheetArray
);
} else {
alert(t("excelDownErrMsg"));
}
setIsDownloadTriggered(false);
}, [rainSttn.excelList, isDownloadTriggered, alert, t, isLaeSeCdReady]);
const excelClick = useCallback(() => {
if (!isLaeSeCdReady) {
return;
}
setIsDownloadTriggered(true);
rainSttnSearch(
const excelData = await rainSttnSearch(
true,
1,
rainSttnResCount,
@@ -244,16 +200,39 @@ export default function RainSttn() {
appliedSearch.fcltNm,
code as unknown as Record<string, SelectDataType[]>
);
if (excelData && excelData.length > 0) {
const sheetArray: SheetParameter[] = [{
data: excelData,
headerNum: 3,
headerRange: "A1:W3",
sheetName: "sheet1",
startCol: "A",
decimalArray0: [6, 10, 12, 14, 16],
}];
excelDownloadNumStyle(
"빗물이용시설 현황",
"sample_rainSttn.xlsx",
sheetArray
);
return;
}
alert(t("excelDownErrMsg"));
}, [
rainSttnSearch,
rainSttnResCount,
code,
appliedSearch.conValue,
appliedSearch.fcltNm,
appliedSearch,
isLaeSeCdReady,
alert,
t,
]);
const handleSearch = useCallback(() => {
if (!isLaeSeCdReady) {
return;
}
const nextSearch = {
conValue: String(getFixedConValue()),
fcltNm,
@@ -262,8 +241,7 @@ export default function RainSttn() {
setAppliedSearch(nextSearch);
isResetPage(true);
setPageNation(1);
setSearchTrigger((prev) => prev + 1);
}, [fcltNm, getFixedConValue]);
}, [fcltNm, getFixedConValue, isLaeSeCdReady]);
useEffect(() => {
const data = tableData(rainSttnRes, { code });
@@ -38,7 +38,7 @@ import { useTranslation } from "react-i18next";
const defaultSigunOptions: SelectDataType[] = [{ text: "전체", val: "0" }];
export default function RswtSttn() {
const { auth, code, rswtSttn, rswtSttnSearch, rswtSttnInit, getLaeSeCdCheck } = useWrStore(
const { auth, code, rswtSttn, rswtSttnSearch, getLaeSeCdCheck } = useWrStore(
(state) => state
);
@@ -49,17 +49,15 @@ export default function RswtSttn() {
const [isChHead, setIsChHead] = useState(false);
const [resetPage, isResetPage] = useState(false);
const [bdstTable, setBdstTable] = useState<customRswtBdst[]>();
const isDownloadTriggered = useRef(false);
const skipNextSearchRef = useRef(true);
const initializedSearchKeyRef = useRef<string | null>(null);
const [selectSgg, setSelectSgg] = useState<SelectDataType[] | []>(defaultSigunOptions)
const [objSearch, setObjSearch] = useState<objSearchType>({ sgg: "", city: "" });
const [detailModal, setDetailModal] = useState<{ isOpen: boolean, data: customRswtBdst | undefined }>({ data: undefined, isOpen: false })
const [fcltNm, setFcltNm] = useState<string>("");
const [appliedSearch, setAppliedSearch] = useState({
conValue: "0",
fcltNm: "",
});
const [searchTrigger, setSearchTrigger] = useState(0);
const [appliedSearch, setAppliedSearch] = useState<{
conValue: string;
fcltNm: string;
} | null>(null);
const { t } = useTranslation('base');
const alert = useAlertStore((state) => state.setMessage);
const isAllAreaUser = laeSeCd === "0";
@@ -95,7 +93,7 @@ export default function RswtSttn() {
setObjSearch({ ...objSearch, sgg: String(val) })
};
const getConValue = (): string => {
const getConValue = useCallback((): string => {
if (!objSearch.city || objSearch.city === "0") {
return "0";
}
@@ -103,7 +101,7 @@ export default function RswtSttn() {
return String(objSearch.sgg);
}
return String(objSearch.city);
};
}, [objSearch]);
const getFixedConValue = useCallback((): string => {
if (laeSeCd === undefined || laeSeCd === "0") {
@@ -111,7 +109,7 @@ export default function RswtSttn() {
}
return laeSeCd.length === 5 ? laeSeCd : laeSeCd.slice(0, 2);
}, [laeSeCd, objSearch]);
}, [laeSeCd, getConValue]);
useEffect(() => {
getLaeSeCdCheck();
@@ -138,7 +136,12 @@ export default function RswtSttn() {
}, [laeSeCd, code.sggCd, isLaeSeCdReady]);
useEffect(() => {
if (!isLaeSeCdReady || rswtSttn.isInit) {
if (!isLaeSeCdReady) {
return;
}
const initialSearchKey = laeSeCd ?? "0";
if (initializedSearchKeyRef.current === initialSearchKey) {
return;
}
@@ -149,20 +152,13 @@ export default function RswtSttn() {
? laeSeCd
: laeSeCd.slice(0, 2);
skipNextSearchRef.current = true;
initializedSearchKeyRef.current = initialSearchKey;
setAppliedSearch({
conValue: initialConValue,
fcltNm: "",
});
rswtSttnInit(
false,
1,
rswtSttnSize,
initialConValue,
"",
code as unknown as Record<string, SelectDataType[]>
);
}, [rswtSttn.isInit, rswtSttnInit, code, isLaeSeCdReady, laeSeCd]);
setPageNation(1);
}, [isLaeSeCdReady, laeSeCd]);
useEffect(() => {
const data = tableData(rswtSttnRes, { code });
@@ -194,56 +190,47 @@ export default function RswtSttn() {
setBdstTable(chData);
}, [code, rswtSttnRes]);
useEffect(() => {
if (!isDownloadTriggered.current) {
const excelClick = useCallback(async () => {
if (!isLaeSeCdReady || !appliedSearch) {
return;
}
if (rswtSttn.excelList.length > 0) {
const excelData = await rswtSttnSearch(
true,
1,
rswtSttnResCount,
appliedSearch.conValue,
appliedSearch.fcltNm,
code as unknown as Record<string, SelectDataType[]>
);
if (excelData && excelData.length > 0) {
const sheetArray: SheetParameter[] = [{
data: rswtSttn.excelList,
data: excelData,
headerNum: 3,
headerRange: "A1:W3",
sheetName: 'sheet1',
startCol: 'A',
decimalArray0: [6,10,12,16], // 0부터 시작
sheetName: "sheet1",
startCol: "A",
decimalArray0: [6, 10, 12, 16],
}];
excelDownloadNumStyle(
"중수도시설 현황",
"sample_rswtSttn.xlsx",
sheetArray
);
} else {
alert(t('excelDownErrMsg'));
}
isDownloadTriggered.current = false;
}, [rswtSttn.excelList]);
const excelClick = useCallback(() => {
if (!isLaeSeCdReady) {
return;
}
isDownloadTriggered.current = true;
rswtSttnSearch(true, 1, rswtSttnResCount, appliedSearch.conValue, appliedSearch.fcltNm, code as unknown as Record<string, SelectDataType[]>);
}, [rswtSttnSearch, rswtSttnResCount, code, appliedSearch.conValue, appliedSearch.fcltNm, isLaeSeCdReady]);
alert(t("excelDownErrMsg"));
}, [rswtSttnSearch, rswtSttnResCount, code, appliedSearch, isLaeSeCdReady, alert, t]);
useEffect(() => {
if (!isLaeSeCdReady) {
return;
}
if (skipNextSearchRef.current) {
skipNextSearchRef.current = false;
return;
}
if (searchTrigger === 0) {
if (!isLaeSeCdReady || !appliedSearch) {
return;
}
rswtSttnSearch(false, pageNation, rswtSttnSize, appliedSearch.conValue, appliedSearch.fcltNm, code as unknown as Record<string, SelectDataType[]>);
isResetPage(false)
}, [pageNation, searchTrigger, rswtSttnSearch, code, appliedSearch.conValue, appliedSearch.fcltNm, isLaeSeCdReady]);
}, [pageNation, rswtSttnSearch, code, appliedSearch, isLaeSeCdReady]);
const handleSearch = useCallback(() => {
if (!isLaeSeCdReady) {
@@ -258,7 +245,6 @@ export default function RswtSttn() {
setAppliedSearch(nextSearch);
isResetPage(true);
setPageNation(1);
setSearchTrigger((prev) => prev + 1);
}, [fcltNm, getFixedConValue, isLaeSeCdReady]);
return (
@@ -14,7 +14,7 @@ export const headMakeprfmnc = (
.map((head: TableUpperProps) =>
!head.upSequnce ? (
<TableHead
className={`border border-gray-600 px-2 py-1 `}
className={`border border-border px-2 py-1 `}
rowSpan={2}
key={head.id}
style={{ width: head.widthPercent ? `${head.widthPercent}%` : undefined }}
@@ -23,7 +23,7 @@ export const headMakeprfmnc = (
</TableHead>
) : head.upSequnce === 1 ? (
<TableHead
className={`border border-gray-600 px-2 py-1 `}
className={`border border-border px-2 py-1 `}
key={head.id}
rowSpan={1}
colSpan={
@@ -43,7 +43,7 @@ export const headMakeprfmnc = (
.map((head: TableUpperProps) =>
makeHeadNm?.find((ch) => ch.headNm === head.upName) ? (
<TableHead
className={`border border-gray-600 px-2 py-1 `}
className={`border border-border px-2 py-1 `}
key={head.id}
style={{ width: head.widthPercent ? `${head.widthPercent}%` : undefined }}
>
@@ -250,7 +250,7 @@ export default function Page() {
<SearchDiv>
<SearchFormLeft>
<div className="col-span-2 flex flex-row space-x-2">
<Label className="w-fit shrink-0 whitespace-nowrap self-center text-xl text-gray-600">
<Label className="w-fit shrink-0 whitespace-nowrap self-center text-xl text-label">
{"지도점검년도"}
</Label>
<SelectBox
@@ -265,7 +265,7 @@ export default function Page() {
/>
</div>
<div className="col-span-2 flex flex-row space-x-2">
<Label className="w-fit shrink-0 whitespace-nowrap self-center text-xl text-gray-600 ">
<Label className="w-fit shrink-0 whitespace-nowrap self-center text-xl text-label ">
{"시설구분"}
</Label>
<SelectBox
@@ -281,7 +281,7 @@ export default function Page() {
/>
</div>
<div className="col-span-2 flex flex-row space-x-2">
<Label className="w-fit shrink-0 whitespace-nowrap self-center text-xl text-gray-600 ">
<Label className="w-fit shrink-0 whitespace-nowrap self-center text-xl text-label ">
{"시설명"}
</Label>
<SearchInput
@@ -378,7 +378,7 @@ export default function Page() {
readonly={mdfcnAuthrtYn === 'N'}
/>
)}
<span className="text-2xl font-bold text-gray-500">
<span className="text-2xl font-bold text-muted-foreground">
☞ 연도별 점검일 최종 데이터를 기준으로 통계 작성합니다.
</span>
</ListDiv>
@@ -240,7 +240,7 @@ export default function PrfmncPrfmncReg() {
<SearchDiv>
<SearchFormLeft>
<div className="col-span-3 flex flex-row space-x-2">
<Label className="w-fit shrink-0 whitespace-nowrap self-center text-xl text-gray-600 ">
<Label className="w-fit shrink-0 whitespace-nowrap self-center text-xl text-label ">
{"실적등록년도"}
</Label>
<SelectBox
@@ -255,7 +255,7 @@ export default function PrfmncPrfmncReg() {
/>
</div>
<div className="col-span-3 flex flex-row space-x-2">
<Label className="w-fit shrink-0 whitespace-nowrap self-center text-xl text-gray-600">
<Label className="w-fit shrink-0 whitespace-nowrap self-center text-xl text-label">
{"시설명"}
</Label>
<SearchInput
@@ -301,7 +301,7 @@ export default function Page() {
<SearchDiv>
<SearchFormLeft>
<div className="col-span-2 flex flex-row space-x-2">
<Label className="w-28 mt-2 text-xl text-gray-600">
<Label className="mt-2 w-28 text-xl text-label">
{"시도"}
</Label>
<SelectBox
@@ -318,7 +318,7 @@ export default function Page() {
/>
</div>
<div className="col-span-2 flex flex-row space-x-2">
<Label className="w-28 mt-2 text-xl text-gray-600">
<Label className="mt-2 w-28 text-xl text-label">
{"시군구"}
</Label>
<SelectBox
@@ -337,7 +337,7 @@ export default function Page() {
/>
</div>
<div className="col-span-2 flex flex-row space-x-2">
<Label className="w-48 mt-2 text-xl text-gray-600">
<Label className="mt-2 w-48 text-xl text-label">
{"지도점검년도"}
</Label>
<SelectBox
@@ -352,7 +352,7 @@ export default function Page() {
/>
</div>
<div className="col-span-2 flex flex-row space-x-2">
<Label className="w-28 mt-2 text-xl text-gray-600">
<Label className="mt-2 w-28 text-xl text-label">
{"시설구분"}
</Label>
<SelectBox
@@ -368,7 +368,7 @@ export default function Page() {
/>
</div>
<div className="col-span-2 flex flex-row space-x-2">
<Label className="w-28 mt-2 text-xl text-gray-600">
<Label className="mt-2 w-28 text-xl text-label">
{"시설명"}
</Label>
<SearchInput
@@ -626,7 +626,7 @@ export default function PbnstPrfmncSttn() {
{/* <div className="flex flex-row items-center"> */}
<div className="flex flex-row items-center gap-x-4 flex-nowrap">
{/* <Label className="w-full text-xl text-gray-600"> */}
<Label className="flex-none whitespace-nowrap text-xl text-gray-600">
<Label className="flex-none whitespace-nowrap text-xl text-label">
하수 재이용년도</Label>
<SelectBox
// className="w-full h-full text-2xl mx-4"
+1 -1
View File
@@ -11,7 +11,7 @@ import {
ruseSeCd,
yn,
} from "./basicCode";
export const btnClass = "p-4 m-2 text-white rounded-sm text-lg font-medium ";
export const btnClass = "p-4 m-2 rounded-sm text-lg font-medium text-primary-foreground ";
const laeSecd = "41";
export const lwCds: Record<string, SelectDataType[]> = {};
+3
View File
@@ -1,3 +1,6 @@
/**
* 표준공사비 산정 섹션에서 후보지 조회
*/
import { getFetch } from "@/components/lib/api";
import type { FacilityInfo, SubsInfo } from "./type";
import { DEV_URL_FO } from "@/components/lib/config";
@@ -9,8 +9,8 @@ export const pubFacHeadListExport: TableUpperProps[] = [
{ id: "addr", title: "위치", widthPercent: 24 },
{ id: "efflWqltStndrdNm", title: "지역구분", widthPercent: 15 },
{ id: "fctCpc", title: "시설용량(m³/일)", widthPercent: 12 },
{ id: "ruse", title: "재이용\n여부", widthPercent: 8 },
{ id: "rswtrYn", title: "등록\n여부", widthPercent: 6 },
{ id: "ruse", title: "등록\n여부", widthPercent: 8 },
{ id: "rswtrYn", title: "재이용\n여부", widthPercent: 6 },
];
export const pbnstMngUpHeadListExport: TableUpperProps[] = [
@@ -103,6 +103,7 @@ export const pubFacStateExport: pubFacState = {
fclts: null,
},
};
// 재사용시설에 대한 정보 조회
export const checkWrRegList = async (get: any, set: any) => {
const { pbnst } = get();
@@ -126,29 +127,24 @@ export const checkWrRegList = async (get: any, set: any) => {
};
// 하수처리시설 정보 조회
const hasuListFetch = async (
get: any,
set: any,
_get: any,
_set: any,
page: number = 1,
sggOrCity: string, // 해당 지자체의 하수처리시설 조회
searchTxt?: string
) => {
const wrData = await checkWrRegList(get, set); // 재사용시설에 대한 정보 조회
const wrBscFctCdSet = new Set(
wrData
.map((wrItem: any) => (typeof wrItem === "string" ? wrItem.trim() : ""))
);
let url = `/prcsFclt/ruseInfo/reg/${page}/${DEFAULT_LIST_SIZE}/A/${sggOrCity ? sggOrCity : "0"}/0/1/SW`;
let url = `/reuseFc/pbnstRegList/${page}/${DEFAULT_LIST_SIZE}/A/${sggOrCity ? sggOrCity : "0"}/0/1/SW`;
if (searchTxt?.trim()) {
url += `?swtrmtFcltNm=${encodeURIComponent(searchTxt.trim())}`;
}
const fcltRes = await getFetchWithCount(fetchType.COMMON, url); // fo
const fcltRes = await getFetchWithCount("wr", url);
const hasuList = fcltRes.data.map((item: any) => {
return {
...item,
fctCpc: Number(item.fctCpc) || 0,
ruse: item?.bscFctCd && wrBscFctCdSet.has(item.bscFctCd) ? "Y" : "N",
ruse: item?.ruse ?? "N",
rswtrYn: item?.rswtrYn ?? "N",
};
});
@@ -76,6 +76,8 @@ export const pubHasuZod = z.object({
tel: z.string().default(""),
fctCpc: z.number().default(0), // 시설용량
addr: z.string().nullish(), // 위치
ruse: z.string().default("N"), // 등록여부
rswtrYn: z.string().default("N"), // 재이용여부
});
export type pubHasuType = z.infer<typeof pubHasuZod>;
@@ -1,5 +1,5 @@
import type { CommonTableProps } from "@/app/store/common/type/header";
import { fetchType, getFetchWithCount } from "@/components/lib/api";
import { getFetchWithCount } from "@/components/lib/api";
import type { SelectDataType } from "@common/business_components/ui";
import type { z } from "zod";
import type { customHtdflBdst } from "../../fcltReg/htdflReg";
@@ -26,10 +26,10 @@ export type htdflSttnState = {
type htdflSttnActions = {
initHtdfl: (isExcel: boolean, page: number, total: number, con: string, txt: string,
sidoCode: SelectDataType[],
sggCode: SelectDataType[]) => void;
sggCode: SelectDataType[]) => Promise<string[][] | void>;
htdflSttnSearch: (isExcel: boolean, page: number, total: number, con: string, txt: string,
sidoCode: SelectDataType[],
sggCode: SelectDataType[]) => void;
sggCode: SelectDataType[]) => Promise<string[][] | void>;
htdflChSttnHead: (
headList: z.infer<typeof sttnHeadListType>,
) => void;
@@ -63,36 +63,51 @@ const initFetch = async (page: number, total: number, con: string, txt: string)
export const htdflSttnActionsExport: (set: any) => htdflSttnActions = (
set: any
) => {
const fetchHtdflSttn = async (
isExcel: boolean,
page: number,
total: number,
con: string,
txt: string,
sidoCode: SelectDataType[],
sggCode: SelectDataType[]
): Promise<string[][] | void> => {
const htdflSttnRes = await initFetch(page, total, con, txt);
if (isExcel) {
const htdflExcel = excelHtdflSttnParsing(htdflSttnRes, sidoCode, sggCode);
set((state: htdflSttnState) => {
return {
...state,
htdflSttn: {
...state.htdflSttn,
excelList: htdflExcel,
htdflSttnResCount: htdflSttnRes.count,
},
};
});
return htdflExcel;
}
set((state: htdflSttnState) => {
return {
...state,
htdflSttn: {
...state.htdflSttn,
htdflSttnRes: htdflSttnRes.data,
htdflSttnResCount: htdflSttnRes.count,
isInit: false,
},
};
});
return;
};
return {
initHtdfl: async (isExcel: boolean, page: number, total: number, con: string, txt: string,
sidoCode: SelectDataType[],
sggCode: SelectDataType[]) => {
try {
const htdflSttnRes = await initFetch(page, total, con, txt);
if (isExcel) {
const htdflExcel = excelHtdflSttnParsing(htdflSttnRes, sidoCode, sggCode);
set((state: htdflSttnState) => {
return {
...state,
htdflSttn: {
...state.htdflSttn,
excelList: htdflExcel,
},
};
});
} else {
set((state: htdflSttnState) => {
return {
...state,
htdflSttn: {
...state.htdflSttn,
htdflSttnRes: htdflSttnRes.data,
htdflSttnResCount: htdflSttnRes.count,
isInit: false,
},
};
});
}
return await fetchHtdflSttn(isExcel, page, total, con, txt, sidoCode, sggCode);
} catch (error) {
console.log(error);
}
@@ -101,38 +116,7 @@ export const htdflSttnActionsExport: (set: any) => htdflSttnActions = (
sidoCode: SelectDataType[],
sggCode: SelectDataType[]) => {
try {
const htdflSttnRes = await getFetchWithCount(
fetchType.WR,
`/drain/facilityList` +
(page ? `/${page}` : "/1") +
(total ? `/${total}` : "/20") +
(con ? `/${con}` : "/0") +
(txt ? `/${txt}` : "")
);
if (isExcel) {
const htdflExcel = excelHtdflSttnParsing(htdflSttnRes, sidoCode, sggCode);
set((state: htdflSttnState) => {
return {
...state,
htdflSttn: {
...state.htdflSttn,
excelList: htdflExcel,
},
};
});
} else
set((state: htdflSttnState) => {
return {
...state,
htdflSttn: {
...state.htdflSttn,
htdflSttnRes: htdflSttnRes.data,
htdflSttnResCount: htdflSttnRes.count,
isInit: false,
},
};
});
return await fetchHtdflSttn(isExcel, page, total, con, txt, sidoCode, sggCode);
} catch (erTxt) {
console.log("error", erTxt);
}
@@ -154,4 +138,4 @@ export const htdflSttnActionsExport: (set: any) => htdflSttnActions = (
}
}
}
@@ -102,8 +102,8 @@ export type rainSttnState = {
};
};
type rainSttnActions = {
rainSttnInit: (isExcel: boolean, page: number, total: number, con: string, txt: string,code: Record<string, SelectDataType[]> ) => void
rainSttnSearch: (isExcel: boolean, page: number, total: number, con: string, txt: string,code: Record<string, SelectDataType[]>) => void;
rainSttnInit: (isExcel: boolean, page: number, total: number, con: string, txt: string,code: Record<string, SelectDataType[]> ) => Promise<string[][] | void>
rainSttnSearch: (isExcel: boolean, page: number, total: number, con: string, txt: string,code: Record<string, SelectDataType[]>) => Promise<string[][] | void>;
rainChSttnHead: (
headList: z.infer<typeof sttnHeadListType>,
) => void;
@@ -129,81 +129,63 @@ export const rainSttnStateExport: rainSttnState = {
export const rainSttnActionsExport: (set: any) => rainSttnActions = (
set: any
) => {
const fetchRainSttn = async (
isExcel: boolean,
page: number,
total: number,
con: string,
txt: string,
code: Record<string, SelectDataType[]>
): Promise<string[][] | void> => {
const rainSttnRes = await getFetchWithCount(
fetchType.WR,
`/reuseFc/rainyInfoList` +
(page ? `/${page}` : "/1") +
(total ? `/${total}` : "/15") +
(con ? `/${con}` : "/0") +
(txt ? `/${txt}` : "")
);
if (isExcel) {
const rainExcel = excelRainSttnParsing(rainSttnRes, code);
set((state: rainSttnState) => {
return {
...state,
rainSttn: {
...state.rainSttn,
excelList: rainExcel,
rainSttnResCount: rainSttnRes.count,
},
};
});
return rainExcel;
}
set((state: rainSttnState) => {
return {
...state,
rainSttn: {
...state.rainSttn,
rainSttnRes: rainSttnRes.data,
rainSttnResCount: rainSttnRes.count,
isInit: false,
},
};
});
return;
};
return {
rainSttnInit: async (isExcel: boolean, page: number, total: number, con: string, txt: string, code: Record<string, SelectDataType[]>) => {
try {
const rainSttnRes = await getFetchWithCount(
fetchType.WR,
`/reuseFc/rainyInfoList` +
(page ? `/${page}` : "/1") +
(total ? `/${total}` : "/15") +
(con ? `/${con}` : "/0") +
(txt ? `/${txt}` : "")
);
if (isExcel) {
const rainExcel = excelRainSttnParsing(rainSttnRes, code);
set((state: rainSttnState) => {
return {
...state,
rainSttn: {
...state.rainSttn,
excelList: rainExcel,
rainSttnResCount: rainSttnRes.count,
},
};
});
} else {
set((state: rainSttnState) => {
return {
...state,
rainSttn: {
...state.rainSttn,
rainSttnRes: rainSttnRes.data,
rainSttnResCount: rainSttnRes.count,
isInit: false,
},
};
});
}
return await fetchRainSttn(isExcel, page, total, con, txt, code);
} catch (e) {
console.log(e)
}
},
rainSttnSearch: async (isExcel: boolean, page: number, total: number, con: string, txt: string,code: Record<string, SelectDataType[]>) => {
try {
const rainSttnRes = await getFetchWithCount(
fetchType.WR,
`/reuseFc/rainyInfoList` +
(page ? `/${page}` : "/1") +
(total ? `/${total}` : "/15") +
(con ? `/${con}` : "/0") +
(txt ? `/${txt}` : "")
);
if (isExcel) {
const rainExcel = excelRainSttnParsing(rainSttnRes,code);
set((state: rainSttnState) => {
return {
...state,
rainSttn: {
...state.rainSttn,
excelList: rainExcel,
rainSttnResCount: rainSttnRes.count,
},
};
});
} else
set((state: rainSttnState) => {
return {
...state,
rainSttn: {
...state.rainSttn,
rainSttnRes: rainSttnRes.data,
rainSttnResCount: rainSttnRes.count,
isInit: false,
},
};
});
return await fetchRainSttn(isExcel, page, total, con, txt, code);
} catch (erTxt) {
console.log("error", erTxt);
}
@@ -30,8 +30,8 @@ export type rswtSttnState = {
};
type rswtSttnActions = {
rswtSttnInit: (isExcel: boolean, page: number, total: number, con: string, txt: string, code: Record<string,SelectDataType[]>) => void;
rswtSttnSearch: (isExcel: boolean, page: number, total: number, con: string, txt: string, code: Record<string,SelectDataType[]>) => void;
rswtSttnInit: (isExcel: boolean, page: number, total: number, con: string, txt: string, code: Record<string,SelectDataType[]>) => Promise<string[][] | void>;
rswtSttnSearch: (isExcel: boolean, page: number, total: number, con: string, txt: string, code: Record<string,SelectDataType[]>) => Promise<string[][] | void>;
rswtChSttnHead: (
headList: z.infer<typeof sttnHeadListType>,
) => void;
@@ -55,79 +55,62 @@ export const rswtSttnStateExport: rswtSttnState = {
export const rswtSttnActionsExport: (set: any) => rswtSttnActions = (
set: any
) => {
const fetchRswtSttn = async (
isExcel: boolean,
page: number,
total: number,
con: string,
txt: string,
code: Record<string, SelectDataType[]>
): Promise<string[][] | void> => {
const rswtSttnRes = await getFetchWithCount(
fetchType.WR,
`/reuseFc/waterreuseFc` +
(page ? `/${page}` : "/1") +
(total ? `/${total}` : "/10") +
(con ? `/${con}` : "/0") +
(txt ? `/${txt}` : "")
);
if (isExcel) {
const rswtExcel = excelRSwtSttnParsing(rswtSttnRes, code);
set((state: rswtSttnState) => {
return {
...state,
rswtSttn: {
...state.rswtSttn,
excelList: rswtExcel,
rswtSttnResCount: rswtSttnRes.count,
},
};
});
return rswtExcel;
}
set((state: rswtSttnState) => {
return {
...state,
rswtSttn: {
...state.rswtSttn,
rswtSttnRes: rswtSttnRes.data,
rswtSttnResCount: rswtSttnRes.count,
isInit: false,
},
};
});
return;
};
return {
rswtSttnInit: async (isExcel: boolean, page: number, total: number, con: string, txt: string, code: Record<string,SelectDataType[]>) => {
try {
const rswtSttnRes = await getFetchWithCount(
fetchType.WR,
`/reuseFc/waterreuseFc` +
(page ? `/${page}` : "/1") +
(total ? `/${total}` : "/10") +
(con ? `/${con}` : "/0") +
(txt ? `/${txt}` : "")
);
if (isExcel) {
const rswtExcel = excelRSwtSttnParsing(rswtSttnRes, code);
set((state: rswtSttnState) => {
return {
...state,
rswtSttn: {
...state.rswtSttn,
excelList: rswtExcel,
},
};
});
}
set((state: rswtSttnState) => {
return {
...state,
rswtSttn: {
...state.rswtSttn,
rswtSttnRes: rswtSttnRes.data,
rswtSttnResCount: rswtSttnRes.count,
isInit: false,
},
};
});
return await fetchRswtSttn(isExcel, page, total, con, txt, code);
} catch (e) {
console.log(e)
}
},
rswtSttnSearch: async (isExcel: boolean, page: number, total: number, con: string, txt: string,code: Record<string,SelectDataType[]>) => {
try {
const rswtSttnRes = await getFetchWithCount(
fetchType.WR,
`/reuseFc/waterreuseFc` +
(page ? `/${page}` : "/1") +
(total ? `/${total}` : "/10") +
(con ? `/${con}` : "/0") +
(txt ? `/${txt}` : "")
);
if (isExcel) {
const rswtExcel = excelRSwtSttnParsing(rswtSttnRes, code);
set((state: rswtSttnState) => {
return {
...state,
rswtSttn: {
...state.rswtSttn,
excelList: rswtExcel,
},
};
});
} else
set((state: rswtSttnState) => {
return {
...state,
rswtSttn: {
...state.rswtSttn,
rswtSttnRes: rswtSttnRes.data,
rswtSttnResCount: rswtSttnRes.count,
isInit: false,
},
};
});
return await fetchRswtSttn(isExcel, page, total, con, txt, code);
} catch (erTxt) {
console.log("error", erTxt);
}
+1 -1
View File
@@ -1,4 +1,4 @@
export const formTableName = 'text-xl my-4 font-bold'
export const commonButonSize = "sm"
export const formH1Class = "bg-[#eaf3fb] text-xl text-center border-2 p-2 mt-4 font-medium text-gray-500"
export const formH1Class = "form-section-title"
export const formH2Class = "text-xl my-4 underline font-bold"
+12 -12
View File
@@ -252,7 +252,7 @@ export const chRowspanTable = (
"splyUswtrUtztnQnt",
"ruseFcltUtztnQnt",
].includes(head.id)
? "underline text-blue-600 cursor-pointer"
? "cursor-pointer underline text-primary"
: ""
}
>
@@ -270,7 +270,7 @@ export const chRowspanTable = (
<TableRow>
<TableCell
colSpan={headList.length}
className="h-24 text-center text-gray-500"
className="h-24 text-center text-muted-foreground"
>
조회된 결과가 없습니다.
</TableCell>
@@ -361,9 +361,9 @@ export const InputTableBody = (
const isTotal = data["구분"] === 0;
const bgClass = isTotal
? "bg-[#d5e1eb] font-bold"
? "bg-accent text-accent-foreground font-bold"
: isSubTotal
? "bg-[#e6eef5] font-semibold"
? "bg-secondary text-secondary-foreground font-semibold"
: "";
return (
@@ -381,7 +381,7 @@ export const InputTableBody = (
<TableCell
key={head.id + headIndex}
colSpan={2}
className="font-bold text-center bg-[#d5e1eb]"
className="bg-accent text-accent-foreground font-bold text-center"
>
총합계
</TableCell>
@@ -397,7 +397,7 @@ export const InputTableBody = (
return (
<TableCell
key={head.id + headIndex}
className="bg-[#d5e1eb] font-bold text-right"
className="bg-accent text-accent-foreground font-bold text-right"
>
{Number(data[head.id])
? formatCommaNumber(data[head.id as keyof any])
@@ -482,7 +482,7 @@ export const InputTableBody = (
<TableRow>
<TableCell
colSpan={headList.length}
className="h-24 text-center text-gray-500"
className="h-24 text-center text-muted-foreground"
>
조회된 결과가 없습니다.
</TableCell>
@@ -519,9 +519,9 @@ export const pbnstTableBody = (
const isTotal = data["구분"] === 0;
const bgClass = isTotal
? "bg-[#d5e1eb] font-bold"
? "bg-accent text-accent-foreground font-bold"
: isSubTotal
? "bg-[#e6eef5] font-semibold"
? "bg-secondary text-secondary-foreground font-semibold"
: "";
return (
@@ -552,7 +552,7 @@ export const pbnstTableBody = (
<TableCell
key={head.id + headIndex}
colSpan={2}
className="font-bold text-center bg-[#d5e1eb]"
className="bg-accent text-accent-foreground font-bold text-center"
>
총합계
</TableCell>
@@ -571,7 +571,7 @@ export const pbnstTableBody = (
return (
<TableCell
key={head.id + headIndex}
className="bg-[#d5e1eb] font-bold text-right"
className="bg-accent text-accent-foreground font-bold text-right"
>
{Number(data[head.id])
? formatCommaNumber(data[head.id as keyof any])
@@ -645,7 +645,7 @@ export const pbnstTableBody = (
<TableRow>
<TableCell
colSpan={headList.length}
className="h-24 text-center text-gray-500"
className="h-24 text-center text-muted-foreground"
>
조회된 결과가 없습니다.
</TableCell>
+3 -3
View File
@@ -291,16 +291,16 @@ export const showConfirmToast = (message: string) => {
toast.custom(
(t) => (
<div
className={`w-[500px] min-h-[140px] bg-white shadow-lg rounded-xl p-6 border border-gray-200
className={`min-h-[140px] w-[500px] rounded-xl border border-border bg-card p-6 text-card-foreground shadow-lg
${t.visible ? "animate-enter" : "animate-leave"}`}
>
<div className="text-3xl font-extrabold text-red-600 bg-yellow-100 px-4 py-2 rounded-md shadow-md">
<div className="rounded-md bg-accent px-4 py-2 text-3xl font-extrabold text-destructive shadow-md">
{message}
</div>{" "}
<div className="flex justify-end gap-2">
<Button
size={"sm"}
className="px-4 py-1.5 rounded bg-gray-200 text-sm hover:bg-gray-300"
className="rounded bg-secondary px-4 py-1.5 text-sm text-secondary-foreground hover:bg-accent"
onClick={() => toast.dismiss(t.id)}
>
확인완료
@@ -1,4 +1,7 @@
"use client";
/**
* 표준공사비
*/
import { useWrStore } from "@/app/store";
import { usageProcData } from "@/app/store/matDmd/coMatRvw/headList";
import type {
@@ -76,7 +79,7 @@ export default function ConCostForm({
const activeForm = form || localForm;
const isSim = type === "simulation";
const readOnlyInputClass = "bg-slate-50 text-gray-700";
const readOnlyInputClass = "bg-secondary text-secondary-foreground";
const {
constCostRes, // 표준사업비
rateFeeRes, // 부대경비요율
@@ -59,14 +59,14 @@ export default function BdstAdd({ form, readonly }: { form: any; readonly?: bool
<>
<h1 className={formH1Class}>증축, 개축, 재축</h1>
{!readonly && (
<div className="flex justify-end gap-2 mt-4">
<div className="rainreg_actions">
<Button type="button" className="" size={"sm"} onClick={plusHist}>
이력추가
</Button>
<Button
type="button"
className="!bg-red-500 text-white"
variant="destructive"
size={"sm"}
onClick={removeSelected}
>
@@ -151,7 +151,7 @@ export default function BdstAdd({ form, readonly }: { form: any; readonly?: bool
</div>
</div>
))}
<div ref={bottomRef} className="border-b border-gray-300 my-4"> </div>
<div ref={bottomRef} className="rainreg_divider"> </div>
</>
)
}
@@ -266,12 +266,12 @@ export default function HtdflRegForm({
}, [isPowerPlant, form]);
return (
<div className="!mx-[1.6rem] !p-0r">
<div className="!mx-[1.6rem] !p-0">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
{/* <div className="flex justify-between items-center mx-3"> */}
<div>
<h1 className="text-2xl font-bold text-gray-900 mt-3 ml-1">
<h1 className="mt-3 ml-1 text-2xl font-bold text-foreground">
{readonly && `(시설명 : ${htdflForm?.bdstNm})`}
</h1>
<div className="grid grid-cols-12 flex items-center card-header">
@@ -289,7 +289,7 @@ export default function HtdflRegForm({
</Button>
<Button
type="button"
className="!bg-red-500 text-white"
variant="destructive"
size={"sm"}
onClick={onDelete}
>
@@ -619,7 +619,7 @@ export default function HtdflRegForm({
readOnly={readonly}
maxLength={6}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
<span className="pointer-events-none absolute inset-y-0 right-3 flex items-center text-xl text-placeholder">
이하
</span>
</div>
@@ -649,7 +649,7 @@ export default function HtdflRegForm({
readOnly={readonly}
maxLength={6}
/>{" "}
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
<span className="pointer-events-none absolute inset-y-0 right-3 flex items-center text-xl text-placeholder">
이하
</span>
</div>
@@ -704,7 +704,7 @@ export default function HtdflRegForm({
readOnly={readonly}
maxLength={6}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
<span className="pointer-events-none absolute inset-y-0 right-3 flex items-center text-xl text-placeholder">
이하
</span>
</div>
@@ -739,7 +739,7 @@ export default function HtdflRegForm({
readOnly={readonly}
maxLength={6}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
<span className="pointer-events-none absolute inset-y-0 right-3 flex items-center text-xl text-placeholder">
이상
</span>
</div>
@@ -768,7 +768,7 @@ export default function HtdflRegForm({
readOnly={readonly}
maxLength={numSize}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
<span className="pointer-events-none absolute inset-y-0 right-3 flex items-center text-xl text-placeholder">
이상
</span>
</div>
@@ -800,7 +800,7 @@ export default function HtdflRegForm({
readOnly={readonly}
maxLength={numSize}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
<span className="pointer-events-none absolute inset-y-0 right-3 flex items-center text-xl text-placeholder">
이하
</span>
</div>
@@ -830,7 +830,7 @@ export default function HtdflRegForm({
readOnly={readonly}
maxLength={7}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
<span className="pointer-events-none absolute inset-y-0 right-3 flex items-center text-xl text-placeholder">
이하
</span>
</div>
@@ -886,7 +886,7 @@ export default function HtdflRegForm({
readOnly={readonly}
maxLength={numSize}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
<span className="pointer-events-none absolute inset-y-0 right-3 flex items-center text-xl text-placeholder">
이하
</span>
</div>
@@ -0,0 +1,27 @@
@tailwind components;
@layer components {
.pbnstbuse_sum_input {
@apply w-32 rounded-md border border-input pr-8;
}
.pbnstbuse_demand_row {
@apply mt-3 grid grid-cols-12 gap-4 border-b border-border pb-3;
}
.pbnstbuse_modal_body {
@apply mx-auto w-full rounded-lg border border-border bg-background p-6 shadow-md;
}
.pbnstbuse_empty {
@apply !mx-[1.6rem] !p-0 flex h-64 flex-col items-center justify-center rounded-md border border-border text-label;
}
.pbnstbuse_empty_title {
@apply text-lg text-foreground;
}
.pbnstbuse_empty_desc {
@apply mb-4 text-sm text-label;
}
}
@@ -1,4 +1,5 @@
"use client";
import "./bugRegForm.css";
import { useWrStore } from "@/app/store";
import {
@@ -325,7 +326,7 @@ export function PbnstBuseForm({
return (
<>
{pbnstBiz && pbnstBiz?.length > 0 ? (
<div className=" !mx-[1.6rem] !p-0r">
<div className=" !mx-[1.6rem] !p-0">
{/* 차수별 등록 현황 폼 */}
<div className="justify-between">
<Form {...form}>
@@ -465,7 +466,7 @@ export function PbnstBuseForm({
type="text"
value={formatCommaNumber(sumCpct)}
readOnly={true}
className="w-32 border-gray-300 rounded pr-8 "
className="pbnstbuse_sum_input"
/>
{ppiYn === "Y" ? (
<>
@@ -481,7 +482,8 @@ export function PbnstBuseForm({
<Button
type="button"
size={commonButonSize}
className="!text-xl !bg-red-500 !text-white ml-1"
variant="destructive"
className="!text-xl ml-1"
onClick={removeSelected}
disabled={readonly}
>
@@ -563,7 +565,7 @@ export function PbnstBuseForm({
{fcDemands.map((field, index) => (
<div
key={field.id} // useFieldArray의 고유 id를 key로 사용
className="grid grid-cols-12 gap-4 mt-3 border-b border-gray-400 pb-3"
className="pbnstbuse_demand_row"
>
<div key={index + "keySon1"} className="col-span-4">
<FormField
@@ -787,7 +789,7 @@ export function PbnstBuseForm({
data: data as DemandCompType,
});
}}
bodyClassNm={"w-full mx-auto bg-white rounded-lg shadow-md p-6"}
bodyClassNm={"pbnstbuse_modal_body"}
openNetwork={true}
></SdmInsertForm>
}
@@ -795,9 +797,9 @@ export function PbnstBuseForm({
></UiModal>
</div>
) : (
<div className="!mx-[1.6rem] !p-0r flex flex-col items-center justify-center h-64 text-gray-500 border rounded-md">
<p className="text-lg">등록된 차수별 정보가 없습니다.</p>
<p className="text-sm mb-4">
<div className="pbnstbuse_empty">
<p className="pbnstbuse_empty_title">등록된 차수별 정보가 없습니다.</p>
<p className="pbnstbuse_empty_desc">
아래 버튼을 눌러 신규 차수 정보를 등록해주세요.
</p>
<Button
@@ -0,0 +1,19 @@
@tailwind components;
@layer components {
.busewq_input {
@apply w-full rounded-md border border-input py-2 pr-16 text-right;
}
.busewq_suffix {
@apply pointer-events-none absolute inset-y-0 right-3 flex items-center text-xl text-label;
}
.busewq_ph_input {
@apply w-28 !text-xl;
}
.busewq_ph_range {
@apply text-xl text-foreground;
}
}
@@ -1,3 +1,5 @@
import "./buseWaterQulity.css";
import type { PbnstRegArrrayType } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
import { formH1Class } from "@/components/lib/class/comClass";
import { FormField, FormUi, SelectBox } from "@common/business_components/ui";
@@ -119,7 +121,7 @@ export function BuseWaterQulity({
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
className="busewq_input"
placeholder=""
onChange={(e) => {
const inputValue = e.target.value.replace(/[^0-9.]/g, ""); // 숫자만 저장
@@ -133,7 +135,7 @@ export function BuseWaterQulity({
readOnly={readonly}
maxLength={6}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
<span className="busewq_suffix">
이하
</span>
</div>
@@ -156,7 +158,7 @@ export function BuseWaterQulity({
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
className="busewq_input"
placeholder=""
onChange={(e) => {
const inputValue = e.target.value.replace(/[^0-9.]/g, ""); // 숫자만 저장
@@ -171,7 +173,7 @@ export function BuseWaterQulity({
maxLength={6}
/>
{/* TODO: 총대장균군수 확인 필요*/}
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
<span className="busewq_suffix">
이하
</span>
</div>
@@ -191,7 +193,7 @@ export function BuseWaterQulity({
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
className="busewq_input"
placeholder=""
onChange={(e) => {
const inputValue = e.target.value.replace(/[^0-9.]/g, ""); // 숫자만 저장
@@ -205,7 +207,7 @@ export function BuseWaterQulity({
readOnly={readonly}
maxLength={6}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
<span className="busewq_suffix">
이상
</span>
</div>
@@ -225,7 +227,7 @@ export function BuseWaterQulity({
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
className="busewq_input"
placeholder=""
onChange={(e) => {
const inputValue = e.target.value.replace(/[^0-9.]/g, ""); // 숫자만 저장
@@ -239,7 +241,7 @@ export function BuseWaterQulity({
readOnly={readonly}
maxLength={6}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
<span className="busewq_suffix">
이상
</span>
</div>
@@ -298,7 +300,7 @@ export function BuseWaterQulity({
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
className="busewq_input"
placeholder=""
onChange={(e) => {
const inputValue = e.target.value.replace(/[^0-9.]/g, ""); // 숫자만 저장
@@ -312,7 +314,7 @@ export function BuseWaterQulity({
readOnly={readonly}
maxLength={6}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
<span className="busewq_suffix">
이하
</span>
</div>
@@ -332,7 +334,7 @@ export function BuseWaterQulity({
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
className="busewq_input"
placeholder=""
onChange={(e) => {
const inputValue = e.target.value.replace(/[^0-9.]/g, ""); // 숫자만 저장
@@ -346,7 +348,7 @@ export function BuseWaterQulity({
readOnly={readonly}
maxLength={6}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
<span className="busewq_suffix">
이하
</span>
</div>
@@ -367,7 +369,7 @@ export function BuseWaterQulity({
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
className="busewq_input"
placeholder=""
onChange={(e) => {
const inputValue = e.target.value.replace(/[^0-9.]/g, ""); // 숫자만 저장
@@ -381,7 +383,7 @@ export function BuseWaterQulity({
readOnly={readonly}
maxLength={6}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
<span className="busewq_suffix">
이하
</span>
</div>
@@ -401,7 +403,7 @@ export function BuseWaterQulity({
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
className="busewq_input"
placeholder=""
onChange={(e) => {
const inputValue = e.target.value.replace(/[^0-9.]/g, ""); // 숫자만 저장
@@ -415,7 +417,7 @@ export function BuseWaterQulity({
readOnly={readonly}
maxLength={7}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
<span className="busewq_suffix">
이하
</span>
</div>
@@ -436,7 +438,7 @@ export function BuseWaterQulity({
<div className="relative flex-1 min-w-0">
<Input
type="text"
className="w-full pr-16 border py-2 text-right rounded-md"
className="busewq_input"
placeholder=""
onChange={(e) => {
const inputValue = e.target.value.replace(/[^0-9.]/g, ""); // 숫자만 저장
@@ -450,7 +452,7 @@ export function BuseWaterQulity({
readOnly={readonly}
maxLength={7}
/>
<span className="text-xl absolute inset-y-0 right-3 flex items-center text-gray-500 pointer-events-none">
<span className="busewq_suffix">
이하
</span>
</div>
@@ -463,7 +465,7 @@ export function BuseWaterQulity({
<Label className="text-xl">수소이온농도(pH)</Label>
<div className="flex flex-row items-center gap-2">
<Input
className="!text-xl w-28"
className="busewq_ph_input"
placeholder=""
type="text"
onChange={(e) =>
@@ -474,9 +476,9 @@ export function BuseWaterQulity({
readOnly={readonly}
maxLength={3}
/>
<span className="text-xl">~</span>
<span className="busewq_ph_range">~</span>
<Input
className="!text-xl w-28"
className="busewq_ph_input"
placeholder=""
type="text"
onChange={(e) => {
@@ -0,0 +1,7 @@
@tailwind components;
@layer components {
.pbnstmng_file_link {
@apply block cursor-pointer text-2xl text-primary hover:underline;
}
}
@@ -1,4 +1,5 @@
"use client";
import "./mngRegForm.css";
import { useWrStore } from "@/app/store";
import {
@@ -265,7 +266,7 @@ export function PbnstMngForm({
return (
<div className=" !mx-[1.6rem] !p-0r">
<div className=" !mx-[1.6rem] !p-0">
<div>
<Form {...form}>
{/* <form onSubmit={form.handleSubmit()}> */}
@@ -332,13 +333,14 @@ export function PbnstMngForm({
>
추가등록
</Button>
<Button
type="button"
size={commonButonSize}
className="!text-xl !bg-red-500 !text-white ml-1"
onClick={removeSelected}
disabled={readonly}
>
<Button
type="button"
size={commonButonSize}
variant="destructive"
className="!text-xl ml-1"
onClick={removeSelected}
disabled={readonly}
>
삭제
</Button>
</div>
@@ -442,7 +444,7 @@ export function PbnstMngForm({
{relationfiles?.map((item, index) => (
<span
key={index + "plan"}
className="text-2xl block cursor-pointer text-blue-600 hover:underline"
className="pbnstmng_file_link"
onClick={() => fileDownLoadName(item.atflCd, item.file.name)}
>
{item.file.name}
@@ -481,7 +483,7 @@ export function PbnstMngForm({
{planFiles?.map((item, index) => (
<span
key={index + "plan"}
className="text-2xl block cursor-pointer text-blue-600 hover:underline"
className="pbnstmng_file_link"
onClick={() => fileDownLoadName(item.atflCd, item.file.name)}
>
{item.file.name}
@@ -0,0 +1,23 @@
@tailwind components;
@layer components {
.rainreg_header_note {
@apply mt-3 ml-1 text-2xl font-bold text-foreground;
}
.rainreg_header_title {
@apply col-span-10 text-2xl font-medium text-label;
}
.rainreg_section_note {
@apply col-span-10 mt-4 pl-3 text-xl text-foreground;
}
.rainreg_actions {
@apply mt-4 flex justify-end gap-2;
}
.rainreg_divider {
@apply my-4 border-b border-border;
}
}
@@ -1,5 +1,7 @@
"use client";
import "./rainRegForm.css";
import { useWrStore } from "@/app/store";
import {
rainZod,
@@ -180,19 +182,19 @@ export default function RainRegForm({
}, [rainForm]);
return (
<div className="!mx-[1.6rem] !p-0r">
<div className="!mx-[1.6rem] !p-0">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div>
<h1 className="text-2xl font-bold text-gray-900 mt-3 ml-1">
<h1 className="rainreg_header_note">
{readonly && `(시설명 : ${rainForm?.bdstNm})`}
</h1>
<div className="grid grid-cols-12 flex items-center card-header">
<h1 className="text-2xl font-medium text-label col-span-10">
<h1 className="rainreg_header_title">
{isModal ? "건축물 정보" : "빗물이용시설 정보"}
</h1>
{!readonly && (
<div className="flex justify-end gap-2 mt-4">
<div className="rainreg_actions">
<Button
type="submit"
className="col-start-12 col-span-1"
@@ -202,7 +204,7 @@ export default function RainRegForm({
</Button>
<Button
type="button"
className="!bg-red-500 text-white"
variant="destructive"
size={"sm"}
onClick={onDelete}
>
@@ -215,7 +217,7 @@ export default function RainRegForm({
{/* <ScrollArea className={divStyle}> */}
{isModal && rainForm != undefined ? <BdstReg form={form} /> : <></>}
{isModal ? (
<h1 className="pl-3 text-xl col-span-10 mt-4">
<h1 className="rainreg_section_note">
빗물이용시설 정보{" "}
</h1>
) : (
@@ -455,7 +457,7 @@ export default function RainRegForm({
// <div className="flex items-center gap-2">
<FormUi label="처리공정">
<Input
className={" h-full !text-xl col-span-4"}
className={selectClass}
placeholder="처리공정 입력"
type="text"
value={field.value ?? ""}
@@ -174,12 +174,12 @@ export default function RswtrRegForm({
return (
<div className="!mx-[1.6rem] !p-0r">
<div className="!mx-[1.6rem] !p-0">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
{/* <div className="flex justify-between items-center mx-3"> */}
<div>
<h1 className="text-2xl font-bold text-gray-900 mt-3 ml-1">
<h1 className="mt-3 ml-1 text-2xl font-bold text-foreground">
{readonly && `(시설명 : ${rswtForm?.bdstNm})`}
</h1>
<div className="grid grid-cols-12 flex items-center card-header">
@@ -197,7 +197,7 @@ export default function RswtrRegForm({
</Button>
<Button
type="button"
className="!bg-red-500 text-white"
variant="destructive"
size={"sm"}
onClick={onDelete}
>
@@ -81,7 +81,7 @@ export function HasuFacility({rtnFeature}:{rtnFeature:(feature:any)=>void } ) {
rightChildren={<></>}
leftChildren={
<div className="col-span-3 flex items-center">
<Label className="w-full mt-2 ml-4 text-xl text-gray-600">
<Label className="mt-2 ml-4 w-full text-xl text-label">
{"하수처리시설명"}
</Label>
<SearchInput
@@ -109,7 +109,7 @@ export function RuseFacility({
className={""}
selectContentClass={''}
/>
<Label className="w-full mt-2 ml-4 text-xl text-gray-600">
<Label className="mt-2 ml-4 w-full text-xl text-label">
{"하수처리시설명"}
</Label>
<SearchInput
@@ -20,7 +20,7 @@ export default function CoSttnDetailPopup({isDetail} :{isDetail:DemandCompType})
rtnInputData={async (data) => {
console.log(data)
}}
bodyClassNm={"w-full mx-auto bg-white rounded-lg shadow-md p-6"}
bodyClassNm={"w-full mx-auto bg-background rounded-lg shadow-md p-6"}
updateList={isDetail}
readOnly={true}
openNetwork={false}
@@ -28,4 +28,4 @@ export default function CoSttnDetailPopup({isDetail} :{isDetail:DemandCompType})
</div>
)
}
}
@@ -11,14 +11,14 @@ export default function WrOverLayTable({overLayData}:{overLayData:any,}){
<TableBody>
{Object.entries(overLayData).map(([key, value]) => (
<TableRow key={key}>
<TableCell className=" w-1/3 bg-slate-100 text-slate-800 font-medium text-sm align-middle border border-slate-300">
<TableCell className="w-1/3 border border-border bg-secondary text-secondary-foreground font-medium text-sm align-middle">
{" "}
{key}{" "}
</TableCell>
<TableCell
className={
"w-1/3 bg-white text-right pr-3 text-sm align-middle border border-slate-300"
"w-1/3 border border-border bg-background pr-3 text-right text-sm align-middle text-foreground"
}
>
{" "}
@@ -30,4 +30,4 @@ export default function WrOverLayTable({overLayData}:{overLayData:any,}){
</Table>
</>
)
}
}
@@ -43,16 +43,16 @@ export default function WrOverLay({
onDrag={(e, data) => setPos({ x: data.x, y: data.y })}
>
<div ref={dragRef}>
<Card className="w-[320px] p-0 shadow-md rounded-xl overflow-hidden border border-slate-300 relative">
<Card className="relative w-[320px] overflow-hidden rounded-xl border border-border bg-card p-0 text-card-foreground shadow-md">
{/* 헤더: 제목 바 + 우측 정렬 버튼 (절대배치 제거, 타이틀 중앙 정렬 유지) */}
<div className="bg-sky-600 text-white font-semibold py-2 text-xl">
<div className="bg-primary py-2 text-xl font-semibold text-primary-foreground">
<div className="grid grid-cols-[auto_1fr_auto] items-center gap-2 px-2">
{/* 좌측 스페이서: 우측 버튼과 동일한 너비를 차지하도록 동일 버튼을 투명하게 복제 */}
<div className="opacity-0 pointer-events-none">
<div className="flex gap-2">
<button
onClick={() => setPos({ x: 0, y: 0 })}
className="p-2 bg-gray-200 text-gray-700 shadow hover:bg-gray-300 hover:shadow-md transition"
className="bg-secondary p-2 text-secondary-foreground shadow transition hover:bg-accent hover:text-accent-foreground hover:shadow-md"
title="원위치"
>
↺
@@ -62,7 +62,7 @@ export default function WrOverLay({
onClick={() => setOpen(false)}
aria-label="축소"
title="축소"
className="p-2 bg-gray-200 text-gray-700 shadow hover:bg-gray-300 hover:shadow-md transition"
className="bg-secondary p-2 text-secondary-foreground shadow transition hover:bg-accent hover:text-accent-foreground hover:shadow-md"
>
–
</button>
@@ -78,7 +78,7 @@ export default function WrOverLay({
<div className="flex gap-2 justify-end">
<button
onClick={() => setPos({ x: 0, y: 0 })}
className="p-2 bg-gray-200 text-gray-700 shadow hover:bg-gray-300 hover:shadow-md transition"
className="bg-secondary p-2 text-secondary-foreground shadow transition hover:bg-accent hover:text-accent-foreground hover:shadow-md"
title="원위치"
>
↺
@@ -88,7 +88,7 @@ export default function WrOverLay({
onClick={() => setOpen(false)}
aria-label="축소"
title="축소"
className="p-2 bg-gray-200 text-gray-700 shadow hover:bg-gray-300 hover:shadow-md transition"
className="bg-secondary p-2 text-secondary-foreground shadow transition hover:bg-accent hover:text-accent-foreground hover:shadow-md"
>
–
</button>
@@ -110,7 +110,7 @@ export default function WrOverLay({
onClick={() => setOpen(true)}
aria-label="확장"
title={title || "정보 보기"}
className="w-[30px] h-[30px] bg-white font-bold shadow flex items-center justify-center border border-slate-300"
className="flex h-[30px] w-[30px] items-center justify-center border border-border bg-popover font-bold text-popover-foreground shadow"
>
{bubbleLabel}
</button>
@@ -265,7 +265,7 @@ export default function ChkgdExcel({
></Title>
</div>
<div className="flex justify-between">
<h3 className="text-2xl font-bold text-gray-900 mt-1 ml-7">
<h3 className="mt-1 ml-7 text-2xl font-bold text-foreground">
{"점검정보 입력"}
</h3>
@@ -289,7 +289,7 @@ export default function ChkgdModal({
<></>
)}
</div>
<h3 className="text-2xl font-bold text-gray-900 mt-2 ml-6">
<h3 className="mt-2 ml-6 text-2xl font-bold text-foreground">
{type === "RNB"
? `시설명 : (${obj.bdstNm})`
: `시설명 : (${obj.building.bdstNm})`}
@@ -102,7 +102,7 @@ export default function PbnPrfExcelModal({
저장
</Button>
</div>
<h3 className="text-2xl font-bold text-gray-900 my-3 ml-7">
<h3 className="my-3 ml-7 text-2xl font-bold text-foreground">
{"점검정보 입력"}
</h3>
@@ -106,7 +106,7 @@ export function ConstCostFeeTable() {
<hr className="mt-4 mb-4"/>
<div className="col-span-5 flex items-center justify-between">
<div className="flex items-center">
<Label className="mr-1 text-xl text-gray-600">기준년도</Label>
<Label className="mr-1 text-xl text-label">기준년도</Label>
<SelectBox
className="h-full w-32 text-2xl"
label=""
@@ -117,7 +117,7 @@ export function ConstCostFeeTable() {
}
/>
</div>
<span className="mr-2 text-xl text-gray-600">단위: 백만원</span>
<span className="mr-2 text-xl text-label">단위: 백만원</span>
</div>
<UiTable
headName={""}
@@ -128,7 +128,7 @@ export function ConstCostFeeTable() {
cellClick={undefined}
onPageChange={() => { }}
/>
<div className="px-3 text-lg text-gray-700 leading-relaxed">
<div className="px-3 text-lg leading-relaxed text-foreground">
시설용량 구간내 공사비는 직선보간법에 의하여 산출
</div>
</div>
@@ -104,14 +104,14 @@ export function RateFeeTableModal() {
return (
<ListDiv className="min-w-[460px] h-[50vh] overflow-auto">
<div className="sticky top-0 z-10 grid grid-cols-6 gap-4 p-4 bg-white">
<div className="sticky top-0 z-10 grid grid-cols-6 gap-4 bg-background p-4">
<h3 className="col-span-6 text-2xl font-medium text-label">
시설부대경비 요율(%)
</h3>
<hr className="col-span-6 border-gray-300" />
<hr className="col-span-6 border-border" />
<div className="col-span-5 flex items-center">
<Label className="mr-1 text-xl text-gray-600">기준년도</Label>
<Label className="mr-1 text-xl text-label">기준년도</Label>
<SelectBox
className="h-full w-32 text-2xl"
label=""
@@ -135,7 +135,7 @@ export function RateFeeTableModal() {
cellClick={undefined}
/>
</div>
<div className="col-span-6 px-3 text-lg text-gray-700 leading-relaxed">
<div className="col-span-6 px-3 text-lg leading-relaxed text-foreground">
출처 : 하수도분야 보조금 편성 및 실무요령
<br />
시설부대경비 및 감리비 요율적용시 공사비가 요율표의 중간에 있을 때는 직선보간법에 의하여 산출
@@ -185,7 +185,7 @@ export default function RuseCaseMngExcel({setIsExcelOpen}:{setIsExcelOpen: (isOp
<Title title={"물재이용 사례 등록"}></Title>
</div>
<div className="flex justify-between">
<h3 className="text-2xl font-bold text-gray-900 mt-1 ml-7">
<h3 className="mt-1 ml-7 text-2xl font-bold text-foreground">
{"물재이용 사례 파일"}
</h3>
<Button className="" type="submit" size={"sm"}>
@@ -108,7 +108,7 @@ export default function CoRegExcel({
<Title title={"잠재수요처 등록"}></Title>
</div>
<div className="flex justify-between">
<h3 className="text-2xl font-bold text-gray-900 mt-1 ml-7">
<h3 className="mt-1 ml-7 text-2xl font-bold text-foreground">
{"잠재수요처 파일"}
</h3>
<div className="flex gap-2">
@@ -177,7 +177,7 @@ export function BdstReg<T extends MoType>({
>
{" "}
</SelectBox>
<Label className="w-full text-xl text-gray-600 text-right">
<Label className="w-full text-right text-xl text-label">
시설명
</Label>
<Input
@@ -196,11 +196,11 @@ export function BdstReg<T extends MoType>({
조회
</Button>
</div>
<div className="border-t border-gray-300 my-4"></div>
<div className="my-4 border-t border-border"></div>
<div className="h-auto">
<h1 className="text-2xl font-medium text-label pl-8">
건축물 목록
<span className="!text-2xl text-base text-blue-500">
<span className="text-base !text-2xl text-primary">
(유의 사항: 신규등록하려는 건축물이 목록에 없을 경우 하단 건축물
정보에 추가하여 주시기 바랍니다.)
</span>
@@ -221,7 +221,7 @@ export function BdstReg<T extends MoType>({
></UiTable>
</div>
</ScrollArea>
<div className="border-t border-gray-300 my-4"></div>
<div className="my-4 border-t border-border"></div>
<ScrollArea className="h-[40vh]">
<div>
{moType === "RNB" ? (
@@ -105,14 +105,14 @@ export default function FcOrgwtsRegModal({
return (
<div className="flex flex-col h-[80vh]">
<div className="shrink-0 p-4 border-b z-10 bg-white">
<div className="z-10 shrink-0 border-b border-border bg-background p-4">
<div className="flex items-center">
<Title title="취수하수처리장 추가" />
<div className="flex items-center gap-2 mt-8">
<Label className="text-xl text-gray-600">시설명</Label>
<Label className="text-xl text-label">시설명</Label>
<Input
className="w-48 text-xl"
type="text"
@@ -144,7 +144,7 @@ export default function FcOrgwtsRegModal({
{/* 왼쪽 테이블 */}
<div className="w-5/12 min-w-[400px]">
<Label className="text-2xl text-gray-600 mb-2 block text-right">
<Label className="mb-2 block text-right text-2xl text-label">
공공하수처리시설 목록
</Label>
<RuseMngTable
@@ -170,7 +170,7 @@ export default function FcOrgwtsRegModal({
}
setbdstTable(unchecked ?? []);
}}
className="w-8 h-8 text-gray-500 cursor-pointer"
className="h-8 w-8 cursor-pointer text-muted-foreground"
/>
<ArrowLeft
onClick={() => {
@@ -187,13 +187,13 @@ export default function FcOrgwtsRegModal({
}
setRightData(unchecked ?? []);
}}
className="w-8 h-8 text-gray-500 cursor-pointer"
className="h-8 w-8 cursor-pointer text-muted-foreground"
/>
</div>
{/* 오른쪽 테이블 */}
<div className="w-5/12 min-w-[400px]">
<Label className="text-2xl text-gray-600 mb-2 block text-right">
<Label className="mb-2 block text-right text-2xl text-label">
취수하수처리장 목록
</Label>
<RuseMngTable
@@ -72,7 +72,7 @@ export function PbnstRegModal({
하수재이용 시설 신규등록
</h1>
<div className="border-t border-gray-300 my-4"></div>
<div className="my-4 border-t border-border"></div>
<Tabs defaultValue="mng" onValueChange={handleTabChange}>
<div className="flex justify-between items-center">
@@ -0,0 +1,19 @@
@tailwind components;
@layer components {
.wrsearchinput_field {
@apply h-[3.2rem] w-full rounded-md border border-input pl-3 !text-xl text-label focus:outline-none focus:ring-2 focus:ring-ring;
}
.wrsearchinput_list {
@apply z-10 mt-2 h-[90px] w-full overflow-auto rounded-md border border-border bg-popover shadow-lg;
}
.wrsearchinput_item {
@apply w-full cursor-pointer overflow-auto px-5 py-3 text-left !text-xl text-foreground hover:bg-accent;
}
.wrsearchinput_empty {
@apply w-full overflow-auto px-5 py-3 !text-xl text-label;
}
}
@@ -1,3 +1,5 @@
import "./searchInput.css";
import { Input } from "@common/components/ui";
import React, { useCallback, useEffect, useRef, useState } from "react";
@@ -73,18 +75,18 @@ export default function SearchInputText({
value={textValue}
onChange={handleInputChange}
/* onFocus={() => setIsOpen(true)} */
className="w-full h-[3.2rem] !text-xl pl-3 text-gray-600 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
className="wrsearchinput_field"
placeholder={`${label}을(를) 입력하세요`}
readOnly={readonly} // readonly 상태 적용
/>
{isOpen && (
<div className=" z-1 w-full mt-2 bg-white border border-gray-300 rounded-md shadow-lg h-90 overflow-auto">
<div className="wrsearchinput_list">
<div className="max-h-72">
{searchList && searchList.length > 0 ? (
searchList.map((item: SearchType, index: number) => (
<div
key={index}
className="!text-xl w-full text-left px-5 py-3 hover:bg-gray-100 cursor-pointer overflow-auto"
className="wrsearchinput_item"
onClick={() => handleSuggestionClick(item.searchNm)}
role="button"
tabIndex={0}
@@ -99,7 +101,7 @@ export default function SearchInputText({
</div>
))
) : (
<div className="!text-xl w-full px-5 py-3 hover:bg-gray-100 cursor-pointer overflow-auto">
<div className="wrsearchinput_empty">
검색 결과가 없습니다.
</div>
)}
@@ -43,7 +43,7 @@ export function GuideDetailsTable({
return (
<>
<div className={`card !m-[1.2rem] !p-0r `}>
<div className={`card !m-[1.2rem] !p-0 `}>
<Table variant="secondary" className="table-fixed">
<colgroup>
{headList
@@ -60,7 +60,7 @@ const RuseMngTable = React.forwardRef<any, RuseMngTableProps>(
{headList.map((head) => (
<TableHead
key={"table-head-row " + head.id}
className={`border border-gray-600 px-2 py-1 `}
className={`border border-border px-2 py-1 `}
>
{head.title}
</TableHead>
@@ -84,7 +84,7 @@ const RuseMngTableForm = React.forwardRef<any, RuseMngTableProps>(
{headList.map((head) => (
<TableHead
key={"table-head-row " + head.id}
className={`border border-gray-600 px-2 py-1 `}
className={`border border-border px-2 py-1 `}
>
{head.title}
</TableHead>
@@ -118,7 +118,7 @@ const RuseMngTableForm = React.forwardRef<any, RuseMngTableProps>(
<FormUi label="">
{head.edit === true ? (
<Input
className={`${head.type === "number" ? "text-right" : "text-center"} text-gray-600`}
className={`${head.type === "number" ? "text-right" : "text-center"} text-foreground`}
type="text"
onChange={(txt) => {
if (head.type === "number") {
@@ -186,7 +186,7 @@ const RuseMngTableForm = React.forwardRef<any, RuseMngTableProps>(
value={
field.value? moment(field.value, "YYYYMMDD").format( "YYYY-MM-DD"): ""
}
className={"w-full text-gray-600"}
className={"w-full text-foreground"}
readOnly={readonly||head.readonly ? true : false}
/>
) : head.type === "checkbox" ? (
@@ -202,7 +202,7 @@ const RuseMngTableForm = React.forwardRef<any, RuseMngTableProps>(
}}
></Checkbox>
) : (
<span className="text-black">
<span className="text-foreground">
{head.type === "number"
? formatCommaNumber(field.value)
: field.value || ""}
+1 -1
View File
@@ -16,7 +16,7 @@ export function ToastFormUi({ body, title, url, buttonName }: toastFormType) {
<div
className={`${
t.visible ? "animate-enter" : "animate-leave"
} w-4/12 h-40 bg-white shadow-lg rounded-lg pointer-events-auto flex ring-1 ring-black ring-opacity-5`}
} h-40 w-4/12 pointer-events-auto flex rounded-lg border border-border bg-popover text-popover-foreground shadow-lg`}
>
<div className="w-full flex-1 py-10">
<div className="flex items-start">
@@ -0,0 +1,300 @@
# 지도 팝업 레이아웃 점검 작업계획
작성일: 2026-06-26
대상 앱: `D:\iipbs\frontend\apps\wr`
대상 화면:
- `http://localhost:3001/mapFeaturePopup?layerNm=tb_wr_bdst_ruse_fclt&featureCd=43150SW001F`
- `http://localhost:3001/mapFeaturePopup?layerNm=tb_wr_tmpr_dmd_co&featureCd=미래산업`
- 선택 팝업 공통 검토 범위:
- `tb_wr_bdst_ruse_fclt` 하수재이용시설
- `tb_wr_tmpr_dmd_co` 잠재수요처
- `tb_wr_ruse_fclt_dmd_co` 수요처
- `tb_wr_rnwtr_utztn_fclt` 빗물이용시설
- `tb_wr_rswtr_fclt` 중수도시설
- `tb_wr_htdfl_fclt` 온배수시설
- `tb_il_swage_prcs_fclt` 하수처리시설
- 공통 진입 파일:
- `apps/wr/app/(popUp-layout)/mapFeaturePopup/page.tsx`
- `apps/wr/components/lib/func/mapUtil.ts`
## 1. 작업 배경
- 지도 팝업에서 두 종류의 레이아웃 문제가 확인되었다.
- `하수재이용시설` 팝업은 컨텐츠 높이가 늘어나지 못해 하단 영역이 잘린다.
- `잠재수요처` 팝업은 컨텐츠 자체는 보이지만, 팝업 창 하단이 검은 배경으로 채워지지 않고 흰색 여백으로 남는다.
- 이 문제는 개별 폼 내부만이 아니라 지도 팝업 공통 래퍼, 팝업별 본문 높이 전략, 새 창 크기 전략을 함께 점검해야 재발을 막을 수 있다.
## 2. 현재 구조 분석
### 2.1 지도 팝업 공통 진입 구조
- `apps/wr/app/(popUp-layout)/mapFeaturePopup/page.tsx`
- `layerNm` 값에 따라 서로 다른 상세 컴포넌트를 렌더링한다.
- 공통 본문 래퍼는 다음 클래스를 사용한다.
- `className="box-border m-0 bg-background p-4 pb-24 text-foreground md:p-6"`
- 현재 이 공통 래퍼에는 `min-h-screen`, `min-h-dvh`, `h-full` 같은 viewport 채움 조건이 없다.
- 따라서 컨텐츠 높이가 창 높이보다 작을 때는 문서 body의 기본 배경색이 하단에 그대로 노출될 수 있다.
### 2.2 하수재이용시설 경로
- `mapFeaturePopup/page.tsx`
- `layerNm === "tb_wr_bdst_ruse_fclt"` 일 때 `RuseDetailPopup` 렌더링
- `apps/wr/components/ui/map/detailPopup/ruseDetailPopup.tsx`
- 시설별 탭을 만들고 각 탭에서 `PbnstSttnModal data={item} isMap={true}` 렌더링
- `apps/wr/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal.tsx:257`
- `const Wrapper: React.ElementType = isMap ? "div" : ScrollArea;`
- `apps/wr/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal.tsx:267`
- 지도 팝업 여부와 무관하게 `<Wrapper className="h-[70vh]">` 적용
- 지도 팝업에서는 `Wrapper`가 일반 `div`이므로:
- 내부 스크롤이 생기지 않고
- 높이만 `70vh`로 고정되어
- 아래쪽 `하수재이용시설 현황`, `하수재이용실적` 테이블이 박스 밖으로 밀린다.
### 2.3 잠재수요처 경로
- `mapFeaturePopup/page.tsx`
- `layerNm === "tb_wr_tmpr_dmd_co"` 일 때 `CoDetailModal isMap={true}` 렌더링
- `apps/wr/components/ui/modal/sttn/sttnDetailModal/coDatailModal.tsx`
- 내부 본문은 `<ScrollArea className="h-[79vh] mx-8">` 사용
- 즉 잠재수요처는 내부 폼 스크롤은 이미 있으나,
- 공통 팝업 바깥 래퍼가 viewport 전체 높이를 채우지 못하면
- 컨텐츠가 끝난 뒤 창 하단의 남는 공간이 `bg-background`가 아닌 흰색으로 보일 수 있다.
- 추가로 `pb-24` 같은 하단 padding이 공통 래퍼에 고정 적용되어 있어,
- 팝업별 실제 본문 높이와 맞지 않으면 시각적으로 불필요한 하단 여백처럼 보일 수 있다.
### 2.4 다른 선택 팝업 경로
- `tb_wr_ruse_fclt_dmd_co`
- `apps/wr/components/ui/map/detailPopup/coSttnDetailPopup/coSttnDetailPopup.tsx`
- 내부 `bodyClassNm={"w-full mx-auto bg-background rounded-lg shadow-md p-6"}` 사용
- `tb_wr_rnwtr_utztn_fclt`, `tb_wr_rswtr_fclt`, `tb_wr_htdfl_fclt`
- `apps/wr/components/ui/modal/sttn/sttnDetailModal/sttnDetailModal.tsx`
- 별도 `ScrollArea`는 주석 처리되어 있고, 최상위 공통 팝업 래퍼 의존도가 높다.
- `tb_il_swage_prcs_fclt`
- `mapFeaturePopup/page.tsx` 안에서 `UiTable` 2개를 직접 렌더링
- 역시 공통 래퍼 배경/높이 정책의 영향을 그대로 받는다.
### 2.5 흰 여백이 없는 팝업과 있는 팝업의 구조 차이
- 흰 여백이 없다고 확인된 팝업:
- `tb_wr_ruse_fclt_dmd_co`
- `tb_wr_rnwtr_utztn_fclt`
- `tb_wr_rswtr_fclt`
- 흰 여백이 나타난다고 확인된 팝업:
- `tb_wr_tmpr_dmd_co`
- `tb_wr_bdst_ruse_fclt`
- `tb_il_swage_prcs_fclt`
차이점 1. 개별 본문 컴포넌트의 배경 책임
- 흰 여백이 없는 `tb_wr_ruse_fclt_dmd_co`는
- `coSttnDetailPopup.tsx:23`에서 `bodyClassNm={"w-full mx-auto bg-background rounded-lg shadow-md p-6"}`를 직접 넘긴다.
- 즉 공통 팝업 래퍼 바깥이 아니라, 실제 본문 카드 자체가 `bg-background`를 명시적으로 가진다.
- 반대로 `tb_wr_tmpr_dmd_co`는
- `coDatailModal.tsx:52`의 최상위가 단순 `<div>`이고,
- 내부는 `ScrollArea className="h-[79vh] mx-8"` 중심 구조다.
- 본문 전체 배경을 개별 카드로 강하게 감싸기보다 공통 팝업 래퍼 배경에 더 의존한다.
- `tb_il_swage_prcs_fclt`는
- `mapFeaturePopup/page.tsx:103` 이하에서 `UiTable` 2개를 직접 렌더링하고,
- 별도 카드형 본문 래퍼 없이 공통 래퍼 위에 바로 쌓는다.
차이점 2. 본문 높이 점유 방식
- 흰 여백이 없는 `tb_wr_rnwtr_utztn_fclt`, `tb_wr_rswtr_fclt`는
- `sttnDetailModal.tsx:81` 이하에서 상세 폼 컴포넌트를 직접 렌더링한다.
- 현재 지도 팝업용 `ScrollArea`는 주석 처리되어 있어, 공통 래퍼 안에서 폼이 자연 높이로 이어진다.
- 실제 데이터량이 비교적 충분하면 본문이 창 하단 가까이까지 내려와 흰 여백이 드러나지 않을 수 있다.
- 반대로 `tb_wr_tmpr_dmd_co`는
- `coDatailModal.tsx:67`의 `h-[79vh]` 스크롤 영역이 명시되어 있고,
- 바깥 공통 래퍼에는 `pb-24`가 남아 있어 하단 잔여 공간이 시각적으로 더 잘 드러날 가능성이 있다.
- `tb_wr_bdst_ruse_fclt`는
- `pbnstSttnModal.tsx:267`에서 `h-[70vh]` 고정 높이를 사용한다.
- 이 경우 본문이 자연스럽게 창 끝까지 이어지지 못하고, 잘림 또는 하단 여백 문제가 함께 나타날 수 있다.
차이점 3. 공통 래퍼 의존도
- 흰 여백이 없는 쪽은
- 개별 컴포넌트가 자체 배경 카드나 충분한 컨텐츠 높이를 가져 공통 래퍼 결함이 눈에 덜 띈다.
- 흰 여백이 있는 쪽은
- 개별 카드 배경이 약하거나
- 본문 높이가 고정되거나 짧아
- `mapFeaturePopup/page.tsx:100`의 공통 래퍼가 viewport 끝까지 채워지지 않는 문제가 바로 노출된다.
### 2.6 팝업 창 자체 크기
- `apps/wr/components/lib/func/mapUtil.ts:146`
- 하수재이용시설: `window.innerHeight - 100`
- 잠재수요처: `window.innerHeight - 300`
- 수요처: `window.innerHeight - 430`
- 팝업 창 크기 자체 차이도 존재하지만,
- 이번 증상은 우선 새 창 크기보다 내부 공통 래퍼와 개별 팝업의 높이 전략 불일치 영향이 더 크다.
## 3. 문제 유형 정리
### 3.1 유형 A: 내부 컨텐츠 잘림
- 대상:
- `tb_wr_bdst_ruse_fclt`
- 원인:
- `isMap=true`인데도 내부 래퍼가 `h-[70vh]`로 고정
- `ScrollArea`가 아닌 일반 `div`라서 컨텐츠가 늘어나지 못함
### 3.2 유형 B: 팝업 하단 흰색 잔여 영역
- 우선 확인 대상:
- `tb_wr_tmpr_dmd_co`
- 함께 점검할 대상:
- `tb_wr_bdst_ruse_fclt`
- `tb_il_swage_prcs_fclt`
- 원인 후보:
- 공통 래퍼가 viewport 전체 높이를 채우지 않음
- 공통 래퍼의 하단 padding이 팝업별 컨텐츠 높이와 맞지 않음
- 팝업별 내부 스크롤 높이와 새 창 높이 계산이 제각각임
- 개별 팝업이 본문 배경을 자체적으로 채우지 못하고 공통 래퍼에 의존함
## 4. 수정 대상
### 4.1 공통 우선 수정 파일
- `D:\iipbs\frontend\apps\wr\app\(popUp-layout)\mapFeaturePopup\page.tsx`
### 4.2 하수재이용시설 직접 수정 파일
- `D:\iipbs\frontend\apps\wr\components\ui\modal\sttn\pbnstSttnModal\pbnstSttnModal.tsx`
- `D:\iipbs\frontend\apps\wr\components\ui\map\detailPopup\ruseDetailPopup.tsx`
- `D:\iipbs\frontend\apps\wr\components\ui\form\fcltReg\pbnstReg\mngReg\mngRegForm.tsx`
### 4.3 잠재수요처 및 타 팝업 점검 파일
- `D:\iipbs\frontend\apps\wr\components\ui\modal\sttn\sttnDetailModal\coDatailModal.tsx`
- `D:\iipbs\frontend\apps\wr\components\ui\map\detailPopup\coSttnDetailPopup\coSttnDetailPopup.tsx`
- `D:\iipbs\frontend\apps\wr\components\ui\modal\sttn\sttnDetailModal\sttnDetailModal.tsx`
- `D:\iipbs\frontend\apps\wr\components\lib\func\mapUtil.ts`
## 5. 권장 구현 방향
### 5.1 공통 팝업 래퍼 정규화
- `mapFeaturePopup/page.tsx`의 최상위 래퍼를 지도 팝업 공통 레이아웃 기준점으로 삼는다.
- 다음 방향으로 정리하는 것이 우선이다.
- `bg-background`가 항상 팝업 창 하단까지 채워지도록 `min-h-screen` 또는 `min-h-dvh` 적용
- 필요 시 `h-full`과 조합해 새 창 viewport를 안정적으로 덮도록 조정
- 공통 `pb-24`는 팝업별로 과도한 여백을 만드는지 점검하고, 필요하면 축소 또는 분기 처리
### 5.2 하수재이용시설 전용 높이 분기
- `PbnstSttnModal`에서 `isMap`일 때 `h-[70vh]`를 제거하거나 `max-h` 기반으로 완화
- 비지도 화면에서는 기존 `ScrollArea + h-[70vh]` 유지
- 지도 팝업에서는:
- 본문 전체가 자연스럽게 늘어나게 하거나
- 큰 테이블만 별도 스크롤 처리
### 5.3 잠재수요처 하단 흰 영역 대응
- `CoDetailModal` 자체의 `ScrollArea h-[79vh]`는 유지하되,
- 먼저 공통 팝업 래퍼의 배경/최소높이 문제를 해결한다.
- 그 다음에도 하얀 영역이 남으면 아래 순서로 추가 점검:
- `pb-24` 제거 또는 축소 시 시각적 개선 여부
- `ScrollArea` 높이를 `79vh` 고정 대신 `calc(100vh - 헤더/패딩)` 기반으로 변경할지 검토
- 팝업 창 자체 높이(`mapUtil.ts`)가 본문 높이 전략과 맞는지 재조정
### 5.4 선택된 타 팝업 공통 점검
- 잠재수요처 수정과 동일한 공통 래퍼 아래에서 동작하는 팝업들은 모두 한 번씩 확인한다.
- 특히 다음을 확인:
- 컨텐츠가 짧을 때 창 하단이 흰색으로 비는지
- 컨텐츠가 길 때 내부 스크롤/전체 스크롤이 자연스러운지
- 배경색이 본문 카드까지만 적용되고 바깥 여백은 흰색으로 남지 않는지
- 이번 확인 결과상 우선순위는 두 그룹으로 나눈다.
- 흰 여백 발생 그룹:
- `tb_wr_tmpr_dmd_co`
- `tb_wr_bdst_ruse_fclt`
- `tb_il_swage_prcs_fclt`
- 비교 기준 그룹:
- `tb_wr_ruse_fclt_dmd_co`
- `tb_wr_rnwtr_utztn_fclt`
- `tb_wr_rswtr_fclt`
## 6. 상세 작업 단계
### 단계 1. 공통 래퍼 수정
- `mapFeaturePopup/page.tsx`
- 최상위 래퍼에 viewport 채움 클래스 추가
- `pb-24`의 필요성 재검토
- 팝업 종류별로 공통 패딩 분기 필요 여부 확인
### 단계 2. 하수재이용시설 잘림 수정
- `pbnstSttnModal.tsx`
- `isMap`일 때 `h-[70vh]` 제거 또는 `max-h`로 전환
- 비지도 화면 동작은 유지
- 필요 시:
- `RuseDetailPopup`의 `TabsContent`
- `PbnstMngForm` 내부 `h-[200px] overflow-y-auto`
- `하수재이용실적` 테이블 영역
- 순서로 추가 보정
### 단계 3. 잠재수요처 하단 흰 영역 점검
- `coDatailModal.tsx`
- 공통 래퍼 수정 후에도 하얀 하단이 남는지 재확인
- 남는 경우 `ScrollArea h-[79vh]`를 `calc()` 기반으로 조정 검토
- 함께 비교:
- `coSttnDetailPopup.tsx`의 `bodyClassNm bg-background` 구조와 어떤 차이로 결과가 갈리는지 확인
- `mapUtil.ts`
- 잠재수요처 팝업 높이(`window.innerHeight - 300`)가 현재 본문 구조와 맞는지 재검토
### 단계 4. 선택된 타 팝업 회귀 점검
- 발생 그룹 확인:
- `tb_wr_tmpr_dmd_co`
- `tb_wr_bdst_ruse_fclt`
- `tb_il_swage_prcs_fclt`
- 비교 그룹 확인:
- `tb_wr_ruse_fclt_dmd_co`
- `tb_wr_rnwtr_utztn_fclt`
- `tb_wr_rswtr_fclt`
- 두 그룹 비교 기준:
- 개별 본문이 `bg-background` 카드를 직접 가지는지
- 본문이 자연 높이인지 고정 `vh` 높이인지
- 공통 래퍼 `pb-24`와 결합될 때 하단 빈 공간이 드러나는지
- 이후 추가 증상이 확인되면:
- 공통 래퍼로 해결 가능한지 우선 판단
- 개별 컴포넌트 고정 높이/패딩이 있으면 2차 수정 대상으로 분리
## 7. 구현 시 주의사항
- 지도 팝업 컴포넌트들은 일반 화면/등록 화면과 공용인 경우가 많다.
- 따라서 수정은 반드시 `isMap` 또는 팝업 전용 경로 기준으로 제한해야 한다.
- `PbnstSttnModal`의 일반 화면 `ScrollArea + h-[70vh]` 동작은 회귀되지 않게 유지해야 한다.
- 잠재수요처의 경우 내부 폼이 아니라 바깥 공통 래퍼 문제일 가능성이 크므로, 개별 폼 높이부터 섣불리 줄이지 않는다.
- `UiTable`, `ScrollArea`, 카드형 래퍼가 혼합되어 있어, 공통 래퍼와 개별 본문 래퍼의 책임을 분리해서 봐야 한다.
## 8. 검증 계획
### 8.1 하수재이용시설 검증
- `tb_wr_bdst_ruse_fclt` 클릭 시 팝업 본문 하단이 잘리지 않는다.
- `하수재이용시설 현황` 아래 `하수재이용실적` 영역이 같은 배경 안에서 정상적으로 이어진다.
- 데이터가 길어도 전체 스크롤 또는 의도한 내부 스크롤이 정상 동작한다.
### 8.2 잠재수요처 검증
- `tb_wr_tmpr_dmd_co` 클릭 시 마지막 입력 필드 아래로 흰색 빈 영역이 남지 않는다.
- 팝업 창 하단까지 `bg-background`가 자연스럽게 이어진다.
- 내부 스크롤은 유지되되, 배경색이 창 하단에서 끊기지 않는다.
### 8.3 선택 팝업 공통 검증
- 발생 그룹인 `tb_wr_tmpr_dmd_co`, `tb_wr_bdst_ruse_fclt`, `tb_il_swage_prcs_fclt`는 흰 여백이 제거되는지 확인한다.
- 비교 그룹인 `tb_wr_ruse_fclt_dmd_co`, `tb_wr_rnwtr_utztn_fclt`, `tb_wr_rswtr_fclt`는 기존처럼 흰 여백 없이 유지되는지 확인한다.
- 컨텐츠가 짧은 팝업에서 하단 흰색 잔여 영역이 남지 않는다.
- 컨텐츠가 긴 팝업에서 잘림 없이 스크롤이 가능하다.
## 9. 결론
- 이번 이슈는 `하수재이용시설`의 고정 높이 문제와 `잠재수요처`·`하수처리시설`의 하단 배경 노출 문제로 나뉘지만, 비교 결과 공통 출발점은 `mapFeaturePopup/page.tsx` 공통 래퍼다.
- 1차 우선순위는 `mapFeaturePopup/page.tsx` 공통 래퍼를 viewport 기준으로 정규화하는 것이다.
- 2차 우선순위는 `PbnstSttnModal`의 지도 팝업 전용 `70vh` 고정 높이 제거다.
- 이후에는 "개별 본문 배경을 직접 가지는 팝업"과 "공통 래퍼 배경에 의존하는 팝업"의 차이를 기준으로, 하단 흰색 잔여 영역과 컨텐츠 잘림을 함께 정리하는 것이 맞다.
@@ -0,0 +1,191 @@
# apps/wr 테마 적용 작업계획
작성일: 2026-06-23
대상 앱: `D:\iipbs\frontend\apps\wr`
수정불가 component : `packages/common/components`와 `packages/common/assets`
수정가능 파일 : `packages/common/business_components/*`, `apps/wr/*`
## 1. 분석 요약
- 현재 테마 구조는 이미 `data-theme` 기반으로 잡혀 있다.
- 실제 테마 키는 `default + black + blue + green + red` 기준으로 운영되고 있다.
- `apps/wr`는 `src/app/index.tsx`에서 `site-theme-key` 쿠키값을 읽어 `<html data-theme=...>`에 주입한다.
- `packages/common/components`와 `packages/common/assets`는 CSS 변수와 컴포넌트별 `.css` 파일을 통해 테마를 적용하는 구조다.
- 이번 작업의 전제는 `business_components`가 1차로 `assets`를 참조하고, 2차로 관련 `components`의 CSS 변경을 따라가야 한다는 점이다.
- 반면 현재 `packages/common/business_components`는 다수의 UI가 TSX 내부 Tailwind 클래스에 직접 의존하고 있어 위 전제를 충분히 만족하지 못한다.
- `apps/wr` 내부에서 `@common/business_components`를 참조하는 파일 수가 많아 영향 범위가 크다.
## 2. 확인된 현재 구조
### 2.1 테마 진입점
- `D:\iipbs\frontend\apps\wr\src\app\index.tsx`
- `site-theme-key` 쿠키를 읽어 `data-theme`를 설정한다.
- `D:\iipbs\frontend\packages\common\components\src\ui\header\index.tsx`
- 사이트 정보 조회 후 `site-theme-key` 쿠키와 `document.documentElement[data-theme]`를 갱신한다.
### 2.2 공통 테마 자산
- `D:\iipbs\frontend\packages\common\assets\src\styles\base.css`
- `:root`, `[data-theme="black"]`, `[data-theme="red"]`, `[data-theme="green"]`에서 CSS 변수를 정의한다.
- `D:\iipbs\frontend\packages\common\components\src\ui\*.css`
- `header.css`, `table.css`, `sidebar.css` 등에서 컴포넌트별 테마 오버라이드를 관리한다.
- `D:\iipbs\frontend\apps\wr\src\app\styles\components*.css`
- 앱 레벨의 blue/green/red/black 보정 CSS가 이미 존재한다.
### 2.3 business_components 현황
- `packages/common/business_components/src/ui` 기준 소스 파일 수: 81
- 같은 경로 기준 CSS 파일 수: 6
- 이 중 일반 UI용 CSS는 사실상 `date_picker/date-picker.css` 중심이고, 나머지는 지도 legacy CSS 성격이 강하다.
- 하드코딩된 `text-gray-*`, `bg-white`, `border-gray-*`, `hover:bg-gray-*`, `dark:*` 패턴이 확인된 파일 수: 28
## 3. 핵심 판단
### 3.1 CSS 전환 가능 여부
- 가능하다.
- 다만 "모든 Tailwind를 제거"가 아니라 "테마 의존 스타일을 CSS로 추출하고, `assets` 및 `components` 참조 구조에 편입"하는 방향이 맞다.
- 즉 배경색, 글자색, 보더색, hover/active 색상, 선택 상태, 강조색은 CSS 변수 기반 클래스로 이동한다.
- 반대로 `flex`, `grid`, `gap`, `w-*`, `h-*`, `p-*` 같은 구조/간격 유틸리티까지 전부 CSS로 옮기는 것은 효율이 낮다.
### 3.2 왜 이 방향이 맞는가
- 기존 `components`가 이미 같은 패턴을 사용한다.
- `assets/base.css`의 토큰(`--background`, `--foreground`, `--primary`, `--border`, `--label`, `--container`, `--title` 등)을 재사용할 수 있다.
- `business_components`는 `주 component가 바뀌면 따라 바뀌어야 한다`는 전제가 있으므로, 자체 색상 하드코딩보다 상위 스타일 자산 참조가 우선되어야 한다.
- 따라서 `business_components` 스타일은 독립 규격이 아니라 `assets -> components -> business_components` 흐름 안에서 정리되어야 한다.
- `apps/wr`는 `business_components` 사용 범위가 넓어, 테마 속성만 먼저 추출해도 화면 일관성이 크게 개선된다.
- 전부 TSX 내부 유틸리티로 유지하면 5개 테마 차이를 한 번에 통제하기 어렵다.
### 3.3 참조 원칙
- 1차 기준: `@common/assets/styles/*`
- 2차 기준: 관련 `@common/components` 하위 CSS
- 3차 보완: 위 두 곳으로 흡수되지 않는 business 전용 표현만 `business_components` 내부 CSS에 작성
즉 `business_components`는 가능한 한 직접 색상을 소유하지 않고, 상위 레이어가 정의한 테마 결과를 따라가는 구조로 맞춘다.
## 4. 우선 전환 대상
### 4.1 1순위: 색상 하드코딩이 직접 보이는 공통 UI
- `packages/common/business_components/src/ui/file-upload/index.tsx`
- `text-gray-*`, `outline-slate-500`, `dark:text-gray-400` 사용
- `packages/common/business_components/src/ui/tab/index.tsx`
- `text-blue-600`, `text-gray-500`, `border-gray-200`, `hover:border-gray-300` 사용
- `packages/common/business_components/src/ui/search_input/index.tsx`
- `packages/common/business_components/src/ui/search_input_select/index.tsx`
- `packages/common/business_components/src/ui/select/index.tsx`
- `packages/common/business_components/src/ui/form_ui/index.tsx`
- `packages/common/business_components/src/ui/search_icon/index.tsx`
이 영역은 `apps/wr`의 검색조건, 입력폼, 탭, 업로드 UI 전반에 재사용될 가능성이 높아서 우선순위가 높다.
### 4.2 2순위: 테이블/검색/모달 계열
- `packages/common/business_components/src/ui/table/index.tsx`
- `packages/common/business_components/src/ui/table/HeadMakeColSpan.tsx`
- `packages/common/business_components/src/ui/search_address/SearchAddressModal.tsx`
- `packages/common/business_components/src/ui/map/searchBar/SearchInputText.tsx`
- `packages/common/business_components/src/ui/map/searchBar/searchBar.tsx`
- `packages/common/business_components/src/ui/map/searchBar/procSearchBar.tsx`
이 영역은 화면 체감도가 높고, 회색/흰색/파란색 하드코딩 비중이 높다.
### 4.3 3순위: 차트 계열
- `packages/common/business_components/src/ui/chart/*.tsx`
- `packages/common/business_components/src/ui/chart/lib/chart.tsx`
- `packages/common/business_components/src/ui/chart/lib/card.tsx`
차트는 레이아웃보다 tooltip, legend, card, axis text 색상이 중요하다. 우선 색상과 surface만 토큰화하고, 차트 팔레트는 기존 CSS 변수(`--chart-*`)를 재사용하는 방식이 적절하다.
### 4.4 별도 트랙: 지도 legacy 영역
- `packages/common/business_components/src/ui/map/**`
- `packages/common/business_components/src/ui/map/lib/css/**`
지도는 legacy JS/CSS, sprite/image, OpenLayers 연동이 섞여 있어 일반 UI와 분리해서 작업하는 것이 안전하다.
## 5. 권장 작업 방식
### 단계 1. 테마 기준 정리
- `business_components`의 참조 순서를 먼저 고정한다.
- 1차: `assets` 토큰/공통 스타일 참조
- 2차: 관련 `components` CSS 참조
- 3차: business 전용 CSS 최소 추가
- `components`에서 이미 쓰는 토큰과 클래스 패턴을 기준 문법으로 고정한다.
- `business_components`에서 새로 쓰는 색상 클래스는 직접 색상값을 쓰지 말고, 우선 상위 참조 자산으로 해결 가능한지 확인한 뒤 CSS 변수 기반으로 통일한다.
- 예시 기준
- 텍스트: `text-foreground`, `text-label`, `text-title`
- 배경: `bg-background`, `bg-card`, `bg-container`, `bg-form`
- 보더: `border-border`
- 포커스/선택: `--primary`, `--primary-transparent`
### 단계 2. 컴포넌트별 CSS 파일 추가
- 먼저 관련 `components`에 이미 존재하는 CSS가 있는지 확인한다.
- 같은 성격의 UI라면 `business_components`가 그 CSS 결과를 따라가도록 구조를 맞춘다.
- 상위 참조만으로 해결되지 않는 경우에만 `components`와 동일하게 각 컴포넌트 폴더에 `.css`를 둔다.
- CSS 파일명은 `packages/common/components`와 동일한 규칙으로 작성한다.
- 즉 CSS 파일명은 컴포넌트 폴더/컴포넌트명 기준의 kebab-case를 유지한다.
- CSS 클래스명은 너무 길게 만들지 않는다.
- 네이밍은 `__` 대신 `_`를 사용한다.
- 컴포넌트명도 필요 이상으로 길게 쓰지 않고 축약형을 허용한다.
- 예시
- `file-upload/file-upload.css`
- `tab/tab.css`
- `search_input/search-input.css`
- `select/select.css`
- `search_address/search-address.css`
- TSX에서는 색상/상태 관련 className을 의미 있는 클래스명으로 치환한다.
- 이때 business 전용 CSS는 상위 CSS와 충돌하지 않도록 override 범위를 최소화한다.
예시 방향
- 변경 전: `text-gray-500`, `bg-white`, `hover:bg-gray-50`, `data-[state=active]:text-blue-600`
- 변경 후: `fileup_hint`, `tab_trigger`, `searchaddr_suggestion`, `is_active`
### 단계 3. 테마별 분기 작성
- 기본값은 `:root` 또는 기본 클래스에서 처리한다.
- 테마 차이가 필요한 경우만 `[data-theme="black"]`, `[data-theme="blue"]`, `[data-theme="green"]`, `[data-theme="red"]` 블록을 추가한다.
- 동일한 값은 중복 정의하지 않는다.
- 가능하면 테마 값은 `assets` 또는 `components`에서 정의하고, `business_components`는 해당 결과를 소비하는 쪽으로 유지한다.
### 단계 4. apps/wr 화면 검증
- `apps/wr`에서 `business_components` 사용도가 높은 화면부터 확인한다.
- 우선 검증 화면
- 통계 화면
- 재이용시설 관리 등록/현황 화면
- 자재관리 검색/등록 화면
- 지도 검색바/상세팝업/레이어팝업
## 6. 실제 작업 순서
1. `business_components` 내 테마 하드코딩 파일 목록 확정
2. 각 대상 UI별로 `assets` 참조와 `components` 참조 가능 여부를 먼저 판정
3. 공통 토큰 매핑표 작성
4. 1순위 UI에 상위 참조 우선 적용 후 부족한 부분만 business 전용 CSS 추가
5. `apps/wr` 주요 화면에서 5개 테마 수동 점검
6. 2순위 테이블/검색/모달 영역 확장
7. 차트 계열 색상 토큰화
8. 지도 legacy 영역은 별도 보수 계획으로 분리하며 배경지도는 수정하지 않는다.
## 7. 검증 기준
- `assets` , `components`가 수정되면 안된다.
- 같은 화면에서 5개 테마 전환 시 텍스트, 보더, 배경, hover, 선택상태가 모두 읽혀야 한다.
- black 테마에서 회색/흰색 하드코딩이 남아 흐릿하거나 과도하게 튀는 영역이 없어야 한다.
- blue/green/red 테마에서 활성 탭, 버튼, 선택상태가 `primary` 계열로 일관되게 바뀌어야 한다.
- `apps/wr` 기존 기능 동작은 그대로 유지되어야 한다.
## 8. 결론
- `business_components`의 Tailwind 스타일을 `assets` 1차 참조, `components` 2차 참조 구조에 맞춰 재편하는 것은 가능하다.
- 다만 전체 유틸리티를 전부 CSS로 옮기기보다, 테마에 영향을 주는 색상/상태 표현을 우선 상위 자산 참조 구조로 흡수하는 방식이 가장 현실적이다.
- 우선순위는 `공통 입력 UI -> 테이블/검색/모달 -> 차트 -> 지도 legacy` 순으로 잡는 것이 좋다.

Some files were not shown because too many files have changed in this diff Show More