From b252174a89aedaec4bb073e097f9f4ea9eb69372 Mon Sep 17 00:00:00 2001 From: chasyuss Date: Thu, 20 Nov 2025 15:27:19 +0900 Subject: [PATCH 001/163] =?UTF-8?q?fix:=20(SRLT-95)=20=EC=82=AC=EC=A7=84?= =?UTF-8?q?=20priority=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/business/components/CreateModal.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/business/components/CreateModal.tsx b/src/app/business/components/CreateModal.tsx index 46888cd..c432e05 100644 --- a/src/app/business/components/CreateModal.tsx +++ b/src/app/business/components/CreateModal.tsx @@ -46,6 +46,7 @@ const CreateModal = ({ width={imageWidth} height={imageHeight} className="self-center object-contain" + priority />
{title}
From 586a96baf73d0c8c8ffb7c18f8955e50717d0988 Mon Sep 17 00:00:00 2001 From: chasyuss Date: Thu, 20 Nov 2025 15:31:59 +0900 Subject: [PATCH 002/163] =?UTF-8?q?feat:=20(SRLT-95)=20=EB=A7=88=EC=9D=B4?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=A0=84=EB=AC=B8=EA=B0=80=20?= =?UTF-8?q?=EB=A6=AC=ED=8F=AC=ED=8A=B8=20=EB=B3=B4=EB=9F=AC=EA=B0=80?= =?UTF-8?q?=EA=B8=B0=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/expert.ts | 11 ++ src/app/mypage/components/PlanCard.tsx | 98 ++++++---- .../mypage/components/UserExpertHeader.tsx | 2 +- src/app/mypage/components/UserExpertModal.tsx | 181 ++++++++++++++---- src/app/mypage/page.tsx | 2 +- src/hooks/queries/useExpertReport.ts | 9 +- src/types/expert/expert.type.ts | 27 +++ 7 files changed, 248 insertions(+), 82 deletions(-) diff --git a/src/api/expert.ts b/src/api/expert.ts index d846b3b..8a9fddd 100644 --- a/src/api/expert.ts +++ b/src/api/expert.ts @@ -5,6 +5,7 @@ import { getExpertReportsResponse, getExpertResponse, getFeedBackExpertResponse, + getUserExpertReportResponse, } from '@/types/expert/expert.type'; import api from './api'; @@ -67,3 +68,13 @@ export async function GetExpertReport( return res.data.data; } + +export async function GetUserExpertReport( + businessPlanId: number +): Promise { + const res = await api.get('/v1/expert-reports', { + params: { businessPlanId }, + }); + + return res.data; +} diff --git a/src/app/mypage/components/PlanCard.tsx b/src/app/mypage/components/PlanCard.tsx index 0e8ed1b..3790640 100644 --- a/src/app/mypage/components/PlanCard.tsx +++ b/src/app/mypage/components/PlanCard.tsx @@ -27,10 +27,12 @@ export default function PlanCard({ }: PlanCardProps) { const [isModal, setIsModal] = useState(false); const router = useRouter(); - const aiStageIndex = stages.findIndex(stage => stage.key === 'ai'); - const expertStageIndex = stages.findIndex(stage => stage.key === 'expert'); - const isAiReportEnabled = aiStageIndex >= 0 && currentStageIndex >= aiStageIndex; - const isExpertReportEnabled = expertStageIndex >= 0 && currentStageIndex >= expertStageIndex; + const aiStageIndex = stages.findIndex((stage) => stage.key === 'ai'); + const expertStageIndex = stages.findIndex((stage) => stage.key === 'expert'); + const isAiReportEnabled = + aiStageIndex >= 0 && currentStageIndex >= aiStageIndex; + const isExpertReportEnabled = + expertStageIndex >= 0 && currentStageIndex >= expertStageIndex; const setPlanId = useBusinessStore((s) => s.setPlanId); const handleTitleClick = () => { @@ -45,18 +47,25 @@ export default function PlanCard({ router.push(`/expert`); }; + const handleExpertReportModalOpen = () => { + setPlanId(businessPlanId); + setIsModal(true); + }; + return ( -
+
{lastSavedAt && ( -
최종 저장 날짜: {formatDate(lastSavedAt)}
+
+ 최종 저장 날짜: {formatDate(lastSavedAt)} +
)}
@@ -64,19 +73,29 @@ export default function PlanCard({ {stages.map((stage, idx) => { const done = idx < currentStageIndex; const doing = idx === currentStageIndex; - const lineColor = done ? 'bg-gray-600' : doing ? 'bg-primary-500' : 'bg-gray-200'; + const lineColor = done + ? 'bg-gray-600' + : doing + ? 'bg-primary-500' + : 'bg-gray-200'; return ( -
-
-
+
+
+
-
-
- {done ? - : doing ? - : } +
+
+ {done ? ( + + ) : doing ? ( + + ) : ( + + )}
- + {stage.label}
@@ -85,28 +104,29 @@ export default function PlanCard({ })}
-
+
-
-
+
+
- {isModal && setIsModal(false)} />} -
+ {isModal && ( + setIsModal(false)} + fileName={title || '이름 없는 사업계획서'} + /> + )} +
); } - diff --git a/src/app/mypage/components/UserExpertHeader.tsx b/src/app/mypage/components/UserExpertHeader.tsx index 931ef89..f4967db 100644 --- a/src/app/mypage/components/UserExpertHeader.tsx +++ b/src/app/mypage/components/UserExpertHeader.tsx @@ -60,7 +60,7 @@ const UserExpertHeader = ({ experts, value, onChange }: ExpertSelectProps) => { {open && ( -
+
{value}
diff --git a/src/app/mypage/components/UserExpertModal.tsx b/src/app/mypage/components/UserExpertModal.tsx index 7c0a0fd..9d69f7c 100644 --- a/src/app/mypage/components/UserExpertModal.tsx +++ b/src/app/mypage/components/UserExpertModal.tsx @@ -1,33 +1,126 @@ 'use client'; -import React, { useState } from 'react'; + +import { useState, useEffect, useMemo } from 'react'; import Close from '@/assets/icons/close.svg'; import Strength from '@/assets/icons/strength_graph.svg'; import Weak from '@/assets/icons/weak_graph.svg'; import UserExpertHeader from './UserExpertHeader'; +import { useBusinessStore } from '@/store/business.store'; +import { useUserExpertReport } from '@/hooks/queries/useExpertReport'; +import Image from 'next/image'; interface ExportModalProps { open?: boolean; onClose?: () => void; - experts?: string[]; fileName?: string; } const UserExpertModal = ({ open = true, onClose, - experts = ['홍길동', '호성정'], - fileName = '파일명', + fileName, }: ExportModalProps) => { - const [selectedExpert, setSelectedExpert] = useState(experts[0] ?? ''); + const businessPlanId = useBusinessStore((s) => s.planId); if (!open) return null; - if (!open) return null; + const { + data: userReport, + isLoading, + error, + } = useUserExpertReport((businessPlanId ?? 0) as number); + + const [selectedExpert, setSelectedExpert] = useState(''); - const historyItems = [ - `${selectedExpert}님의 이력`, - '최근 활동', - '포인트 내역', - ]; + const submittedReports = useMemo( + () => userReport?.data?.filter((item) => item.status === 'SUBMITTED') ?? [], + [userReport] + ); + + const experts = useMemo( + () => submittedReports.map((item) => item.expertDetailResponse.name), + [submittedReports] + ); + + const selectedReport = useMemo(() => { + if (experts.length === 0) return undefined; + if (!selectedExpert) return submittedReports[0]; + return ( + submittedReports.find( + (item) => item.expertDetailResponse.name === selectedExpert + ) ?? submittedReports[0] + ); + }, [selectedExpert, submittedReports, experts]); + + useEffect(() => { + if (!selectedExpert && experts.length > 0) { + setSelectedExpert(experts[0]); + } + }, [experts, selectedExpert]); + + if (isLoading) { + return ( +
+
+
+ 리포트를 불러오는 중 +
+ +
+
+ ); + } + + if (error || !userReport) { + return ( +
+
+
+ 리포트를 불러오지 못했습니다. +
+ +
+
+ ); + } + + if (!selectedReport) { + return ( +
+
+
+ 전문가가 피드백을 완료한 리포트가 없습니다. +
+ +
+
+ ); + } + + const profileUrl = selectedReport.expertDetailResponse.profileImageUrl; + + const strengthDetails = + selectedReport.details?.filter((d) => d.commentType === 'STRENGTH') ?? []; + + const weaknessDetails = + selectedReport.details?.filter((d) => d.commentType === 'WEAKNESS') ?? []; return (
@@ -55,28 +148,47 @@ const UserExpertModal = ({
-
+
+ {profileUrl ? ( + {`${selectedReport.expertDetailResponse.name} + ) : ( + + 전문가 이미지가 없습니다. + + )} +
- {selectedExpert} 전문가_ 피드백 보고서 + {selectedReport.expertDetailResponse.name} 전문가_피드백 + 보고서
-
#BM
-
#문제정의
+ {selectedReport.expertDetailResponse.tags?.map((tag) => ( + #{tag} + ))}
- {historyItems.map((text, i) => ( -
- {text} -
- ))} +
+ {selectedReport.expertDetailResponse.careers?.map( + (career, idx) => ( +
{career}
+ ) + )} +
@@ -84,8 +196,7 @@ const UserExpertModal = ({
총평
- “전체적으로 밸런스가 잘 잡힌 사업계획서이지만, 수익성 부분 보완이 - 필요해보입니다.” + "{selectedReport.overallComment}"
@@ -99,16 +210,9 @@ const UserExpertModal = ({
- 2030세대의 자기 투영 욕구를 기반으로 한 통찰력이 매우 날카롭고 - 설득력 있음. 2030세대의 자기 투영 욕구를 기반으로 한 통찰력이 - 매우 날카롭고 설득력 있음.2030세대의 자기 투영 욕구를 기반으로 - 한 통찰력이 매우 날카롭고 설득력 있음.2030세대의 자기 투영 - 욕구를 기반으로 한 통찰력이 매우 날카롭고 설득력 있음.2030세대의 - 자기 투영 욕구를 기반으로 한 통찰력이 매우 날카롭고 설득력 - 있음.2030세대의 자기 투영 욕구를 기반으로 한 통찰력이 매우 - 날카롭고 설득력 있음.2030세대의 자기 투영 욕구를 기반으로 한 - 통찰력이 매우 날카롭고 설득력 있음.2030세대의 자기 투영 욕구를 - 기반으로 한 통찰력이 매우 날카롭고 설득력 있음. + {strengthDetails.map((d, idx) => ( +
{d.content}
+ ))}
@@ -121,16 +225,9 @@ const UserExpertModal = ({
- 2030세대의 자기 투영 욕구를 기반으로 한 통찰력이 매우 날카롭고 - 설득력 있음. 2030세대의 자기 투영 욕구를 기반으로 한 통찰력이 - 매우 날카롭고 설득력 있음.2030세대의 자기 투영 욕구를 기반으로 - 한 통찰력이 매우 날카롭고 설득력 있음.2030세대의 자기 투영 - 욕구를 기반으로 한 통찰력이 매우 날카롭고 설득력 있음.2030세대의 - 자기 투영 욕구를 기반으로 한 통찰력이 매우 날카롭고 설득력 - 있음.2030세대의 자기 투영 욕구를 기반으로 한 통찰력이 매우 - 날카롭고 설득력 있음.2030세대의 자기 투영 욕구를 기반으로 한 - 통찰력이 매우 날카롭고 설득력 있음.2030세대의 자기 투영 욕구를 - 기반으로 한 통찰력이 매우 날카롭고 설득력 있음. + {weaknessDetails.map((d, idx) => ( +
{d.content}
+ ))}
diff --git a/src/app/mypage/page.tsx b/src/app/mypage/page.tsx index e989b19..02819f9 100644 --- a/src/app/mypage/page.tsx +++ b/src/app/mypage/page.tsx @@ -4,7 +4,7 @@ import PlanList from './components/PlanList'; const page = () => { return ( -
+
마이페이지
diff --git a/src/hooks/queries/useExpertReport.ts b/src/hooks/queries/useExpertReport.ts index 86f8fdf..68bf1a0 100644 --- a/src/hooks/queries/useExpertReport.ts +++ b/src/hooks/queries/useExpertReport.ts @@ -1,4 +1,4 @@ -import { GetExpertReport } from '@/api/expert'; +import { GetExpertReport, GetUserExpertReport } from '@/api/expert'; import { useQuery } from '@tanstack/react-query'; export function useExpertReport(token: string) { @@ -8,3 +8,10 @@ export function useExpertReport(token: string) { enabled: !!token, }); } + +export function useUserExpertReport(businessPlanId: number) { + return useQuery({ + queryKey: ['GetUserExpertReport', businessPlanId], + queryFn: () => GetUserExpertReport(businessPlanId), + }); +} diff --git a/src/types/expert/expert.type.ts b/src/types/expert/expert.type.ts index 7009ad5..a1fd706 100644 --- a/src/types/expert/expert.type.ts +++ b/src/types/expert/expert.type.ts @@ -69,3 +69,30 @@ export interface getExpertReportsData { categories: string[]; }; } + +export interface getUserExpertReportResponse { + result: string; + data: getUserExpertReportResponseData[]; + error: null; +} + +export interface getUserExpertReportResponseData { + canEdit: boolean; + details: { + commentType: string; + content: string; + }[]; + expertDetailResponse: { + id: number; + name: string; + profileImageUrl: string; + workedPeriod: string; + email: string; + mentoringPriceWon: number; + careers: string[]; + tags: string[]; + categories: string[]; + }; + status: string; + overallComment: string; +} From 334d55f79edb12230b358a35fd925f12031656e2 Mon Sep 17 00:00:00 2001 From: parknari02 Date: Thu, 20 Nov 2025 21:02:59 +0900 Subject: [PATCH 003/163] =?UTF-8?q?feat:=20(SRLT-88)=20=EC=9D=B4=EC=A4=91?= =?UTF-8?q?=20=EB=B6=88=EB=A0=9B=20=EB=A7=89=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/business/components/WriteForm.tsx | 5 ++++- src/lib/business/editor/extensions.ts | 19 +++++++++++++++++++ src/lib/generatePdf.ts | 8 +++----- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/app/business/components/WriteForm.tsx b/src/app/business/components/WriteForm.tsx index d089a31..59fd022 100644 --- a/src/app/business/components/WriteForm.tsx +++ b/src/app/business/components/WriteForm.tsx @@ -21,7 +21,7 @@ import { applySpellHighlights, clearSpellErrors } from '@/util/spellMark'; import SpellError from '@/util/spellError'; import { mapSpellResponse } from '@/types/business/business.type'; import { useEditorStore } from '@/store/editor.store'; -import { DeleteTableOnDelete, ImageCutPaste, ResizableImage, SelectTableOnBorderClick } from '../../../lib/business/editor/extensions'; +import { DeleteTableOnDelete, ImageCutPaste, ResizableImage, SelectTableOnBorderClick, PreventNestedLists } from '../../../lib/business/editor/extensions'; import { createPasteHandler } from '../../../lib/business/editor/useEditorConfig'; import { ImageCommandAttributes } from '@/lib/business/editor/types'; import WriteFormHeader from './editor/WriteFormHeader'; @@ -42,6 +42,7 @@ const WriteForm = ({ const editorFeatures = useEditor({ extensions: [ StarterKit, + PreventNestedLists, SpellError, DeleteTableOnDelete, ImageCutPaste, @@ -70,6 +71,7 @@ const WriteForm = ({ const editorSkills = useEditor({ extensions: [ StarterKit, + PreventNestedLists, SpellError, DeleteTableOnDelete, ImageCutPaste, @@ -98,6 +100,7 @@ const WriteForm = ({ const editorGoals = useEditor({ extensions: [ StarterKit, + PreventNestedLists, SpellError, DeleteTableOnDelete, ImageCutPaste, diff --git a/src/lib/business/editor/extensions.ts b/src/lib/business/editor/extensions.ts index 1075372..21e8a6f 100644 --- a/src/lib/business/editor/extensions.ts +++ b/src/lib/business/editor/extensions.ts @@ -523,3 +523,22 @@ export const SelectTableOnBorderClick = Extension.create({ }, }); +export const PreventNestedLists = Extension.create({ + addKeyboardShortcuts() { + return { + Tab: () => { + if (this.editor?.isActive('bulletList') || this.editor?.isActive('orderedList')) { + return true; + } + return false; + }, + 'Shift-Tab': () => { + if (this.editor?.isActive('bulletList') || this.editor?.isActive('orderedList')) { + return true; + } + return false; + }, + }; + }, +}); + diff --git a/src/lib/generatePdf.ts b/src/lib/generatePdf.ts index 833da03..d75fa75 100644 --- a/src/lib/generatePdf.ts +++ b/src/lib/generatePdf.ts @@ -218,7 +218,8 @@ const renderPageHtml = ( -
+
+
${showHeader ? `

${title}

@@ -532,10 +533,7 @@ export const generatePdfFromSubsections = async ( }); }); - const pageElement = pageDoc.body.firstElementChild as HTMLElement; - if (!pageElement) { - throw new Error('Page element not found'); - } + const pageElement = await waitForElement(pageDoc, 'pdf-page-root'); const canvas = await html2canvas(pageElement, { scale: 2, From 66c1cd8745ff1264579b2163251d4adca6c63a37 Mon Sep 17 00:00:00 2001 From: parknari02 Date: Thu, 20 Nov 2025 21:08:27 +0900 Subject: [PATCH 004/163] =?UTF-8?q?feat:=20(SRLT-88)=20pdf=20=EC=B6=94?= =?UTF-8?q?=EC=B6=9C=20=EC=8B=A4=ED=8C=A8=EC=8B=9C=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/expert/components/MentorCard.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/app/expert/components/MentorCard.tsx b/src/app/expert/components/MentorCard.tsx index c6b8522..da391d6 100644 --- a/src/app/expert/components/MentorCard.tsx +++ b/src/app/expert/components/MentorCard.tsx @@ -52,8 +52,16 @@ const MentorCard = ({ } } - // PDF 생성 (Preview와 동일한 방식) - const pdfFile = await generatePdfFromSubsections(response, title); + let pdfFile: File; + try { + // PDF 생성 (Preview와 동일한 방식) + pdfFile = await generatePdfFromSubsections(response, title); + } catch (pdfError) { + console.error('PDF 생성 실패, 빈 파일로 대체합니다:', pdfError); + pdfFile = new File([new Uint8Array()], 'empty.pdf', { + type: 'application/pdf', + }); + } // PDF 다운로드 // const pdfUrl = URL.createObjectURL(pdfFile); From 7f4f7c555c43340d22a7ed993ebd87a124ab3d80 Mon Sep 17 00:00:00 2001 From: chasyuss Date: Thu, 20 Nov 2025 23:25:23 +0900 Subject: [PATCH 005/163] =?UTF-8?q?fix:=20(SRLT-27)=20tsconfig=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 2b452a7..fa48167 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,7 +15,7 @@ "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "react-jsx", + "jsx": "preserve", "incremental": true, "plugins": [ { From c97fe923770c7d8a59715cc6c3ad2e054a2446a8 Mon Sep 17 00:00:00 2001 From: parknari02 Date: Thu, 20 Nov 2025 23:47:50 +0900 Subject: [PATCH 006/163] =?UTF-8?q?feat:=20(SRLT-88)=20=EC=A4=91=EC=B2=A9?= =?UTF-8?q?=EB=B6=88=EB=A0=9B=20=EC=B2=98=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/business/components/WriteForm.tsx | 5 +- .../business/converter/editorContentMapper.ts | 50 +++- src/lib/business/converter/responseMapper.ts | 238 +++++++++++++++--- src/lib/business/editor/extensions.ts | 19 -- 4 files changed, 241 insertions(+), 71 deletions(-) diff --git a/src/app/business/components/WriteForm.tsx b/src/app/business/components/WriteForm.tsx index 59fd022..d089a31 100644 --- a/src/app/business/components/WriteForm.tsx +++ b/src/app/business/components/WriteForm.tsx @@ -21,7 +21,7 @@ import { applySpellHighlights, clearSpellErrors } from '@/util/spellMark'; import SpellError from '@/util/spellError'; import { mapSpellResponse } from '@/types/business/business.type'; import { useEditorStore } from '@/store/editor.store'; -import { DeleteTableOnDelete, ImageCutPaste, ResizableImage, SelectTableOnBorderClick, PreventNestedLists } from '../../../lib/business/editor/extensions'; +import { DeleteTableOnDelete, ImageCutPaste, ResizableImage, SelectTableOnBorderClick } from '../../../lib/business/editor/extensions'; import { createPasteHandler } from '../../../lib/business/editor/useEditorConfig'; import { ImageCommandAttributes } from '@/lib/business/editor/types'; import WriteFormHeader from './editor/WriteFormHeader'; @@ -42,7 +42,6 @@ const WriteForm = ({ const editorFeatures = useEditor({ extensions: [ StarterKit, - PreventNestedLists, SpellError, DeleteTableOnDelete, ImageCutPaste, @@ -71,7 +70,6 @@ const WriteForm = ({ const editorSkills = useEditor({ extensions: [ StarterKit, - PreventNestedLists, SpellError, DeleteTableOnDelete, ImageCutPaste, @@ -100,7 +98,6 @@ const WriteForm = ({ const editorGoals = useEditor({ extensions: [ StarterKit, - PreventNestedLists, SpellError, DeleteTableOnDelete, ImageCutPaste, diff --git a/src/lib/business/converter/editorContentMapper.ts b/src/lib/business/converter/editorContentMapper.ts index 64ce2b0..00e9c4e 100644 --- a/src/lib/business/converter/editorContentMapper.ts +++ b/src/lib/business/converter/editorContentMapper.ts @@ -16,7 +16,10 @@ const parseDimension = (value: unknown): number | null => { return null; }; -export const convertToMarkdown = (node: JSONNode | null | undefined): string => { +export const convertToMarkdown = ( + node: JSONNode | null | undefined, + depth: number = 0 +): string => { if (!node) return ''; if (node.type === 'text') { let text = node.text || ''; @@ -60,27 +63,48 @@ export const convertToMarkdown = (node: JSONNode | null | undefined): string => } if (node.type === 'paragraph') { - const content = (node.content || []).map((child) => convertToMarkdown(child)).join(''); + const content = (node.content || []).map((child) => convertToMarkdown(child, depth)).join(''); return content; } if (node.type === 'heading') { const level = (node.attrs?.level as number) || 1; - const content = (node.content || []).map((child) => convertToMarkdown(child)).join(''); + const content = (node.content || []).map((child) => convertToMarkdown(child, depth)).join(''); return content ? `${'#'.repeat(level)} ${content.trim()}` : ''; } if (node.type === 'bulletList' || node.type === 'orderedList') { - const contentArray = node.content || []; - const items = contentArray + const indent = ' '.repeat(depth); + const isOrdered = node.type === 'orderedList'; + const items = (node.content || []) .map((item, index: number) => { - const itemContent = (item.content || []).map((child) => convertToMarkdown(child)).join('').trim(); - const prefix = node.type === 'orderedList' ? `${index + 1}. ` : '- '; - const isLast = index === contentArray.length - 1; - return itemContent ? `${prefix}${itemContent}${isLast ? '' : '\n'}` : ''; + if (!item || item.type !== 'listItem') return ''; + const prefix = isOrdered ? `${index + 1}. ` : '- '; + + const textParts: string[] = []; + const nestedParts: string[] = []; + + (item.content || []).forEach((child) => { + if (!child) return; + if (child.type === 'bulletList' || child.type === 'orderedList') { + nestedParts.push(convertToMarkdown(child, depth + 1)); + } else { + const value = convertToMarkdown(child, depth + 1).trim(); + if (value) { + textParts.push(value); + } + } + }); + + const textContent = textParts.join(' ').trim(); + const baseLine = `${indent}${prefix}${textContent}`.trimEnd(); + const nestedContent = nestedParts.length > 0 ? `\n${nestedParts.join('\n')}` : ''; + + return baseLine + nestedContent; }) - .join(''); - return items ? `${items}` : ''; + .filter(Boolean); + + return items.length > 0 ? items.join('\n') : ''; } if (node.type === 'table') { @@ -90,7 +114,7 @@ export const convertToMarkdown = (node: JSONNode | null | undefined): string => const cells: string[] = []; (row.content || []).forEach((cell) => { if (cell.type === 'tableCell' || cell.type === 'tableHeader') { - const cellContent = (cell.content || []).map((child) => convertToMarkdown(child)).join('').trim().replace(/\n/g, ' '); + const cellContent = (cell.content || []).map((child) => convertToMarkdown(child, depth)).join('').trim().replace(/\n/g, ' '); cells.push(cellContent); } }); @@ -121,7 +145,7 @@ export const convertToMarkdown = (node: JSONNode | null | undefined): string => } if (node.content && Array.isArray(node.content)) { - return node.content.map((child) => convertToMarkdown(child)).join(''); + return node.content.map((child) => convertToMarkdown(child, depth)).join(''); } return ''; }; diff --git a/src/lib/business/converter/responseMapper.ts b/src/lib/business/converter/responseMapper.ts index b81be7c..cc42b6d 100644 --- a/src/lib/business/converter/responseMapper.ts +++ b/src/lib/business/converter/responseMapper.ts @@ -133,8 +133,14 @@ const convertContentItemToEditorJson = (item: BlockContentItem): JSONNode[] => { const lines = text.split('\n'); const nodes: JSONNode[] = []; let currentBlock: JSONNode | null = null; - let currentList: JSONNode | null = null; - let currentListType: 'bulletList' | 'orderedList' | null = null; + + // 중첩 리스트를 위한 스택 구조 + type ListStackItem = { + list: JSONNode; + listType: 'bulletList' | 'orderedList'; + depth: number; + }; + const listStack: ListStackItem[] = []; const hasContent = (block: JSONNode | null): block is JSONNode => { return block !== null && @@ -143,12 +149,49 @@ const convertContentItemToEditorJson = (item: BlockContentItem): JSONNode[] => { block.content.length > 0; }; - const flushList = () => { - if (hasContent(currentList)) { - nodes.push(currentList!); + // 리스트 스택을 모두 비우고 최상위 노드에 추가 + const flushAllLists = () => { + if (listStack.length === 0) return; + + // 가장 깊은 리스트부터 상위로 올라가며 중첩 구조 완성 + while (listStack.length > 1) { + const child = listStack.pop()!; + const parent = listStack[listStack.length - 1]; + + // 부모 리스트의 마지막 아이템에 자식 리스트 추가 + if (parent.list.content && parent.list.content.length > 0) { + const lastItem = parent.list.content[parent.list.content.length - 1]; + if (lastItem.type === 'listItem' && lastItem.content) { + // 이미 중첩 리스트가 있는지 확인 (중복 방지) + const hasNestedList = lastItem.content.some( + (c) => c.type === 'orderedList' || c.type === 'bulletList' + ); + if (!hasNestedList) { + lastItem.content.push(child.list); + } + } + } + } + + // 최상위 리스트를 nodes에 추가 + if (listStack.length > 0) { + nodes.push(listStack[0].list); + listStack.length = 0; } - currentList = null; - currentListType = null; + }; + + // 들여쓰기 공백 수를 계산하여 깊이 반환 + const getIndentDepth = (line: string): number => { + let depth = 0; + for (let i = 0; i < line.length; i++) { + if (line[i] === ' ') { + depth++; + } else { + break; + } + } + // 공백 2개 = 깊이 1 + return Math.floor(depth / 2); }; lines.forEach((line, index) => { @@ -162,7 +205,7 @@ const convertContentItemToEditorJson = (item: BlockContentItem): JSONNode[] => { nodes.push(currentBlock); currentBlock = null; } - flushList(); + flushAllLists(); nodes.push({ type: 'paragraph', content: [] }); return; } @@ -175,7 +218,7 @@ const convertContentItemToEditorJson = (item: BlockContentItem): JSONNode[] => { nodes.push(currentBlock); currentBlock = null; } - flushList(); + flushAllLists(); const level = headingMatch[1].length; const headingText = headingMatch[2]; @@ -194,8 +237,8 @@ const convertContentItemToEditorJson = (item: BlockContentItem): JSONNode[] => { return; } - // OrderedList 마크다운 체크 (1. 2. 3. 등) - const orderedListMatch = trimmedLine.match(/^(\d+)\.\s+(.+)$/); + // OrderedList 마크다운 체크 (들여쓰기 고려) + const orderedListMatch = line.match(/^(\s*)(\d+)\.\s+(.+)$/); if (orderedListMatch) { // 이전 블록 저장 if (hasContent(currentBlock)) { @@ -203,15 +246,11 @@ const convertContentItemToEditorJson = (item: BlockContentItem): JSONNode[] => { currentBlock = null; } - // 리스트 타입이 다르면 이전 리스트 저장 - if (currentListType !== 'orderedList') { - flushList(); - currentListType = 'orderedList'; - currentList = { type: 'orderedList', content: [] }; - } - - const itemText = orderedListMatch[2]; + const indent = orderedListMatch[1]; + const itemText = orderedListMatch[3]; + const depth = getIndentDepth(line); const parsedNodes = parseMarkdownText(itemText); + const listItem: JSONNode = { type: 'listItem', content: [{ @@ -220,14 +259,81 @@ const convertContentItemToEditorJson = (item: BlockContentItem): JSONNode[] => { }], }; - if (currentList && currentList.content) { - currentList.content.push(listItem); + // 현재 깊이보다 깊은 리스트들을 상위로 병합 + while (listStack.length > 0 && listStack[listStack.length - 1].depth > depth) { + const child = listStack.pop()!; + if (listStack.length > 0) { + const parent = listStack[listStack.length - 1]; + if (parent.list.content && parent.list.content.length > 0) { + const lastItem = parent.list.content[parent.list.content.length - 1]; + if (lastItem.type === 'listItem' && lastItem.content) { + // 이미 중첩 리스트가 있는지 확인 + const hasNestedList = lastItem.content.some( + (c) => c.type === 'orderedList' || c.type === 'bulletList' + ); + if (!hasNestedList) { + lastItem.content.push(child.list); + } + } + } + } else { + nodes.push(child.list); + } + } + + // 현재 깊이에 맞는 리스트 찾기 또는 생성 + if (listStack.length === 0) { + // 새 리스트 시작 + flushAllLists(); + const newList: JSONNode = { type: 'orderedList', content: [] }; + listStack.push({ list: newList, listType: 'orderedList', depth }); + } else { + const topStack = listStack[listStack.length - 1]; + if (topStack.depth < depth) { + // 중첩 리스트 생성: 부모 리스트의 마지막 아이템에 추가 + if (topStack.list.content && topStack.list.content.length > 0) { + const lastItem = topStack.list.content[topStack.list.content.length - 1]; + if (lastItem.type === 'listItem' && lastItem.content) { + // 이미 중첩 리스트가 있는지 확인 + const existingNestedList = lastItem.content.find( + (c) => c.type === 'orderedList' || c.type === 'bulletList' + ) as JSONNode | undefined; + + if (existingNestedList && existingNestedList.type === 'orderedList') { + // 이미 중첩 번호 리스트가 있으면 그 리스트를 스택에 추가 + listStack.push({ list: existingNestedList, listType: 'orderedList', depth }); + } else if (!existingNestedList) { + // 중첩 리스트가 없으면 새로 생성 + const nestedList: JSONNode = { type: 'orderedList', content: [] }; + lastItem.content.push(nestedList); + listStack.push({ list: nestedList, listType: 'orderedList', depth }); + } + } + } + } else if (topStack.depth === depth && topStack.listType !== 'orderedList') { + // 같은 깊이지만 타입이 다르면 이전 리스트 종료 후 새로 시작 + flushAllLists(); + const newList: JSONNode = { type: 'orderedList', content: [] }; + listStack.push({ list: newList, listType: 'orderedList', depth }); + } + // 같은 깊이, 같은 타입이면 현재 리스트에 추가 (아래에서 처리) + } + + // 현재 리스트에 아이템 추가 + if (listStack.length > 0) { + const currentListStack = listStack[listStack.length - 1]; + // 깊이가 일치하는지 확인 + if (currentListStack.depth === depth && currentListStack.listType === 'orderedList') { + if (currentListStack.list.content) { + currentListStack.list.content.push(listItem); + } + } } return; } - // BulletList 마크다운 체크 (- 또는 *) - const bulletListMatch = trimmedLine.match(/^[-*]\s+(.+)$/); + // BulletList 마크다운 체크 (들여쓰기 고려) + const bulletListMatch = line.match(/^(\s*)[-*]\s+(.+)$/); if (bulletListMatch) { // 이전 블록 저장 if (hasContent(currentBlock)) { @@ -235,15 +341,10 @@ const convertContentItemToEditorJson = (item: BlockContentItem): JSONNode[] => { currentBlock = null; } - // 리스트 타입이 다르면 이전 리스트 저장 - if (currentListType !== 'bulletList') { - flushList(); - currentListType = 'bulletList'; - currentList = { type: 'bulletList', content: [] }; - } - - const itemText = bulletListMatch[1]; + const itemText = bulletListMatch[2]; + const depth = getIndentDepth(line); const parsedNodes = parseMarkdownText(itemText); + const listItem: JSONNode = { type: 'listItem', content: [{ @@ -252,14 +353,81 @@ const convertContentItemToEditorJson = (item: BlockContentItem): JSONNode[] => { }], }; - if (currentList && currentList.content) { - currentList.content.push(listItem); + // 현재 깊이보다 깊은 리스트들을 상위로 병합 + while (listStack.length > 0 && listStack[listStack.length - 1].depth > depth) { + const child = listStack.pop()!; + if (listStack.length > 0) { + const parent = listStack[listStack.length - 1]; + if (parent.list.content && parent.list.content.length > 0) { + const lastItem = parent.list.content[parent.list.content.length - 1]; + if (lastItem.type === 'listItem' && lastItem.content) { + // 이미 중첩 리스트가 있는지 확인 + const hasNestedList = lastItem.content.some( + (c) => c.type === 'orderedList' || c.type === 'bulletList' + ); + if (!hasNestedList) { + lastItem.content.push(child.list); + } + } + } + } else { + nodes.push(child.list); + } + } + + // 현재 깊이에 맞는 리스트 찾기 또는 생성 + if (listStack.length === 0) { + // 새 리스트 시작 + flushAllLists(); + const newList: JSONNode = { type: 'bulletList', content: [] }; + listStack.push({ list: newList, listType: 'bulletList', depth }); + } else { + const topStack = listStack[listStack.length - 1]; + if (topStack.depth < depth) { + // 중첩 리스트 생성: 부모 리스트의 마지막 아이템에 추가 + if (topStack.list.content && topStack.list.content.length > 0) { + const lastItem = topStack.list.content[topStack.list.content.length - 1]; + if (lastItem.type === 'listItem' && lastItem.content) { + // 이미 중첩 리스트가 있는지 확인 + const existingNestedList = lastItem.content.find( + (c) => c.type === 'bulletList' || c.type === 'orderedList' + ) as JSONNode | undefined; + + if (existingNestedList && existingNestedList.type === 'bulletList') { + // 이미 중첩 불렛 리스트가 있으면 그 리스트를 스택에 추가 + listStack.push({ list: existingNestedList, listType: 'bulletList', depth }); + } else if (!existingNestedList) { + // 중첩 리스트가 없으면 새로 생성 + const nestedList: JSONNode = { type: 'bulletList', content: [] }; + lastItem.content.push(nestedList); + listStack.push({ list: nestedList, listType: 'bulletList', depth }); + } + } + } + } else if (topStack.depth === depth && topStack.listType !== 'bulletList') { + // 같은 깊이지만 타입이 다르면 이전 리스트 종료 후 새로 시작 + flushAllLists(); + const newList: JSONNode = { type: 'bulletList', content: [] }; + listStack.push({ list: newList, listType: 'bulletList', depth }); + } + // 같은 깊이, 같은 타입이면 현재 리스트에 추가 (아래에서 처리) + } + + // 현재 리스트에 아이템 추가 + if (listStack.length > 0) { + const currentListStack = listStack[listStack.length - 1]; + // 깊이가 일치하는지 확인 + if (currentListStack.depth === depth && currentListStack.listType === 'bulletList') { + if (currentListStack.list.content) { + currentListStack.list.content.push(listItem); + } + } } return; } // 리스트가 진행 중이면 리스트 종료 - flushList(); + flushAllLists(); // 일반 텍스트 줄: paragraph에 추가 const parsedNodes = parseMarkdownText(line); @@ -303,7 +471,7 @@ const convertContentItemToEditorJson = (item: BlockContentItem): JSONNode[] => { if (hasContent(currentBlock)) { nodes.push(currentBlock); } - flushList(); + flushAllLists(); return nodes.length > 0 ? nodes : [{ type: 'paragraph' }]; } diff --git a/src/lib/business/editor/extensions.ts b/src/lib/business/editor/extensions.ts index 21e8a6f..1075372 100644 --- a/src/lib/business/editor/extensions.ts +++ b/src/lib/business/editor/extensions.ts @@ -523,22 +523,3 @@ export const SelectTableOnBorderClick = Extension.create({ }, }); -export const PreventNestedLists = Extension.create({ - addKeyboardShortcuts() { - return { - Tab: () => { - if (this.editor?.isActive('bulletList') || this.editor?.isActive('orderedList')) { - return true; - } - return false; - }, - 'Shift-Tab': () => { - if (this.editor?.isActive('bulletList') || this.editor?.isActive('orderedList')) { - return true; - } - return false; - }, - }; - }, -}); - From 9004ff8d9cd8302a22fdb60214074fff44c0e673 Mon Sep 17 00:00:00 2001 From: parknari02 Date: Fri, 21 Nov 2025 00:34:21 +0900 Subject: [PATCH 007/163] =?UTF-8?q?fix:=20(SRLT-88)=20=EA=B0=81=20?= =?UTF-8?q?=EC=A4=84=EC=9D=84=20=EB=B3=84=EB=8F=84=EC=9D=98=20paragraph?= =?UTF-8?q?=EB=A1=9C=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/business/converter/responseMapper.ts | 40 ++++++++------------ 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/src/lib/business/converter/responseMapper.ts b/src/lib/business/converter/responseMapper.ts index cc42b6d..f23b732 100644 --- a/src/lib/business/converter/responseMapper.ts +++ b/src/lib/business/converter/responseMapper.ts @@ -429,37 +429,27 @@ const convertContentItemToEditorJson = (item: BlockContentItem): JSONNode[] => { // 리스트가 진행 중이면 리스트 종료 flushAllLists(); - // 일반 텍스트 줄: paragraph에 추가 + // 일반 텍스트 줄: 각 줄을 별도의 paragraph로 처리 const parsedNodes = parseMarkdownText(line); - if (!currentBlock) { - currentBlock = { type: 'paragraph', content: [] }; + // 현재 블록이 있으면 먼저 저장 + if (hasContent(currentBlock)) { + nodes.push(currentBlock); + currentBlock = null; } - if (!currentBlock.content) { - currentBlock.content = []; + // 새로운 paragraph 생성 (각 줄마다 별도의 paragraph) + if (parsedNodes.length > 0) { + currentBlock = { + type: 'paragraph', + content: parsedNodes, + }; + } else { + currentBlock = { type: 'paragraph', content: [] }; } - // 파싱된 노드들을 현재 블록에 추가 - parsedNodes.forEach((node) => { - if (currentBlock && currentBlock.content) { - currentBlock.content.push(node); - } - }); - - // 다음 줄이 존재하고 비어있지 않다면 'hardBreak' 추가 (Enter 1회 -> 줄바꿈) - // 단, 다음 줄이 리스트(bulletList 또는 orderedList)인 경우 제외 - if (nextLine !== null && nextLine.trim() !== '') { - const trimmedNextLine = nextLine.trim(); - const isNextLineList = - /^[-*]\s+/.test(trimmedNextLine) || // bulletList: - 또는 *로 시작 - /^\d+\.\s+/.test(trimmedNextLine); // orderedList: 숫자. 로 시작 - - if (!isNextLineList && currentBlock && currentBlock.content) { - currentBlock.content.push({ type: 'hardBreak' }); - } - } else { - // 다음 줄이 없거나 빈 줄이면 현재 블록 저장 + // 다음 줄이 없거나 빈 줄이면 현재 블록 저장 + if (nextLine === null || nextLine.trim() === '') { if (hasContent(currentBlock)) { nodes.push(currentBlock); } From c96a9d8f447fcdcc90fae0bdb84c1daa16adc2ec Mon Sep 17 00:00:00 2001 From: chasyuss Date: Fri, 21 Nov 2025 01:41:49 +0900 Subject: [PATCH 008/163] =?UTF-8?q?fix:=20(SRLT-27)=20=EA=B7=B8=EB=9E=98?= =?UTF-8?q?=ED=94=84=20=EA=B0=81=20=ED=8C=8C=ED=8A=B8=20=EB=B3=84=20?= =?UTF-8?q?=EB=A7=8C=EC=A0=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/report/components/ChartCard.tsx | 41 +++++++++++++++++-------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/src/app/report/components/ChartCard.tsx b/src/app/report/components/ChartCard.tsx index 01425c0..c4e6447 100644 --- a/src/app/report/components/ChartCard.tsx +++ b/src/app/report/components/ChartCard.tsx @@ -21,32 +21,46 @@ const ChartCard = () => { { radius: 75, isDashed: true, color: '#DADFE7', bgColor: '#F3F5F9' }, { radius: 100, isDashed: false, color: '#EBEEF3', bgColor: '#FBFCFD' }, ] as const; - const labelDistances = [30, 40, 30, 60] as const; + const labelDistances = [20, 40, 30, 60] as const; const planId = useBusinessStore((s) => s.planId); const { data, isLoading, isError } = useGetGrade((planId ?? 0) as number); const radarData = useMemo(() => { const d = data?.data; - return [ + + const raw = [ { subject: '문제 정의', - value: d?.problemRecognitionScore ?? 0, + score: d?.problemRecognitionScore ?? 0, fullMark: 20, }, - { subject: '실현 가능성', value: d?.feasibilityScore ?? 0, fullMark: 30 }, + { + subject: '실현 가능성', + score: d?.feasibilityScore ?? 0, + fullMark: 30, + }, { subject: '성장 전략', - value: d?.growthStrategyScore ?? 0, + score: d?.growthStrategyScore ?? 0, fullMark: 30, }, - { subject: '팀 역량', value: d?.teamCompetenceScore ?? 0, fullMark: 20 }, + { + subject: '팀 역량', + score: d?.teamCompetenceScore ?? 0, + fullMark: 20, + }, ]; + + return raw.map((item) => ({ + ...item, + ratio: item.fullMark > 0 ? item.score / item.fullMark : 0, + })); }, [data]); if (!planId) { return ( -
+

계획서를 저장/채점한 후 확인할 수 있어요.

@@ -55,21 +69,21 @@ const ChartCard = () => { } if (isLoading) { return ( -
+

차트 불러오는 중입니다.

); } if (isError) { return ( -
+

차트를 불러올 수 없습니다.

); } return ( -
+
{ style={{ position: 'absolute', top: 0, - left: '50%', + left: '51%', transform: 'translateX(-50%)', }} > @@ -137,6 +151,7 @@ const ChartCard = () => { outerRadius + labelDistances[index as 0 | 1 | 2 | 3]; const newX = cx + distance * Math.cos(angle); const newY = cy - distance * Math.sin(angle); + return ( { /> Date: Fri, 21 Nov 2025 02:19:26 +0900 Subject: [PATCH 009/163] =?UTF-8?q?fix:=20(SRLT-95)=20=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pnpm-lock.yaml | 218 ++++++++++++++++++++++++------------------------- 1 file changed, 109 insertions(+), 109 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 60a4e57..228967c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -61,7 +61,7 @@ importers: version: 1.4.1 jspdf: specifier: ^3.0.3 - version: 3.0.3 + version: 3.0.4 next: specifier: 15.5.4 version: 15.5.4(@babel/core@7.28.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -73,10 +73,10 @@ importers: version: 19.1.0(react@19.1.0) recharts: specifier: ^3.3.0 - version: 3.4.1(@types/react@19.2.5)(react-dom@19.1.0(react@19.1.0))(react-is@16.13.1)(react@19.1.0)(redux@5.0.1) + version: 3.4.1(@types/react@19.2.6)(react-dom@19.1.0(react@19.1.0))(react-is@16.13.1)(react@19.1.0)(redux@5.0.1) zustand: specifier: ^5.0.8 - version: 5.0.8(@types/react@19.2.5)(immer@10.2.0)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0)) + version: 5.0.8(@types/react@19.2.6)(immer@10.2.0)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0)) devDependencies: '@eslint/eslintrc': specifier: ^3 @@ -95,10 +95,10 @@ importers: version: 20.19.25 '@types/react': specifier: ^19 - version: 19.2.5 + version: 19.2.6 '@types/react-dom': specifier: ^19 - version: 19.2.3(@types/react@19.2.5) + version: 19.2.3(@types/react@19.2.6) eslint: specifier: ^9 version: 9.39.1(jiti@2.6.1) @@ -1424,8 +1424,8 @@ packages: peerDependencies: '@types/react': ^19.2.0 - '@types/react@19.2.5': - resolution: {integrity: sha512-keKxkZMqnDicuvFoJbzrhbtdLSPhj/rZThDlKWCDbgXmUg0rEUFtRssDXKYmtXluZlIqiC5VqkCgRwzuyLHKHw==} + '@types/react@19.2.6': + resolution: {integrity: sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==} '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} @@ -1433,63 +1433,63 @@ packages: '@types/use-sync-external-store@0.0.6': resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} - '@typescript-eslint/eslint-plugin@8.46.4': - resolution: {integrity: sha512-R48VhmTJqplNyDxCyqqVkFSZIx1qX6PzwqgcXn1olLrzxcSBDlOsbtcnQuQhNtnNiJ4Xe5gREI1foajYaYU2Vg==} + '@typescript-eslint/eslint-plugin@8.47.0': + resolution: {integrity: sha512-fe0rz9WJQ5t2iaLfdbDc9T80GJy0AeO453q8C3YCilnGozvOyCG5t+EZtg7j7D88+c3FipfP/x+wzGnh1xp8ZA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.46.4 + '@typescript-eslint/parser': ^8.47.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.46.4': - resolution: {integrity: sha512-tK3GPFWbirvNgsNKto+UmB/cRtn6TZfyw0D6IKrW55n6Vbs7KJoZtI//kpTKzE/DUmmnAFD8/Ca46s7Obs92/w==} + '@typescript-eslint/parser@8.47.0': + resolution: {integrity: sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.46.4': - resolution: {integrity: sha512-nPiRSKuvtTN+no/2N1kt2tUh/HoFzeEgOm9fQ6XQk4/ApGqjx0zFIIaLJ6wooR1HIoozvj2j6vTi/1fgAz7UYQ==} + '@typescript-eslint/project-service@8.47.0': + resolution: {integrity: sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.46.4': - resolution: {integrity: sha512-tMDbLGXb1wC+McN1M6QeDx7P7c0UWO5z9CXqp7J8E+xGcJuUuevWKxuG8j41FoweS3+L41SkyKKkia16jpX7CA==} + '@typescript-eslint/scope-manager@8.47.0': + resolution: {integrity: sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.46.4': - resolution: {integrity: sha512-+/XqaZPIAk6Cjg7NWgSGe27X4zMGqrFqZ8atJsX3CWxH/jACqWnrWI68h7nHQld0y+k9eTTjb9r+KU4twLoo9A==} + '@typescript-eslint/tsconfig-utils@8.47.0': + resolution: {integrity: sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.46.4': - resolution: {integrity: sha512-V4QC8h3fdT5Wro6vANk6eojqfbv5bpwHuMsBcJUJkqs2z5XnYhJzyz9Y02eUmF9u3PgXEUiOt4w4KHR3P+z0PQ==} + '@typescript-eslint/type-utils@8.47.0': + resolution: {integrity: sha512-QC9RiCmZ2HmIdCEvhd1aJELBlD93ErziOXXlHEZyuBo3tBiAZieya0HLIxp+DoDWlsQqDawyKuNEhORyku+P8A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.46.4': - resolution: {integrity: sha512-USjyxm3gQEePdUwJBFjjGNG18xY9A2grDVGuk7/9AkjIF1L+ZrVnwR5VAU5JXtUnBL/Nwt3H31KlRDaksnM7/w==} + '@typescript-eslint/types@8.47.0': + resolution: {integrity: sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.46.4': - resolution: {integrity: sha512-7oV2qEOr1d4NWNmpXLR35LvCfOkTNymY9oyW+lUHkmCno7aOmIf/hMaydnJBUTBMRCOGZh8YjkFOc8dadEoNGA==} + '@typescript-eslint/typescript-estree@8.47.0': + resolution: {integrity: sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.46.4': - resolution: {integrity: sha512-AbSv11fklGXV6T28dp2Me04Uw90R2iJ30g2bgLz529Koehrmkbs1r7paFqr1vPCZi7hHwYxYtxfyQMRC8QaVSg==} + '@typescript-eslint/utils@8.47.0': + resolution: {integrity: sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.46.4': - resolution: {integrity: sha512-/++5CYLQqsO9HFGLI7APrxBJYo+5OCMpViuhV8q5/Qa3o5mMrF//eQHks+PXcsAVaLdn817fMuS7zqoXNNZGaw==} + '@typescript-eslint/visitor-keys@8.47.0': + resolution: {integrity: sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@unrs/resolver-binding-android-arm-eabi@1.11.1': @@ -1690,8 +1690,8 @@ packages: resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==} engines: {node: '>= 0.6.0'} - baseline-browser-mapping@2.8.29: - resolution: {integrity: sha512-sXdt2elaVnhpDNRDz+1BDx1JQoJRuNk7oVlAlbGiFkLikHCAQiccexF/9e91zVi6RCgqspl04aP+6Cnl9zRLrA==} + baseline-browser-mapping@2.8.30: + resolution: {integrity: sha512-aTUKW4ptQhS64+v2d6IkPzymEzzhw+G0bA1g3uBRV3+ntkH+svttKseW5IOR4Ed6NUVKqnY7qT3dKvzQ7io4AA==} hasBin: true boolbase@1.0.0: @@ -1732,8 +1732,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001755: - resolution: {integrity: sha512-44V+Jm6ctPj7R52Na4TLi3Zri4dWUljJd+RDm+j8LtNCc/ihLCT+X1TzoOAkRETEWqjuLnh9581Tl80FvK7jVA==} + caniuse-lite@1.0.30001756: + resolution: {integrity: sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==} canvg@3.0.11: resolution: {integrity: sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==} @@ -1771,11 +1771,11 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - core-js-compat@3.46.0: - resolution: {integrity: sha512-p9hObIIEENxSV8xIu+V68JjSeARg6UVMG5mR+JEUguG3sI6MsiS1njz2jHmyJDvA+8jX/sytkBHup6kxhM9law==} + core-js-compat@3.47.0: + resolution: {integrity: sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==} - core-js@3.46.0: - resolution: {integrity: sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA==} + core-js@3.47.0: + resolution: {integrity: sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==} cosmiconfig@8.3.6: resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} @@ -1947,8 +1947,8 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - electron-to-chromium@1.5.254: - resolution: {integrity: sha512-DcUsWpVhv9svsKRxnSCZ86SjD+sp32SGidNB37KpqXJncp1mfUgKbHvBomE89WJDbfVKw1mdv5+ikrvd43r+Bg==} + electron-to-chromium@1.5.258: + resolution: {integrity: sha512-rHUggNV5jKQ0sSdWwlaRDkFc3/rRJIVnOSe9yR4zrR07m3ZxhP4N27Hlg8VeJGGYgFTxK5NqDmWI4DSH72vIJg==} emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} @@ -2470,8 +2470,8 @@ packages: engines: {node: '>=6'} hasBin: true - jspdf@3.0.3: - resolution: {integrity: sha512-eURjAyz5iX1H8BOYAfzvdPfIKK53V7mCpBTe7Kb16PaM8JSXEcUQNBQaiWMI8wY5RvNOPj4GccMjTlfwRBd+oQ==} + jspdf@3.0.4: + resolution: {integrity: sha512-dc6oQ8y37rRcHn316s4ngz/nOjayLF/FFxBF4V9zamQKRqXxyiH1zagkCdktdWhtoQId5K20xt1lB90XzkB+hQ==} jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} @@ -4011,7 +4011,7 @@ snapshots: babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.28.5) babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.5) babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.28.5) - core-js-compat: 3.46.0 + core-js-compat: 3.47.0 semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -4313,7 +4313,7 @@ snapshots: '@popperjs/core@2.11.8': {} - '@reduxjs/toolkit@2.10.1(react-redux@9.2.0(@types/react@19.2.5)(react@19.1.0)(redux@5.0.1))(react@19.1.0)': + '@reduxjs/toolkit@2.10.1(react-redux@9.2.0(@types/react@19.2.6)(react@19.1.0)(redux@5.0.1))(react@19.1.0)': dependencies: '@standard-schema/spec': 1.0.0 '@standard-schema/utils': 0.3.0 @@ -4323,7 +4323,7 @@ snapshots: reselect: 5.1.1 optionalDependencies: react: 19.1.0 - react-redux: 9.2.0(@types/react@19.2.5)(react@19.1.0)(redux@5.0.1) + react-redux: 9.2.0(@types/react@19.2.6)(react@19.1.0)(redux@5.0.1) '@remirror/core-constants@3.0.0': {} @@ -4761,11 +4761,11 @@ snapshots: '@types/raf@3.4.3': optional: true - '@types/react-dom@19.2.3(@types/react@19.2.5)': + '@types/react-dom@19.2.3(@types/react@19.2.6)': dependencies: - '@types/react': 19.2.5 + '@types/react': 19.2.6 - '@types/react@19.2.5': + '@types/react@19.2.6': dependencies: csstype: 3.2.3 @@ -4774,14 +4774,14 @@ snapshots: '@types/use-sync-external-store@0.0.6': {} - '@typescript-eslint/eslint-plugin@8.46.4(@typescript-eslint/parser@8.46.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.46.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.46.4 - '@typescript-eslint/type-utils': 8.46.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.46.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.46.4 + '@typescript-eslint/parser': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.47.0 + '@typescript-eslint/type-utils': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.47.0 eslint: 9.39.1(jiti@2.6.1) graphemer: 1.4.0 ignore: 7.0.5 @@ -4791,41 +4791,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.46.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.46.4 - '@typescript-eslint/types': 8.46.4 - '@typescript-eslint/typescript-estree': 8.46.4(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.46.4 + '@typescript-eslint/scope-manager': 8.47.0 + '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.47.0 debug: 4.4.3 eslint: 9.39.1(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.46.4(typescript@5.9.3)': + '@typescript-eslint/project-service@8.47.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.46.4(typescript@5.9.3) - '@typescript-eslint/types': 8.46.4 + '@typescript-eslint/tsconfig-utils': 8.47.0(typescript@5.9.3) + '@typescript-eslint/types': 8.47.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.46.4': + '@typescript-eslint/scope-manager@8.47.0': dependencies: - '@typescript-eslint/types': 8.46.4 - '@typescript-eslint/visitor-keys': 8.46.4 + '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/visitor-keys': 8.47.0 - '@typescript-eslint/tsconfig-utils@8.46.4(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.47.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.46.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.46.4 - '@typescript-eslint/typescript-estree': 8.46.4(typescript@5.9.3) - '@typescript-eslint/utils': 8.46.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.1(jiti@2.6.1) ts-api-utils: 2.1.0(typescript@5.9.3) @@ -4833,14 +4833,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.46.4': {} + '@typescript-eslint/types@8.47.0': {} - '@typescript-eslint/typescript-estree@8.46.4(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.47.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.46.4(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.46.4(typescript@5.9.3) - '@typescript-eslint/types': 8.46.4 - '@typescript-eslint/visitor-keys': 8.46.4 + '@typescript-eslint/project-service': 8.47.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.47.0(typescript@5.9.3) + '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/visitor-keys': 8.47.0 debug: 4.4.3 fast-glob: 3.3.3 is-glob: 4.0.3 @@ -4851,20 +4851,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.46.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.46.4 - '@typescript-eslint/types': 8.46.4 - '@typescript-eslint/typescript-estree': 8.46.4(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.47.0 + '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) eslint: 9.39.1(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.46.4': + '@typescript-eslint/visitor-keys@8.47.0': dependencies: - '@typescript-eslint/types': 8.46.4 + '@typescript-eslint/types': 8.47.0 eslint-visitor-keys: 4.2.1 '@unrs/resolver-binding-android-arm-eabi@1.11.1': @@ -5049,7 +5049,7 @@ snapshots: dependencies: '@babel/core': 7.28.5 '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.5) - core-js-compat: 3.46.0 + core-js-compat: 3.47.0 transitivePeerDependencies: - supports-color @@ -5064,7 +5064,7 @@ snapshots: base64-arraybuffer@1.0.2: {} - baseline-browser-mapping@2.8.29: {} + baseline-browser-mapping@2.8.30: {} boolbase@1.0.0: {} @@ -5083,9 +5083,9 @@ snapshots: browserslist@4.28.0: dependencies: - baseline-browser-mapping: 2.8.29 - caniuse-lite: 1.0.30001755 - electron-to-chromium: 1.5.254 + baseline-browser-mapping: 2.8.30 + caniuse-lite: 1.0.30001756 + electron-to-chromium: 1.5.258 node-releases: 2.0.27 update-browserslist-db: 1.1.4(browserslist@4.28.0) @@ -5110,13 +5110,13 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001755: {} + caniuse-lite@1.0.30001756: {} canvg@3.0.11: dependencies: '@babel/runtime': 7.28.4 '@types/raf': 3.4.3 - core-js: 3.46.0 + core-js: 3.47.0 raf: 3.4.1 regenerator-runtime: 0.13.11 rgbcolor: 1.0.1 @@ -5149,11 +5149,11 @@ snapshots: convert-source-map@2.0.0: {} - core-js-compat@3.46.0: + core-js-compat@3.47.0: dependencies: browserslist: 4.28.0 - core-js@3.46.0: + core-js@3.47.0: optional: true cosmiconfig@8.3.6(typescript@5.9.3): @@ -5329,7 +5329,7 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - electron-to-chromium@1.5.254: {} + electron-to-chromium@1.5.258: {} emoji-regex@9.2.2: {} @@ -5455,12 +5455,12 @@ snapshots: dependencies: '@next/eslint-plugin-next': 15.5.4 '@rushstack/eslint-patch': 1.15.0 - '@typescript-eslint/eslint-plugin': 8.46.4(@typescript-eslint/parser@8.46.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.46.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.1(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-react-hooks: 5.2.0(eslint@9.39.1(jiti@2.6.1)) @@ -5490,22 +5490,22 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.46.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.46.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.1(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -5516,7 +5516,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.1(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -5528,7 +5528,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.46.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -5988,14 +5988,14 @@ snapshots: json5@2.2.3: {} - jspdf@3.0.3: + jspdf@3.0.4: dependencies: '@babel/runtime': 7.28.4 fast-png: 6.4.0 fflate: 0.8.2 optionalDependencies: canvg: 3.0.11 - core-js: 3.46.0 + core-js: 3.47.0 dompurify: 3.3.0 html2canvas: 1.4.1 @@ -6152,7 +6152,7 @@ snapshots: dependencies: '@next/env': 15.5.4 '@swc/helpers': 0.5.15 - caniuse-lite: 1.0.30001755 + caniuse-lite: 1.0.30001756 postcss: 8.4.31 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) @@ -6430,20 +6430,20 @@ snapshots: react-is@16.13.1: {} - react-redux@9.2.0(@types/react@19.2.5)(react@19.1.0)(redux@5.0.1): + react-redux@9.2.0(@types/react@19.2.6)(react@19.1.0)(redux@5.0.1): dependencies: '@types/use-sync-external-store': 0.0.6 react: 19.1.0 use-sync-external-store: 1.6.0(react@19.1.0) optionalDependencies: - '@types/react': 19.2.5 + '@types/react': 19.2.6 redux: 5.0.1 react@19.1.0: {} - recharts@3.4.1(@types/react@19.2.5)(react-dom@19.1.0(react@19.1.0))(react-is@16.13.1)(react@19.1.0)(redux@5.0.1): + recharts@3.4.1(@types/react@19.2.6)(react-dom@19.1.0(react@19.1.0))(react-is@16.13.1)(react@19.1.0)(redux@5.0.1): dependencies: - '@reduxjs/toolkit': 2.10.1(react-redux@9.2.0(@types/react@19.2.5)(react@19.1.0)(redux@5.0.1))(react@19.1.0) + '@reduxjs/toolkit': 2.10.1(react-redux@9.2.0(@types/react@19.2.6)(react@19.1.0)(redux@5.0.1))(react@19.1.0) clsx: 2.1.1 decimal.js-light: 2.5.1 es-toolkit: 1.42.0 @@ -6452,7 +6452,7 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) react-is: 16.13.1 - react-redux: 9.2.0(@types/react@19.2.5)(react@19.1.0)(redux@5.0.1) + react-redux: 9.2.0(@types/react@19.2.6)(react@19.1.0)(redux@5.0.1) reselect: 5.1.1 tiny-invariant: 1.3.3 use-sync-external-store: 1.6.0(react@19.1.0) @@ -6961,9 +6961,9 @@ snapshots: yocto-queue@0.1.0: {} - zustand@5.0.8(@types/react@19.2.5)(immer@10.2.0)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0)): + zustand@5.0.8(@types/react@19.2.6)(immer@10.2.0)(react@19.1.0)(use-sync-external-store@1.6.0(react@19.1.0)): optionalDependencies: - '@types/react': 19.2.5 + '@types/react': 19.2.6 immer: 10.2.0 react: 19.1.0 use-sync-external-store: 1.6.0(react@19.1.0) From ab82445797c46b6b5d497be756aaff89d732862a Mon Sep 17 00:00:00 2001 From: chasyuss Date: Fri, 21 Nov 2025 02:27:48 +0900 Subject: [PATCH 010/163] =?UTF-8?q?fix:=20(SRLT-95)=20=EB=B9=8C=EB=93=9C?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/mypage/components/UserExpertModal.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/mypage/components/UserExpertModal.tsx b/src/app/mypage/components/UserExpertModal.tsx index 9d69f7c..de8db2e 100644 --- a/src/app/mypage/components/UserExpertModal.tsx +++ b/src/app/mypage/components/UserExpertModal.tsx @@ -21,7 +21,6 @@ const UserExpertModal = ({ fileName, }: ExportModalProps) => { const businessPlanId = useBusinessStore((s) => s.planId); - if (!open) return null; const { data: userReport, @@ -57,6 +56,8 @@ const UserExpertModal = ({ } }, [experts, selectedExpert]); + if (!open) return null; + if (isLoading) { return (
@@ -196,7 +197,7 @@ const UserExpertModal = ({
총평
- "{selectedReport.overallComment}" + {`"${selectedReport.overallComment}"`}
From 975bfa6ca8c68cb292dbb125d9081067831dad2d Mon Sep 17 00:00:00 2001 From: parknari02 Date: Fri, 21 Nov 2025 02:30:00 +0900 Subject: [PATCH 011/163] =?UTF-8?q?feat:=20(SRLT-88)=20=ED=97=A4=EB=94=A9?= =?UTF-8?q?=20=ED=91=9C=EC=8B=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/business/converter/editorToHtml.ts | 26 +++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/lib/business/converter/editorToHtml.ts b/src/lib/business/converter/editorToHtml.ts index 9217bf8..e919c6e 100644 --- a/src/lib/business/converter/editorToHtml.ts +++ b/src/lib/business/converter/editorToHtml.ts @@ -46,7 +46,31 @@ export const convertToHtml = (node: JSONNode | null | undefined): string => { if (node.type === 'heading') { const level = (node.attrs?.level as number) || 1; const content = (node.content || []).map((child) => convertToHtml(child)).join(''); - return content ? `${content}` : ''; + if (!content) return ''; + + // heading level에 따른 스타일 설정 + let fontSize: string; + let fontWeight = '600'; // font-semibold + let lineHeight = '150%'; + let letterSpacing = '-0.02em'; + + switch (level) { + case 1: + fontSize = '28px'; // ds-heading (--text-3xl) + break; + case 2: + fontSize = '20px'; // ds-title (--text-2xl) + break; + case 3: + fontSize = '18px'; // ds-subtitle (--text-xl) + break; + default: + fontSize = '18px'; + break; + } + + const style = `font-size: ${fontSize} !important; line-height: ${lineHeight} !important; font-weight: ${fontWeight} !important; letter-spacing: ${letterSpacing} !important; margin: 0.75rem 0 0.5rem 0 !important;`; + return `${content}`; } if (node.type === 'bulletList') { From 3bf6ce9214413c3ac817428a1202b2f72e605d46 Mon Sep 17 00:00:00 2001 From: chasyuss Date: Fri, 21 Nov 2025 03:22:41 +0900 Subject: [PATCH 012/163] =?UTF-8?q?feat:=20(SRLT-96)=20=EC=A0=84=EB=AC=B8?= =?UTF-8?q?=EA=B0=80=20=ED=94=BC=EB=93=9C=EB=B0=B1=20=EC=A0=9C=EC=B6=9C=20?= =?UTF-8?q?=EB=AA=A8=EB=8B=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/FeedBackModal.tsx | 58 +++++++++++++++++++ src/assets/icons/error.svg | 4 ++ 2 files changed, 62 insertions(+) create mode 100644 src/app/expert-reports/components/FeedBackModal.tsx create mode 100644 src/assets/icons/error.svg diff --git a/src/app/expert-reports/components/FeedBackModal.tsx b/src/app/expert-reports/components/FeedBackModal.tsx new file mode 100644 index 0000000..40ef523 --- /dev/null +++ b/src/app/expert-reports/components/FeedBackModal.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import Close from '@/assets/icons/close.svg'; +import Error from '@/assets/icons/error.svg'; +import Button from '@/app/_components/common/Button'; + +interface FeedBackModalProps { + onClose: () => void; +} + +const FeedBackModal = ({ onClose }: FeedBackModalProps) => { + return ( +
+
+ + +
+ + +
+
+ 피드백을 제출하시겠어요? +
+
+ 최종 제출 후에는 피드백 수정이 어려워요.
+ 링크를 통해 피드백을 언제든 확인할 수 있어요. +
+
+ +
+
+
+
+
+ ); +}; + +export default FeedBackModal; diff --git a/src/assets/icons/error.svg b/src/assets/icons/error.svg new file mode 100644 index 0000000..0068887 --- /dev/null +++ b/src/assets/icons/error.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From 8e9f107bf862306fd68641d514befc3bd15cb0d3 Mon Sep 17 00:00:00 2001 From: chasyuss Date: Fri, 21 Nov 2025 03:31:59 +0900 Subject: [PATCH 013/163] =?UTF-8?q?feat:=20(SRLT-96)=20=ED=94=BC=EB=93=9C?= =?UTF-8?q?=EB=B0=B1=20=EB=B3=B4=EB=82=B4=EA=B8=B0=EB=A5=BC=20=EB=88=84?= =?UTF-8?q?=EB=A5=B4=EB=A9=B4=20mutate=20=ED=98=B8=EC=B6=9C=EB=90=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/FeedBackHeader.tsx | 21 ++++++++++++++++++- .../components/FeedBackModal.tsx | 4 +++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/app/expert-reports/components/FeedBackHeader.tsx b/src/app/expert-reports/components/FeedBackHeader.tsx index bb7d00f..07ef442 100644 --- a/src/app/expert-reports/components/FeedBackHeader.tsx +++ b/src/app/expert-reports/components/FeedBackHeader.tsx @@ -1,21 +1,40 @@ +'use client'; + +import { useState } from 'react'; +import FeedBackModal from './FeedBackModal'; + type FeedBackHeaderProps = { onComplete: () => void; disabled?: boolean; }; const FeedBackHeader = ({ onComplete, disabled }: FeedBackHeaderProps) => { + const [isModal, setIsModal] = useState(false); + + const handleSubmit = () => { + onComplete(); + setIsModal(false); + }; + return (
+ + {isModal && ( + setIsModal(false)} + onSubmit={handleSubmit} + /> + )}
); }; diff --git a/src/app/expert-reports/components/FeedBackModal.tsx b/src/app/expert-reports/components/FeedBackModal.tsx index 40ef523..9f413d7 100644 --- a/src/app/expert-reports/components/FeedBackModal.tsx +++ b/src/app/expert-reports/components/FeedBackModal.tsx @@ -5,9 +5,10 @@ import Button from '@/app/_components/common/Button'; interface FeedBackModalProps { onClose: () => void; + onSubmit: () => void; } -const FeedBackModal = ({ onClose }: FeedBackModalProps) => { +const FeedBackModal = ({ onClose, onSubmit }: FeedBackModalProps) => { return (
@@ -46,6 +47,7 @@ const FeedBackModal = ({ onClose }: FeedBackModalProps) => { text="피드백 보내기" size="L" color="primary" + onClick={onSubmit} className={'ds-text h-11 w-full rounded-lg px-8 py-2.5'} />
From c640d51f632940bed4137ee7d4e21e7135f0a883 Mon Sep 17 00:00:00 2001 From: parknari02 Date: Fri, 21 Nov 2025 04:03:21 +0900 Subject: [PATCH 014/163] =?UTF-8?q?fix:=20(SRLT-88)=20=ED=97=A4=EB=8D=94?= =?UTF-8?q?=20=EC=84=B9=EC=85=98=20=EA=B9=A8=EC=A7=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/business/components/Preview.tsx | 113 ++++++++++++++++++++++-- src/lib/pdfDownload.ts | 16 ++++ 2 files changed, 121 insertions(+), 8 deletions(-) diff --git a/src/app/business/components/Preview.tsx b/src/app/business/components/Preview.tsx index d0482a2..87f66cb 100644 --- a/src/app/business/components/Preview.tsx +++ b/src/app/business/components/Preview.tsx @@ -36,11 +36,60 @@ const Preview = () => { return (
-
-
- {sectionNumber} +
+
+ + {sectionNumber} +
-

+

{section.title.replace(/^\d+\.\s*/, '')}

@@ -275,11 +324,59 @@ const Preview = () => { if (needsSectionHeader) { currentPageContent.push(
-
-
- {sectionNumber} +
+
+ + {sectionNumber} +
-

+

{sectionTitle}

diff --git a/src/lib/pdfDownload.ts b/src/lib/pdfDownload.ts index c895277..7db4596 100644 --- a/src/lib/pdfDownload.ts +++ b/src/lib/pdfDownload.ts @@ -115,6 +115,22 @@ export const downloadPDF = async (fileName: string = '사업계획서') => { clonedScrollContainer.style.flex = 'none'; clonedScrollContainer.scrollTop = 0; } + + // 섹션 헤더 스타일 적용 + const sectionHeaders = clonedDoc.querySelectorAll('.bg-gray-100'); + sectionHeaders.forEach((header) => { + const sectionNumberContainer = header.querySelector('.bg-gray-900.rounded-full') as HTMLElement; + if (sectionNumberContainer) { + const sectionNumberText = sectionNumberContainer.querySelector('span') as HTMLElement; + if (sectionNumberText) { + sectionNumberText.style.setProperty('top', '20%', 'important'); + } + } + const sectionTitle = header.querySelector('h2.ds-subtitle') as HTMLElement; + if (sectionTitle) { + sectionTitle.style.setProperty('top', '20%', 'important'); + } + }); } }, }); From c5784a4ffa0b51408b2e77401197c487157be9c0 Mon Sep 17 00:00:00 2001 From: chasyuss Date: Fri, 21 Nov 2025 04:29:59 +0900 Subject: [PATCH 015/163] =?UTF-8?q?feat:=20(SRLT-96)=20=EC=A0=9C=EC=B6=9C?= =?UTF-8?q?=20=EC=99=84=EB=A3=8C=EC=8B=9C=EC=97=90=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8A=94=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=B3=80=EA=B2=BD=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/expert-reports/[token]/page.tsx | 32 +++++++++++-------- .../components/FeedBackForm.tsx | 19 ++++++++--- .../components/FeedBackHeader.tsx | 1 - src/hooks/queries/useExpertReport.ts | 5 +-- 4 files changed, 36 insertions(+), 21 deletions(-) diff --git a/src/app/expert-reports/[token]/page.tsx b/src/app/expert-reports/[token]/page.tsx index 026b520..8deff17 100644 --- a/src/app/expert-reports/[token]/page.tsx +++ b/src/app/expert-reports/[token]/page.tsx @@ -2,10 +2,14 @@ import { useRef } from 'react'; import { useParams } from 'next/navigation'; +import { useQueryClient } from '@tanstack/react-query'; import FeedBackHeader from '../components/FeedBackHeader'; import FeedBackForm from '../components/FeedBackForm'; import { FeedBackFormHandle, SectionKey } from '@/types/feedback/sections'; -import { expertReportsResponse } from '@/types/expert/expert.type'; +import { + expertReportsResponse, + getExpertReportsResponse, +} from '@/types/expert/expert.type'; import { useExpertReportFeedback } from '@/hooks/mutation/useExpertReportFeedback'; import { useExpertReport } from '@/hooks/queries/useExpertReport'; @@ -16,8 +20,10 @@ const ExpertWritePage = () => { const params = useParams<{ token: string }>(); const token = params?.token ?? ''; - const { mutate } = useExpertReportFeedback(token); + const queryClient = useQueryClient(); + const { data, isLoading } = useExpertReport(token); + const { mutate } = useExpertReportFeedback(token); const initialFeedback: FeedbackMap | undefined = data && { summary: data.overallComment ?? '', @@ -29,7 +35,10 @@ const ExpertWritePage = () => { ?.content ?? '', }; - const isCompleteDisabled = data?.canEdit === false; + const typedData = data as getExpertReportsResponse | undefined; + const isSubmitted = !!typedData && typedData.canEdit === false; + + const isCompleteDisabled = isSubmitted; const handleComplete = () => { if (!formRef.current || !token) return; @@ -40,20 +49,16 @@ const ExpertWritePage = () => { saveType: 'FINAL', overallComment: raw.summary, details: [ - { - commentType: 'STRENGTH', - content: raw.strength, - }, - { - commentType: 'WEAKNESS', - content: raw.weakness, - }, + { commentType: 'STRENGTH', content: raw.strength }, + { commentType: 'WEAKNESS', content: raw.weakness }, ], }; mutate(body, { - onSuccess: (data) => { - console.log(data); + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ['GetExpertReport', token], + }); }, onError: (error) => { console.error(error); @@ -71,6 +76,7 @@ const ExpertWritePage = () => { ref={formRef} initialFeedback={initialFeedback} loading={isLoading} + isSubmitted={isSubmitted} /> ); diff --git a/src/app/expert-reports/components/FeedBackForm.tsx b/src/app/expert-reports/components/FeedBackForm.tsx index 54656a3..879772f 100644 --- a/src/app/expert-reports/components/FeedBackForm.tsx +++ b/src/app/expert-reports/components/FeedBackForm.tsx @@ -19,10 +19,11 @@ type FeedbackMap = Record; type FeedBackFormProps = { initialFeedback?: Partial; loading?: boolean; + isSubmitted?: boolean; }; const FeedBackForm = forwardRef( - ({ initialFeedback, loading }, ref) => { + ({ initialFeedback, loading, isSubmitted }, ref) => { const initialState = useMemo( () => sections.reduce( @@ -39,7 +40,6 @@ const FeedBackForm = forwardRef( useEffect(() => { if (!initialFeedback) return; - setFeedback((prev) => ({ ...prev, ...initialFeedback, @@ -76,7 +76,16 @@ const FeedBackForm = forwardRef(
-
+
+
+ {isSubmitted ? '제출완료' : '제출전'} +

스타라이트의 사업계획서

@@ -90,11 +99,11 @@ const FeedBackForm = forwardRef(