From 6d8044eb4132f561c6f66e6012d790b84d3e710e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A4=EC=A0=95=ED=95=9C?= <165637494+jeonghanyun@users.noreply.github.com> Date: Sat, 2 Aug 2025 12:44:48 +0900 Subject: [PATCH 1/2] feat: Add Korean date expression support with Jira-style Today arrow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add getWeekOfMonth function for monthly week calculation - Add Korean locale support (locale='kor') in calendar component - Year view: Display as '2024년 1월' - Month view: Show month names in Korean - Week view: Display as '1월 1주' format - Day view: Display as '1월 1일 (월)' format - Hour/PartOfDay views: Korean date format support - Add Jira-style Today indicator with orange arrow - Replace simple rect with SVG containing vertical line and triangle arrow - Orange color (#fea362) for better visibility - Improve tooltip date format - Separate task name and date information - Use 'YYYY-MM-DD ~ YYYY-MM-DD' format - Simplify project visual style - Remove triangular decorations from project bars - Clean rectangular design only - Add fontSize={12} to task labels for better readability - Add Korean locale examples in demo app (locale='kor') - Update calendar styling with divider lines and center alignment Based on PR #254 from MaTeMaTuK/gantt-task-react by ohshinyeop Korean date expressions are fundamentally different from English formats --- example/src/App.tsx | 2 + src/components/calendar/calendar.tsx | 227 +++++++++++++------ src/components/grid/grid-body.tsx | 42 +++- src/components/other/tooltip.tsx | 19 +- src/components/task-item/project/project.tsx | 37 --- src/components/task-item/task-item.tsx | 1 + src/helpers/date-helper.ts | 24 ++ 7 files changed, 235 insertions(+), 117 deletions(-) diff --git a/example/src/App.tsx b/example/src/App.tsx index c2ab602..6e9482e 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -78,6 +78,7 @@ const App = () => { { = ({ const date = dateSetup.dates[i]; const bottomValue = date.getFullYear(); bottomValues.push( - - {bottomValue} - + + + + {bottomValue} + + ); if ( i === 0 || @@ -129,14 +139,24 @@ export const Calendar: React.FC = ({ const date = dateSetup.dates[i]; const bottomValue = getLocaleMonth(date, locale); bottomValues.push( - - {bottomValue} - + + + + {bottomValue} + + ); if ( i === 0 || @@ -176,20 +196,37 @@ export const Calendar: React.FC = ({ let topValue = ""; if (i === 0 || date.getMonth() !== dates[i - 1].getMonth()) { // top - topValue = `${getLocaleMonth(date, locale)}, ${date.getFullYear()}`; + if (locale === "kor") { + topValue = `${date.getFullYear()}년 ${getLocaleMonth(date, locale)}`; + } else { + topValue = `${getLocaleMonth(date, locale)}, ${date.getFullYear()}`; + } } // bottom - const bottomValue = `W${getWeekNumberISO8601(date)}`; + const bottomValue = + locale === "kor" + ? `${getLocaleMonth(date, locale)} ${getWeekOfMonth(date)}주` + : `W${getWeekOfMonth(date)}`; bottomValues.push( - - {bottomValue} - + + + + {bottomValue} + + ); if (topValue) { @@ -221,19 +258,39 @@ export const Calendar: React.FC = ({ const dates = dateSetup.dates; for (let i = 0; i < dates.length; i++) { const date = dates[i]; - const bottomValue = `${getLocalDayOfWeek(date, locale, "short")}, ${date - .getDate() - .toString()}`; + const bottomValue = + locale === "kor" + ? `${getLocaleMonth( + date, + locale + )} ${date.getDate()}일 (${getLocalDayOfWeek( + date, + locale, + "short" + )})` + : `${getLocalDayOfWeek(date, locale, "short")}, ${date + .getDate() + .toString()}`; bottomValues.push( - - {bottomValue} - + + + + {bottomValue} + + ); if ( i + 1 !== dates.length && @@ -275,22 +332,41 @@ export const Calendar: React.FC = ({ }).format(date); bottomValues.push( - - {bottomValue} - + + + + {bottomValue} + + ); if (i === 0 || date.getDate() !== dates[i - 1].getDate()) { - const topValue = `${getLocalDayOfWeek( - date, - locale, - "short" - )}, ${date.getDate()} ${getLocaleMonth(date, locale)}`; + const topValue = + locale === "kor" + ? `${getLocaleMonth( + date, + locale + )} ${date.getDate()}일 (${getLocalDayOfWeek( + date, + locale, + "short" + )})` + : `${getLocalDayOfWeek( + date, + locale, + "short" + )}, ${date.getDate()} ${getLocaleMonth(date, locale)}`; topValues.push( = ({ }).format(date); bottomValues.push( - - {bottomValue} - + + + + {bottomValue} + + ); if (i !== 0 && date.getDate() !== dates[i - 1].getDate()) { const displayDate = dates[i - 1]; - const topValue = `${getLocalDayOfWeek( - displayDate, - locale, - "long" - )}, ${displayDate.getDate()} ${getLocaleMonth(displayDate, locale)}`; + const topValue = + locale === "kor" + ? `${getLocaleMonth( + displayDate, + locale + )} ${displayDate.getDate()}일 (${getLocalDayOfWeek( + displayDate, + locale, + "long" + )})` + : `${getLocalDayOfWeek( + date, + locale, + "long" + )}, ${date.getDate()} ${getLocaleMonth(date, locale)}`; const topPosition = (date.getHours() - 24) / 2; topValues.push( = ({ ).getTime() >= now.getTime()) ) { today = ( - + + {/* 사각형 (배경) */} + + + {/* 중앙 세로선 */} + + + {/* 세로로 긴 역삼각형 화살표 머리 */} + + ); } // rtl for today diff --git a/src/components/other/tooltip.tsx b/src/components/other/tooltip.tsx index 7542a14..938a090 100644 --- a/src/components/other/tooltip.tsx +++ b/src/components/other/tooltip.tsx @@ -123,13 +123,18 @@ export const StandardTooltipContent: React.FC<{ }; return (
- {`${ - task.name - }: ${task.start.getDate()}-${ - task.start.getMonth() + 1 - }-${task.start.getFullYear()} - ${task.end.getDate()}-${ - task.end.getMonth() + 1 - }-${task.end.getFullYear()}`} + {`${task.name}`} +

+

+ {`date : ${task.start.getFullYear()}-${ + task.start.getMonth() + 1 + }-${task.start.getDate()} ~ ${task.end.getFullYear()}-${ + task.end.getMonth() + 1 + }-${task.end.getDate()}`} +

{task.end.getTime() - task.start.getTime() !== 0 && (

{`Duration: ${~~( (task.end.getTime() - task.start.getTime()) / diff --git a/src/components/task-item/project/project.tsx b/src/components/task-item/project/project.tsx index 5a47ba9..8793b2c 100644 --- a/src/components/task-item/project/project.tsx +++ b/src/components/task-item/project/project.tsx @@ -11,23 +11,6 @@ export const Project: React.FC = ({ task, isSelected }) => { : task.styles.progressColor; const projectWith = task.x2 - task.x1; - const projectLeftTriangle = [ - task.x1, - task.y + task.height / 2 - 1, - task.x1, - task.y + task.height, - task.x1 + 15, - task.y + task.height / 2 - 1, - ].join(","); - const projectRightTriangle = [ - task.x2, - task.y + task.height / 2 - 1, - task.x2, - task.y + task.height, - task.x2 - 15, - task.y + task.height / 2 - 1, - ].join(","); - return ( = ({ task, isSelected }) => { rx={task.barCornerRadius} fill={processColor} /> - - - ); }; diff --git a/src/components/task-item/task-item.tsx b/src/components/task-item/task-item.tsx index 0f5c769..cbdf829 100644 --- a/src/components/task-item/task-item.tsx +++ b/src/components/task-item/task-item.tsx @@ -117,6 +117,7 @@ export const TaskItem: React.FC = props => { : style.barLabel && style.barLabelOutside } ref={textRef} + fontSize={12} > {task.name} diff --git a/src/helpers/date-helper.ts b/src/helpers/date-helper.ts index 8c495a3..d5285b2 100644 --- a/src/helpers/date-helper.ts +++ b/src/helpers/date-helper.ts @@ -216,6 +216,30 @@ const getMonday = (date: Date) => { return new Date(date.setDate(diff)); }; +export const getWeekOfMonth = (date: any) => { + // 주어진 날짜의 첫 번째 날을 가져옵니다. + const startOfMonth = new Date(date.getFullYear(), date.getMonth(), 1); + + // 주어진 날짜의 첫 번째 월요일을 가져옵니다. + const firstMonday: any = new Date(startOfMonth); + while (firstMonday.getDay() !== 1) { + firstMonday.setDate(firstMonday.getDate() + 1); + } + + // 주어진 날짜와 첫 번째 월요일 사이의 일수를 계산합니다. + const diffDays = Math.ceil((date - firstMonday) / (24 * 60 * 60 * 1000)); + + // 주 수를 계산합니다. 주어진 날짜가 첫 번째 월요일 이전이면 1주차로 간주합니다. + const weekNumber = Math.ceil((diffDays + 1) / 7); + + // 만약 주어진 날짜가 첫 번째 월요일보다 앞에 있다면 첫 번째 주로 간주합니다. + if (diffDays < 0) { + return 1; + } + + return weekNumber; +}; + export const getWeekNumberISO8601 = (date: Date) => { const tmpDate = new Date(date.valueOf()); const dayNumber = (tmpDate.getDay() + 6) % 7; From 394df6b60d77ca2bb1a9a868bdb87235dc2d252b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A4=EC=A0=95=ED=95=9C?= <165637494+jeonghanyun@users.noreply.github.com> Date: Sat, 2 Aug 2025 13:12:17 +0900 Subject: [PATCH 2/2] fix: Address code review feedback for Korean date support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Improved type safety in getWeekOfMonth function - Changed parameter type from 'any' to 'Date' - Added explicit return type 'number' - Fixed date arithmetic to use getTime() for proper type safety 2. Fixed SVG and styling issues in grid-body.tsx - Removed ineffective z-index from SVG element - Extracted TODAY_ARROW_COLOR constant for maintainability 3. Cleaned up tooltip formatting - Removed empty

tag and adjusted marginTop to 12px 4. Fixed date reference bug in calendar.tsx - Corrected non-Korean locale to use displayDate instead of date - Ensures consistent date handling across all locales 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/components/calendar/calendar.tsx | 4 ++-- src/components/grid/grid-body.tsx | 12 +++++++----- src/components/other/tooltip.tsx | 3 +-- src/helpers/date-helper.ts | 6 +++--- src/index.tsx | 2 ++ 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/components/calendar/calendar.tsx b/src/components/calendar/calendar.tsx index f77c494..0e9c154 100644 --- a/src/components/calendar/calendar.tsx +++ b/src/components/calendar/calendar.tsx @@ -428,10 +428,10 @@ export const Calendar: React.FC = ({ "long" )})` : `${getLocalDayOfWeek( - date, + displayDate, locale, "long" - )}, ${date.getDate()} ${getLocaleMonth(date, locale)}`; + )}, ${displayDate.getDate()} ${getLocaleMonth(displayDate, locale)}`; const topPosition = (date.getHours() - 24) / 2; topValues.push( = ({ ).getTime() >= now.getTime()) ) { today = ( - + {/* 사각형 (배경) */} = ({ width={1} height={y - 5} style={{ - fill: "#fea362", - stroke: "#fea362", + fill: TODAY_ARROW_COLOR, + stroke: TODAY_ARROW_COLOR, strokeWidth: 1, }} /> @@ -117,8 +119,8 @@ export const GridBody: React.FC = ({ tickX + columnWidth / 2 + 5 },0 ${tickX + columnWidth / 2},5 `} style={{ - fill: "#fea362", - stroke: "#fea362", + fill: TODAY_ARROW_COLOR, + stroke: TODAY_ARROW_COLOR, strokeWidth: 1, }} /> diff --git a/src/components/other/tooltip.tsx b/src/components/other/tooltip.tsx index 938a090..e7ec666 100644 --- a/src/components/other/tooltip.tsx +++ b/src/components/other/tooltip.tsx @@ -124,10 +124,9 @@ export const StandardTooltipContent: React.FC<{ return (

{`${task.name}`} -

{`date : ${task.start.getFullYear()}-${ task.start.getMonth() + 1 diff --git a/src/helpers/date-helper.ts b/src/helpers/date-helper.ts index d5285b2..3ee158c 100644 --- a/src/helpers/date-helper.ts +++ b/src/helpers/date-helper.ts @@ -216,18 +216,18 @@ const getMonday = (date: Date) => { return new Date(date.setDate(diff)); }; -export const getWeekOfMonth = (date: any) => { +export const getWeekOfMonth = (date: Date): number => { // 주어진 날짜의 첫 번째 날을 가져옵니다. const startOfMonth = new Date(date.getFullYear(), date.getMonth(), 1); // 주어진 날짜의 첫 번째 월요일을 가져옵니다. - const firstMonday: any = new Date(startOfMonth); + const firstMonday = new Date(startOfMonth); while (firstMonday.getDay() !== 1) { firstMonday.setDate(firstMonday.getDate() + 1); } // 주어진 날짜와 첫 번째 월요일 사이의 일수를 계산합니다. - const diffDays = Math.ceil((date - firstMonday) / (24 * 60 * 60 * 1000)); + const diffDays = Math.ceil((date.getTime() - firstMonday.getTime()) / (24 * 60 * 60 * 1000)); // 주 수를 계산합니다. 주어진 날짜가 첫 번째 월요일 이전이면 1주차로 간주합니다. const weekNumber = Math.ceil((diffDays + 1) / 7); diff --git a/src/index.tsx b/src/index.tsx index 47e8b63..e401f44 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,3 +1,5 @@ +import "./index.css"; + export { Gantt } from "./components/gantt/gantt"; export { ViewMode } from "./types/public-types"; export type {