Merge remote-tracking branch 'origin/iipbs/st/dev' into feature/review-skeleton-sein
This commit is contained in:
+2
-1
@@ -38,4 +38,5 @@ yarn-error.log*
|
||||
next-env.d.ts
|
||||
|
||||
.env*
|
||||
!.env.example
|
||||
!.env.example
|
||||
*.md
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"plugins": ["prettier-plugin-tailwindcss"]
|
||||
"plugins": ["prettier-plugin-tailwindcss"],
|
||||
"tabWidth": 4
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"@common/assets": "workspace:*",
|
||||
"@common/components": "workspace:*",
|
||||
"@common/config": "workspace:*",
|
||||
"@rsone/components": "workspace:*",
|
||||
"@faker-js/faker": "^9.9.0",
|
||||
"@fontsource-variable/noto-sans-kr": "^5.2.5",
|
||||
"@frontend-opensource/use-react-hooks": "^1.1.4",
|
||||
"@fullcalendar/core": "^6.1.17",
|
||||
@@ -26,6 +26,7 @@
|
||||
"@radix-ui/react-label": "^2.1.2",
|
||||
"@radix-ui/react-slot": "^1.1.2",
|
||||
"@radix-ui/react-tooltip": "^1.1.8",
|
||||
"@rsone/components": "workspace:*",
|
||||
"@tanstack/react-query": "^5.67.1",
|
||||
"@tanstack/react-query-devtools": "^5.75.5",
|
||||
"@tanstack/react-table": "^8.21.2",
|
||||
|
||||
@@ -155,16 +155,16 @@ function updateHeaderSortIndicator(
|
||||
function createOnSortingHandler(
|
||||
columns: { fieldName?: string; name: string; mergeCells?: boolean; sortdisable?: boolean; valueType?: ValueType }[],
|
||||
setRowData: React.Dispatch<React.SetStateAction<Record<string, string | number>[]>>,
|
||||
sortDirRef: React.MutableRefObject<Record<string, "asc" | "desc" | "none">>,
|
||||
originalRowsRef: React.MutableRefObject<Record<string, Record<string, string | number>[]>>
|
||||
sortDirRef: { current: Record<string, "asc" | "desc" | "none"> },
|
||||
originalRowsRef: { current: Record<string, Record<string, string | number>[]> }
|
||||
) {
|
||||
const fieldNames = [...new Set(columns.map((c) => c.fieldName).filter((x): x is string => typeof x === "string"))];
|
||||
// mergeCells: true인 컬럼 찾기 (시군구 등) - 그룹 필드로 사용
|
||||
// 시군구를 우선으로 찾고, 없으면 첫 번째 mergeCells 컬럼 사용
|
||||
const mergeCellCols = columns.filter((c) => c.mergeCells === true && c.fieldName);
|
||||
const mergeCellCol = mergeCellCols.find((c) => c.fieldName === '시군구') || mergeCellCols[0];
|
||||
const mergeCellCol = mergeCellCols.find((c) => c.fieldName === 'gugun') || mergeCellCols[0];
|
||||
const groupField = mergeCellCol?.fieldName || null;
|
||||
const orderByField = '구분'; // 고정값 또는 설정 가능하게 할 수 있음
|
||||
const orderByField = 'rgnNm'; // 고정값 또는 설정 가능하게 할 수 있음
|
||||
const hasMergeCells = mergeCellCols.length > 0;
|
||||
|
||||
return function onSorting(g: unknown, fields: unknown[], _directions: unknown[]) {
|
||||
|
||||
@@ -118,6 +118,18 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSggChange = (value: string) => {
|
||||
const selectedBsnm = value === 'ALL'
|
||||
? (sidoList?.data.sidoList.find((v) => v.sido === values.sido)?.bsnmCd || '')
|
||||
: (sggList?.data?.find((v) => v.gugun === value)?.bsnmCd || '');
|
||||
|
||||
setValues((prev) => ({
|
||||
...prev,
|
||||
gugun: value,
|
||||
bsnmCd: selectedBsnm,
|
||||
}));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
props.onValuesChange?.(values);
|
||||
}, [values]);
|
||||
@@ -151,7 +163,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
<Label className="text-label">
|
||||
시군구
|
||||
</Label>
|
||||
<Select value={values.gugun} onValueChange={(value) => setValues({ ...values, gugun: value })}>
|
||||
<Select value={values.gugun} onValueChange={handleSggChange}>
|
||||
<SelectTrigger className="w-[12rem]">
|
||||
<SelectValue placeholder="전체"></SelectValue>
|
||||
</SelectTrigger>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { DataGrid } from "@rsone/components";
|
||||
import { useState, useMemo } from "react";
|
||||
// import { DataGrid } from "@rsone/components";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import SaveButton from "@/components/button/SaveButton";
|
||||
import ApproveButton from "@/components/button/ApproveButton";
|
||||
@@ -12,12 +13,27 @@ import { sewerageSupplyRateReviewApi } from "@/entities/statistics/api/statistic
|
||||
import type { SewerageSupplyRateReviewItem } from "@/entities/statistics/model/statistics-management-interface";
|
||||
import { SeweragePenetrationRateInfoList } from "@/components/util/column/review/SeweragePenetrationRateRvwColumn";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
// 하수도 통계 검토 01. 하수도 보급률
|
||||
const SeweragePenetrationRateRvw = () => {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
|
||||
const [rowData, setRowData] = useState<any[]>([]);
|
||||
|
||||
const columnInfo = useMemo(() => {
|
||||
const { columns, columnLayout, fixedColumns, rowStartNum } = SeweragePenetrationRateInfoList;
|
||||
return {
|
||||
columnLayout,
|
||||
fixedColumns,
|
||||
rowStartNum,
|
||||
columns: columns.map((col) => ({
|
||||
...col,
|
||||
editable: false as const,
|
||||
styleName: col.styleName ?? ("rg-cell-readonly" as const),
|
||||
})),
|
||||
};
|
||||
}, []);
|
||||
|
||||
const [rowData, setRowData] = useState<any[]>([]);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const handleReset = () => {
|
||||
@@ -29,10 +45,10 @@ const [rowData, setRowData] = useState<any[]>([]);
|
||||
setIsLoading(true);
|
||||
|
||||
const response = await sewerageSupplyRateReviewApi.getSewerageSupplyRateReviewList({
|
||||
sido: "경기도",
|
||||
gugun: "과천시",
|
||||
yr: 2024,
|
||||
bsnmCd: "4129000000"
|
||||
sido: values.sido,
|
||||
gugun: values.gugun === 'ALL' ? '' : values.gugun,
|
||||
bsnmCd: values.bsnmCd,
|
||||
yr: values.year,
|
||||
});
|
||||
|
||||
console.log("네트워크 응답 전체:", response);
|
||||
@@ -42,14 +58,13 @@ const [rowData, setRowData] = useState<any[]>([]);
|
||||
|
||||
console.log("받아온 리스트:", rawList);
|
||||
|
||||
|
||||
const convertedData: SewerageSupplyRateReviewItem[] = rawList.map((item) => ({
|
||||
...item,
|
||||
|
||||
speclCntrplnAr: !item.speclCntrplnAr ? '해당없음' : item.speclCntrplnAr,
|
||||
}));
|
||||
|
||||
setRowData(convertedData);
|
||||
setRowData(toGridRows(convertedData));
|
||||
|
||||
} else {
|
||||
setRowData([]);
|
||||
@@ -80,12 +95,12 @@ const [rowData, setRowData] = useState<any[]>([]);
|
||||
</div>
|
||||
</div>
|
||||
<DataGrid
|
||||
columnInfo={SeweragePenetrationRateInfoList}
|
||||
isExcelImport={false}
|
||||
fixedColumns={SeweragePenetrationRateInfoList.fixedColumns}
|
||||
rowStartNum={SeweragePenetrationRateInfoList.rowStartNum}
|
||||
columnInfo={columnInfo}
|
||||
isExcelImport={true}
|
||||
fixedColumns={columnInfo.fixedColumns}
|
||||
rowStartNum={columnInfo.rowStartNum}
|
||||
rowData={rowData}
|
||||
setRowData={setRowData}
|
||||
setRowData={setRowData}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,482 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { DataGrid } from "@rsone/components";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
Label,
|
||||
Select,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectValue,
|
||||
Checkbox,
|
||||
} from "@common/components";
|
||||
import { verificationHistoryColumnInfo } from "@/entities/statistics/model/verification-history-column";
|
||||
import { verificationHistoryMockData } from "@/entities/statistics/model/verification-history-mock";
|
||||
|
||||
interface VerificationFormValues {
|
||||
mu: string;
|
||||
percentile: string;
|
||||
a: string;
|
||||
c: string;
|
||||
verificationYear: string;
|
||||
checkboxOptions: {
|
||||
sidoDistribution: boolean;
|
||||
countyDistribution: boolean;
|
||||
countyDistributionUserManagement: boolean;
|
||||
lowerDistribution: boolean;
|
||||
sidoServiceAvailability: boolean;
|
||||
individualService: boolean;
|
||||
legalServiceUsage: boolean;
|
||||
excessUse: boolean;
|
||||
lowerOrgStatistics: boolean;
|
||||
studentStatistics: boolean;
|
||||
developmentStatistics: boolean;
|
||||
lowerManagementEnterprise: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
const ManagementVerificationRule = () => {
|
||||
const [verificationHistoryRowData, setVerificationHistoryRowData] =
|
||||
useState<Record<string, string | number>[]>(
|
||||
verificationHistoryMockData,
|
||||
);
|
||||
|
||||
const [formValues, setFormValues] = useState<VerificationFormValues>({
|
||||
mu: "0",
|
||||
percentile: "0",
|
||||
a: "0",
|
||||
c: "0",
|
||||
verificationYear: "2025",
|
||||
checkboxOptions: {
|
||||
sidoDistribution: false,
|
||||
countyDistribution: false,
|
||||
countyDistributionUserManagement: false,
|
||||
lowerDistribution: false,
|
||||
sidoServiceAvailability: false,
|
||||
individualService: false,
|
||||
legalServiceUsage: false,
|
||||
excessUse: false,
|
||||
lowerOrgStatistics: false,
|
||||
studentStatistics: false,
|
||||
developmentStatistics: false,
|
||||
lowerManagementEnterprise: false,
|
||||
},
|
||||
});
|
||||
|
||||
const handleCheckboxChange = (
|
||||
key: keyof VerificationFormValues["checkboxOptions"],
|
||||
) => {
|
||||
setFormValues((prev) => ({
|
||||
...prev,
|
||||
checkboxOptions: {
|
||||
...prev.checkboxOptions,
|
||||
[key]: !prev.checkboxOptions[key],
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
const handleInputChange = (
|
||||
key: keyof Omit<VerificationFormValues, "checkboxOptions">,
|
||||
value: string,
|
||||
) => {
|
||||
setFormValues((prev) => ({
|
||||
...prev,
|
||||
[key]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
console.log("검증 실행:", formValues);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
console.log("초기화");
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchForm />
|
||||
</>
|
||||
<div className="card card-border mt-4">
|
||||
<div className="p-2">
|
||||
{/* 통계 검증 섹션 */}
|
||||
<div className="mb-6">
|
||||
<h3 className="px-2 text-[1.5rem] font-semibold">
|
||||
통계 검증
|
||||
</h3>
|
||||
|
||||
<div className="flex gap-2">
|
||||
{/* 왼쪽: 검증 변수 */}
|
||||
<div className="card card-border flex-[0.5] rounded p-4">
|
||||
<h4 className="mb-3 rounded bg-gray-100 p-2 text-[1.4rem] font-medium">
|
||||
검증 변수
|
||||
</h4>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-4">
|
||||
<Label className="w-32 text-label">μ</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={formValues.mu}
|
||||
onChange={(e) =>
|
||||
handleInputChange(
|
||||
"mu",
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
className="flex-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<Label className="w-32 text-label">
|
||||
Percentile
|
||||
</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={formValues.percentile}
|
||||
onChange={(e) =>
|
||||
handleInputChange(
|
||||
"percentile",
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
className="flex-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<Label className="w-32 text-label">A</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={formValues.a}
|
||||
onChange={(e) =>
|
||||
handleInputChange(
|
||||
"a",
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
className="flex-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<Label className="w-32 text-label">C</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={formValues.c}
|
||||
onChange={(e) =>
|
||||
handleInputChange(
|
||||
"c",
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
className="flex-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<Label className="w-32 text-label">
|
||||
검증 년도
|
||||
</Label>
|
||||
<Select
|
||||
value={formValues.verificationYear}
|
||||
onValueChange={(value) =>
|
||||
handleInputChange(
|
||||
"verificationYear",
|
||||
value,
|
||||
)
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="flex-1">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="2025">
|
||||
2025년
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 오른쪽: 검증 대상 */}
|
||||
<div className="card card-border flex-1 rounded p-4">
|
||||
<h4 className="mb-3 rounded bg-gray-100 p-2 text-[1.4rem] font-medium">
|
||||
검증 대상
|
||||
</h4>
|
||||
|
||||
<div className="space-y-4 p-2">
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={
|
||||
formValues.checkboxOptions
|
||||
.sidoDistribution
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(
|
||||
"sidoDistribution",
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Label className="cursor-pointer text-label">
|
||||
하수도 보급률
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={
|
||||
formValues.checkboxOptions
|
||||
.countyDistribution
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(
|
||||
"countyDistribution",
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Label className="cursor-pointer text-label">
|
||||
하수관로
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={
|
||||
formValues.checkboxOptions
|
||||
.countyDistributionUserManagement
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(
|
||||
"countyDistributionUserManagement",
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Label className="cursor-pointer text-label">
|
||||
하수관로 유지관리
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={
|
||||
formValues.checkboxOptions
|
||||
.lowerDistribution
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(
|
||||
"lowerDistribution",
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Label className="cursor-pointer text-label">
|
||||
하수저류시설 및 빗물펌프장
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={
|
||||
formValues.checkboxOptions
|
||||
.sidoServiceAvailability
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(
|
||||
"sidoServiceAvailability",
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Label className="cursor-pointer text-label">
|
||||
펌프장
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={
|
||||
formValues.checkboxOptions
|
||||
.individualService
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(
|
||||
"individualService",
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Label className="cursor-pointer text-label">
|
||||
공공하수처리시설
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={
|
||||
formValues.checkboxOptions
|
||||
.sidoServiceAvailability
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(
|
||||
"sidoServiceAvailability",
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Label className="cursor-pointer text-label">
|
||||
분뇨처리시설
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={
|
||||
formValues.checkboxOptions
|
||||
.individualService
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(
|
||||
"individualService",
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Label className="cursor-pointer text-label">
|
||||
개인하수처리시설
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={
|
||||
formValues.checkboxOptions
|
||||
.lowerOrgStatistics
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(
|
||||
"lowerOrgStatistics",
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Label className="cursor-pointer text-label">
|
||||
하수도재정
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={
|
||||
formValues.checkboxOptions
|
||||
.studentStatistics
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(
|
||||
"studentStatistics",
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Label className="cursor-pointer text-label">
|
||||
물 재이용
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={
|
||||
formValues.checkboxOptions
|
||||
.lowerOrgStatistics
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(
|
||||
"lowerOrgStatistics",
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Label className="cursor-pointer text-label">
|
||||
하수 및 분뇨찌꺼기(슬러지) 처리
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={
|
||||
formValues.checkboxOptions
|
||||
.studentStatistics
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(
|
||||
"studentStatistics",
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Label className="cursor-pointer text-label">
|
||||
하수 및 분뇨관련산업
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
checked={
|
||||
formValues.checkboxOptions
|
||||
.lowerManagementEnterprise
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
handleCheckboxChange(
|
||||
"lowerManagementEnterprise",
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Label className="cursor-pointer text-label">
|
||||
하수도 서비스 수준
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 버튼 */}
|
||||
<div className="mt-6 flex justify-center gap-4 pt-4">
|
||||
<Button
|
||||
color="green"
|
||||
className="h-[3.6rem] w-[120px]"
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
검증 실행
|
||||
</Button>
|
||||
<Button
|
||||
color="gray"
|
||||
className="h-[3.6rem] w-[120px]"
|
||||
onClick={handleReset}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 검증 이력 섹션 */}
|
||||
<div className="mt-8 border-t pt-6">
|
||||
<h3 className="mb-4 px-2 text-[1.5rem] font-semibold">
|
||||
검증 이력
|
||||
</h3>
|
||||
<DataGrid
|
||||
columnInfo={verificationHistoryColumnInfo}
|
||||
rowData={verificationHistoryRowData}
|
||||
setRowData={setVerificationHistoryRowData}
|
||||
isExcelImport={false}
|
||||
isExcelExport={false}
|
||||
showHeaderCheckbox={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ManagementVerificationRule;
|
||||
export default ManagementVerificationRule;
|
||||
|
||||
@@ -13,7 +13,7 @@ export const SeweragePenetrationRateInpColumn = {
|
||||
|
||||
// 수계 및 권역
|
||||
{ name: 'rvbsn', fieldName: 'rvbsn', header: { text: '수계' }, editable: false },
|
||||
{ name: 'bstrm', fieldName: 'bstrm', header: { text: '지류' }, editable: false },
|
||||
{ name: 'bstrm', fieldName: 'bstrm', header: { text: '지류' } },
|
||||
|
||||
// 수계영향권별분류
|
||||
{ name: 'mdbsn', fieldName: 'mdbsn', header: { text: '세부단위유역' }, editable: false },
|
||||
|
||||
@@ -48,8 +48,8 @@ export const getFooterAvgSummary = (grid: GridView, column: DataColumn) => {
|
||||
return Math.floor(avg * 10) / 10;
|
||||
}
|
||||
|
||||
// "구분" 필드의 고유값들을 찾는 함수 (첫 번째, 두 번째)
|
||||
const getDivisionValues = (grid: GridView, divisionFieldName: string = '구분'): string[] => {
|
||||
// 구분 필드의 고유값들을 찾는 함수 (첫 번째, 두 번째). 기본 필드명 'rgnNm'
|
||||
const getDivisionValues = (grid: GridView, divisionFieldName: string = 'rgnNm'): string[] => {
|
||||
const dataSource = grid.getDataSource();
|
||||
const rowCount = dataSource.getRowCount();
|
||||
const uniqueValues = new Set<string>();
|
||||
@@ -65,13 +65,13 @@ const getDivisionValues = (grid: GridView, divisionFieldName: string = '구분')
|
||||
return Array.from(uniqueValues);
|
||||
};
|
||||
|
||||
// "구분" 필드 값에 따라 필터링하여 합계 계산하는 함수
|
||||
// 구분 필드 값에 따라 필터링하여 합계 계산하는 함수. 기본 필드명 'rgnNm'
|
||||
// divisionIndex: 0 = 첫 번째 고유값, 1 = 두 번째 고유값
|
||||
export const getFooterSummaryByDivision = (
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
divisionIndex: number,
|
||||
divisionFieldName: string = '구분'
|
||||
divisionFieldName: string = 'rgnNm'
|
||||
) => {
|
||||
const dataSource = grid.getDataSource();
|
||||
const rowCount = dataSource.getRowCount();
|
||||
@@ -107,13 +107,13 @@ export const getFooterSummaryByDivision = (
|
||||
return sum;
|
||||
};
|
||||
|
||||
// "구분" 필드 값에 따라 필터링하여 평균 계산하는 함수
|
||||
// 구분 필드 값에 따라 필터링하여 평균 계산하는 함수. 기본 필드명 'rgnNm'
|
||||
// divisionIndex: 0 = 첫 번째 고유값, 1 = 두 번째 고유값
|
||||
export const getFooterAvgSummaryByDivision = (
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
divisionIndex: number,
|
||||
divisionFieldName: string = '구분'
|
||||
divisionFieldName: string = 'rgnNm'
|
||||
) => {
|
||||
const dataSource = grid.getDataSource();
|
||||
const rowCount = dataSource.getRowCount();
|
||||
@@ -173,67 +173,51 @@ const createAvgFooter = (format: string) => ({
|
||||
},
|
||||
});
|
||||
|
||||
// "구분" 값에 따라 필터링하는 Footer 배열 생성 함수 (2줄)
|
||||
const createSumFooterArrayByDivision = (format: string) => [
|
||||
// 구분 값에 따라 필터링하는 Footer 배열 생성 함수 (2줄). 기본 필드명 'rgnNm'
|
||||
const createSumFooterArrayByDivision = (format: string, divisionFieldName: string = 'rgnNm') => [
|
||||
{
|
||||
numberFormat: format,
|
||||
valueCallback: (
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
footerIndex: number,
|
||||
columnFooter: unknown,
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
footerIndex: number,
|
||||
columnFooter: unknown,
|
||||
value: number | string | undefined
|
||||
) => {
|
||||
// footerIndex: 0 = 첫 번째 footer 행 (첫 번째 "구분" 값), 1 = 두 번째 footer 행 (두 번째 "구분" 값)
|
||||
// footerIndex를 divisionIndex로 사용
|
||||
return getFooterSummaryByDivision(grid, column, footerIndex);
|
||||
},
|
||||
) => getFooterSummaryByDivision(grid, column, footerIndex, divisionFieldName),
|
||||
},
|
||||
{
|
||||
numberFormat: format,
|
||||
valueCallback: (
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
footerIndex: number,
|
||||
columnFooter: unknown,
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
footerIndex: number,
|
||||
columnFooter: unknown,
|
||||
value: number | string | undefined
|
||||
) => {
|
||||
// footerIndex: 0 = 첫 번째 footer 행 (첫 번째 "구분" 값), 1 = 두 번째 footer 행 (두 번째 "구분" 값)
|
||||
// footerIndex를 divisionIndex로 사용
|
||||
return getFooterSummaryByDivision(grid, column, footerIndex);
|
||||
},
|
||||
) => getFooterSummaryByDivision(grid, column, footerIndex, divisionFieldName),
|
||||
},
|
||||
];
|
||||
|
||||
// "구분" 값에 따라 필터링하는 평균 Footer 배열 생성 함수 (2줄)
|
||||
const createAvgFooterArrayByDivision = (format: string) => [
|
||||
// 구분 값에 따라 필터링하는 평균 Footer 배열 생성 함수 (2줄). 기본 필드명 'rgnNm'
|
||||
const createAvgFooterArrayByDivision = (format: string, divisionFieldName: string = 'rgnNm') => [
|
||||
{
|
||||
numberFormat: format,
|
||||
valueCallback: (
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
footerIndex: number,
|
||||
columnFooter: unknown,
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
footerIndex: number,
|
||||
columnFooter: unknown,
|
||||
value: number | string | undefined
|
||||
) => {
|
||||
// footerIndex: 0 = 첫 번째 footer 행 (첫 번째 "구분" 값), 1 = 두 번째 footer 행 (두 번째 "구분" 값)
|
||||
// footerIndex를 divisionIndex로 사용
|
||||
return getFooterAvgSummaryByDivision(grid, column, footerIndex);
|
||||
},
|
||||
) => getFooterAvgSummaryByDivision(grid, column, footerIndex, divisionFieldName),
|
||||
},
|
||||
{
|
||||
numberFormat: format,
|
||||
valueCallback: (
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
footerIndex: number,
|
||||
columnFooter: unknown,
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
footerIndex: number,
|
||||
columnFooter: unknown,
|
||||
value: number | string | undefined
|
||||
) => {
|
||||
// footerIndex: 0 = 첫 번째 footer 행 (첫 번째 "구분" 값), 1 = 두 번째 footer 행 (두 번째 "구분" 값)
|
||||
// footerIndex를 divisionIndex로 사용
|
||||
return getFooterAvgSummaryByDivision(grid, column, footerIndex);
|
||||
},
|
||||
) => getFooterAvgSummaryByDivision(grid, column, footerIndex, divisionFieldName),
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import type { SewerageSupplyRateItem } from "@/entities/statistics/model/statistics-input-interface";
|
||||
|
||||
export function toGridRows(items: SewerageSupplyRateItem[]): Record<string, string | number>[] {
|
||||
/**
|
||||
* 객체 배열을 그리드 row 형식으로 변환 (null/undefined → "").
|
||||
* 어떤 엔티티 타입이든 공통 사용 가능.
|
||||
*/
|
||||
export function toGridRows<T extends object>(
|
||||
items: T[]
|
||||
): Record<string, string | number>[] {
|
||||
return items.map((item) => {
|
||||
const row: Record<string, string | number> = {};
|
||||
for (const [key, value] of Object.entries(item)) {
|
||||
row[key] = value === null || value === undefined ? "" : value;
|
||||
row[key] =
|
||||
value === null || value === undefined ? "" : (value as string | number);
|
||||
}
|
||||
return row;
|
||||
});
|
||||
|
||||
@@ -4,6 +4,11 @@ export interface Sido {
|
||||
bsnmCd: string;
|
||||
}
|
||||
|
||||
export interface SearchCondition {
|
||||
yearList: string[];
|
||||
sidoList: Sido[];
|
||||
}
|
||||
|
||||
export interface Sgg {
|
||||
adstDstrCd: string;
|
||||
gugun: string;
|
||||
@@ -17,6 +22,6 @@ export interface ApiResponse<T> {
|
||||
}
|
||||
|
||||
export interface StatisticsService {
|
||||
getSidoList(): Promise<ApiResponse<Sido[]>>;
|
||||
getSidoList(): Promise<ApiResponse<SearchCondition>>;
|
||||
getSggList(sidoName: string): Promise<ApiResponse<Sgg[]>>;
|
||||
}
|
||||
@@ -83,7 +83,7 @@ export type SewerageSupplyRateReviewApiResponse = ApiResponse<SewerageSupplyRate
|
||||
export interface SewerageSupplyRateReviewRequestParams {
|
||||
sido?: string;
|
||||
gugun?: string;
|
||||
yr?: number;
|
||||
yr?: string;
|
||||
bsnmCd?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
import { ValueType } from "realgrid";
|
||||
import type { ColumnInfo } from "@rsone/components";
|
||||
|
||||
/** 검증 이력 그리드 컬럼 설정 */
|
||||
export const verificationHistoryColumnInfo: ColumnInfo = {
|
||||
columns: [
|
||||
{
|
||||
name: "executedAt",
|
||||
fieldName: "executedAt",
|
||||
header: { text: "검증 실행 일시" },
|
||||
editable: false,
|
||||
sortdisable: false,
|
||||
},
|
||||
{
|
||||
name: "progressStatus",
|
||||
fieldName: "progressStatus",
|
||||
header: { text: "진행 상태" },
|
||||
editable: false,
|
||||
sortdisable: false,
|
||||
},
|
||||
{
|
||||
name: "endedAt",
|
||||
fieldName: "endedAt",
|
||||
header: { text: "종료 일시" },
|
||||
editable: false,
|
||||
sortdisable: false,
|
||||
},
|
||||
{
|
||||
name: "verificationYear",
|
||||
fieldName: "verificationYear",
|
||||
header: { text: "검증년도" },
|
||||
editable: false,
|
||||
sortdisable: false,
|
||||
},
|
||||
{
|
||||
name: "mu",
|
||||
fieldName: "mu",
|
||||
header: { text: "μ" },
|
||||
editable: false,
|
||||
sortdisable: false,
|
||||
valueType: ValueType.NUMBER,
|
||||
dataFormat: "#,##0.###",
|
||||
},
|
||||
{
|
||||
name: "percentile",
|
||||
fieldName: "percentile",
|
||||
header: { text: "Percentile" },
|
||||
editable: false,
|
||||
sortdisable: false,
|
||||
valueType: ValueType.NUMBER,
|
||||
dataFormat: "#,##0.###",
|
||||
},
|
||||
{
|
||||
name: "a",
|
||||
fieldName: "a",
|
||||
header: { text: "A" },
|
||||
editable: false,
|
||||
sortdisable: false,
|
||||
valueType: ValueType.NUMBER,
|
||||
dataFormat: "#,##0.###",
|
||||
},
|
||||
{
|
||||
name: "c",
|
||||
fieldName: "c",
|
||||
header: { text: "C" },
|
||||
editable: false,
|
||||
sortdisable: false,
|
||||
valueType: ValueType.NUMBER,
|
||||
dataFormat: "#,##0.###",
|
||||
},
|
||||
{
|
||||
name: "target",
|
||||
fieldName: "target",
|
||||
header: { text: "검증 대상" },
|
||||
editable: false,
|
||||
sortdisable: false,
|
||||
},
|
||||
{
|
||||
name: "normalCount",
|
||||
fieldName: "normalCount",
|
||||
header: { text: "정상건수" },
|
||||
editable: false,
|
||||
sortdisable: false,
|
||||
valueType: ValueType.NUMBER,
|
||||
dataFormat: "#,##0",
|
||||
},
|
||||
{
|
||||
name: "excludedCount",
|
||||
fieldName: "excludedCount",
|
||||
header: { text: "제외건수" },
|
||||
editable: false,
|
||||
sortdisable: false,
|
||||
valueType: ValueType.NUMBER,
|
||||
dataFormat: "#,##0",
|
||||
},
|
||||
{
|
||||
name: "abnormalCount",
|
||||
fieldName: "abnormalCount",
|
||||
header: { text: "이상건수" },
|
||||
editable: false,
|
||||
sortdisable: false,
|
||||
valueType: ValueType.NUMBER,
|
||||
dataFormat: "#,##0",
|
||||
},
|
||||
],
|
||||
columnLayout: [
|
||||
"executedAt",
|
||||
"progressStatus",
|
||||
"endedAt",
|
||||
"verificationYear",
|
||||
"mu",
|
||||
"percentile",
|
||||
"a",
|
||||
"c",
|
||||
"target",
|
||||
"normalCount",
|
||||
"excludedCount",
|
||||
"abnormalCount",
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
/** 검증 이력 한 건 (그리드 행 데이터) */
|
||||
export interface VerificationHistoryItem {
|
||||
/** 검증 실행 일시 */
|
||||
executedAt: string;
|
||||
/** 진행 상태 */
|
||||
progressStatus: string;
|
||||
/** 종료 일시 */
|
||||
endedAt: string;
|
||||
/** 검증년도 */
|
||||
verificationYear: string;
|
||||
/** μ */
|
||||
mu: number;
|
||||
/** Percentile */
|
||||
percentile: number;
|
||||
/** A */
|
||||
a: number;
|
||||
/** C */
|
||||
c: number;
|
||||
/** 검증 대상 */
|
||||
target: string;
|
||||
/** 정상건수 */
|
||||
normalCount: number;
|
||||
/** 제외건수 */
|
||||
excludedCount: number;
|
||||
/** 이상건수 */
|
||||
abnormalCount: number;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { fakerKO as faker } from "@faker-js/faker";
|
||||
import type { VerificationHistoryItem } from "./verification-history-interface";
|
||||
|
||||
const VERIFICATION_TARGETS = [
|
||||
"하수도 보급률",
|
||||
"하수관로",
|
||||
"하수관로 유지관리",
|
||||
"하수저류시설 및 빗물펌프장",
|
||||
"펌프장",
|
||||
"공공하수처리시설",
|
||||
"분뇨처리시설",
|
||||
"개인하수처리시설",
|
||||
"하수도재정",
|
||||
"물 재이용",
|
||||
"하수도 서비스 수준",
|
||||
];
|
||||
|
||||
const PROGRESS_STATUSES = ["대기", "진행중", "완료", "실패"];
|
||||
|
||||
function formatDateTime(date: Date): string {
|
||||
const y = date.getFullYear();
|
||||
const m = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const d = String(date.getDate()).padStart(2, "0");
|
||||
const h = String(date.getHours()).padStart(2, "0");
|
||||
const min = String(date.getMinutes()).padStart(2, "0");
|
||||
return `${y}-${m}-${d} ${h}:${min}`;
|
||||
}
|
||||
|
||||
/** 검증 이력 목데이터 생성 (그리드 행 형식) */
|
||||
export function createVerificationHistoryMock(
|
||||
count = 10,
|
||||
): Record<string, string | number>[] {
|
||||
faker.seed(42);
|
||||
return Array.from({ length: count }, () => {
|
||||
const executedAt = faker.date.recent({ days: 90 });
|
||||
const durationMinutes = faker.number.int({ min: 1, max: 120 });
|
||||
const endedAt = new Date(
|
||||
executedAt.getTime() + durationMinutes * 60 * 1000,
|
||||
);
|
||||
const status = faker.helpers.arrayElement(PROGRESS_STATUSES);
|
||||
const normalCount = faker.number.int({ min: 0, max: 500 });
|
||||
const excludedCount = faker.number.int({ min: 0, max: 50 });
|
||||
const abnormalCount = faker.number.int({ min: 0, max: 30 });
|
||||
const item: VerificationHistoryItem = {
|
||||
executedAt: formatDateTime(executedAt),
|
||||
progressStatus: status,
|
||||
endedAt: formatDateTime(endedAt),
|
||||
verificationYear: String(executedAt.getFullYear()),
|
||||
mu: faker.number.float({ min: 0, max: 1, fractionDigits: 3 }),
|
||||
percentile: faker.number.float({
|
||||
min: 0,
|
||||
max: 100,
|
||||
fractionDigits: 2,
|
||||
}),
|
||||
a: faker.number.float({ min: 0, max: 10, fractionDigits: 3 }),
|
||||
c: faker.number.float({ min: 0, max: 10, fractionDigits: 3 }),
|
||||
target: faker.helpers.arrayElement(VERIFICATION_TARGETS),
|
||||
normalCount,
|
||||
excludedCount,
|
||||
abnormalCount,
|
||||
};
|
||||
return { ...item } as Record<string, string | number>;
|
||||
});
|
||||
}
|
||||
|
||||
/** 검증 이력 기본 목데이터 (10건) */
|
||||
export const verificationHistoryMockData = createVerificationHistoryMock(10);
|
||||
Generated
+28
-24
@@ -170,10 +170,10 @@ importers:
|
||||
version: 5.28.5
|
||||
eslint:
|
||||
specifier: ^9.0.0
|
||||
version: 9.39.2(jiti@1.21.7)
|
||||
version: 9.39.2(jiti@2.6.1)
|
||||
eslint-config-next:
|
||||
specifier: 15.4.0
|
||||
version: 15.4.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
|
||||
version: 15.4.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
postcss:
|
||||
specifier: ^8.4.49
|
||||
version: 8.5.6
|
||||
@@ -897,6 +897,9 @@ importers:
|
||||
'@common/config':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/common/config
|
||||
'@faker-js/faker':
|
||||
specifier: ^9.9.0
|
||||
version: 9.9.0
|
||||
'@fontsource-variable/noto-sans-kr':
|
||||
specifier: ^5.2.5
|
||||
version: 5.2.9
|
||||
@@ -1035,10 +1038,10 @@ importers:
|
||||
version: 5.28.5
|
||||
eslint:
|
||||
specifier: ^9
|
||||
version: 9.39.2(jiti@2.6.1)
|
||||
version: 9.39.2(jiti@1.21.7)
|
||||
eslint-config-next:
|
||||
specifier: 15.1.7
|
||||
version: 15.1.7(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
version: 15.1.7(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
|
||||
postcss:
|
||||
specifier: ^8
|
||||
version: 8.5.6
|
||||
@@ -5036,6 +5039,7 @@ packages:
|
||||
|
||||
glob@10.5.0:
|
||||
resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
|
||||
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||
hasBin: true
|
||||
|
||||
globals@14.0.0:
|
||||
@@ -10316,6 +10320,26 @@ snapshots:
|
||||
optionalDependencies:
|
||||
source-map: 0.7.6
|
||||
|
||||
eslint-config-next@15.1.7(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@next/eslint-plugin-next': 15.1.7
|
||||
'@rushstack/eslint-patch': 1.15.0
|
||||
'@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
|
||||
eslint: 9.39.2(jiti@1.21.7)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@1.21.7))
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@1.21.7))
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@1.21.7))
|
||||
eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@1.21.7))
|
||||
eslint-plugin-react-hooks: 5.2.0(eslint@9.39.2(jiti@1.21.7))
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- eslint-import-resolver-webpack
|
||||
- eslint-plugin-import-x
|
||||
- supports-color
|
||||
|
||||
eslint-config-next@15.1.7(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@next/eslint-plugin-next': 15.1.7
|
||||
@@ -10336,26 +10360,6 @@ snapshots:
|
||||
- eslint-plugin-import-x
|
||||
- supports-color
|
||||
|
||||
eslint-config-next@15.4.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@next/eslint-plugin-next': 15.4.0
|
||||
'@rushstack/eslint-patch': 1.15.0
|
||||
'@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
|
||||
eslint: 9.39.2(jiti@1.21.7)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@1.21.7))
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@1.21.7))
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@1.21.7))
|
||||
eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@1.21.7))
|
||||
eslint-plugin-react-hooks: 5.2.0(eslint@9.39.2(jiti@1.21.7))
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- eslint-import-resolver-webpack
|
||||
- eslint-plugin-import-x
|
||||
- supports-color
|
||||
|
||||
eslint-config-next@15.4.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@next/eslint-plugin-next': 15.4.0
|
||||
|
||||
Reference in New Issue
Block a user