jskim-node #420

Merged
kjs merged 16 commits from jskim-node into main 2026-03-17 21:09:11 +09:00
38 changed files with 144 additions and 83 deletions
Showing only changes of commit ae4fe7a66e - Show all commits

6
.gitignore vendored
View File

@ -182,3 +182,9 @@ scripts/browser-test-*.js
# 개인 작업 문서 # 개인 작업 문서
popdocs/ popdocs/
.cursor/rules/popdocs-safety.mdc .cursor/rules/popdocs-safety.mdc
# 멀티 에이전트 MCP 태스크 큐
mcp-task-queue/
.cursor/rules/multi-agent-pm.mdc
.cursor/rules/multi-agent-worker.mdc
.cursor/rules/multi-agent-tester.mdc

View File

@ -1,4 +1,5 @@
import "dotenv/config"; import "dotenv/config";
process.env.TZ = "Asia/Seoul";
import "express-async-errors"; // async 라우트 핸들러의 에러를 Express 에러 핸들러로 자동 전달 import "express-async-errors"; // async 라우트 핸들러의 에러를 Express 에러 핸들러로 자동 전달
import express from "express"; import express from "express";
import cors from "cors"; import cors from "cors";

View File

@ -66,8 +66,9 @@ export const initializePool = (): Pool => {
// 연결 풀 이벤트 핸들러 // 연결 풀 이벤트 핸들러
pool.on("connect", (client) => { pool.on("connect", (client) => {
client.query("SET timezone = 'Asia/Seoul'");
if (config.debug) { if (config.debug) {
console.log("✅ PostgreSQL 클라이언트 연결 생성"); console.log("✅ PostgreSQL 클라이언트 연결 생성 (timezone: Asia/Seoul)");
} }
}); });

View File

@ -82,7 +82,8 @@ export function DashboardTopMenu({
) => { ) => {
if (format === "png") { if (format === "png") {
const link = document.createElement("a"); const link = document.createElement("a");
const filename = `${dashboardTitle || "dashboard"}_${new Date().toISOString().split("T")[0]}.png`; const _fd = new Date();
const filename = `${dashboardTitle || "dashboard"}_${_fd.getFullYear()}-${String(_fd.getMonth() + 1).padStart(2, "0")}-${String(_fd.getDate()).padStart(2, "0")}.png`;
link.download = filename; link.download = filename;
link.href = dataUrl; link.href = dataUrl;
document.body.appendChild(link); document.body.appendChild(link);
@ -111,7 +112,8 @@ export function DashboardTopMenu({
}); });
pdf.addImage(dataUrl, "PNG", 0, 0, imgWidth, imgHeight); pdf.addImage(dataUrl, "PNG", 0, 0, imgWidth, imgHeight);
const filename = `${dashboardTitle || "dashboard"}_${new Date().toISOString().split("T")[0]}.pdf`; const _pd = new Date();
const filename = `${dashboardTitle || "dashboard"}_${_pd.getFullYear()}-${String(_pd.getMonth() + 1).padStart(2, "0")}-${String(_pd.getDate()).padStart(2, "0")}.pdf`;
pdf.save(filename); pdf.save(filename);
} }
}; };

View File

@ -100,36 +100,37 @@ export function getQuickDateRange(range: "today" | "week" | "month" | "year"): {
} { } {
const now = new Date(); const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const fmtDate = (d: Date) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
switch (range) { switch (range) {
case "today": case "today":
return { return {
startDate: today.toISOString().split("T")[0], startDate: fmtDate(today),
endDate: today.toISOString().split("T")[0], endDate: fmtDate(today),
}; };
case "week": { case "week": {
const weekStart = new Date(today); const weekStart = new Date(today);
weekStart.setDate(today.getDate() - today.getDay()); // 일요일부터 weekStart.setDate(today.getDate() - today.getDay());
return { return {
startDate: weekStart.toISOString().split("T")[0], startDate: fmtDate(weekStart),
endDate: today.toISOString().split("T")[0], endDate: fmtDate(today),
}; };
} }
case "month": { case "month": {
const monthStart = new Date(today.getFullYear(), today.getMonth(), 1); const monthStart = new Date(today.getFullYear(), today.getMonth(), 1);
return { return {
startDate: monthStart.toISOString().split("T")[0], startDate: fmtDate(monthStart),
endDate: today.toISOString().split("T")[0], endDate: fmtDate(today),
}; };
} }
case "year": { case "year": {
const yearStart = new Date(today.getFullYear(), 0, 1); const yearStart = new Date(today.getFullYear(), 0, 1);
return { return {
startDate: yearStart.toISOString().split("T")[0], startDate: fmtDate(yearStart),
endDate: today.toISOString().split("T")[0], endDate: fmtDate(today),
}; };
} }

View File

@ -217,7 +217,8 @@ export function DashboardViewer({
if (format === "png") { if (format === "png") {
console.log("💾 PNG 다운로드 시작..."); console.log("💾 PNG 다운로드 시작...");
const link = document.createElement("a"); const link = document.createElement("a");
const filename = `${dashboardTitle || "dashboard"}_${new Date().toISOString().split("T")[0]}.png`; const _dvd = new Date();
const filename = `${dashboardTitle || "dashboard"}_${_dvd.getFullYear()}-${String(_dvd.getMonth() + 1).padStart(2, "0")}-${String(_dvd.getDate()).padStart(2, "0")}.png`;
link.download = filename; link.download = filename;
link.href = dataUrl; link.href = dataUrl;
document.body.appendChild(link); document.body.appendChild(link);
@ -253,7 +254,8 @@ export function DashboardViewer({
}); });
pdf.addImage(dataUrl, "PNG", 0, 0, imgWidth, imgHeight); pdf.addImage(dataUrl, "PNG", 0, 0, imgWidth, imgHeight);
const filename = `${dashboardTitle || "dashboard"}_${new Date().toISOString().split("T")[0]}.pdf`; const _dvp = new Date();
const filename = `${dashboardTitle || "dashboard"}_${_dvp.getFullYear()}-${String(_dvp.getMonth() + 1).padStart(2, "0")}-${String(_dvp.getDate()).padStart(2, "0")}.pdf`;
pdf.save(filename); pdf.save(filename);
console.log("✅ PDF 다운로드 완료:", filename); console.log("✅ PDF 다운로드 완료:", filename);
} }

View File

@ -61,7 +61,8 @@ export default function DeliveryTodayStatsWidget({ element }: DeliveryTodayStats
// 데이터 처리 // 데이터 처리
if (result.success && result.data?.rows) { if (result.success && result.data?.rows) {
const rows = result.data.rows; const rows = result.data.rows;
const today = new Date().toISOString().split("T")[0]; const _td = new Date();
const today = `${_td.getFullYear()}-${String(_td.getMonth() + 1).padStart(2, "0")}-${String(_td.getDate()).padStart(2, "0")}`;
// 오늘 발송 건수 (created_at 기준) // 오늘 발송 건수 (created_at 기준)
const shippedToday = rows.filter((row: any) => { const shippedToday = rows.filter((row: any) => {

View File

@ -101,7 +101,7 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
const [routePoints, setRoutePoints] = useState<RoutePoint[]>([]); const [routePoints, setRoutePoints] = useState<RoutePoint[]>([]);
const [selectedUserId, setSelectedUserId] = useState<string | null>(null); const [selectedUserId, setSelectedUserId] = useState<string | null>(null);
const [routeLoading, setRouteLoading] = useState(false); const [routeLoading, setRouteLoading] = useState(false);
const [routeDate, setRouteDate] = useState<string>(new Date().toISOString().split("T")[0]); // YYYY-MM-DD 형식 const [routeDate, setRouteDate] = useState<string>(() => { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`; });
// 공차/운행 정보 상태 // 공차/운행 정보 상태
const [tripInfo, setTripInfo] = useState<Record<string, any>>({}); const [tripInfo, setTripInfo] = useState<Record<string, any>>({});

View File

@ -1120,7 +1120,8 @@ export function ReportPreviewModal({ isOpen, onClose }: ReportPreviewModalProps)
const blob = new Blob([response.data], { const blob = new Blob([response.data], {
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
}); });
const timestamp = new Date().toISOString().slice(0, 10); const _rpd = new Date();
const timestamp = `${_rpd.getFullYear()}-${String(_rpd.getMonth() + 1).padStart(2, "0")}-${String(_rpd.getDate()).padStart(2, "0")}`;
const url = window.URL.createObjectURL(blob); const url = window.URL.createObjectURL(blob);
const link = document.createElement("a"); const link = document.createElement("a");
link.href = url; link.href = url;

View File

@ -86,13 +86,16 @@ export const EnhancedInteractiveScreenViewer: React.FC<EnhancedInteractiveScreen
const generateAutoValue = useCallback( const generateAutoValue = useCallback(
async (autoValueType: string, ruleId?: string): Promise<string> => { async (autoValueType: string, ruleId?: string): Promise<string> => {
const now = new Date(); const now = new Date();
const pad = (n: number) => String(n).padStart(2, "0");
const localDate = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
const localTime = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
switch (autoValueType) { switch (autoValueType) {
case "current_datetime": case "current_datetime":
return now.toISOString().slice(0, 19).replace("T", " "); return `${localDate} ${localTime}`;
case "current_date": case "current_date":
return now.toISOString().slice(0, 10); return localDate;
case "current_time": case "current_time":
return now.toTimeString().slice(0, 8); return localTime;
case "current_user": case "current_user":
return userName || "사용자"; return userName || "사용자";
case "uuid": case "uuid":

View File

@ -1155,13 +1155,16 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
const generateAutoValue = useCallback( const generateAutoValue = useCallback(
(autoValueType: string): string => { (autoValueType: string): string => {
const now = new Date(); const now = new Date();
const pad = (n: number) => String(n).padStart(2, "0");
const localDate = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
const localTime = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
switch (autoValueType) { switch (autoValueType) {
case "current_datetime": case "current_datetime":
return now.toISOString().slice(0, 19); // YYYY-MM-DDTHH:mm:ss return `${localDate} ${localTime}`;
case "current_date": case "current_date":
return now.toISOString().slice(0, 10); // YYYY-MM-DD return localDate;
case "current_time": case "current_time":
return now.toTimeString().slice(0, 8); // HH:mm:ss return localTime;
case "current_user": case "current_user":
return currentUser?.userName || currentUser?.userId || "unknown_user"; return currentUser?.userName || currentUser?.userId || "unknown_user";
case "uuid": case "uuid":

View File

@ -357,13 +357,16 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
// 자동값 생성 함수 // 자동값 생성 함수
const generateAutoValue = useCallback(async (autoValueType: string, ruleId?: string): Promise<string> => { const generateAutoValue = useCallback(async (autoValueType: string, ruleId?: string): Promise<string> => {
const now = new Date(); const now = new Date();
const pad = (n: number) => String(n).padStart(2, "0");
const localDate = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
const localTime = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
switch (autoValueType) { switch (autoValueType) {
case "current_datetime": case "current_datetime":
return now.toISOString().slice(0, 19).replace("T", " "); // YYYY-MM-DD HH:mm:ss return `${localDate} ${localTime}`;
case "current_date": case "current_date":
return now.toISOString().slice(0, 10); // YYYY-MM-DD return localDate;
case "current_time": case "current_time":
return now.toTimeString().slice(0, 8); // HH:mm:ss return localTime;
case "current_user": case "current_user":
// 실제 접속중인 사용자명 사용 // 실제 접속중인 사용자명 사용
return userName || "사용자"; // 사용자명이 없으면 기본값 return userName || "사용자"; // 사용자명이 없으면 기본값

View File

@ -183,13 +183,16 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
const generateAutoValue = useCallback( const generateAutoValue = useCallback(
(autoValueType: string): string => { (autoValueType: string): string => {
const now = new Date(); const now = new Date();
const pad = (n: number) => String(n).padStart(2, "0");
const localDate = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
const localTime = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
switch (autoValueType) { switch (autoValueType) {
case "current_datetime": case "current_datetime":
return now.toISOString().slice(0, 19).replace("T", " "); return `${localDate} ${localTime}`;
case "current_date": case "current_date":
return now.toISOString().slice(0, 10); return localDate;
case "current_time": case "current_time":
return now.toTimeString().slice(0, 8); return localTime;
case "current_user": case "current_user":
return userName || "사용자"; return userName || "사용자";
case "uuid": case "uuid":

View File

@ -3852,7 +3852,6 @@ function ControlManagementTab({
openModalWithData: "데이터+모달", openModalWithData: "데이터+모달",
openRelatedModal: "연관모달", openRelatedModal: "연관모달",
transferData: "데이터전달", transferData: "데이터전달",
quickInsert: "즉시저장",
control: "제어흐름", control: "제어흐름",
view_table_history: "이력보기", view_table_history: "이력보기",
excel_download: "엑셀다운", excel_download: "엑셀다운",

View File

@ -56,9 +56,11 @@ export const DateConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
// 현재 날짜 설정 // 현재 날짜 설정
const setCurrentDate = (field: "minDate" | "maxDate" | "defaultValue") => { const setCurrentDate = (field: "minDate" | "maxDate" | "defaultValue") => {
const now = new Date(); const now = new Date();
const pad = (n: number) => String(n).padStart(2, "0");
const d = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
const dateString = localConfig.showTime const dateString = localConfig.showTime
? now.toISOString().slice(0, 16) // YYYY-MM-DDTHH:mm ? `${d}T${pad(now.getHours())}:${pad(now.getMinutes())}`
: now.toISOString().slice(0, 10); // YYYY-MM-DD : d;
updateConfig(field, dateString); updateConfig(field, dateString);
}; };

View File

@ -263,7 +263,6 @@ export const BasicTab: React.FC<ButtonTabProps> = ({
</SelectItem> </SelectItem>
{/* 고급 기능 */} {/* 고급 기능 */}
<SelectItem value="quickInsert"> </SelectItem>
<SelectItem value="control"> </SelectItem> <SelectItem value="control"> </SelectItem>
<SelectItem value="approval"> </SelectItem> <SelectItem value="approval"> </SelectItem>
@ -271,9 +270,6 @@ export const BasicTab: React.FC<ButtonTabProps> = ({
<SelectItem value="barcode_scan"> </SelectItem> <SelectItem value="barcode_scan"> </SelectItem>
<SelectItem value="operation_control"> </SelectItem> <SelectItem value="operation_control"> </SelectItem>
{/* 이벤트 버스 */}
<SelectItem value="event"> </SelectItem>
{/* 복사 */} {/* 복사 */}
<SelectItem value="copy"> ( )</SelectItem> <SelectItem value="copy"> ( )</SelectItem>

View File

@ -1018,7 +1018,8 @@ export function FlowWidget({
const wb = XLSX.utils.book_new(); const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "Data"); XLSX.utils.book_append_sheet(wb, ws, "Data");
const fileName = `${flowName || "flow"}_data_${new Date().toISOString().split("T")[0]}.xlsx`; const _fxd = new Date();
const fileName = `${flowName || "flow"}_data_${_fxd.getFullYear()}-${String(_fxd.getMonth() + 1).padStart(2, "0")}-${String(_fxd.getDate()).padStart(2, "0")}.xlsx`;
XLSX.writeFile(wb, fileName); XLSX.writeFile(wb, fileName);
toast.success(`${exportData.length}개 행이 Excel로 내보내기 되었습니다.`); toast.success(`${exportData.length}개 행이 Excel로 내보내기 되었습니다.`);
@ -1183,7 +1184,8 @@ export function FlowWidget({
} }
} }
const fileName = `${flowName || "flow"}_data_${new Date().toISOString().split("T")[0]}.pdf`; const _fpd = new Date();
const fileName = `${flowName || "flow"}_data_${_fpd.getFullYear()}-${String(_fpd.getMonth() + 1).padStart(2, "0")}-${String(_fpd.getDate()).padStart(2, "0")}.pdf`;
doc.save(fileName); doc.save(fileName);
toast.success(`${exportData.length}개 행이 PDF로 내보내기 되었습니다.`, { id: "pdf-export" }); toast.success(`${exportData.length}개 행이 PDF로 내보내기 되었습니다.`, { id: "pdf-export" });

View File

@ -52,8 +52,10 @@ export const DateWidget: React.FC<WebTypeComponentProps> = ({ component, value,
const getDefaultValue = (): string => { const getDefaultValue = (): string => {
if (config?.defaultValue === "current") { if (config?.defaultValue === "current") {
const now = new Date(); const now = new Date();
if (isDatetime) return now.toISOString().slice(0, 16); const pad = (n: number) => String(n).padStart(2, "0");
return now.toISOString().slice(0, 10); const d = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
if (isDatetime) return `${d}T${pad(now.getHours())}:${pad(now.getMinutes())}`;
return d;
} }
return ""; return "";
}; };

View File

@ -680,11 +680,15 @@ export const UnifiedRepeater: React.FC<UnifiedRepeaterProps> = ({
const now = new Date(); const now = new Date();
switch (col.autoFill.type) { switch (col.autoFill.type) {
case "currentDate": case "currentDate": {
return now.toISOString().split("T")[0]; // YYYY-MM-DD const pad = (n: number) => String(n).padStart(2, "0");
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
}
case "currentDateTime": case "currentDateTime": {
return now.toISOString().slice(0, 19).replace("T", " "); // YYYY-MM-DD HH:mm:ss const pad = (n: number) => String(n).padStart(2, "0");
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
}
case "sequence": case "sequence":
return rowIndex + 1; // 1부터 시작하는 순번 return rowIndex + 1; // 1부터 시작하는 순번

View File

@ -1041,12 +1041,15 @@ export const V2Repeater: React.FC<V2RepeaterProps> = ({
const now = new Date(); const now = new Date();
const pad = (n: number) => String(n).padStart(2, "0");
const localDate = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
const localTime = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
switch (col.autoFill.type) { switch (col.autoFill.type) {
case "currentDate": case "currentDate":
return now.toISOString().split("T")[0]; // YYYY-MM-DD return localDate;
case "currentDateTime": case "currentDateTime":
return now.toISOString().slice(0, 19).replace("T", " "); // YYYY-MM-DD HH:mm:ss return `${localDate} ${localTime}`;
case "sequence": case "sequence":
return rowIndex + 1; // 1부터 시작하는 순번 return rowIndex + 1; // 1부터 시작하는 순번

View File

@ -130,12 +130,6 @@ const ACTION_TYPE_CARDS = [
title: "엑셀 업로드", title: "엑셀 업로드",
description: "엑셀 파일을 올려요", description: "엑셀 파일을 올려요",
}, },
{
value: "quickInsert",
icon: Zap,
title: "즉시 저장",
description: "바로 저장해요",
},
{ {
value: "approval", value: "approval",
icon: Check, icon: Check,
@ -148,12 +142,6 @@ const ACTION_TYPE_CARDS = [
title: "제어 흐름", title: "제어 흐름",
description: "흐름을 제어해요", description: "흐름을 제어해요",
}, },
{
value: "event",
icon: Send,
title: "이벤트 발송",
description: "이벤트를 보내요",
},
{ {
value: "copy", value: "copy",
icon: Copy, icon: Copy,

View File

@ -56,10 +56,10 @@ export default function VehicleReport() {
// 일별 통계 // 일별 통계
const [dailyData, setDailyData] = useState<DailyStat[]>([]); const [dailyData, setDailyData] = useState<DailyStat[]>([]);
const [dailyStartDate, setDailyStartDate] = useState( const [dailyStartDate, setDailyStartDate] = useState(
new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().split("T")[0] (() => { const d = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`; })()
); );
const [dailyEndDate, setDailyEndDate] = useState( const [dailyEndDate, setDailyEndDate] = useState(
new Date().toISOString().split("T")[0] (() => { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`; })()
); );
const [dailyLoading, setDailyLoading] = useState(false); const [dailyLoading, setDailyLoading] = useState(false);

View File

@ -53,7 +53,8 @@ export const DateInputComponent: React.FC<DateInputComponentProps> = ({
// 자동생성 로직 // 자동생성 로직
useEffect(() => { useEffect(() => {
if (finalAutoGeneration?.enabled) { if (finalAutoGeneration?.enabled) {
const today = new Date().toISOString().split("T")[0]; // YYYY-MM-DD const n = new Date();
const today = `${n.getFullYear()}-${String(n.getMonth() + 1).padStart(2, "0")}-${String(n.getDate()).padStart(2, "0")}`;
setAutoGeneratedValue(today); setAutoGeneratedValue(today);
// 인터랙티브 모드에서 폼 데이터에도 설정 // 인터랙티브 모드에서 폼 데이터에도 설정

View File

@ -653,9 +653,9 @@ export function RepeaterTable({
if (typeof val === "string" && val.includes("T")) { if (typeof val === "string" && val.includes("T")) {
return val.split("T")[0]; return val.split("T")[0];
} }
// Date 객체이면 변환 // Date 객체이면 로컬 날짜로 변환
if (val instanceof Date) { if (val instanceof Date) {
return val.toISOString().split("T")[0]; return `${val.getFullYear()}-${String(val.getMonth() + 1).padStart(2, "0")}-${String(val.getDate()).padStart(2, "0")}`;
} }
return String(val); return String(val);
}; };

View File

@ -448,7 +448,8 @@ export function SimpleRepeaterTableComponent({
} else if (col.type === "number") { } else if (col.type === "number") {
newRow[col.field] = 0; newRow[col.field] = 0;
} else if (col.type === "date") { } else if (col.type === "date") {
newRow[col.field] = new Date().toISOString().split("T")[0]; const _n = new Date();
newRow[col.field] = `${_n.getFullYear()}-${String(_n.getMonth() + 1).padStart(2, "0")}-${String(_n.getDate()).padStart(2, "0")}`;
} else { } else {
newRow[col.field] = ""; newRow[col.field] = "";
} }

View File

@ -2707,7 +2707,8 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
XLSX.utils.book_append_sheet(wb, ws, tableLabel || "데이터"); XLSX.utils.book_append_sheet(wb, ws, tableLabel || "데이터");
// 파일명 생성 // 파일명 생성
const fileName = `${tableLabel || tableConfig.selectedTable || "export"}_${new Date().toISOString().split("T")[0]}.xlsx`; const _en = new Date();
const fileName = `${tableLabel || tableConfig.selectedTable || "export"}_${_en.getFullYear()}-${String(_en.getMonth() + 1).padStart(2, "0")}-${String(_en.getDate()).padStart(2, "0")}.xlsx`;
// 파일 다운로드 // 파일 다운로드
XLSX.writeFile(wb, fileName); XLSX.writeFile(wb, fileName);

View File

@ -3006,7 +3006,8 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
XLSX.utils.book_append_sheet(wb, ws, tableLabel || "데이터"); XLSX.utils.book_append_sheet(wb, ws, tableLabel || "데이터");
// 파일명 생성 // 파일명 생성
const fileName = `${tableLabel || tableConfig.selectedTable || "export"}_${new Date().toISOString().split("T")[0]}.xlsx`; const _en = new Date();
const fileName = `${tableLabel || tableConfig.selectedTable || "export"}_${_en.getFullYear()}-${String(_en.getMonth() + 1).padStart(2, "0")}-${String(_en.getDate()).padStart(2, "0")}.xlsx`;
// 파일 다운로드 // 파일 다운로드
XLSX.writeFile(wb, fileName); XLSX.writeFile(wb, fileName);

View File

@ -227,7 +227,7 @@ export function TimelineSchedulerComponent({
if (onCellClick) { if (onCellClick) {
onCellClick({ onCellClick({
resourceId, resourceId,
date: date.toISOString().split("T")[0], date: `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`,
}); });
} }
}, },
@ -343,7 +343,7 @@ export function TimelineSchedulerComponent({
if (onAddSchedule && effectiveResources.length > 0) { if (onAddSchedule && effectiveResources.length > 0) {
onAddSchedule( onAddSchedule(
effectiveResources[0].id, effectiveResources[0].id,
new Date().toISOString().split("T")[0] (() => { const _n = new Date(); return `${_n.getFullYear()}-${String(_n.getMonth() + 1).padStart(2, "0")}-${String(_n.getDate()).padStart(2, "0")}`; })()
); );
} }
}, [onAddSchedule, effectiveResources]); }, [onAddSchedule, effectiveResources]);
@ -383,7 +383,8 @@ export function TimelineSchedulerComponent({
const items = Array.from(grouped.entries()).map(([code, rows]) => { const items = Array.from(grouped.entries()).map(([code, rows]) => {
const totalQty = rows.reduce((sum: number, r: any) => sum + (Number(r[qtyField]) || 0), 0); const totalQty = rows.reduce((sum: number, r: any) => sum + (Number(r[qtyField]) || 0), 0);
const dates = rows.map((r: any) => r[dateField]).filter(Boolean).sort(); const dates = rows.map((r: any) => r[dateField]).filter(Boolean).sort();
const earliestDate = dates[0] || new Date().toISOString().split("T")[0]; const _dn = new Date();
const earliestDate = dates[0] || `${_dn.getFullYear()}-${String(_dn.getMonth() + 1).padStart(2, "0")}-${String(_dn.getDate()).padStart(2, "0")}`;
const first = rows[0]; const first = rows[0];
return { return {
item_code: code, item_code: code,

View File

@ -28,7 +28,7 @@ interface ItemTimelineCardProps {
onScheduleClick?: (schedule: ScheduleItem) => void; onScheduleClick?: (schedule: ScheduleItem) => void;
} }
const toDateString = (d: Date) => d.toISOString().split("T")[0]; const toDateString = (d: Date) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
const addDays = (d: Date, n: number) => { const addDays = (d: Date, n: number) => {
const r = new Date(d); const r = new Date(d);

View File

@ -13,7 +13,7 @@ const SCHEDULE_TABLE = "schedule_mng";
* ISO ( ) * ISO ( )
*/ */
const toDateString = (date: Date): string => { const toDateString = (date: Date): string => {
return date.toISOString().split("T")[0]; return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
}; };
/** /**

View File

@ -54,5 +54,5 @@ export function detectConflicts(schedules: ScheduleItem[]): Set<string> {
export function addDaysToDateString(dateStr: string, days: number): string { export function addDaysToDateString(dateStr: string, days: number): string {
const date = new Date(dateStr); const date = new Date(dateStr);
date.setDate(date.getDate() + days); date.setDate(date.getDate() + days);
return date.toISOString().split("T")[0]; return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
} }

View File

@ -251,7 +251,7 @@ export function computeDateRange(
preset: DatePresetOption preset: DatePresetOption
): { preset: DatePresetOption; from: string; to: string } | null { ): { preset: DatePresetOption; from: string; to: string } | null {
const now = new Date(); const now = new Date();
const fmt = (d: Date) => d.toISOString().split("T")[0]; const fmt = (d: Date) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
switch (preset) { switch (preset) {
case "today": case "today":

View File

@ -349,7 +349,7 @@ export class EnhancedFormService {
if (lowerDataType.includes("date")) { if (lowerDataType.includes("date")) {
const date = new Date(value); const date = new Date(value);
return isNaN(date.getTime()) ? null : date.toISOString().split("T")[0]; return isNaN(date.getTime()) ? null : `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
} }
if (lowerDataType.includes("time")) { if (lowerDataType.includes("time")) {

View File

@ -1,6 +1,7 @@
"use client"; "use client";
import { AutoGenerationType, AutoGenerationConfig } from "@/types/screen"; import { AutoGenerationType, AutoGenerationConfig } from "@/types/screen";
import { toLocalDate, toLocalTime, toLocalDateTime } from "@/lib/utils/localDate";
/** /**
* *
@ -52,19 +53,19 @@ export class AutoGenerationUtils {
let result: string; let result: string;
switch (format) { switch (format) {
case "date": case "date":
result = now.toISOString().split("T")[0]; // YYYY-MM-DD result = toLocalDate(now);
break; break;
case "time": case "time":
result = now.toTimeString().split(" ")[0]; // HH:mm:ss result = toLocalTime(now);
break; break;
case "datetime": case "datetime":
result = now.toISOString().replace("T", " ").split(".")[0]; // YYYY-MM-DD HH:mm:ss result = toLocalDateTime(now);
break; break;
case "timestamp": case "timestamp":
result = now.getTime().toString(); result = now.getTime().toString();
break; break;
default: default:
result = now.toISOString(); // ISO 8601 format result = toLocalDateTime(now);
break; break;
} }

View File

@ -5156,7 +5156,8 @@ export class ButtonActionExecutor {
const menuName = localStorage.getItem("currentMenuName"); const menuName = localStorage.getItem("currentMenuName");
if (menuName) defaultFileName = menuName; if (menuName) defaultFileName = menuName;
} }
const fileName = config.excelFileName || `${defaultFileName}_${new Date().toISOString().split("T")[0]}.xlsx`; const _xd = new Date();
const fileName = config.excelFileName || `${defaultFileName}_${_xd.getFullYear()}-${String(_xd.getMonth() + 1).padStart(2, "0")}-${String(_xd.getDate()).padStart(2, "0")}.xlsx`;
const sheetName = config.excelSheetName || "Sheet1"; const sheetName = config.excelSheetName || "Sheet1";
await exportToExcel(dataToExport, fileName, sheetName, true); await exportToExcel(dataToExport, fileName, sheetName, true);
@ -5262,7 +5263,8 @@ export class ButtonActionExecutor {
} }
} }
const fileName = config.excelFileName || `${defaultFileName}_${new Date().toISOString().split("T")[0]}.xlsx`; const _xd2 = new Date();
const fileName = config.excelFileName || `${defaultFileName}_${_xd2.getFullYear()}-${String(_xd2.getMonth() + 1).padStart(2, "0")}-${String(_xd2.getDate()).padStart(2, "0")}.xlsx`;
const sheetName = config.excelSheetName || "Sheet1"; const sheetName = config.excelSheetName || "Sheet1";
const includeHeaders = config.excelIncludeHeaders !== false; const includeHeaders = config.excelIncludeHeaders !== false;

View File

@ -440,7 +440,7 @@ const validateDateField = (fieldName: string, value: any, config?: Record<string
} }
} }
return { isValid: true, transformedValue: dateValue.toISOString().split("T")[0] }; return { isValid: true, transformedValue: `${dateValue.getFullYear()}-${String(dateValue.getMonth() + 1).padStart(2, "0")}-${String(dateValue.getDate()).padStart(2, "0")}` };
}; };
/** /**

View File

@ -0,0 +1,30 @@
/**
* () /
* DB Asia/Seoul로
*
* toISOString() UTC를
*/
export function toLocalDate(date: Date = new Date()): string {
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, "0");
const d = String(date.getDate()).padStart(2, "0");
return `${y}-${m}-${d}`;
}
export function toLocalTime(date: Date = new Date()): string {
const h = String(date.getHours()).padStart(2, "0");
const min = String(date.getMinutes()).padStart(2, "0");
const sec = String(date.getSeconds()).padStart(2, "0");
return `${h}:${min}:${sec}`;
}
export function toLocalDateTime(date: Date = new Date()): string {
return `${toLocalDate(date)} ${toLocalTime(date)}`;
}
export function toLocalDateTimeForInput(date: Date = new Date()): string {
const h = String(date.getHours()).padStart(2, "0");
const min = String(date.getMinutes()).padStart(2, "0");
return `${toLocalDate(date)}T${h}:${min}`;
}

View File

@ -91,8 +91,8 @@ function getDefaultPeriod(): { start: string; end: string } {
const start = new Date(now.getFullYear(), now.getMonth(), 1); const start = new Date(now.getFullYear(), now.getMonth(), 1);
const end = new Date(now.getFullYear(), now.getMonth() + 1, 0); const end = new Date(now.getFullYear(), now.getMonth() + 1, 0);
return { return {
start: start.toISOString().split("T")[0], start: `${start.getFullYear()}-${String(start.getMonth() + 1).padStart(2, "0")}-${String(start.getDate()).padStart(2, "0")}`,
end: end.toISOString().split("T")[0], end: `${end.getFullYear()}-${String(end.getMonth() + 1).padStart(2, "0")}-${String(end.getDate()).padStart(2, "0")}`,
}; };
} }