feat(cafe24): 예약 시각을 한글 형식("2026년 08월 20일" / "오후 07시 30분")으로 표시
datetime-local 을 date+time 으로 나눈 뒤에도 여전히 브라우저가 강제하는 표시
형식("2026. 08. 20.", "오후 07:30")이 남아 있었다. 네이티브 date/time input 은
표시 형식을 CSS 로 바꿀 수 없다(브라우저·로캘가 강제) — 지원되는 방법이 없다.
코드 편집기의 오버레이 방식(.cf24-code-input 투명 textarea 를 .cf24-code-hl 색칠된
글자 위에 겹치는 것)을 그대로 적용했다. 네이티브 date/time input 을 opacity:0 으로
완전히 투명하게 두고, 우리가 형식화한 텍스트(.cf24-dt-display) 바로 위에 포개 놓는다.
클릭·키보드·달력 팝업은 네이티브가 그대로 처리하고(elementFromPoint 로 클릭이
네이티브에 도달하는지 확인함), 화면에 보이는 글자만 JS(paintDate/paintTime)가
input/change 마다 다시 그린다. input.value 자체는 형식과 무관하게 항상
YYYY-MM-DD / HH:MM 이라 제출 시 합치는 로직(store.parse_schedule_at 이 받는 형식)은
전혀 바뀌지 않았다.
정오/자정 12시간제 변환(0시→오전 12시, 12시→오후 12시)을 포함해 처리한다.
폭도 줄여달라는 요청에 맞춰, 실측으로 잘리지 않는 최소값(글자크기 12px·필드폭
117px)을 찾아 적용했다 — 이전 174px 대비 크게 줄었다.
검증(브라우저 실측): 표시 텍스트가 정확한 한글 형식으로 나옴, 정오/자정 처리 정확,
잘림 없음, 클릭이 실제 네이티브 input 에 도달, 제출 시 hidden 필드가
"2026-08-20T19:30" 으로 정확히 조합됨, 가로 스크롤 없음. 백엔드 무변경이라
유닛테스트 61개 그대로 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -96,9 +96,22 @@
|
|||||||
<div class="cf24-schedule-grid">
|
<div class="cf24-schedule-grid">
|
||||||
<label class="cf24-schedule-datetime">
|
<label class="cf24-schedule-datetime">
|
||||||
<span>예약 시각</span>
|
<span>예약 시각</span>
|
||||||
|
{# 네이티브 date/time input 은 브라우저가 표시 형식을 강제한다(CSS로 못 바꿈).
|
||||||
|
그래서 코드 편집기와 같은 방식을 쓴다 — 투명한 네이티브 input 을 우리가
|
||||||
|
그린 형식화된 텍스트 위에 겹친다. 클릭·키보드·달력 팝업은 네이티브 그대로
|
||||||
|
동작하고, 보이는 글자만 "2026년 08월 20일"/"오후 07시 30분" 형식이다.
|
||||||
|
value 는 그대로 YYYY-MM-DD / HH:MM 이라 제출 시 합치는 로직은 그대로다. #}
|
||||||
<span class="cf24-schedule-datetime-row">
|
<span class="cf24-schedule-datetime-row">
|
||||||
<input class="cf24-schedule-input" type="date" id="cf24-schedule-date" required />
|
<span class="cf24-dt-field">
|
||||||
<input class="cf24-schedule-input" type="time" id="cf24-schedule-time" required />
|
<span class="cf24-dt-display" id="cf24-schedule-date-display" aria-hidden="true">연도.월.일</span>
|
||||||
|
<input class="cf24-dt-native" type="date" id="cf24-schedule-date"
|
||||||
|
aria-label="예약 날짜" required />
|
||||||
|
</span>
|
||||||
|
<span class="cf24-dt-field">
|
||||||
|
<span class="cf24-dt-display" id="cf24-schedule-time-display" aria-hidden="true">시:분</span>
|
||||||
|
<input class="cf24-dt-native" type="time" id="cf24-schedule-time"
|
||||||
|
aria-label="예약 시간" required />
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{% extends "erp_base.html" %}
|
{% extends "erp_base.html" %}
|
||||||
|
|
||||||
{% block head_extra %}
|
{% block head_extra %}
|
||||||
<link rel="stylesheet" href="/static/cafe24.css?v=20260814u" />
|
<link rel="stylesheet" href="/static/cafe24.css?v=20260814v" />
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
@@ -234,6 +234,44 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 예약 시각 — 네이티브 date/time input 은 투명하게 두고(opacity:0), 보이는
|
||||||
|
// 글자는 우리가 원하는 형식으로 직접 그린다("2026년 08월 20일" / "오후 07시 30분").
|
||||||
|
// input 의 value 자체는 그대로 YYYY-MM-DD / HH:MM 이라 제출 로직은 안 바뀐다.
|
||||||
|
(function setupScheduleDateTime() {
|
||||||
|
var dateEl = document.getElementById("cf24-schedule-date");
|
||||||
|
var timeEl = document.getElementById("cf24-schedule-time");
|
||||||
|
var dateOut = document.getElementById("cf24-schedule-date-display");
|
||||||
|
var timeOut = document.getElementById("cf24-schedule-time-display");
|
||||||
|
if (!dateEl || !timeEl) return;
|
||||||
|
|
||||||
|
function pad2(n) { return (n < 10 ? "0" : "") + n; }
|
||||||
|
|
||||||
|
function paintDate() {
|
||||||
|
var v = dateEl.value; // "YYYY-MM-DD"
|
||||||
|
if (!v) { dateOut.textContent = "연도.월.일"; return; }
|
||||||
|
var p = v.split("-");
|
||||||
|
dateOut.textContent = p[0] + "년 " + p[1] + "월 " + p[2] + "일";
|
||||||
|
}
|
||||||
|
|
||||||
|
function paintTime() {
|
||||||
|
var v = timeEl.value; // "HH:MM" (24시간제 고정 — 로캘과 무관)
|
||||||
|
if (!v) { timeOut.textContent = "시:분"; return; }
|
||||||
|
var parts = v.split(":");
|
||||||
|
var h = parseInt(parts[0], 10);
|
||||||
|
var period = h < 12 ? "오전" : "오후";
|
||||||
|
var h12 = h % 12;
|
||||||
|
if (h12 === 0) h12 = 12;
|
||||||
|
timeOut.textContent = period + " " + pad2(h12) + "시 " + parts[1] + "분";
|
||||||
|
}
|
||||||
|
|
||||||
|
dateEl.addEventListener("input", paintDate);
|
||||||
|
dateEl.addEventListener("change", paintDate);
|
||||||
|
timeEl.addEventListener("input", paintTime);
|
||||||
|
timeEl.addEventListener("change", paintTime);
|
||||||
|
paintDate();
|
||||||
|
paintTime();
|
||||||
|
})();
|
||||||
|
|
||||||
// 예약 폼 — 편집기 내용을 hidden 에 복사해 함께 보낸다(폼이 서로 형제라서).
|
// 예약 폼 — 편집기 내용을 hidden 에 복사해 함께 보낸다(폼이 서로 형제라서).
|
||||||
// 날짜·시간도 두 개의 별도 input 값을 합쳐 서버가 기대하는 형식으로 만든다.
|
// 날짜·시간도 두 개의 별도 input 값을 합쳐 서버가 기대하는 형식으로 만든다.
|
||||||
var schedForm = pane.querySelector("#cf24-schedule-form");
|
var schedForm = pane.querySelector("#cf24-schedule-form");
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{% extends "erp_base.html" %}
|
{% extends "erp_base.html" %}
|
||||||
|
|
||||||
{% block head_extra %}
|
{% block head_extra %}
|
||||||
<link rel="stylesheet" href="/static/cafe24.css?v=20260814u" />
|
<link rel="stylesheet" href="/static/cafe24.css?v=20260814v" />
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{% extends "erp_base.html" %}
|
{% extends "erp_base.html" %}
|
||||||
|
|
||||||
{% block head_extra %}
|
{% block head_extra %}
|
||||||
<link rel="stylesheet" href="/static/cafe24.css?v=20260814u" />
|
<link rel="stylesheet" href="/static/cafe24.css?v=20260814v" />
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|||||||
+45
-4
@@ -622,20 +622,61 @@
|
|||||||
|
|
||||||
/* 날짜/시간을 별도 input 으로 나눈다 — datetime-local 은 한국어 로캘에서
|
/* 날짜/시간을 별도 input 으로 나눈다 — datetime-local 은 한국어 로캘에서
|
||||||
"2026. 08. 14. 오후 07:00" 형태로 길어져 auto-fit minmax(160px, 1fr) 칸에
|
"2026. 08. 14. 오후 07:00" 형태로 길어져 auto-fit minmax(160px, 1fr) 칸에
|
||||||
잘렸다(실측). date/time 은 각각 폭이 고정이라 잘릴 일이 없다. */
|
잘렸다(실측). 지금은 표시 텍스트를 우리가 직접 그리므로(아래 .cf24-dt-*) 폭이
|
||||||
|
예측 가능해 예전만큼 넓게 잡을 필요가 없다. */
|
||||||
.cf24-schedule-datetime {
|
.cf24-schedule-datetime {
|
||||||
grid-column: span 2;
|
grid-column: span 2;
|
||||||
min-width: 220px;
|
max-width: 240px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cf24-schedule-datetime-row {
|
.cf24-schedule-datetime-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--sp-8, 8px);
|
gap: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cf24-schedule-datetime-row .cf24-schedule-input {
|
/* ── 날짜/시간 표시 형식을 직접 그리는 오버레이 ──
|
||||||
|
네이티브 date/time input 은 표시 형식을 CSS 로 바꿀 수 없다(브라우저·로캘가
|
||||||
|
강제). 코드 편집기(.cf24-code-input 위 .cf24-code-hl)와 같은 원리로, 투명한
|
||||||
|
네이티브 input 을 우리가 그린 텍스트 위에 완전히 겹쳐 클릭·키보드·달력 팝업은
|
||||||
|
네이티브 그대로 쓰고 보이는 글자만 형식화한다. */
|
||||||
|
.cf24-dt-field {
|
||||||
|
position: relative;
|
||||||
flex: 1 1 0;
|
flex: 1 1 0;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cf24-dt-display {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0 6px;
|
||||||
|
border: 1px solid var(--color-subtle-ash, #e5e5e5);
|
||||||
|
border-radius: var(--r-lg, 10px);
|
||||||
|
background: var(--color-canvas-white, #fff);
|
||||||
|
color: var(--color-rich-black, #0a0a0a);
|
||||||
|
/* 12px 는 실측으로 고른 값 — "2026년 08월 20일" 이 117px 칸에 잘리지 않는 최소
|
||||||
|
폭·글자크기 조합이다(13px 는 4px 부족해 잘렸다). */
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: none; /* 클릭은 위에 겹친 네이티브 input 이 받는다 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.cf24-dt-native {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
opacity: 0; /* 완전히 투명 — 아래 .cf24-dt-display 가 그대로 비쳐 보인다 */
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cf24-schedule-input {
|
.cf24-schedule-input {
|
||||||
|
|||||||
+13
-4
@@ -176,10 +176,19 @@ app/modules/cafe24/ ← 상품관리 모듈
|
|||||||
JS 가 hidden 에 복사해 함께 보낸다.
|
JS 가 hidden 에 복사해 함께 보낸다.
|
||||||
- **입력은 날짜/시간 별도 input** 이다(`type=date` + `type=time`). `datetime-local`
|
- **입력은 날짜/시간 별도 input** 이다(`type=date` + `type=time`). `datetime-local`
|
||||||
단일 입력은 한국어 로캘에서 표시 폭이 브라우저마다 달라 잘리는 사고가 있었다(실제
|
단일 입력은 한국어 로캘에서 표시 폭이 브라우저마다 달라 잘리는 사고가 있었다(실제
|
||||||
발생 — "2026. 08. 14. 오후 07:00" 형태). 두 input 은 각각 폭이 고정이라 잘릴 일이
|
발생 — "2026. 08. 14. 오후 07:00" 형태).
|
||||||
없다. 제출 직전 JS 가 두 값을 `"YYYY-MM-DD" + "T" + "HH:MM"` 로 합쳐 hidden
|
- **보이는 글자는 우리가 직접 그린다** — 네이티브 date/time input 은 표시 형식을
|
||||||
`scheduled_at` 에 넣는다 — 서버(`store.parse_schedule_at`)는 예전과 같은 형식을
|
CSS 로 바꿀 수 없어서(브라우저·로캘가 강제), 코드 편집기(`.cf24-code-input` 위
|
||||||
받으므로 백엔드는 그대로다.
|
`.cf24-code-hl`)와 같은 원리로 투명한(`opacity:0`) 네이티브 input 을 형식화한
|
||||||
|
텍스트(`.cf24-dt-display`, `"2026년 08월 20일"` / `"오후 07시 30분"`) 위에 완전히
|
||||||
|
겹친다. 클릭·키보드·달력 팝업은 네이티브 그대로 동작하고(`elementFromPoint` 로
|
||||||
|
클릭이 네이티브에 꽂히는지 확인함), 보이는 글자만 형식화된다. `input`/`change`
|
||||||
|
이벤트마다 JS(`paintDate`/`paintTime`)가 다시 그린다.
|
||||||
|
글자크기 12px·필드폭 117px 는 "2026년 08월 20일" 이 잘리지 않는 실측 최소값이다
|
||||||
|
(13px 는 4px 부족해 잘렸다).
|
||||||
|
- 제출 직전 JS 가 두 input 의 `value`(형식과 무관하게 항상 `YYYY-MM-DD` / `HH:MM`)를
|
||||||
|
`"YYYY-MM-DD" + "T" + "HH:MM"` 로 합쳐 hidden `scheduled_at` 에 넣는다 —
|
||||||
|
서버(`store.parse_schedule_at`)는 예전과 같은 형식을 받으므로 백엔드는 그대로다.
|
||||||
- **시각은 KST 로 해석**한다. 과거 시각은 거부하되
|
- **시각은 KST 로 해석**한다. 과거 시각은 거부하되
|
||||||
폼을 채우는 동안 시간이 흐른 경우를 위해 1분 여유를 둔다.
|
폼을 채우는 동안 시간이 흐른 경우를 위해 1분 여유를 둔다.
|
||||||
- **실행은 `app/modules/cafe24/worker.py`** — compose 서비스 `dbx-cafe24-worker` 가
|
- **실행은 `app/modules/cafe24/worker.py`** — compose 서비스 `dbx-cafe24-worker` 가
|
||||||
|
|||||||
Reference in New Issue
Block a user