Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev

This commit is contained in:
2026-10-20 17:49:42 +09:00
2 changed files with 233 additions and 233 deletions
@@ -1,100 +1,100 @@
export interface DashboardSearchParams {
statsYr: string;
dtlInstCd: string;
enoCd?: string;
laeSeCd?: string;
sewerBzmnCd?: string;
sido?: string;
sggNm?: string;
unSubmitOnly?: boolean;
}
export interface DashboardApiResponse<T> {
status: string;
message: string;
data: T;
code: string;
httpStatus: number;
}
export interface VrfcReportResponse {
typeCounts: Record<string, number>;
list: VrfcReportSelect[];
}
export interface VrfcReportSelect {
vrfcCd: string;
statsNm: string;
rsnExpln: string;
admdstNm: string;
nowVlCn: string;
compVlCn: string;
}
export interface VrfcErrCountResponse {
statsYr: string;
vrfcErrCount: number;
}
export interface UnSubmitCountResponse {
statsYr: string;
statusList: UnSubmitStatsResponse[];
submittedCount: number;
totalCount: number;
unSubmittedCount: number;
}
export interface UnSubmitStatsResponse {
sbmsnYn: string;
sidoSggNm: string;
statsCd: string;
statsNm: string;
statusCd: string;
statusNm: string;
}
export interface StatsSubmissionResponse {
totalCount: number;
submittedCount: number;
submissionRate: number;
statusLevel: string;
}
export interface EcptReqCountResponse {
statsYr: string;
ecptReqCount: number;
}
export interface ThreeStageStatusResponse {
statsYr: string;
lclgvSbmsnSttsCd: string;
lclgvSbmsnSttsNm: string;
lclgvRejected: boolean;
mctpvPicSbmsnSttsCd: string;
mctpvPicSbmsnSttsNm: string;
mctpvRejected: boolean;
pbcpSbmsnSttsCd: string;
pbcpSbmsnSttsNm: string;
pbcpRejected: boolean;
}
export interface DashboardService {
getVrfcReport(
params: DashboardSearchParams,
): Promise<DashboardApiResponse<VrfcReportResponse>>;
getVrfcErrCount(
params: DashboardSearchParams,
): Promise<DashboardApiResponse<VrfcErrCountResponse>>;
getUnSubmitCount(
params: DashboardSearchParams,
): Promise<DashboardApiResponse<UnSubmitCountResponse>>;
getStatsSubmissionRate(
params: DashboardSearchParams,
): Promise<DashboardApiResponse<StatsSubmissionResponse>>;
getEcptReqCount(
params: DashboardSearchParams,
): Promise<DashboardApiResponse<EcptReqCountResponse>>;
getThreeStageStatus(
params: DashboardSearchParams,
): Promise<DashboardApiResponse<ThreeStageStatusResponse>>;
}
export interface DashboardSearchParams {
statsYr: string;
dtlInstCd: string;
enoCd?: string;
laeSeCd?: string;
sewerBzmnCd?: string;
sido?: string;
sggNm?: string;
unSubmitOnly?: boolean;
}
export interface DashboardApiResponse<T> {
status: string;
message: string;
data: T;
code: string;
httpStatus: number;
}
export interface VrfcReportResponse {
typeCounts: Record<string, number>;
list: VrfcReportSelect[];
}
export interface VrfcReportSelect {
vrfcCd: string;
statsNm: string;
rsnExpln: string;
admdstNm: string;
nowCN: string;
cmprCn: string | null;
}
export interface VrfcErrCountResponse {
statsYr: string;
vrfcErrCount: number;
}
export interface UnSubmitCountResponse {
statsYr: string;
statusList: UnSubmitStatsResponse[];
submittedCount: number;
totalCount: number;
unSubmittedCount: number;
}
export interface UnSubmitStatsResponse {
sbmsnYn: string;
sidoSggNm: string;
statsCd: string;
statsNm: string;
statusCd: string;
statusNm: string;
}
export interface StatsSubmissionResponse {
totalCount: number;
submittedCount: number;
submissionRate: number;
statusLevel: string;
}
export interface EcptReqCountResponse {
statsYr: string;
ecptReqCount: number;
}
export interface ThreeStageStatusResponse {
statsYr: string;
lclgvSbmsnSttsCd: string;
lclgvSbmsnSttsNm: string;
lclgvRejected: boolean;
mctpvPicSbmsnSttsCd: string;
mctpvPicSbmsnSttsNm: string;
mctpvRejected: boolean;
pbcpSbmsnSttsCd: string;
pbcpSbmsnSttsNm: string;
pbcpRejected: boolean;
}
export interface DashboardService {
getVrfcReport(
params: DashboardSearchParams,
): Promise<DashboardApiResponse<VrfcReportResponse>>;
getVrfcErrCount(
params: DashboardSearchParams,
): Promise<DashboardApiResponse<VrfcErrCountResponse>>;
getUnSubmitCount(
params: DashboardSearchParams,
): Promise<DashboardApiResponse<UnSubmitCountResponse>>;
getStatsSubmissionRate(
params: DashboardSearchParams,
): Promise<DashboardApiResponse<StatsSubmissionResponse>>;
getEcptReqCount(
params: DashboardSearchParams,
): Promise<DashboardApiResponse<EcptReqCountResponse>>;
getThreeStageStatus(
params: DashboardSearchParams,
): Promise<DashboardApiResponse<ThreeStageStatusResponse>>;
}
@@ -1,133 +1,133 @@
"use client";
import { useDashboardVrfcReport } from "@/entities/statistics";
import type { DashboardSearchParams } from "@/entities/statistics/model/dashboard/dashboard-interface";
import {
Badge,
type BadgeProps,
LoadingWithAnimation,
} from "@common/components";
import { useDragHandle } from "../sortable-panel";
interface VrfcReportPanelProps {
params: DashboardSearchParams | null;
}
const TYPE_CODE_NAME_MAP: Record<string, string> = {
NV: "일반",
IMR: "I-MR",
HB: "HB",
};
const TYPE_CODE_COLOR_MAP: Record<string, BadgeProps["color"]> = {
NV: "gray",
IMR: "red",
HB: "blue",
};
export function VrfcReportPanel({ params }: VrfcReportPanelProps) {
const { dragHandleProps, isDragging } = useDragHandle();
const { data, isLoading, error } = useDashboardVrfcReport(params);
return (
<div className="card card-border !m-0 h-full border-border bg-background text-foreground">
<div
className={`card-header cursor-grab border-border active:cursor-grabbing ${isDragging ? "opacity-50" : ""}`}
{...dragHandleProps}
title="헤더를 드래그하여 패널 순서 변경"
>
<h3 className="text-2xl font-semibold">이상치 선별보고서</h3>
</div>
<div className="card-body max-h-[calc(100%-3.25rem)] py-2.5">
{isLoading && (
<div className="flex h-full items-center justify-center">
<div className="relative size-[5rem]">
<LoadingWithAnimation color="primary" />
</div>
</div>
)}
{error && (
<div className="flex h-full items-center justify-center">
데이터를 불러올 수 없습니다.
</div>
)}
{data && (
<div className="space-y-2">
{/* TBD: typeCounts 표시 방법 (탭? 필터?) */}
{data.typeCounts &&
Object.keys(data.typeCounts).length > 0 && (
<div className="flex flex-wrap gap-2">
{Object.entries(data.typeCounts).map(
([type, count]) => (
<Badge
key={type}
color={
TYPE_CODE_COLOR_MAP[type]
}
className="rounded-full px-2 py-1 text-lg"
>
{TYPE_CODE_NAME_MAP[type]}:{" "}
{count}
</Badge>
),
)}
</div>
)}
{/* TBD: 테이블 컬럼 정의, 증감 표시 방법 */}
<div className="max-h-[44rem] overflow-y-auto">
{data.list.length === 0 ? (
<div className="flex h-full items-center justify-center">
이상치 항목이 없습니다.
</div>
) : (
<div className="space-y-2 tracking-tight">
{data.list.map((item, index) => (
<div
key={index}
className="flex flex-col rounded-sm border border-border p-3"
>
<div className="mb-2 flex items-center justify-between">
<span className="font-medium">
{item.statsNm}
</span>
<Badge
color={
TYPE_CODE_COLOR_MAP[
item.vrfcCd
]
}
className="text-sm"
>
{
TYPE_CODE_NAME_MAP[
item.vrfcCd
]
}
</Badge>
</div>
<p className="text-lg text-muted-foreground">
{item.rsnExpln}
</p>
<div className="flex justify-between text-lg text-muted-foreground">
<span>{item.admdstNm}</span>
<span>
{/* TBD: 증감 표시 방법 (nowVlCn - compVlCn 계산?) */}
현재: {item.nowVlCn ?? "-"}{" "}
/ 비교:{" "}
{item.compVlCn ?? "-"}
</span>
</div>
{/* TBD: 등급/점수/단계/업데이트 시각 필드 미제공 */}
</div>
))}
</div>
)}
</div>
</div>
)}
</div>
</div>
);
}
"use client";
import { useDashboardVrfcReport } from "@/entities/statistics";
import type { DashboardSearchParams } from "@/entities/statistics/model/dashboard/dashboard-interface";
import {
Badge,
type BadgeProps,
LoadingWithAnimation,
} from "@common/components";
import { useDragHandle } from "../sortable-panel";
interface VrfcReportPanelProps {
params: DashboardSearchParams | null;
}
const TYPE_CODE_NAME_MAP: Record<string, string> = {
NV: "일반",
IMR: "I-MR",
HB: "HB",
};
const TYPE_CODE_COLOR_MAP: Record<string, BadgeProps["color"]> = {
NV: "gray",
IMR: "red",
HB: "blue",
};
export function VrfcReportPanel({ params }: VrfcReportPanelProps) {
const { dragHandleProps, isDragging } = useDragHandle();
const { data, isLoading, error } = useDashboardVrfcReport(params);
return (
<div className="card card-border !m-0 h-full border-border bg-background text-foreground">
<div
className={`card-header cursor-grab border-border active:cursor-grabbing ${isDragging ? "opacity-50" : ""}`}
{...dragHandleProps}
title="헤더를 드래그하여 패널 순서 변경"
>
<h3 className="text-2xl font-semibold">이상치 선별보고서</h3>
</div>
<div className="card-body max-h-[calc(100%-3.25rem)] py-2.5">
{isLoading && (
<div className="flex h-full items-center justify-center">
<div className="relative size-[5rem]">
<LoadingWithAnimation color="primary" />
</div>
</div>
)}
{error && (
<div className="flex h-full items-center justify-center">
데이터를 불러올 수 없습니다.
</div>
)}
{data && (
<div className="space-y-2">
{/* TBD: typeCounts 표시 방법 (탭? 필터?) */}
{data.typeCounts &&
Object.keys(data.typeCounts).length > 0 && (
<div className="flex flex-wrap gap-2">
{Object.entries(data.typeCounts).map(
([type, count]) => (
<Badge
key={type}
color={
TYPE_CODE_COLOR_MAP[type]
}
className="rounded-full px-2 py-1 text-lg"
>
{TYPE_CODE_NAME_MAP[type]}:{" "}
{count}
</Badge>
),
)}
</div>
)}
{/* TBD: 테이블 컬럼 정의, 증감 표시 방법 */}
<div className="max-h-[44rem] overflow-y-auto">
{data.list.length === 0 ? (
<div className="flex h-full items-center justify-center">
이상치 항목이 없습니다.
</div>
) : (
<div className="space-y-2 tracking-tight">
{data.list.map((item, index) => (
<div
key={index}
className="flex flex-col rounded-sm border border-border p-3"
>
<div className="mb-2 flex items-center justify-between">
<span className="font-medium">
{item.statsNm}
</span>
<Badge
color={
TYPE_CODE_COLOR_MAP[
item.vrfcCd
]
}
className="text-sm"
>
{
TYPE_CODE_NAME_MAP[
item.vrfcCd
]
}
</Badge>
</div>
<p className="text-lg text-muted-foreground">
{item.rsnExpln}
</p>
<div className="flex justify-between text-lg text-muted-foreground">
<span>{item.admdstNm}</span>
<span>
{/* TBD: 증감 표시 방법 (nowCN - cmprCn 계산?) */}
현재: {item.nowCN ?? "-"}{" "}
/ 비교:{" "}
{item.cmprCn ?? "-"}
</span>
</div>
{/* TBD: 등급/점수/단계/업데이트 시각 필드 미제공 */}
</div>
))}
</div>
)}
</div>
</div>
)}
</div>
</div>
);
}