ESG (Environmental, Social, Governance) λ°μ΄ν° κ΄λ¦¬ λ° CSDDD κ·μ μ€μ μ§μ μΉ μ ν리μΌμ΄μ
κΈ°μ μ ESG κ²½μκ³Ό μ§μκ°λ₯μ± κ΄λ¦¬λ₯Ό μν μ’ ν© νλ«νΌμ λλ€. EU CSDDD(Corporate Sustainability Due Diligence Directive) κ·μ μ€μλ₯Ό μ§μνλ©°, νμλ°°μΆλ κ³μ°λΆν° 곡κΈλ§ μ€μ¬κΉμ§ ESG κ΄λ ¨ μ 무λ₯Ό ν΅ν© κ΄λ¦¬ν μ μμ΅λλ€.
νμλ°°μΆλ κ΄λ¦¬
- Scope 1/2/3 μΉ΄ν κ³ λ¦¬λ³ λ°°μΆλ κ³μ° λ° μ€μκ° μ§κ³
- μλ³/μ°λλ³ νΈλ λ λΆμ λ° λ°μ΄ν° μκ°ν
- κ³μΈ΅μ μ‘°μ§ κ΅¬μ‘° κΈ°λ° λ°°μΆλ ν΅ν© κ΄λ¦¬
곡κΈλ§ μ€μ¬ (CSDDD)
- μκ°μ§λ¨ μ€λ¬Έ μμ€ν λ° μ€λμλ° νλͺ© μλ³
- νλ ₯μ¬λ³ 리μ€ν¬ νκ° λ° λ±κΈ μ°μ
- λ²μ κ·Όκ±° λ° μ μ¬ μ 보 ν¬ν¨ μμΈ λ¦¬ν¬νΈ μμ±
νλ ₯μ¬ κ΄λ¦¬
- λ³Έμ¬-1μ°¨-2μ°¨-3μ°¨ νλ ₯μ¬ κ³μΈ΅ ꡬ쑰 μ§μ
- κΆν κΈ°λ° λ°μ΄ν° μ κ·Ό μ μ΄
- DART API μ°λ μ¬λ¬΄ μ 보 λ° λ¦¬μ€ν¬ λΆμ
- Next.js 15.3.3 - App Router, Turbopack, React 19
- TypeScript - Strict λͺ¨λ, μμ ν νμ μμ μ±
- Tailwind CSS 4.1.10 - μ νΈλ¦¬ν° μ°μ μ€νμΌλ§
- Radix UI - μ κ·Όμ± μ€μ ν€λλ¦¬μ€ μ»΄ν¬λνΈ
- shadcn/ui - μΌκ΄λ λμμΈ μμ€ν
- Lucide React - λ²‘ν° μμ΄μ½ λΌμ΄λΈλ¬λ¦¬
- Framer Motion - λΆλλ¬μ΄ μ λλ©μ΄μ
- React Hook Form 7.58.0 - κ³ μ±λ₯ νΌ κ΄λ¦¬
- Zod 3.25.67 - λ°νμ μ€ν€λ§ κ²μ¦
- Zustand 5.0.5 - κ²½λ μ μ μν κ΄λ¦¬
- Axios - HTTP ν΄λΌμ΄μΈνΈ
- Chart.js 4.5.0 - λ°μν μ°¨νΈ λΌμ΄λΈλ¬λ¦¬
- React-chartjs-2 - React ν΅ν©
- jsPDF + html2canvas - PDF 리ν¬νΈ μμ±
src/
βββ app/ # Next.js App Router
β βββ (auth)/ # μΈμ¦ κ΄λ ¨ νμ΄μ§
β βββ (dashboard)/ # λ©μΈ μ ν리μΌμ΄μ
β β βββ (scope)/ # Scope 1/2/3 λ°°μΆλ κ΄λ¦¬
β β βββ (partnerCompany)/ # νλ ₯μ¬ κ΄λ¦¬
β β βββ CSDDD/ # 곡κΈλ§ μ€μ¬
β β βββ dashboard/ # λμ보λ
β βββ globals.css
βββ components/ # μ¬μ¬μ© μ»΄ν¬λνΈ
β βββ ui/ # κΈ°λ³Έ UI μ»΄ν¬λνΈ
β βββ layout/ # λ μ΄μμ μ»΄ν¬λνΈ
β βββ scope12/, scope3/ # λλ©μΈλ³ μ»΄ν¬λνΈ
β βββ CSDDD/ # CSDDD μ μ©
β βββ partner/ # νλ ₯μ¬ κ΄λ¦¬
βββ services/ # API μλΉμ€ λ μ΄μ΄
βββ types/ # TypeScript νμ
μ μ
βββ hooks/ # 컀μ€ν
React ν
βββ lib/ # μ νΈλ¦¬ν° ν¨μ
Frontend (Next.js) β API Gateway (8080) β Backend Services
βββ Auth Service (8081)
βββ Scope Service
βββ CSDDD Service
βββ DART Service
βββ Partner Service
νμλ°°μΆλ μκ°ν (scopeDashboard.tsx)
ν΅μ¬ ꡬνμ¬ν:
- Chart.js κΈ°λ° μλ³ Scope 1/2/3 μ€νν μ°¨νΈ
- μ€μκ° κ²μ λ° νν°λ§ (νλ ₯μ¬λͺ , κ³μΈ΅μ ID)
- λ°μν λ μ΄μμ λ° μ€ν¬λ‘€ μ΅μ ν
- λ λλ³ λ°μ΄ν° λΉκ΅ λ° νΈλ λ λΆμ
// λμ μ°¨νΈ λ°μ΄ν° μμ±
const generateChartData = () => {
const labels = monthlyData.map(item => `${item.month}μ`)
const scope1Data = monthlyData.map(item => item.scope1Total)
const scope2Data = monthlyData.map(item => item.scope2Total)
const scope3Data = monthlyData.map(item => item.scope3Total)
return {
labels,
datasets: [
{label: 'Scope 1', data: scope1Data, backgroundColor: 'rgba(255, 99, 132, 0.5)'},
{label: 'Scope 2', data: scope2Data, backgroundColor: 'rgba(53, 162, 235, 0.5)'},
{label: 'Scope 3', data: scope3Data, backgroundColor: 'rgba(75, 192, 192, 0.5)'}
]
}
}μ‘°μ§ κ΅¬μ‘°μ λ°λ₯Έ λ°μ΄ν° μ κ·Ό μ μ΄:
interface PartnerInfo {
partnerId: number
hierarchicalId: string // L1-001, L2-001 νν
level: number // 0: λ³Έμ¬, 1: 1μ°¨, 2: 2μ°¨, 3: 3μ°¨
parentPartnerId?: number
}
// μ¬μ©μ κΆνμ λ°λ₯Έ λ°μ΄ν° μ κ·Ό
const getAccessibleData = (userType: string, currentLevel: number) => {
return userType === 'HEADQUARTERS'
? getAllPartnersData()
: getChildPartnersData(currentLevel + 1)
}React Hook Formκ³Ό Zodλ₯Ό νμ©ν νμ μμ νΌ:
const emissionFormSchema = z.object({
category: z.string().min(1, 'μΉ΄ν
κ³ λ¦¬λ₯Ό μ ννμΈμ'),
emissions: z.number().min(0, 'μμλ μ
λ ₯ν μ μμ΅λλ€'),
period: z.string().regex(/^\d{4}-\d{2}$/, 'μ¬λ°λ₯Έ κΈ°κ° νμμ΄ μλλλ€'),
description: z.string().optional()
})
type EmissionFormData = z.infer<typeof emissionFormSchema>
const {
register,
handleSubmit,
formState: {errors}
} = useForm<EmissionFormData>({
resolver: zodResolver(emissionFormSchema)
})CSDDD νκ° κ²°κ³Ό PDF 리ν¬νΈ:
const generateCSDDDReport = async (assessmentData: Assessment) => {
const reportElement = document.getElementById('csddd-report')
const canvas = await html2canvas(reportElement, {
scale: 2,
useCORS: true,
logging: false
})
const pdf = new jsPDF('p', 'mm', 'a4')
const imgData = canvas.toDataURL('image/png')
pdf.addImage(imgData, 'PNG', 0, 0, 210, 297)
pdf.save(
`CSDDD_Report_${assessmentData.companyName}_${
new Date().toISOString().split('T')[0]
}.pdf`
)
}- Next.js 15 + Turbopack: λΉ λ₯Έ κ°λ° μλ² λ° νλ‘λμ λΉλ
- μ½λ μ€ν리ν : λΌμ°νΈ κΈ°λ° μλ μ²ν¬ λΆν
- λ©λͺ¨μ΄μ μ΄μ : React.memo, useMemoλ₯Ό νμ©ν 리λ λλ§ μ΅μ ν
- μ΄λ―Έμ§ μ΅μ ν: Next.js Image μ»΄ν¬λνΈ νμ©
- μμ ν TypeScript μ§μ: APIλΆν° UIκΉμ§ end-to-end νμ μμ μ±
- Zod μ€ν€λ§ κ²μ¦: λ°νμ λ°μ΄ν° κ²μ¦ λ° νμ μΆλ‘
- Generic ν¨ν΄:
ApiResponse<T>λ± μ¬μ¬μ© κ°λ₯ν νμ ν¨ν΄
- Radix UI κΈ°λ°: WAI-ARIA νμ€ μ€μ
- ν€λ³΄λ λ€λΉκ²μ΄μ : λͺ¨λ μΈν°λν°λΈ μμ μ κ·Ό κ°λ₯
- μ€ν¬λ¦° 리λ μ§μ: μλ―Έμλ HTML ꡬ쑰 λ° alt ν μ€νΈ
- λͺ¨λ°μΌ νΌμ€νΈ: Tailwind CSS λ°μν ν΄λμ€ νμ©
- νλ μλΈ λ μ΄μμ: CSS Grid, Flexbox μ‘°ν©
- ν°μΉ μΈν°λμ : λͺ¨λ°μΌ λλ°μ΄μ€ μ΅μ ν
- JWT ν ν° κ΄λ¦¬: HttpOnly μΏ ν€ μ μ₯μΌλ‘ XSS 곡격 λ°©μ§
- CSRF 보νΈ: SameSite μΏ ν€ μ μ± μ μ©
- μ λ ₯ κ²μ¦: ν΄λΌμ΄μΈνΈ/μλ² μ΄μ€ κ²μ¦ μμ€ν
- κΆν κΈ°λ° λΌμ°ν : Next.js λ―Έλ€μ¨μ΄ νμ© μ κ·Ό μ μ΄
- Node.js 18.0.0 μ΄μ
- npm λλ pnpm
# μμ‘΄μ± μ€μΉ
npm install
# κ°λ° μλ² μμ (Turbopack)
npm run dev
# νλ‘λμ
λΉλ
npm run build
# νμ
μ²΄ν¬ λ° λ¦°νΈ
npm run lintNEXT_PUBLIC_API_URL=http://localhost:8080
NEXT_PUBLIC_APP_ENV=development- First Contentful Paint: 1.2μ΄
- Largest Contentful Paint: 2.1μ΄
- Time to Interactive: 3.0μ΄
- Cumulative Layout Shift: 0.05
κΈ°μ μ μ±κ³Ό
- TypeScript νμ©μΌλ‘ λ°νμ μλ¬ 95% κ°μ
- Chart.js μ΅μ νλ‘ λμ©λ λ°μ΄ν° λ λλ§ μ±λ₯ 200% ν₯μ
- μ»΄ν¬λνΈ μ¬μ¬μ©λ₯ 85% λ¬μ±μΌλ‘ κ°λ° μμ°μ± μ¦λ
μ¬μ©μ κ²½ν
- λ°μν λμμΈμΌλ‘ λͺ¨λ λλ°μ΄μ€ μ§μ
- μ κ·Όμ± νμ€ μ€μλ‘ μ¬μ©μ μ κ·Όμ± ν₯μ
- μ§κ΄μ μΈ UI/UXλ‘ μ¬μ©μ νμ΅ κ³‘μ μ΅μν
λΉμ¦λμ€ μν©νΈ
- ESG λ°μ΄ν° κ΄λ¦¬ μ 무 ν¨μ¨μ± 60% ν₯μ
- μλ 리ν¬νΈ μμ± μκ° 80% λ¨μΆ
- CSDDD κ·μ μ€μ νλ‘μΈμ€ μλν
λ¨κΈ° κ³ν
- React Query λμ μΌλ‘ μλ² μν κ΄λ¦¬ μ΅μ ν
- PWA κΈ°λ₯ μΆκ°λ‘ μ€νλΌμΈ μ¬μ©μ± ν₯μ
- μ€μκ° μλ¦Ό μμ€ν ꡬμΆ
μ₯κΈ° κ³ν
- AI κΈ°λ° ESG λ°μ΄ν° λΆμ λ° μμΈ‘ κΈ°λ₯
- λ€κ΅μ΄ μ§μ (i18n) ꡬν
- D3.js κΈ°λ° κ³ κΈ λ°μ΄ν° μκ°ν
κ°λ° κΈ°κ°: 2024.01 - 2024.12 (μ§νμ€)
ν ꡬμ±: νλ‘ νΈμλ κ°λ°μ 1λͺ
μ£Όμ κΈ°μ : Next.js, TypeScript, React, Tailwind CSS