diff options
Diffstat (limited to 'app')
| -rw-r--r-- | app/[lng]/evcp/(evcp)/risk-management/page.tsx | 112 | ||||
| -rw-r--r-- | app/api/risks/send-risk-email/route.ts | 36 |
2 files changed, 148 insertions, 0 deletions
diff --git a/app/[lng]/evcp/(evcp)/risk-management/page.tsx b/app/[lng]/evcp/(evcp)/risk-management/page.tsx new file mode 100644 index 00000000..c372865e --- /dev/null +++ b/app/[lng]/evcp/(evcp)/risk-management/page.tsx @@ -0,0 +1,112 @@ +/* IMPORT */ +import { DataTableSkeleton } from '@/components/data-table/data-table-skeleton'; +import { getPreviousWeekday, searchParamsCache } from '@/lib/risk-management/validations'; +import { getRisksView } from '@/lib/risk-management/service'; +import { getValidFilters } from '@/lib/data-table'; +import { InformationButton } from '@/components/information/information-button'; +import { RISK_DASHBOARD_MENU_ITEM_LIST } from '@/config/risksConfig'; +import RisksDashboard from '@/lib/risk-management/table/risks-dashboard'; +import RisksDateRangePicker from '@/lib/risk-management/table/risks-date-range-picker'; +import RisksTable from '@/lib/risk-management/table/risks-table'; +import { Shell } from '@/components/shell'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Suspense } from 'react'; +import { type DateRange } from 'react-day-picker'; +import { type SearchParams } from '@/types/table'; + +// ---------------------------------------------------------------------------------------------------- + +/* TYPES */ +interface RiskManagementPageProps { + searchParams: Promise<SearchParams>; +} + +// ---------------------------------------------------------------------------------------------------- + +/* RISK MANAGEMENT PAGE */ +async function RiskManagementPage(props: RiskManagementPageProps) { + const { searchParams } = props; + const searchParamsResult = await searchParams; + const search = searchParamsCache.parse(searchParamsResult); + const validFilters = getValidFilters(search.filters); + const promises = Promise.all([ + getRisksView({ + ...search, + filters: validFilters, + }), + ]); + const previousWorkday = getPreviousWeekday(new Date()); + const defaultFrom = new Date(previousWorkday); + defaultFrom.setHours(0, 0, 0, 0); + const defaultTo = new Date(previousWorkday); + defaultTo.setHours(23, 59, 59, 999); + const defaultDateRange: DateRange = { + from: defaultFrom, + to: defaultTo, + }; + + return ( + <Shell className="gap-2"> + <div className="flex items-center justify-between space-y-2"> + <div className="flex items-center justify-between space-y-2"> + <div> + <div className="flex items-center gap-2"> + <h2 className="text-2xl font-bold tracking-tight"> + 협력업체 리스크 관리 + </h2> + <InformationButton pagePath="evcp/risk-management" /> + </div> + <p className="text-muted-foreground"> + 신용평가사 정보를 기반으로 국내 협력업체 리스크를 관리할 수 있습니다. + </p> + </div> + </div> + </div> + + <Suspense fallback={<Skeleton className="h-7 w-52" />}> + <RisksDateRangePicker + defaultDateRange={defaultDateRange} + triggerSize="sm" + triggerClassName="ml-auto w-56 sm:w-60" + align="end" + shallow={false} + /> + </Suspense> + <Suspense + fallback={ + <div className="max-w-lg grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-x-16 gap-y-4 pb-8"> + {RISK_DASHBOARD_MENU_ITEM_LIST.map((item) => ( + <div key={item} className="w-32 flex flex-col items-center justify-center gap-2"> + <div className="font-bold">{item}</div> + <Skeleton className="w-full h-12" /> + </div> + ))} + </div> + } + > + <RisksDashboard + targetValues={RISK_DASHBOARD_MENU_ITEM_LIST} + defaultDateRange={defaultDateRange} + /> + </Suspense> + <Suspense + fallback={ + <DataTableSkeleton + columnCount={11} + searchableColumnCount={1} + filterableColumnCount={2} + cellWidths={['10rem', '40rem', '12rem', '12rem', '8rem', '8rem']} + shrinkZero + /> + } + > + <RisksTable promises={promises} /> + </Suspense> + </Shell> + ) +} + +// ---------------------------------------------------------------------------------------------------- + +/* EXPORT */ +export default RiskManagementPage;
\ No newline at end of file diff --git a/app/api/risks/send-risk-email/route.ts b/app/api/risks/send-risk-email/route.ts new file mode 100644 index 00000000..e21bc68d --- /dev/null +++ b/app/api/risks/send-risk-email/route.ts @@ -0,0 +1,36 @@ +/* IMPORT */ +import { NextResponse } from 'next/server'; +import { sendRiskEmail } from '@/lib/risk-management/service'; + +// ---------------------------------------------------------------------------------------------------- + +/* HANDLER FOR POST REQUEST */ +export async function POST(req: Request) { + try { + const formData = await req.formData(); + const vendorId = Number(formData.get('vendorId')); + const managerId = Number(formData.get('managerId')); + const adminComment = String(formData.get('adminComment')); + const selectedEventTypeMapJson = formData.get('selectedEventTypeMap') as string | null; + const selectedEventTypeMap = selectedEventTypeMapJson ? JSON.parse(selectedEventTypeMapJson) : {}; + const file = formData.get('attachment') as File | null; + + let attachment = undefined; + if (file) { + const arrayBuffer = await file.arrayBuffer(); + const buffer = Buffer.from(arrayBuffer); + + attachment = { + filename: file.name, + content: buffer, + }; + } + + await sendRiskEmail(vendorId, managerId, adminComment, selectedEventTypeMap, attachment); + + return NextResponse.json({ message: '메일 전송 성공' }); + } catch (error) { + console.error('메일 전송 실패:', error); + return NextResponse.json({ message: '메일 전송 중 오류 발생' }, { status: 500 }); + } +}
\ No newline at end of file |
