도시침수 예방사업 파일업로드 추가
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import type { AxiosResponse } from "axios";
|
||||
|
||||
interface SortConfig {
|
||||
empty: boolean;
|
||||
sorted: boolean;
|
||||
unsorted: boolean;
|
||||
}
|
||||
|
||||
interface PageableConfig {
|
||||
sort: SortConfig;
|
||||
offset: number;
|
||||
pageNumber: number;
|
||||
pageSize: number;
|
||||
paged: boolean;
|
||||
unpaged: boolean;
|
||||
}
|
||||
|
||||
export interface PageableList<T> {
|
||||
content: T[];
|
||||
pageable: PageableConfig;
|
||||
sort: SortConfig;
|
||||
totalPages: number;
|
||||
totalElements: number;
|
||||
number: number;
|
||||
numberOfElements: number;
|
||||
size: number;
|
||||
first: boolean;
|
||||
last: boolean;
|
||||
empty: boolean;
|
||||
}
|
||||
|
||||
export interface PageableParams {
|
||||
page?: number;
|
||||
size?: number;
|
||||
sort?: string[];
|
||||
}
|
||||
|
||||
export interface AttachmentParams extends PageableParams {
|
||||
keywordType?: string;
|
||||
keyword?: string;
|
||||
atflCd?: string;
|
||||
atflSn?: number;
|
||||
atflId?: string;
|
||||
orgnlFileNm?: string;
|
||||
atflFlieSz?: number;
|
||||
atflType?: string;
|
||||
dwnldNocs?: number;
|
||||
delYn?: string;
|
||||
lnkgDmnId?: string;
|
||||
lnkgDmnNm?: string;
|
||||
}
|
||||
|
||||
export interface Attachment {
|
||||
atflCd: string;
|
||||
atflSn: number;
|
||||
atflId: string;
|
||||
physFileNm: string;
|
||||
orgnlFileNm: string;
|
||||
atflFlieSz: number;
|
||||
atflType: string;
|
||||
dwnldNocs: number;
|
||||
delYn: string;
|
||||
lnkgDmnId: string;
|
||||
lnkgDmnNm: string;
|
||||
regDt: string;
|
||||
}
|
||||
|
||||
export type AttachmentList = PageableList<Attachment>;
|
||||
|
||||
export interface AttachmentService {
|
||||
getAttachmentsList: (params: AttachmentParams) => Promise<AttachmentList>;
|
||||
getAttachmentsInfo: (atflCd: string) => Promise<Attachment[]>;
|
||||
deleteAttachmentByAtflCd: (atflCd: string) => Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import type { AxiosResponse } from "axios";
|
||||
import type {
|
||||
Attachment,
|
||||
AttachmentList,
|
||||
AttachmentParams,
|
||||
AttachmentService,
|
||||
} from "../attachment/attachment-interface";
|
||||
import { getData, deleteData } from "../index";
|
||||
|
||||
export const ENDPOINT = {
|
||||
ATTACH_SERVICE: {
|
||||
TUS_UPLOAD: "/attach-service/api/v1/tus/upload",
|
||||
EDITOR_UPLOAD: "/attach-service/api/v1/upload/editor",
|
||||
IMAGES: "/attach-service/api/v1/images",
|
||||
MOVIE_THUMBNAIL: "/attach-service/api/v1/movie/thumbnail",
|
||||
ATTACHMENTS: "/attach-service/api/v1/atfl/attachments",
|
||||
DOWNLOAD: "/attach-service/api/v1/atflcd/download",
|
||||
STREAM_VIDEO: "/attach-service/api/v1/stream/video",
|
||||
},
|
||||
};
|
||||
|
||||
export class AttachmentApiService implements AttachmentService {
|
||||
public async getAttachmentsList(
|
||||
params: AttachmentParams,
|
||||
): Promise<AttachmentList> {
|
||||
const response = await getData(`${ENDPOINT.ATTACH_SERVICE.ATTACHMENTS}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getAttachmentsInfo(atflCd: string): Promise<Attachment[]> {
|
||||
const response = await getData(
|
||||
`${ENDPOINT.ATTACH_SERVICE.ATTACHMENTS}/${atflCd}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async deleteAttachmentByAtflCd(atflCd: string): Promise<void> {
|
||||
await deleteData(`${ENDPOINT.ATTACH_SERVICE.ATTACHMENTS}/atflCd/${atflCd}`);
|
||||
}
|
||||
}
|
||||
|
||||
export const AttachmentApi = new AttachmentApiService();
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Upload, type HttpResponse } from "tus-js-client";
|
||||
import { getCookie, setCookie } from "@utils";
|
||||
import { ENDPOINT } from "../api/attachment/attachment-service";
|
||||
|
||||
const AUTH_USER_ID = process.env.AUTH_USER_ID || '';
|
||||
const CLAIM_NAME = process.env.CLAIM_NAME || '';
|
||||
const BASE_URL = process.env.NEXT_PUBLIC_SERVER_API_URL || '';
|
||||
const END_URL = `${BASE_URL}/attach-service/api/v1/tus/upload`;
|
||||
const AUTH_USER_ID = process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id";
|
||||
const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization";
|
||||
|
||||
type UploadOptions = {
|
||||
handleUploadStart?: (upload: Upload) => void;
|
||||
@@ -24,7 +23,7 @@ export const handleUpload = (files: FileList, options?: UploadOptions) => {
|
||||
};
|
||||
|
||||
const upload = new Upload(file, {
|
||||
endpoint: END_URL,
|
||||
endpoint: ENDPOINT.ATTACH_SERVICE.TUS_UPLOAD,
|
||||
chunkSize: 10 * 1024 * 1024,
|
||||
headers: {
|
||||
[CLAIM_NAME]: getCookie(CLAIM_NAME) ?? "",
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
"use client"
|
||||
import { FileUpload, SelectBox } from '@common/business_components/ui';
|
||||
|
||||
import { Button, Input } from '@common/components/ui';
|
||||
import React, { useState } from 'react'
|
||||
import type { HttpResponse, Upload } from "tus-js-client";
|
||||
import { FileUploader } from "@common/components";
|
||||
import { handleUpload } from "@utils";
|
||||
|
||||
const FileUploadForm = ({ title }: { title: string }) => {
|
||||
const [files, setFiles] = useState<File[] | null>(null);
|
||||
const [selectedYear, setSelectedYear] = useState<string>('');
|
||||
const [selectedQuarter, setSelectedQuarter] = useState<string>('');
|
||||
const [upload, setUpload] = useState<Upload | null>(null);
|
||||
|
||||
const handleUploadResponse = (file: File) => async (res: HttpResponse) => {
|
||||
const fileInfo = res.getHeader("X-File-Info");
|
||||
if (!fileInfo) return;
|
||||
|
||||
const { atflCd, atflId } = JSON.parse(fileInfo) ?? {};
|
||||
const orgnlFileNm = file.name;
|
||||
|
||||
console.log(atflCd, atflId, orgnlFileNm);
|
||||
};
|
||||
|
||||
// 현재 년도부터 10년 전까지의 년도 옵션 생성
|
||||
const yearOptions = Array.from({ length: 10 }, (_, i) => {
|
||||
@@ -23,38 +36,63 @@ const FileUploadForm = ({ title }: { title: string }) => {
|
||||
<div className="card p-6">
|
||||
<div className="grid grid-col-2 items-center space-x-4">
|
||||
<div className='flex'>
|
||||
<SelectBox
|
||||
className="w-50 mr-3 ml-4"
|
||||
selectClass="rounded-lg bg-white"
|
||||
label="년도"
|
||||
selectArray={yearOptions}
|
||||
onSelectValue={(value: string | number) => setSelectedYear(String(value))}
|
||||
/><SelectBox
|
||||
className="w-50"
|
||||
selectClass="rounded-lg bg-white"
|
||||
label="분기"
|
||||
selectArray={quater}
|
||||
onSelectValue={(value: string | number) => setSelectedQuarter(String(value))}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex mt-5 items-center'>
|
||||
<div className="w-[200px] mr-4">
|
||||
<Input id="file" type="file" />
|
||||
</div>
|
||||
<Button
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
if (!files?.[0] || !selectedYear || !selectedQuarter) {
|
||||
alert('파일과 등록년도를 선택해주세요.');
|
||||
return;
|
||||
<SelectBox
|
||||
className="w-50 mr-3 ml-4"
|
||||
selectClass="rounded-lg bg-white"
|
||||
label="년도"
|
||||
selectArray={yearOptions}
|
||||
onSelectValue={(value: string | number) => setSelectedYear(String(value))}
|
||||
/><SelectBox
|
||||
className="w-50"
|
||||
selectClass="rounded-lg bg-white"
|
||||
label="분기"
|
||||
selectArray={quater}
|
||||
onSelectValue={(value: string | number) => setSelectedQuarter(String(value))}
|
||||
/>
|
||||
<div className='flex ml-5 items-center'>
|
||||
<div className="w-[80px] mr-4">
|
||||
<FileUploader
|
||||
id="atflCd"
|
||||
className="!h-auto !max-w-none flex-col !items-start"
|
||||
accept=".jpeg,.jpg,.png"
|
||||
onUpload={(e) => {
|
||||
const selectedFiles = e.target.files;
|
||||
if (!selectedFiles) return;
|
||||
|
||||
const fileArray = Array.from(selectedFiles);
|
||||
setFiles(fileArray);
|
||||
if (fileArray[0]) {
|
||||
handleUpload(selectedFiles, {
|
||||
handleUploadStart: setUpload,
|
||||
handleSuccess: setUpload,
|
||||
handleResponse: handleUploadResponse(fileArray[0]),
|
||||
});
|
||||
}
|
||||
// TODO: 파일 업로드 처리
|
||||
console.log('Upload file:', files[0], 'Year:', selectedYear);
|
||||
}}
|
||||
>
|
||||
등록
|
||||
</Button>
|
||||
customPreview={<></>}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
if (!files?.[0]) {
|
||||
alert('파일을 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
if (!selectedYear) {
|
||||
alert('등록년도를 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
if (!selectedQuarter) {
|
||||
alert('분기기를 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
}}
|
||||
>
|
||||
등록
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,7 @@ const Page = () => {
|
||||
|
||||
const handleDownload = async () => {
|
||||
try {
|
||||
const response = await fetch('/sn/attach-service/api/v1/download/6066b08c-2a55-438c-b8ff-2aed9739a0c6', {
|
||||
const response = await fetch('/attach-service/api/v1/download/0dda8ecd-63dd-4e90-bbe1-08d6fcd08c9a', {
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
|
||||
@@ -22,4 +22,8 @@
|
||||
@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,54 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
const AUTH_USER_ID = process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id";
|
||||
const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization";
|
||||
const serverApiUrl =
|
||||
process.env.NEXT_PUBLIC_SERVER_API_URL || "http://localhost:3002";
|
||||
const geoServerUrl = process.env.NEXT_PUBLIC_GEOSERVER_URL || "";
|
||||
|
||||
export function middleware(req: NextRequest) {
|
||||
console.log("Middleware 진입:", req.nextUrl.pathname);
|
||||
const pathname = req.nextUrl.pathname;
|
||||
const searchParams = req.nextUrl.searchParams;
|
||||
|
||||
if (pathname.startsWith("/server")) {
|
||||
const queryParams = searchParams.toString();
|
||||
const newUrl = `${serverApiUrl}${pathname.replace("/server", "")}${
|
||||
queryParams ? `?${queryParams}` : ""
|
||||
}`;
|
||||
console.log(`[Middleware] Rewriting from '/server' path to: ${newUrl}`);
|
||||
return NextResponse.rewrite(new URL(newUrl));
|
||||
}
|
||||
|
||||
if (pathname.startsWith("/geoserver")) {
|
||||
const queryParams = searchParams.toString();
|
||||
const newUrl = `${geoServerUrl}${pathname.replace("/geoserver", "")}${
|
||||
queryParams ? `?${queryParams}` : ""
|
||||
}`;
|
||||
console.log(`[Middleware] Rewriting from '/geoserver' path to: ${newUrl}`);
|
||||
return NextResponse.rewrite(new URL(newUrl));
|
||||
}
|
||||
|
||||
if (pathname.startsWith("/attach-service")) {
|
||||
const requestHeaders = new Headers(req.headers);
|
||||
const token = req.cookies.get(CLAIM_NAME)?.value;
|
||||
const userId = req.cookies.get(AUTH_USER_ID)?.value;
|
||||
const newUrl = `${serverApiUrl}${pathname}`;
|
||||
|
||||
if (token) requestHeaders.set(CLAIM_NAME, token);
|
||||
if (userId) requestHeaders.set(AUTH_USER_ID, userId);
|
||||
|
||||
console.log(
|
||||
`[Middleware] Rewriting from '/attach-service' path to: ${newUrl}`,
|
||||
);
|
||||
return NextResponse.rewrite(new URL(newUrl), {
|
||||
headers: requestHeaders,
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: ["/server/:path*","/geoserver/:path*", "/attach-service/:path*"],
|
||||
};
|
||||
+13
-13
@@ -6,7 +6,7 @@ const cnServerUrl = process.env.NEXT_PUBLIC_SERVER_API_URL?.replace(/\/$/, '') ?
|
||||
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
basePath: "/sn",
|
||||
// basePath: "/sn",
|
||||
typescript: {
|
||||
ignoreBuildErrors: true, // 이 줄 추가
|
||||
},
|
||||
@@ -44,18 +44,18 @@ const nextConfig: NextConfig = {
|
||||
|
||||
return config;
|
||||
},
|
||||
async rewrites() {
|
||||
return [
|
||||
{
|
||||
source: '/geoserver/:hasudo*',
|
||||
destination: `${geoServerUrl}/:hasudo*`,
|
||||
},
|
||||
{
|
||||
source: '/attach-service/:path*',
|
||||
destination: `${cnServerUrl}/attach-service/:path*`,
|
||||
},
|
||||
];
|
||||
},
|
||||
// async rewrites() {
|
||||
// return [
|
||||
// {
|
||||
// source: '/geoserver/:hasudo*',
|
||||
// destination: `${geoServerUrl}/:hasudo*`,
|
||||
// },
|
||||
// {
|
||||
// source: '/attach-service/:path*',
|
||||
// destination: `${cnServerUrl}/attach-service/:path*`,
|
||||
// },
|
||||
// ];
|
||||
// },
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
export default function MainDiv(props: {children: React.ReactNode}) {
|
||||
return (
|
||||
<div className="min-h-screen bg-container">
|
||||
<div className="min-h-[89vh] bg-container">
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -28,8 +28,8 @@ GisApp.layerInfos = [];
|
||||
//기본정보
|
||||
//GisApp.GEOSERVER_URL = 'http://rsone.iptime.org:10010/geoserver/hasudo/ows';
|
||||
//GisApp.GEOSERVER_WMS_URL = 'http://rsone.iptime.org:10010/geoserver/hasudo/wms';
|
||||
GisApp.GEOSERVER_URL = '/sn/geoserver/hasudo/ows';
|
||||
GisApp.GEOSERVER_WMS_URL = '/sn/geoserver/hasudo/wms';
|
||||
GisApp.GEOSERVER_URL = '/geoserver/hasudo/ows';
|
||||
GisApp.GEOSERVER_WMS_URL = '/geoserver/hasudo/wms';
|
||||
|
||||
//맵 정보
|
||||
GisApp.EMAP_URL = '/emap/';
|
||||
@@ -47,7 +47,7 @@ GisApp.BASE_MAP_TYPES = [];
|
||||
GisApp.FROM_PROJ = 'EPSG:4326';
|
||||
GisApp.TO_PROJ = 'EPSG:3857';
|
||||
GisApp.TO_DATA_PROJ = 'EPSG:4326';
|
||||
GisApp.CONTEXT_LAYER_PATH = '/sn/images/map/layericon/';
|
||||
GisApp.CONTEXT_LAYER_PATH = '/images/map/layericon/';
|
||||
GisApp.layerCode = {};
|
||||
GisApp.hlLayerId = 'LightFeatureList';
|
||||
GisApp.hLayerId = 'highLightFeature';
|
||||
|
||||
Reference in New Issue
Block a user