추가 수정사항 반영

1. 페이지네이션, 스크롤, 날짜 선택 ui 추가
2. 공통코드 조회 api 적용 (위성 조회, 등록 팝업 등)
3. 필수값 입력 메세지 추가
This commit is contained in:
jeonghyo.K 2026-02-11 16:49:26 +09:00
부모 1c991d8229
커밋 81255c4839
6개의 변경된 파일369개의 추가작업 그리고 257개의 파일을 삭제

파일 보기

@ -71,6 +71,8 @@
.schInput{height: 3.5rem; font-family: 'NanumSquare', sans-serif;font-size: var(--fs-m); color: var(--white);background-color: var(--tertiary1);padding: 0 1.2rem; border: 0;} .schInput{height: 3.5rem; font-family: 'NanumSquare', sans-serif;font-size: var(--fs-m); color: var(--white);background-color: var(--tertiary1);padding: 0 1.2rem; border: 0;}
.schInput::placeholder { color:rgba(var(--white-rgb),.3); } .schInput::placeholder { color:rgba(var(--white-rgb),.3); }
.dateInput {background:url(../images/ico_input_cal.svg) no-repeat center right .5rem/2.4rem; padding-right: 3rem; cursor: pointer;} .dateInput {background:url(../images/ico_input_cal.svg) no-repeat center right .5rem/2.4rem; padding-right: 3rem; cursor: pointer;}
.dateInput { position: relative; }
.dateInput::-webkit-calendar-picker-indicator { opacity: 0; position: absolute; right: 0; width: 3rem; height: 100%;cursor: pointer; }
.dateInput::placeholder { color:var(--white); } .dateInput::placeholder { color:var(--white); }
/* ========================= /* =========================
@ -107,6 +109,15 @@
.colList.lineSB li a .title {font-size: var(--fs-m); font-weight: var(--fw-bold);} .colList.lineSB li a .title {font-size: var(--fs-m); font-weight: var(--fw-bold);}
.colList.lineSB li a .meta {font-size: var(--fs-s); font-weight: var(--fw-regular); color:rgba(var(--white-rgb),.5);} .colList.lineSB li a .meta {font-size: var(--fs-s); font-weight: var(--fw-regular); color:rgba(var(--white-rgb),.5);}
/* 페이지네이션 */
.pagination {display: flex; align-items: center; justify-content: center; gap: .5rem; padding: 1.4rem 0;}
.pagination button {min-width: 2.8rem; height: 2.8rem; padding: 0 .6rem; border-radius: .4rem; background-color: var(--secondary1); color: var(--white); font-size: var(--fs-m); font-weight: var(--fw-bold); border: 1px solid var(--secondary3); cursor: pointer; transition: background-color .15s ease, border-color .15s ease;}
.pagination button:hover {background-color: var(--secondary3); border-color: var(--secondary4);}
.pagination button.on {background-color: var(--primary1); border-color: var(--primary1);}
.pagination button.on:hover {background-color: var(--primary2); border-color: var(--primary2);}
.pagination button.disabled {opacity: 0.4; cursor: default; pointer-events: none;}
.pagination .ellipsis {color: rgba(var(--white-rgb), .4); font-size: var(--fs-m); padding: 0 .2rem; user-select: none;}
/* 아코디언리스트 */ /* 아코디언리스트 */
.accordionWrap {display: flex;flex-direction: column;transition: max-height 0.3s ease;} .accordionWrap {display: flex;flex-direction: column;transition: max-height 0.3s ease;}
.accordionWrap .acdHeader {display: flex; justify-content: space-between; align-items: center; height: 4rem; background-color: var(--secondary1); padding: 1rem; border-bottom: .1rem solid var(--secondary3);} .accordionWrap .acdHeader {display: flex; justify-content: space-between; align-items: center; height: 4rem; background-color: var(--secondary1); padding: 1rem; border-bottom: .1rem solid var(--secondary3);}
@ -292,7 +303,7 @@
align-items: center; align-items: center;
z-index: 999; z-index: 999;
} }
.popupUtillWrap { position: absolute;top: 50%; left: 50%;transform: translate(-50%, -50%);z-index :85;} .popupUtillWrap { position: fixed;top: 50%; left: 50%;transform: translate(-50%, -50%);z-index :100;}
.popupUtill {display: flex; flex-direction: column; width: 52.5rem; height:auto;max-height: 80vh;overflow: hidden; background-color: var(--secondary2); border: .1rem solid var(--secondary3); padding:2.5rem 3rem;} .popupUtill {display: flex; flex-direction: column; width: 52.5rem; height:auto;max-height: 80vh;overflow: hidden; background-color: var(--secondary2); border: .1rem solid var(--secondary3); padding:2.5rem 3rem;}
.popupUtill > .puHeader {display: flex; justify-content: space-between; align-items: center; padding-bottom: 2rem;} .popupUtill > .puHeader {display: flex; justify-content: space-between; align-items: center; padding-bottom: 2rem;}
.popupUtill > .puHeader > .title {font-weight: var(--fw-bold); font-size: var(--fs-xl);} .popupUtill > .puHeader > .title {font-weight: var(--fw-bold); font-size: var(--fs-xl);}

파일 보기

@ -1,4 +1,5 @@
import { useState, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { fetchCommonCodeList } from '@/api/commonApi';
import { fetchSatelliteVideoList, fetchSatelliteCsvFeatures } from '@/api/satelliteApi'; import { fetchSatelliteVideoList, fetchSatelliteCsvFeatures } from '@/api/satelliteApi';
import { useSatelliteStore } from '@/stores/satelliteStore'; import { useSatelliteStore } from '@/stores/satelliteStore';
import { useMapStore } from '@/stores/mapStore'; import { useMapStore } from '@/stores/mapStore';
@ -11,6 +12,12 @@ const LIMIT = 10;
export default function SatelliteImageManage() { export default function SatelliteImageManage() {
const [isAccordionOpen, setIsAccordionOpen] = useState(false); const [isAccordionOpen, setIsAccordionOpen] = useState(false);
//
const [videoKindOptions, setVideoKindOptions] = useState([]);
const [videoOriginOptions, setVideoOriginOptions] = useState([]);
const [videoOrbitOptions, setVideoOrbitOptions] = useState([]);
const [videoCycleOptions, setVideoCycleOptions] = useState([]);
// state // state
const [startDate, setStartDate] = useState(''); const [startDate, setStartDate] = useState('');
const [endDate, setEndDate] = useState(''); const [endDate, setEndDate] = useState('');
@ -42,6 +49,14 @@ export default function SatelliteImageManage() {
const setOpacity = useSatelliteStore((s) => s.setOpacity); const setOpacity = useSatelliteStore((s) => s.setOpacity);
const setBrightness = useSatelliteStore((s) => s.setBrightness); const setBrightness = useSatelliteStore((s) => s.setBrightness);
//
useEffect(() => {
fetchCommonCodeList('000109').then(setVideoKindOptions).catch(() => setVideoKindOptions([]));
fetchCommonCodeList('000111').then(setVideoOriginOptions).catch(() => setVideoOriginOptions([]));
fetchCommonCodeList('000110').then(setVideoOrbitOptions).catch(() => setVideoOrbitOptions([]));
fetchCommonCodeList('000108').then(setVideoCycleOptions).catch(() => setVideoCycleOptions([]));
}, []);
const toggleAccordion = () => setIsAccordionOpen((prev) => !prev); const toggleAccordion = () => setIsAccordionOpen((prev) => !prev);
const search = useCallback(async (targetPage) => { const search = useCallback(async (targetPage) => {
@ -162,11 +177,11 @@ export default function SatelliteImageManage() {
onChange={(e) => setSatelliteVideoKind(e.target.value)} onChange={(e) => setSatelliteVideoKind(e.target.value)}
> >
<option value="">전체</option> <option value="">전체</option>
<option value="VIRS">VIIRS</option> {videoKindOptions.map((opt) => (
<option value="ICEYE_SAR">ICEYE_SAR</option> <option key={opt.commonCodeTypeNumber} value={opt.commonCodeTypeNumber}>
<option value="광학">광학</option> {opt.commonCodeTypeName}
<option value="예약">예약</option> </option>
<option value="RF">RF</option> ))}
</select> </select>
</label> </label>
<label> <label>
@ -176,10 +191,11 @@ export default function SatelliteImageManage() {
onChange={(e) => setSatelliteVideoOrigin(e.target.value)} onChange={(e) => setSatelliteVideoOrigin(e.target.value)}
> >
<option value="">전체</option> <option value="">전체</option>
<option value="국내/자동">국내/자동</option> {videoOriginOptions.map((opt) => (
<option value="국내/수동">국내/수동</option> <option key={opt.commonCodeTypeNumber} value={opt.commonCodeTypeNumber}>
<option value="국외/수동">국외/수동</option> {opt.commonCodeTypeName}
<option value="기타">기타</option> </option>
))}
</select> </select>
</label> </label>
</li> </li>
@ -191,10 +207,11 @@ export default function SatelliteImageManage() {
onChange={(e) => setSatelliteVideoOrbit(e.target.value)} onChange={(e) => setSatelliteVideoOrbit(e.target.value)}
> >
<option value="">전체</option> <option value="">전체</option>
<option value="저궤도">저궤도</option> {videoOrbitOptions.map((opt) => (
<option value="중궤도">중궤도</option> <option key={opt.commonCodeTypeNumber} value={opt.commonCodeTypeNumber}>
<option value="정지궤도">정지궤도</option> {opt.commonCodeTypeName}
<option value="기타">기타</option> </option>
))}
</select> </select>
</label> </label>
<label> <label>
@ -204,10 +221,11 @@ export default function SatelliteImageManage() {
onChange={(e) => setSatelliteVideoTransmissionCycle(e.target.value)} onChange={(e) => setSatelliteVideoTransmissionCycle(e.target.value)}
> >
<option value="">전체</option> <option value="">전체</option>
<option value="0">0</option> {videoCycleOptions.map((opt) => (
<option value="10">10</option> <option key={opt.commonCodeTypeNumber} value={opt.commonCodeTypeNumber}>
<option value="30">30</option> {opt.commonCodeTypeName}
<option value="60">60</option> </option>
))}
</select> </select>
</label> </label>
</li> </li>

파일 보기

@ -1,6 +1,9 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import {createPortal} from "react-dom";
import { showToast } from '@/components/common/Toast';
import { fetchCommonCodeList } from '@/api/commonApi'; import { fetchCommonCodeList } from '@/api/commonApi';
import { import {
fetchSatelliteCompanyList,
saveSatelliteManage, saveSatelliteManage,
fetchSatelliteManageDetail, fetchSatelliteManageDetail,
updateSatelliteManage, updateSatelliteManage,
@ -16,6 +19,7 @@ export default function SatelliteManageRegisterPopup({ satelliteManageId, onClos
const isEditMode = !!satelliteManageId; const isEditMode = !!satelliteManageId;
const [sensorTypeOptions, setSensorTypeOptions] = useState([]); const [sensorTypeOptions, setSensorTypeOptions] = useState([]);
const [companyOptions, setCompanyOptions] = useState([]);
const [companyNo, setCompanyNo] = useState(''); const [companyNo, setCompanyNo] = useState('');
const [satelliteName, setSatelliteName] = useState(''); const [satelliteName, setSatelliteName] = useState('');
@ -38,9 +42,13 @@ export default function SatelliteManageRegisterPopup({ satelliteManageId, onClos
setError(null); setError(null);
try { try {
const codeList = await fetchCommonCodeList('000092'); const [codeList, companyList] = await Promise.all([
fetchCommonCodeList('000092'),
fetchSatelliteCompanyList(),
]);
if (cancelled) return; if (cancelled) return;
setSensorTypeOptions(codeList); setSensorTypeOptions(codeList);
setCompanyOptions(companyList);
if (satelliteManageId) { if (satelliteManageId) {
const data = await fetchSatelliteManageDetail(satelliteManageId); const data = await fetchSatelliteManageDetail(satelliteManageId);
@ -65,6 +73,15 @@ export default function SatelliteManageRegisterPopup({ satelliteManageId, onClos
}, [satelliteManageId]); }, [satelliteManageId]);
const handleSave = async () => { const handleSave = async () => {
if (!companyNo) {
showToast('사업자명을 선택해주세요.');
return;
}
if (!satelliteName.trim()) {
showToast('위성명을 입력해주세요.');
return;
}
setIsSaving(true); setIsSaving(true);
setError(null); setError(null);
@ -98,134 +115,140 @@ export default function SatelliteManageRegisterPopup({ satelliteManageId, onClos
} }
}; };
return ( return createPortal(
<div className="popupUtillWrap" style={{ transform: `translate(calc(-50% + ${position.x}px), calc(-50% + ${position.y}px))` }}> <div className="popupUtillWrap" style={{ transform: `translate(calc(-50% + ${position.x}px), calc(-50% + ${position.y}px))` }}>
<div className="popupUtill"> <div className="popupUtill">
<div className="puHeader" onMouseDown={handleMouseDown} style={{ cursor: 'grab' }}> <div className="puHeader" onMouseDown={handleMouseDown} style={{ cursor: 'grab' }}>
<span className="title"> <span className="title">
{isEditMode ? '위성 관리 상세' : '위성 관리 등록'} {isEditMode ? '위성 관리 상세' : '위성 관리 등록'}
</span> </span>
<button <button
type="button" type="button"
className="puClose" className="puClose"
aria-label="닫기" aria-label="닫기"
onClick={onClose} onClick={onClose}
/> />
</div> </div>
<div className="puBody"> <div className="puBody">
{isLoading && <div className="loading">조회 ...</div>} {isLoading && <div className="loading">조회 ...</div>}
{error && <div className="error">{error}</div>} {error && <div className="error">{error}</div>}
{!isLoading && ( {!isLoading && (
<table className="table"> <table className="table">
<caption>위성 관리 등록 - 사업자명, 위성명, 센서 타입, 촬영 해상도, 주파수, 상세내역에 대한 내용을 등록하는 표입니다.</caption> <caption>위성 관리 등록 - 사업자명, 위성명, 센서 타입, 촬영 해상도, 주파수, 상세내역에 대한 내용을 등록하는 표입니다.</caption>
<colgroup> <colgroup>
<col style={{ width: '30%' }} /> <col style={{ width: '30%' }} />
<col style={{ width: '' }} /> <col style={{ width: '' }} />
</colgroup> </colgroup>
<tbody> <tbody>
<tr> <tr>
<th scope="row">사업자명 <span className="required">*</span></th> <th scope="row">사업자명 <span className="required">*</span></th>
<td> <td>
<input <select
type="text" aria-label="사업자명"
placeholder="사업자명" value={companyNo}
aria-label="사업자명" onChange={(e) => setCompanyNo(e.target.value)}
value={companyNo} >
onChange={(e) => setCompanyNo(e.target.value)} <option value="">선택</option>
/> {companyOptions.map((opt) => (
</td> <option key={opt.companyNo} value={opt.companyNo}>
</tr> {opt.companyName}
<tr> </option>
<th scope="row">위성명 <span className="required">*</span></th> ))}
<td> </select>
<input </td>
type="text" </tr>
placeholder="위성명" <tr>
aria-label="위성명" <th scope="row">위성명 <span className="required">*</span></th>
value={satelliteName} <td>
onChange={(e) => setSatelliteName(e.target.value)} <input
/> type="text"
</td> placeholder="위성명"
</tr> aria-label="위성명"
<tr> value={satelliteName}
<th scope="row">센서 타입</th> onChange={(e) => setSatelliteName(e.target.value)}
<td> />
<select </td>
aria-label="센서 타입" </tr>
value={sensorType} <tr>
onChange={(e) => setSensorType(e.target.value)} <th scope="row">센서 타입</th>
> <td>
<option value="">선택</option> <select
{sensorTypeOptions.map((opt) => ( aria-label="센서 타입"
<option key={opt.commonCodeTypeNumber} value={opt.commonCodeTypeNumber}> value={sensorType}
{opt.commonCodeTypeName} onChange={(e) => setSensorType(e.target.value)}
</option> >
))} <option value="">선택</option>
</select> {sensorTypeOptions.map((opt) => (
</td> <option key={opt.commonCodeTypeNumber} value={opt.commonCodeTypeNumber}>
</tr> {opt.commonCodeTypeName}
<tr> </option>
<th scope="row">촬영 해상도</th> ))}
<td> </select>
<input </td>
type="text" </tr>
placeholder="촬영 해상도" <tr>
aria-label="촬영 해상도" <th scope="row">촬영 해상도</th>
value={photoResolution} <td>
onChange={(e) => setPhotoResolution(e.target.value)} <input
/> type="text"
</td> placeholder="촬영 해상도"
</tr> aria-label="촬영 해상도"
<tr> value={photoResolution}
<th scope="row">주파수</th> onChange={(e) => setPhotoResolution(e.target.value)}
<td> />
<input </td>
type="text" </tr>
placeholder="주파수" <tr>
aria-label="주파수" <th scope="row">주파수</th>
value={frequency} <td>
onChange={(e) => setFrequency(e.target.value)} <input
/> type="text"
</td> placeholder="주파수"
</tr> aria-label="주파수"
<tr> value={frequency}
<th scope="row">상세내역</th> onChange={(e) => setFrequency(e.target.value)}
<td> />
</td>
</tr>
<tr>
<th scope="row">상세내역</th>
<td>
<textarea <textarea
placeholder="내용을 입력하세요" placeholder="내용을 입력하세요"
aria-label="상세내역" aria-label="상세내역"
value={photoDetail} value={photoDetail}
onChange={(e) => setPhotoDetail(e.target.value)} onChange={(e) => setPhotoDetail(e.target.value)}
/> />
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
)} )}
</div> </div>
<div className="puFooter"> <div className="puFooter">
<div className="popBtnWrap"> <div className="popBtnWrap">
<button <button
type="button" type="button"
className="btn basic" className="btn basic"
onClick={handleSave} onClick={handleSave}
disabled={isSaving} disabled={isSaving}
> >
{isSaving ? '저장 중...' : isEditMode ? '수정' : '저장'} {isSaving ? '저장 중...' : isEditMode ? '수정' : '저장'}
</button> </button>
<button <button
type="button" type="button"
className="btn dark" className="btn dark"
onClick={onClose} onClick={onClose}
> >
취소 취소
</button> </button>
</div>
</div> </div>
</div> </div>
</div> </div>,
</div> document.body
); );
} }

파일 보기

@ -1,4 +1,6 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import {createPortal} from "react-dom";
import { showToast } from '@/components/common/Toast';
import { fetchCommonCodeList } from '@/api/commonApi'; import { fetchCommonCodeList } from '@/api/commonApi';
import { import {
saveSatelliteCompany, saveSatelliteCompany,
@ -56,13 +58,13 @@ export default function SatelliteProviderRegisterPopup({ companyNo, onClose, onS
// companyTypeName companyTypeCode // companyTypeName companyTypeCode
const matchedType = codeList.find( const matchedType = codeList.find(
(opt) => opt.commonCodeTypeName === data.companyTypeName (opt) => opt.commonCodeTypeName === data.companyTypeName
); );
setCompanyTypeCode(matchedType?.commonCodeTypeNumber || ''); setCompanyTypeCode(matchedType?.commonCodeTypeNumber || '');
// nationalCodeName nationalCode // nationalCodeName nationalCode
const matchedNation = NATIONAL_OPTIONS.find( const matchedNation = NATIONAL_OPTIONS.find(
(opt) => opt.name === data.nationalCodeName (opt) => opt.name === data.nationalCodeName
); );
setNationalCode(matchedNation?.code || ''); setNationalCode(matchedNation?.code || '');
@ -82,6 +84,19 @@ export default function SatelliteProviderRegisterPopup({ companyNo, onClose, onS
}, [companyNo]); }, [companyNo]);
const handleSave = async () => { const handleSave = async () => {
if (!companyTypeCode) {
showToast('사업자 분류를 선택해주세요.');
return;
}
if (!companyName.trim()) {
showToast('사업자명을 입력해주세요.');
return;
}
if (!nationalCode) {
showToast('국가를 선택해주세요.');
return;
}
setIsSaving(true); setIsSaving(true);
setError(null); setError(null);
@ -113,129 +128,130 @@ export default function SatelliteProviderRegisterPopup({ companyNo, onClose, onS
} }
}; };
return ( return createPortal(
<div className="popupUtillWrap" style={{ transform: `translate(calc(-50% + ${position.x}px), calc(-50% + ${position.y}px))` }}> <div className="popupUtillWrap" style={{ transform: `translate(calc(-50% + ${position.x}px), calc(-50% + ${position.y}px))` }}>
<div className="popupUtill"> <div className="popupUtill">
<div className="puHeader" onMouseDown={handleMouseDown} style={{ cursor: 'grab' }}> <div className="puHeader" onMouseDown={handleMouseDown} style={{ cursor: 'grab' }}>
<span className="title"> <span className="title">
{isEditMode ? '위성 사업자 상세' : '위성 사업자 등록'} {isEditMode ? '위성 사업자 상세' : '위성 사업자 등록'}
</span> </span>
<button <button
type="button" type="button"
className="puClose" className="puClose"
aria-label="닫기" aria-label="닫기"
onClick={onClose} onClick={onClose}
/> />
</div> </div>
<div className="puBody"> <div className="puBody">
{isLoading && <div className="loading">조회 ...</div>} {isLoading && <div className="loading">조회 ...</div>}
{error && <div className="error">{error}</div>} {error && <div className="error">{error}</div>}
{!isLoading && ( {!isLoading && (
<table className="table"> <table className="table">
<caption> <caption>
위성 사업자 {isEditMode ? '상세' : '등록'} - 사업자 분류, 사업자명, 국가, 소재지, 상세내역에 대한 내용을 {isEditMode ? '조회/수정' : '등록'}하는 표입니다. 위성 사업자 {isEditMode ? '상세' : '등록'} - 사업자 분류, 사업자명, 국가, 소재지, 상세내역에 대한 내용을 {isEditMode ? '조회/수정' : '등록'}하는 표입니다.
</caption> </caption>
<colgroup> <colgroup>
<col style={{ width: '30%' }} /> <col style={{ width: '30%' }} />
<col style={{ width: '' }} /> <col style={{ width: '' }} />
</colgroup> </colgroup>
<tbody> <tbody>
<tr> <tr>
<th scope="row">사업자 분류 <span className="required">*</span></th> <th scope="row">사업자 분류 <span className="required">*</span></th>
<td> <td>
<select <select
aria-label="사업자 분류" aria-label="사업자 분류"
value={companyTypeCode} value={companyTypeCode}
onChange={(e) => setCompanyTypeCode(e.target.value)} onChange={(e) => setCompanyTypeCode(e.target.value)}
> >
<option value="">선택</option> <option value="">선택</option>
{companyTypeOptions.map((opt) => ( {companyTypeOptions.map((opt) => (
<option key={opt.commonCodeTypeNumber} value={opt.commonCodeTypeNumber}> <option key={opt.commonCodeTypeNumber} value={opt.commonCodeTypeNumber}>
{opt.commonCodeTypeName} {opt.commonCodeTypeName}
</option> </option>
))} ))}
</select> </select>
</td> </td>
</tr> </tr>
<tr> <tr>
<th scope="row">사업자명 <span className="required">*</span></th> <th scope="row">사업자명 <span className="required">*</span></th>
<td> <td>
<input <input
type="text" type="text"
placeholder="사업자명" placeholder="사업자명"
aria-label="사업자명" aria-label="사업자명"
value={companyName} value={companyName}
onChange={(e) => setCompanyName(e.target.value)} onChange={(e) => setCompanyName(e.target.value)}
/> />
</td> </td>
</tr> </tr>
<tr> <tr>
<th scope="row">국가 <span className="required">*</span></th> <th scope="row">국가 <span className="required">*</span></th>
<td> <td>
<select <select
aria-label="국가" aria-label="국가"
value={nationalCode} value={nationalCode}
onChange={(e) => setNationalCode(e.target.value)} onChange={(e) => setNationalCode(e.target.value)}
> >
<option value="">선택</option> <option value="">선택</option>
{NATIONAL_OPTIONS.map((opt) => ( {NATIONAL_OPTIONS.map((opt) => (
<option key={opt.code} value={opt.code}> <option key={opt.code} value={opt.code}>
{opt.name} {opt.name}
</option> </option>
))} ))}
</select> </select>
</td> </td>
</tr> </tr>
<tr> <tr>
<th scope="row">소재지</th> <th scope="row">소재지</th>
<td> <td>
<input <input
type="text" type="text"
placeholder="소재지" placeholder="소재지"
aria-label="소재지" aria-label="소재지"
value={location} value={location}
onChange={(e) => setLocation(e.target.value)} onChange={(e) => setLocation(e.target.value)}
/> />
</td> </td>
</tr> </tr>
<tr> <tr>
<th scope="row">상세내역</th> <th scope="row">상세내역</th>
<td> <td>
<textarea <textarea
placeholder="내용을 입력하세요" placeholder="내용을 입력하세요"
aria-label="상세내역" aria-label="상세내역"
value={companyDetail} value={companyDetail}
onChange={(e) => setCompanyDetail(e.target.value)} onChange={(e) => setCompanyDetail(e.target.value)}
/> />
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
)} )}
</div> </div>
<div className="puFooter"> <div className="puFooter">
<div className="popBtnWrap"> <div className="popBtnWrap">
<button <button
type="button" type="button"
className="btn basic" className="btn basic"
onClick={handleSave} onClick={handleSave}
disabled={isSaving} disabled={isSaving}
> >
{isSaving ? '저장 중...' : isEditMode ? '수정' : '저장'} {isSaving ? '저장 중...' : isEditMode ? '수정' : '저장'}
</button> </button>
<button <button
type="button" type="button"
className="btn dark" className="btn dark"
onClick={onClose} onClick={onClose}
> >
취소 취소
</button> </button>
</div>
</div> </div>
</div> </div>
</div> </div>,
</div> document.body
); );
} }

파일 보기

@ -1,4 +1,6 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import {createPortal} from "react-dom";
import { showToast } from '@/components/common/Toast';
import { import {
fetchSatelliteCompanyList, fetchSatelliteCompanyList,
fetchSatelliteManageList, fetchSatelliteManageList,
@ -120,6 +122,23 @@ export default function SatelliteRegisterPopup({ satelliteId, onClose, onSaved }
}, [companyNo, isEditMode]); }, [companyNo, isEditMode]);
const handleSave = async () => { const handleSave = async () => {
if (!isEditMode && !satelliteManageId) {
showToast('사업자명/위성명을 선택해주세요.');
return;
}
if (!photographDate) {
showToast('영상 촬영일을 입력해주세요.');
return;
}
if (!isEditMode && !tifFile) {
showToast('위성영상파일을 선택해주세요.');
return;
}
if (!satelliteVideoName.trim()) {
showToast('위성영상명을 입력해주세요.');
return;
}
setIsSaving(true); setIsSaving(true);
setError(null); setError(null);
@ -172,7 +191,7 @@ export default function SatelliteRegisterPopup({ satelliteId, onClose, onSaved }
// setPhotographDate(changedFormat); // setPhotographDate(changedFormat);
// } // }
return ( return createPortal(
<div className="popupUtillWrap" style={{ transform: `translate(calc(-50% + ${position.x}px), calc(-50% + ${position.y}px))` }}> <div className="popupUtillWrap" style={{ transform: `translate(calc(-50% + ${position.x}px), calc(-50% + ${position.y}px))` }}>
<div className="popupUtill w61r"> <div className="popupUtill w61r">
<div className="puHeader" onMouseDown={handleMouseDown} style={{ cursor: 'grab' }}> <div className="puHeader" onMouseDown={handleMouseDown} style={{ cursor: 'grab' }}>
@ -478,6 +497,7 @@ export default function SatelliteRegisterPopup({ satelliteId, onClose, onSaved }
</div> </div>
</div> </div>
</div> </div>
</div> </div>,
document.body
); );
} }

파일 보기

@ -416,6 +416,30 @@
} }
} }
// 위성/기상 메뉴 스크롤바 다크 테마
.tabBtm,
.tabBtmCnt {
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: rgba(var(--white-rgb), 0.3);
border-radius: 3px;
&:hover {
background: rgba(var(--white-rgb), 0.5);
}
}
scrollbar-width: thin;
scrollbar-color: rgba(255, 255, 255, 0.3) transparent;
}
} }
.toogle { .toogle {