jskim-node #420
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
|
||||||
|
|
@ -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)");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
|
|
@ -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>>({});
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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":
|
||||||
|
|
|
||||||
|
|
@ -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":
|
||||||
|
|
|
||||||
|
|
@ -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 || "사용자"; // 사용자명이 없으면 기본값
|
||||||
|
|
|
||||||
|
|
@ -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":
|
||||||
|
|
|
||||||
|
|
@ -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: "엑셀다운",
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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" });
|
||||||
|
|
|
||||||
|
|
@ -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 "";
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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부터 시작하는 순번
|
||||||
|
|
|
||||||
|
|
@ -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부터 시작하는 순번
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
// 인터랙티브 모드에서 폼 데이터에도 설정
|
// 인터랙티브 모드에서 폼 데이터에도 설정
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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] = "";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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")}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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")}`;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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":
|
||||||
|
|
|
||||||
|
|
@ -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")) {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 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")}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue