jskim-node #420
|
|
@ -182,3 +182,9 @@ scripts/browser-test-*.js
|
|||
# 개인 작업 문서
|
||||
popdocs/
|
||||
.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
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import "dotenv/config";
|
||||
process.env.TZ = "Asia/Seoul";
|
||||
import "express-async-errors"; // async 라우트 핸들러의 에러를 Express 에러 핸들러로 자동 전달
|
||||
import express from "express";
|
||||
import cors from "cors";
|
||||
|
|
|
|||
|
|
@ -66,8 +66,9 @@ export const initializePool = (): Pool => {
|
|||
|
||||
// 연결 풀 이벤트 핸들러
|
||||
pool.on("connect", (client) => {
|
||||
client.query("SET timezone = 'Asia/Seoul'");
|
||||
if (config.debug) {
|
||||
console.log("✅ PostgreSQL 클라이언트 연결 생성");
|
||||
console.log("✅ PostgreSQL 클라이언트 연결 생성 (timezone: Asia/Seoul)");
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -82,7 +82,8 @@ export function DashboardTopMenu({
|
|||
) => {
|
||||
if (format === "png") {
|
||||
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.href = dataUrl;
|
||||
document.body.appendChild(link);
|
||||
|
|
@ -111,7 +112,8 @@ export function DashboardTopMenu({
|
|||
});
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -100,36 +100,37 @@ export function getQuickDateRange(range: "today" | "week" | "month" | "year"): {
|
|||
} {
|
||||
const now = new Date();
|
||||
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) {
|
||||
case "today":
|
||||
return {
|
||||
startDate: today.toISOString().split("T")[0],
|
||||
endDate: today.toISOString().split("T")[0],
|
||||
startDate: fmtDate(today),
|
||||
endDate: fmtDate(today),
|
||||
};
|
||||
|
||||
case "week": {
|
||||
const weekStart = new Date(today);
|
||||
weekStart.setDate(today.getDate() - today.getDay()); // 일요일부터
|
||||
weekStart.setDate(today.getDate() - today.getDay());
|
||||
return {
|
||||
startDate: weekStart.toISOString().split("T")[0],
|
||||
endDate: today.toISOString().split("T")[0],
|
||||
startDate: fmtDate(weekStart),
|
||||
endDate: fmtDate(today),
|
||||
};
|
||||
}
|
||||
|
||||
case "month": {
|
||||
const monthStart = new Date(today.getFullYear(), today.getMonth(), 1);
|
||||
return {
|
||||
startDate: monthStart.toISOString().split("T")[0],
|
||||
endDate: today.toISOString().split("T")[0],
|
||||
startDate: fmtDate(monthStart),
|
||||
endDate: fmtDate(today),
|
||||
};
|
||||
}
|
||||
|
||||
case "year": {
|
||||
const yearStart = new Date(today.getFullYear(), 0, 1);
|
||||
return {
|
||||
startDate: yearStart.toISOString().split("T")[0],
|
||||
endDate: today.toISOString().split("T")[0],
|
||||
startDate: fmtDate(yearStart),
|
||||
endDate: fmtDate(today),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -217,7 +217,8 @@ export function DashboardViewer({
|
|||
if (format === "png") {
|
||||
console.log("💾 PNG 다운로드 시작...");
|
||||
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.href = dataUrl;
|
||||
document.body.appendChild(link);
|
||||
|
|
@ -253,7 +254,8 @@ export function DashboardViewer({
|
|||
});
|
||||
|
||||
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);
|
||||
console.log("✅ PDF 다운로드 완료:", filename);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,7 +61,8 @@ export default function DeliveryTodayStatsWidget({ element }: DeliveryTodayStats
|
|||
// 데이터 처리
|
||||
if (result.success && 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 기준)
|
||||
const shippedToday = rows.filter((row: any) => {
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
const [routePoints, setRoutePoints] = useState<RoutePoint[]>([]);
|
||||
const [selectedUserId, setSelectedUserId] = useState<string | null>(null);
|
||||
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>>({});
|
||||
|
|
|
|||
|
|
@ -1120,7 +1120,8 @@ export function ReportPreviewModal({ isOpen, onClose }: ReportPreviewModalProps)
|
|||
const blob = new Blob([response.data], {
|
||||
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 link = document.createElement("a");
|
||||
link.href = url;
|
||||
|
|
|
|||
|
|
@ -86,13 +86,16 @@ export const EnhancedInteractiveScreenViewer: React.FC<EnhancedInteractiveScreen
|
|||
const generateAutoValue = useCallback(
|
||||
async (autoValueType: string, ruleId?: string): Promise<string> => {
|
||||
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) {
|
||||
case "current_datetime":
|
||||
return now.toISOString().slice(0, 19).replace("T", " ");
|
||||
return `${localDate} ${localTime}`;
|
||||
case "current_date":
|
||||
return now.toISOString().slice(0, 10);
|
||||
return localDate;
|
||||
case "current_time":
|
||||
return now.toTimeString().slice(0, 8);
|
||||
return localTime;
|
||||
case "current_user":
|
||||
return userName || "사용자";
|
||||
case "uuid":
|
||||
|
|
|
|||
|
|
@ -1155,13 +1155,16 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
|
|||
const generateAutoValue = useCallback(
|
||||
(autoValueType: string): string => {
|
||||
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) {
|
||||
case "current_datetime":
|
||||
return now.toISOString().slice(0, 19); // YYYY-MM-DDTHH:mm:ss
|
||||
return `${localDate} ${localTime}`;
|
||||
case "current_date":
|
||||
return now.toISOString().slice(0, 10); // YYYY-MM-DD
|
||||
return localDate;
|
||||
case "current_time":
|
||||
return now.toTimeString().slice(0, 8); // HH:mm:ss
|
||||
return localTime;
|
||||
case "current_user":
|
||||
return currentUser?.userName || currentUser?.userId || "unknown_user";
|
||||
case "uuid":
|
||||
|
|
|
|||
|
|
@ -357,13 +357,16 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
|||
// 자동값 생성 함수
|
||||
const generateAutoValue = useCallback(async (autoValueType: string, ruleId?: string): Promise<string> => {
|
||||
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) {
|
||||
case "current_datetime":
|
||||
return now.toISOString().slice(0, 19).replace("T", " "); // YYYY-MM-DD HH:mm:ss
|
||||
return `${localDate} ${localTime}`;
|
||||
case "current_date":
|
||||
return now.toISOString().slice(0, 10); // YYYY-MM-DD
|
||||
return localDate;
|
||||
case "current_time":
|
||||
return now.toTimeString().slice(0, 8); // HH:mm:ss
|
||||
return localTime;
|
||||
case "current_user":
|
||||
// 실제 접속중인 사용자명 사용
|
||||
return userName || "사용자"; // 사용자명이 없으면 기본값
|
||||
|
|
|
|||
|
|
@ -183,13 +183,16 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
|||
const generateAutoValue = useCallback(
|
||||
(autoValueType: string): string => {
|
||||
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) {
|
||||
case "current_datetime":
|
||||
return now.toISOString().slice(0, 19).replace("T", " ");
|
||||
return `${localDate} ${localTime}`;
|
||||
case "current_date":
|
||||
return now.toISOString().slice(0, 10);
|
||||
return localDate;
|
||||
case "current_time":
|
||||
return now.toTimeString().slice(0, 8);
|
||||
return localTime;
|
||||
case "current_user":
|
||||
return userName || "사용자";
|
||||
case "uuid":
|
||||
|
|
|
|||
|
|
@ -3852,7 +3852,6 @@ function ControlManagementTab({
|
|||
openModalWithData: "데이터+모달",
|
||||
openRelatedModal: "연관모달",
|
||||
transferData: "데이터전달",
|
||||
quickInsert: "즉시저장",
|
||||
control: "제어흐름",
|
||||
view_table_history: "이력보기",
|
||||
excel_download: "엑셀다운",
|
||||
|
|
|
|||
|
|
@ -56,9 +56,11 @@ export const DateConfigPanel: React.FC<WebTypeConfigPanelProps> = ({
|
|||
// 현재 날짜 설정
|
||||
const setCurrentDate = (field: "minDate" | "maxDate" | "defaultValue") => {
|
||||
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
|
||||
? now.toISOString().slice(0, 16) // YYYY-MM-DDTHH:mm
|
||||
: now.toISOString().slice(0, 10); // YYYY-MM-DD
|
||||
? `${d}T${pad(now.getHours())}:${pad(now.getMinutes())}`
|
||||
: d;
|
||||
updateConfig(field, dateString);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -263,7 +263,6 @@ export const BasicTab: React.FC<ButtonTabProps> = ({
|
|||
</SelectItem>
|
||||
|
||||
{/* 고급 기능 */}
|
||||
<SelectItem value="quickInsert">즉시 저장</SelectItem>
|
||||
<SelectItem value="control">제어 흐름</SelectItem>
|
||||
<SelectItem value="approval">결재 요청</SelectItem>
|
||||
|
||||
|
|
@ -271,9 +270,6 @@ export const BasicTab: React.FC<ButtonTabProps> = ({
|
|||
<SelectItem value="barcode_scan">바코드 스캔</SelectItem>
|
||||
<SelectItem value="operation_control">운행알림 및 종료</SelectItem>
|
||||
|
||||
{/* 이벤트 버스 */}
|
||||
<SelectItem value="event">이벤트 발송</SelectItem>
|
||||
|
||||
{/* 복사 */}
|
||||
<SelectItem value="copy">복사 (품목코드 초기화)</SelectItem>
|
||||
|
||||
|
|
|
|||
|
|
@ -1018,7 +1018,8 @@ export function FlowWidget({
|
|||
const wb = XLSX.utils.book_new();
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
toast.success(`${exportData.length}개 행이 PDF로 내보내기 되었습니다.`, { id: "pdf-export" });
|
||||
|
|
|
|||
|
|
@ -52,8 +52,10 @@ export const DateWidget: React.FC<WebTypeComponentProps> = ({ component, value,
|
|||
const getDefaultValue = (): string => {
|
||||
if (config?.defaultValue === "current") {
|
||||
const now = new Date();
|
||||
if (isDatetime) return now.toISOString().slice(0, 16);
|
||||
return now.toISOString().slice(0, 10);
|
||||
const pad = (n: number) => String(n).padStart(2, "0");
|
||||
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 "";
|
||||
};
|
||||
|
|
|
|||
|
|
@ -680,11 +680,15 @@ export const UnifiedRepeater: React.FC<UnifiedRepeaterProps> = ({
|
|||
const now = new Date();
|
||||
|
||||
switch (col.autoFill.type) {
|
||||
case "currentDate":
|
||||
return now.toISOString().split("T")[0]; // YYYY-MM-DD
|
||||
case "currentDate": {
|
||||
const pad = (n: number) => String(n).padStart(2, "0");
|
||||
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
|
||||
}
|
||||
|
||||
case "currentDateTime":
|
||||
return now.toISOString().slice(0, 19).replace("T", " "); // YYYY-MM-DD HH:mm:ss
|
||||
case "currentDateTime": {
|
||||
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":
|
||||
return rowIndex + 1; // 1부터 시작하는 순번
|
||||
|
|
|
|||
|
|
@ -1041,12 +1041,15 @@ export const V2Repeater: React.FC<V2RepeaterProps> = ({
|
|||
|
||||
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) {
|
||||
case "currentDate":
|
||||
return now.toISOString().split("T")[0]; // YYYY-MM-DD
|
||||
return localDate;
|
||||
|
||||
case "currentDateTime":
|
||||
return now.toISOString().slice(0, 19).replace("T", " "); // YYYY-MM-DD HH:mm:ss
|
||||
return `${localDate} ${localTime}`;
|
||||
|
||||
case "sequence":
|
||||
return rowIndex + 1; // 1부터 시작하는 순번
|
||||
|
|
|
|||
|
|
@ -130,12 +130,6 @@ const ACTION_TYPE_CARDS = [
|
|||
title: "엑셀 업로드",
|
||||
description: "엑셀 파일을 올려요",
|
||||
},
|
||||
{
|
||||
value: "quickInsert",
|
||||
icon: Zap,
|
||||
title: "즉시 저장",
|
||||
description: "바로 저장해요",
|
||||
},
|
||||
{
|
||||
value: "approval",
|
||||
icon: Check,
|
||||
|
|
@ -148,12 +142,6 @@ const ACTION_TYPE_CARDS = [
|
|||
title: "제어 흐름",
|
||||
description: "흐름을 제어해요",
|
||||
},
|
||||
{
|
||||
value: "event",
|
||||
icon: Send,
|
||||
title: "이벤트 발송",
|
||||
description: "이벤트를 보내요",
|
||||
},
|
||||
{
|
||||
value: "copy",
|
||||
icon: Copy,
|
||||
|
|
|
|||
|
|
@ -56,10 +56,10 @@ export default function VehicleReport() {
|
|||
// 일별 통계
|
||||
const [dailyData, setDailyData] = useState<DailyStat[]>([]);
|
||||
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(
|
||||
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);
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,8 @@ export const DateInputComponent: React.FC<DateInputComponentProps> = ({
|
|||
// 자동생성 로직
|
||||
useEffect(() => {
|
||||
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);
|
||||
|
||||
// 인터랙티브 모드에서 폼 데이터에도 설정
|
||||
|
|
|
|||
|
|
@ -653,9 +653,9 @@ export function RepeaterTable({
|
|||
if (typeof val === "string" && val.includes("T")) {
|
||||
return val.split("T")[0];
|
||||
}
|
||||
// Date 객체이면 변환
|
||||
// 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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -448,7 +448,8 @@ export function SimpleRepeaterTableComponent({
|
|||
} else if (col.type === "number") {
|
||||
newRow[col.field] = 0;
|
||||
} 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 {
|
||||
newRow[col.field] = "";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2707,7 +2707,8 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -3006,7 +3006,8 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -227,7 +227,7 @@ export function TimelineSchedulerComponent({
|
|||
if (onCellClick) {
|
||||
onCellClick({
|
||||
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) {
|
||||
onAddSchedule(
|
||||
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]);
|
||||
|
|
@ -383,7 +383,8 @@ export function TimelineSchedulerComponent({
|
|||
const items = Array.from(grouped.entries()).map(([code, rows]) => {
|
||||
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 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];
|
||||
return {
|
||||
item_code: code,
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ interface ItemTimelineCardProps {
|
|||
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 r = new Date(d);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const SCHEDULE_TABLE = "schedule_mng";
|
|||
* 날짜를 ISO 문자열로 변환 (시간 제외)
|
||||
*/
|
||||
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")}`;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -54,5 +54,5 @@ export function detectConflicts(schedules: ScheduleItem[]): Set<string> {
|
|||
export function addDaysToDateString(dateStr: string, days: number): string {
|
||||
const date = new Date(dateStr);
|
||||
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")}`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -251,7 +251,7 @@ export function computeDateRange(
|
|||
preset: DatePresetOption
|
||||
): { preset: DatePresetOption; from: string; to: string } | null {
|
||||
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) {
|
||||
case "today":
|
||||
|
|
|
|||
|
|
@ -349,7 +349,7 @@ export class EnhancedFormService {
|
|||
|
||||
if (lowerDataType.includes("date")) {
|
||||
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")) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { AutoGenerationType, AutoGenerationConfig } from "@/types/screen";
|
||||
import { toLocalDate, toLocalTime, toLocalDateTime } from "@/lib/utils/localDate";
|
||||
|
||||
/**
|
||||
* 자동생성 값 생성 유틸리티
|
||||
|
|
@ -52,19 +53,19 @@ export class AutoGenerationUtils {
|
|||
let result: string;
|
||||
switch (format) {
|
||||
case "date":
|
||||
result = now.toISOString().split("T")[0]; // YYYY-MM-DD
|
||||
result = toLocalDate(now);
|
||||
break;
|
||||
case "time":
|
||||
result = now.toTimeString().split(" ")[0]; // HH:mm:ss
|
||||
result = toLocalTime(now);
|
||||
break;
|
||||
case "datetime":
|
||||
result = now.toISOString().replace("T", " ").split(".")[0]; // YYYY-MM-DD HH:mm:ss
|
||||
result = toLocalDateTime(now);
|
||||
break;
|
||||
case "timestamp":
|
||||
result = now.getTime().toString();
|
||||
break;
|
||||
default:
|
||||
result = now.toISOString(); // ISO 8601 format
|
||||
result = toLocalDateTime(now);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5156,7 +5156,8 @@ export class ButtonActionExecutor {
|
|||
const menuName = localStorage.getItem("currentMenuName");
|
||||
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";
|
||||
|
||||
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 includeHeaders = config.excelIncludeHeaders !== false;
|
||||
|
||||
|
|
|
|||
|
|
@ -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")}` };
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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}`;
|
||||
}
|
||||
|
|
@ -91,8 +91,8 @@ function getDefaultPeriod(): { start: string; end: string } {
|
|||
const start = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
const end = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
||||
return {
|
||||
start: start.toISOString().split("T")[0],
|
||||
end: end.toISOString().split("T")[0],
|
||||
start: `${start.getFullYear()}-${String(start.getMonth() + 1).padStart(2, "0")}-${String(start.getDate()).padStart(2, "0")}`,
|
||||
end: `${end.getFullYear()}-${String(end.getMonth() + 1).padStart(2, "0")}-${String(end.getDate()).padStart(2, "0")}`,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue