Compare commits
10 Commits
b9ae7d707d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 7d06f51740 | |||
| cb74569c97 | |||
| 11cd74cac3 | |||
| f5ca452a85 | |||
| 60f82f1224 | |||
| a91425bd2d | |||
| f6c7af7cbc | |||
| f1f5c760c2 | |||
| 2ca1f4ff54 | |||
| 89bd555dc1 |
@@ -5,21 +5,43 @@
|
||||
See: .planning/PROJECT.md (updated 2026-01-16)
|
||||
|
||||
**Core value:** Email format identical to legacy — office staff workflow depends on the exact HTML table structure.
|
||||
**Current focus:** Post-v1.0 bug fixes (manual testing discoveries)
|
||||
**Current focus:** Post-v1.0 polish — UI modernization, email format corrections, developer tooling
|
||||
|
||||
## Current Position
|
||||
|
||||
Phase: 10 of 10 (Post-Release Fixes)
|
||||
Plan: 1 of 1 complete
|
||||
Status: Phase complete ✓
|
||||
Last activity: 2026-02-07 — Fixed form submit 404, handler timing, CPT meta box
|
||||
Phase: 10 of 10 (all roadmap phases complete)
|
||||
Status: Milestone v1.0 COMPLETE + post-milestone enhancements
|
||||
Last activity: 2026-02-13 — Test email admin page, comprehensive form fill button
|
||||
|
||||
Progress: ██████████ 100% (11/11 plans)
|
||||
|
||||
## Post-Milestone Work (after v1.0 completion)
|
||||
|
||||
Work completed outside the GSD roadmap since milestone completion:
|
||||
|
||||
### UI/UX Modernization (feature/ui-ux-modernization branch, merged)
|
||||
- Modernized wizard UX with smart rows, steppers, transitions, and edit links
|
||||
- Redesigned Step 8 (additional work) with flat list layout matching room steps
|
||||
- Added 3 switchable color palettes with Slate Blue & Amber as default
|
||||
- Polished form UX and step 9 summary translations
|
||||
- Fixed step navigation back-click bug, added shortcode lang attribute
|
||||
|
||||
### Email Generator Rewrite (`60f82f1`)
|
||||
- Rewrote "Weitere Arbeiten" section as single 3-column table matching legacy format
|
||||
- Moved grand totals into last room's table instead of standalone section
|
||||
- Added Packarbeiten/Anfahrt sub-headers matching legacy HTML structure
|
||||
- Added get_field_key/get_field_value/get_field_anzahl helper methods
|
||||
|
||||
### Developer Tooling (`f5ca452`)
|
||||
- New `Umzugsliste_Test_Email` class with comprehensive test data generator
|
||||
- Admin page (Moving List > Test Email) with inline email preview iframe + send button
|
||||
- Upgraded dev "Fill" button to "Fill All" — populates every field across all 9 form steps
|
||||
- Fixed getFieldVal crash when select has no selection (defensive coding)
|
||||
|
||||
## Performance Metrics
|
||||
|
||||
**Velocity:**
|
||||
- Total plans completed: 11
|
||||
- Total roadmap plans completed: 11
|
||||
- Average duration: ~24 min per plan
|
||||
- Total execution time: ~4.5 hours
|
||||
|
||||
@@ -35,13 +57,14 @@ Progress: ██████████ 100% (11/11 plans)
|
||||
| 6 | 1 | Form handler, email generator, wp_mail() integration |
|
||||
| 7 | 1 | Captcha verification and inline validation |
|
||||
| 8 | 2/2 | Bug fixes & legacy parity (gap closure) |
|
||||
| 9 | 2/2 | Internationalization (gap closure) ✓ |
|
||||
| 10 | 1/1 | Post-release fixes (manual testing bugs) ✓ |
|
||||
| 9 | 2/2 | Internationalization (gap closure) |
|
||||
| 10 | 1/1 | Post-release fixes (manual testing bugs) |
|
||||
|
||||
**Overall Trend:**
|
||||
- Phases 1-7 completed successfully
|
||||
- Milestone audit found 4 gaps requiring phases 8-9
|
||||
- Manual testing found 3 runtime bugs requiring phase 10
|
||||
- Post-milestone: UI modernization, email format correction, dev tooling
|
||||
- No blockers encountered
|
||||
|
||||
## Accumulated Context
|
||||
@@ -72,6 +95,10 @@ Recent decisions affecting current work:
|
||||
| 10 | Rename day/month/year fields to umzug_day/umzug_month/umzug_year | WordPress reserved query vars caused 404 on form POST |
|
||||
| 10 | Move handler instantiation to init_hooks() | init callback too late for handler's own init hook |
|
||||
| 10 | Add CPT submission details meta box | No way to view stored submission data in admin |
|
||||
| Post | Weitere Arbeiten as single 3-column table | Matches legacy format exactly — one table with sub-section headers |
|
||||
| Post | Grand totals in last room's table | Legacy places grand totals as final rows of last room table |
|
||||
| Post | Test email admin page with iframe preview | Instant verification of email output without filling the full form |
|
||||
| Post | Fill All dev button (WP_DEBUG only) | Populates every form field for rapid end-to-end testing |
|
||||
|
||||
### Deferred Issues
|
||||
|
||||
@@ -85,7 +112,7 @@ None.
|
||||
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-02-07
|
||||
Stopped at: Documented phase 10 post-release fixes - PHASE 10 COMPLETE ✓
|
||||
Last session: 2026-02-13
|
||||
Stopped at: Test email admin page + Fill All button complete and committed
|
||||
Resume file: None
|
||||
Next up: All phases complete! Plugin shipped and working end-to-end.
|
||||
Next up: All roadmap phases complete. Plugin is functional and polished. Available for new milestone or ad-hoc work.
|
||||
|
||||
@@ -27,6 +27,123 @@
|
||||
--umzug-transition: 0.2s ease;
|
||||
}
|
||||
|
||||
/* ===== Color Palettes ===== */
|
||||
|
||||
/* Palette A: Deep Teal — Grounded Confidence */
|
||||
.palette-a.umzugsliste-wizard {
|
||||
--umzug-primary: #1F7A7A;
|
||||
--umzug-primary-light: rgba(31, 122, 122, 0.2);
|
||||
--umzug-primary-dark: #155858;
|
||||
--umzug-success: #4A7C59;
|
||||
--umzug-success-light: rgba(74, 124, 89, 0.1);
|
||||
--umzug-error: #C14E3A;
|
||||
--umzug-error-light: #FCEAE6;
|
||||
--umzug-bg: #FAF8F5;
|
||||
--umzug-surface: #FFFFFF;
|
||||
--umzug-border: #D4CEC4;
|
||||
--umzug-text: #3E2E28;
|
||||
--umzug-text-secondary: #6B5D56;
|
||||
}
|
||||
|
||||
.palette-a .room-totals {
|
||||
background: #F5F2ED;
|
||||
}
|
||||
|
||||
.palette-a .wizard-btn-back:hover {
|
||||
background: #F2EFEA;
|
||||
color: var(--umzug-text);
|
||||
}
|
||||
|
||||
.palette-a .wizard-btn-submit:hover {
|
||||
background: #3D6A4A;
|
||||
}
|
||||
|
||||
body.umzugsliste-standalone:has(.palette-a) {
|
||||
background: #FAF8F5;
|
||||
}
|
||||
|
||||
/* Palette B: Slate Blue & Amber — Modern Logistics */
|
||||
.palette-b.umzugsliste-wizard {
|
||||
--umzug-primary: #4A6FA5;
|
||||
--umzug-primary-light: rgba(74, 111, 165, 0.2);
|
||||
--umzug-primary-dark: #2F4668;
|
||||
--umzug-success: #52A07B;
|
||||
--umzug-success-light: rgba(82, 160, 123, 0.1);
|
||||
--umzug-error: #D15847;
|
||||
--umzug-error-light: #FDEAE8;
|
||||
--umzug-bg: #F5F7FA;
|
||||
--umzug-surface: #FFFFFF;
|
||||
--umzug-border: #D8DFE8;
|
||||
--umzug-text: #1A2332;
|
||||
--umzug-text-secondary: #5F6B7A;
|
||||
}
|
||||
|
||||
.palette-b .room-totals {
|
||||
background: #EDF0F5;
|
||||
}
|
||||
|
||||
.palette-b .wizard-btn-back:hover {
|
||||
background: #ECEFF4;
|
||||
color: var(--umzug-text);
|
||||
}
|
||||
|
||||
.palette-b .wizard-btn-submit:hover {
|
||||
background: #448A69;
|
||||
}
|
||||
|
||||
.palette-b .wizard-btn-next {
|
||||
background: #E8A63C;
|
||||
}
|
||||
|
||||
.palette-b .wizard-btn-next:hover {
|
||||
background: #D69529;
|
||||
}
|
||||
|
||||
body.umzugsliste-standalone:has(.palette-b) {
|
||||
background: #F5F7FA;
|
||||
}
|
||||
|
||||
/* Palette C: Rich Olive & Copper — Heritage & Motion */
|
||||
.palette-c.umzugsliste-wizard {
|
||||
--umzug-primary: #5C6E54;
|
||||
--umzug-primary-light: rgba(92, 110, 84, 0.2);
|
||||
--umzug-primary-dark: #3D4738;
|
||||
--umzug-success: #6B9F6F;
|
||||
--umzug-success-light: rgba(107, 159, 111, 0.1);
|
||||
--umzug-error: #B85A3D;
|
||||
--umzug-error-light: #F8EDE8;
|
||||
--umzug-bg: #F8F6F1;
|
||||
--umzug-surface: #FEFDFB;
|
||||
--umzug-border: #D9D3C8;
|
||||
--umzug-text: #2F3126;
|
||||
--umzug-text-secondary: #6B6E64;
|
||||
}
|
||||
|
||||
.palette-c .room-totals {
|
||||
background: #F0EDE6;
|
||||
}
|
||||
|
||||
.palette-c .wizard-btn-back:hover {
|
||||
background: #F0EDE6;
|
||||
color: var(--umzug-text);
|
||||
}
|
||||
|
||||
.palette-c .wizard-btn-submit:hover {
|
||||
background: #5A8A5E;
|
||||
}
|
||||
|
||||
.palette-c .wizard-btn-next {
|
||||
background: #C17E5D;
|
||||
}
|
||||
|
||||
.palette-c .wizard-btn-next:hover {
|
||||
background: #A96A4B;
|
||||
}
|
||||
|
||||
body.umzugsliste-standalone:has(.palette-c) {
|
||||
background: #F8F6F1;
|
||||
}
|
||||
|
||||
/* ===== Reset & Base ===== */
|
||||
.umzugsliste-standalone {
|
||||
margin: 0;
|
||||
@@ -132,7 +249,7 @@
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
color: var(--umzug-text-secondary);
|
||||
transition: all var(--umzug-transition);
|
||||
transition: background var(--umzug-transition), border-color var(--umzug-transition), color var(--umzug-transition);
|
||||
}
|
||||
|
||||
.dot-label {
|
||||
@@ -167,6 +284,14 @@
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
/* ===== Step Counter ===== */
|
||||
.progress-counter {
|
||||
text-align: center;
|
||||
font-size: 0.8rem;
|
||||
color: var(--umzug-text-secondary);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* ===== Running Totals Bar ===== */
|
||||
.running-totals {
|
||||
position: sticky;
|
||||
@@ -175,17 +300,24 @@
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
background: var(--umzug-surface);
|
||||
border-top: 3px solid var(--umzug-primary);
|
||||
border: 1px solid var(--umzug-border);
|
||||
border-radius: var(--umzug-radius);
|
||||
padding: 12px 20px;
|
||||
margin-bottom: 12px;
|
||||
text-align: center;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
|
||||
display: none;
|
||||
box-shadow: var(--umzug-shadow-lg);
|
||||
transform: translateY(100%);
|
||||
opacity: 0;
|
||||
transition: transform 0.3s, opacity 0.3s;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.running-totals.visible {
|
||||
display: block;
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.running-totals-label {
|
||||
@@ -219,10 +351,30 @@
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes slideInForward {
|
||||
from { opacity: 0; transform: translateX(30px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
|
||||
@keyframes slideInBackward {
|
||||
from { opacity: 0; transform: translateX(-30px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
|
||||
.wizard-step.active.forward {
|
||||
animation: slideInForward 0.3s ease;
|
||||
}
|
||||
|
||||
.wizard-step.active.backward {
|
||||
animation: slideInBackward 0.3s ease;
|
||||
}
|
||||
|
||||
.step-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 20px;
|
||||
margin: 0 0 16px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid var(--umzug-border);
|
||||
color: var(--umzug-text);
|
||||
}
|
||||
|
||||
@@ -245,6 +397,32 @@
|
||||
border-bottom: 1px solid var(--umzug-border);
|
||||
}
|
||||
|
||||
/* ===== Step Sections (inner dividers within a step-card) ===== */
|
||||
.step-section {
|
||||
padding-top: 16px;
|
||||
margin-top: 16px;
|
||||
border-top: 1px solid var(--umzug-border);
|
||||
}
|
||||
|
||||
.step-section:first-of-type {
|
||||
border-top: none;
|
||||
padding-top: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* ===== Address Sections (grid items within step-card) ===== */
|
||||
.address-section {
|
||||
background: var(--umzug-surface);
|
||||
border: 1px solid var(--umzug-border);
|
||||
border-radius: var(--umzug-radius);
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
/* Summary sections inside step-card: lighter treatment */
|
||||
.step-card #wizard-summary .summary-section {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* ===== Date Selector ===== */
|
||||
.date-selector {
|
||||
display: flex;
|
||||
@@ -287,7 +465,7 @@
|
||||
font-size: 0.8rem;
|
||||
color: var(--umzug-text-secondary);
|
||||
margin-top: 12px;
|
||||
margin-bottom: 0;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.privacy-note a {
|
||||
@@ -394,27 +572,78 @@
|
||||
gap: 12px;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
opacity: 0.55;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.furniture-item.has-quantity,
|
||||
.furniture-item:hover,
|
||||
.furniture-item:focus-within {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.furniture-item .montage-toggle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.furniture-item.has-quantity .montage-toggle {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.furniture-item:last-of-type {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.quantity-input {
|
||||
width: 56px;
|
||||
height: 36px;
|
||||
padding: 0 8px;
|
||||
.quantity-stepper {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border: 1px solid var(--umzug-border);
|
||||
border-radius: var(--umzug-radius-sm);
|
||||
border-radius: var(--umzug-radius);
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.qty-btn {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border: none;
|
||||
background: var(--umzug-bg);
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background var(--umzug-transition);
|
||||
color: var(--umzug-text);
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.qty-btn:hover {
|
||||
background: var(--umzug-border);
|
||||
}
|
||||
|
||||
.qty-btn:active {
|
||||
background: var(--umzug-primary-light);
|
||||
}
|
||||
|
||||
.quantity-stepper .quantity-input {
|
||||
border: none;
|
||||
border-left: 1px solid var(--umzug-border);
|
||||
border-right: 1px solid var(--umzug-border);
|
||||
border-radius: 0;
|
||||
width: 48px;
|
||||
height: 36px;
|
||||
padding: 0 4px;
|
||||
font-size: 0.95rem;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
color: var(--umzug-text);
|
||||
background: var(--umzug-surface);
|
||||
transition: border-color var(--umzug-transition), box-shadow var(--umzug-transition);
|
||||
}
|
||||
|
||||
.quantity-input:focus {
|
||||
.quantity-stepper .quantity-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--umzug-primary);
|
||||
box-shadow: 0 0 0 3px var(--umzug-primary-light);
|
||||
@@ -432,9 +661,9 @@
|
||||
}
|
||||
|
||||
.item-cbm {
|
||||
width: 52px;
|
||||
width: 75px;
|
||||
text-align: right;
|
||||
font-size: 0.85rem;
|
||||
font-size: 13px;
|
||||
color: var(--umzug-text-secondary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@@ -446,7 +675,14 @@
|
||||
}
|
||||
|
||||
.montage-toggle .radio-label {
|
||||
font-size: 0.8rem;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.montage-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--umzug-text-secondary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ===== Room Totals ===== */
|
||||
@@ -547,6 +783,98 @@
|
||||
box-shadow: 0 0 0 3px var(--umzug-primary-light);
|
||||
}
|
||||
|
||||
/* ===== Step 8 Design ===== */
|
||||
.additional-field input[type="checkbox"] {
|
||||
accent-color: var(--umzug-primary);
|
||||
}
|
||||
|
||||
.wizard-step[data-step="8"] .additional-field {
|
||||
padding: 8px 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
border-radius: 0;
|
||||
opacity: 0.55;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.wizard-step[data-step="8"] .additional-field:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.wizard-step[data-step="8"] .additional-field:hover,
|
||||
.wizard-step[data-step="8"] .additional-field:focus-within,
|
||||
.wizard-step[data-step="8"] .additional-field:has(input:checked),
|
||||
.wizard-step[data-step="8"] .additional-field:has(.qty-small:not(:placeholder-shown)) {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.wizard-step[data-step="8"] .additional-field-abbau .radio-group {
|
||||
display: inline-flex;
|
||||
gap: 16px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.wizard-step[data-step="8"] .additional-field-abbau .radio-label {
|
||||
padding: 0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.wizard-step[data-step="8"] .additional-field-abbau .radio-label input[type="radio"] {
|
||||
position: static;
|
||||
opacity: 1;
|
||||
width: auto;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ===== Dev Auto-Fill ===== */
|
||||
.dev-autofill-btn {
|
||||
position: fixed;
|
||||
bottom: 60px;
|
||||
right: 16px;
|
||||
z-index: 300;
|
||||
background: #ff6b35;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
padding: 6px 14px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
opacity: 0.6;
|
||||
transition: opacity var(--umzug-transition);
|
||||
}
|
||||
|
||||
.dev-autofill-btn:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* ===== Palette Switcher (WP_DEBUG) ===== */
|
||||
.dev-palette-btn {
|
||||
position: fixed;
|
||||
bottom: 90px;
|
||||
right: 16px;
|
||||
z-index: 300;
|
||||
background: #7c3aed;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
padding: 6px 14px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
opacity: 0.6;
|
||||
transition: opacity var(--umzug-transition);
|
||||
}
|
||||
|
||||
.dev-palette-btn:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* ===== Sonstiges ===== */
|
||||
.sonstiges-textarea {
|
||||
width: 100%;
|
||||
@@ -619,7 +947,7 @@
|
||||
}
|
||||
|
||||
#wizard-summary .summary-item-cbm {
|
||||
width: 60px;
|
||||
width: 80px;
|
||||
text-align: right;
|
||||
color: var(--umzug-text-secondary);
|
||||
}
|
||||
@@ -630,6 +958,19 @@
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.summary-edit {
|
||||
float: right;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
color: var(--umzug-primary);
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.summary-edit:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#wizard-summary .summary-grand-total {
|
||||
background: var(--umzug-primary);
|
||||
color: #fff;
|
||||
@@ -730,7 +1071,8 @@
|
||||
flex-basis: calc(100% - 80px);
|
||||
}
|
||||
|
||||
.quantity-input {
|
||||
.quantity-input,
|
||||
.quantity-stepper {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
@@ -745,6 +1087,15 @@
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.montage-label {
|
||||
flex-basis: 100%;
|
||||
margin-bottom: -4px;
|
||||
}
|
||||
|
||||
.wizard-step[data-step="8"] .additional-field {
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
.additional-field-abbau {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
@@ -769,18 +1120,30 @@
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.progress-dot {
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.dot-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.progress-dot.active .dot-label {
|
||||
display: block;
|
||||
font-size: 0.6rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.dot-number {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.progress-track {
|
||||
top: 14px;
|
||||
top: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -789,3 +1152,19 @@
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.progress-bar {
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.dot-number {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
font-size: 0.65rem;
|
||||
}
|
||||
|
||||
.progress-track {
|
||||
top: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,14 +48,20 @@
|
||||
function showStep(n) {
|
||||
if (n < 1 || n > TOTAL_STEPS) return;
|
||||
|
||||
// Hide all steps
|
||||
// Determine direction
|
||||
var direction = n > currentStep ? 'forward' : 'backward';
|
||||
|
||||
// Hide all steps and remove direction classes
|
||||
qsa('.wizard-step').forEach(function(el) {
|
||||
el.classList.remove('active');
|
||||
el.classList.remove('active', 'forward', 'backward');
|
||||
});
|
||||
|
||||
// Show target step
|
||||
// Show target step with direction
|
||||
var target = qs('.wizard-step[data-step="' + n + '"]');
|
||||
if (target) target.classList.add('active');
|
||||
if (target) {
|
||||
target.classList.add(direction);
|
||||
target.classList.add('active');
|
||||
}
|
||||
|
||||
currentStep = n;
|
||||
if (n > highestStep) highestStep = n;
|
||||
@@ -103,6 +109,12 @@
|
||||
var pct = ((highestStep - 1) / (TOTAL_STEPS - 1)) * 100;
|
||||
fill.style.width = pct + '%';
|
||||
}
|
||||
|
||||
// Update step counter
|
||||
var counter = qs('#progress-counter');
|
||||
if (counter) {
|
||||
counter.textContent = (l10n.stepLabel || 'Step') + ' ' + currentStep + ' ' + (l10n.stepOf || 'of') + ' ' + TOTAL_STEPS;
|
||||
}
|
||||
}
|
||||
|
||||
function updateNavButtons() {
|
||||
@@ -271,6 +283,11 @@
|
||||
|
||||
// ===== Summary Generation =====
|
||||
|
||||
function summaryHeading(text, gotoStep) {
|
||||
var editLabel = escHtml(l10n.summaryEdit || 'Edit');
|
||||
return '<h3>' + escHtml(text) + ' <a class="summary-edit" data-goto="' + gotoStep + '" role="button">' + editLabel + '</a></h3>';
|
||||
}
|
||||
|
||||
function generateSummary() {
|
||||
var container = qs('#wizard-summary');
|
||||
if (!container) return;
|
||||
@@ -279,56 +296,56 @@
|
||||
|
||||
// Customer info
|
||||
html += '<div class="summary-section">';
|
||||
html += '<h3>' + escHtml(l10n.summaryMovingDate || 'Moving Date') + '</h3>';
|
||||
var day = getFieldVal('day');
|
||||
var month = getFieldVal('month');
|
||||
var year = getFieldVal('year');
|
||||
html += summaryHeading(l10n.summaryMovingDate || 'Moving Date', 1);
|
||||
var day = getFieldVal('umzug_day');
|
||||
var month = getFieldVal('umzug_month');
|
||||
var year = getFieldVal('umzug_year');
|
||||
html += summaryRow(l10n.summaryMovingDate || 'Moving Date', day + '.' + month + '.' + year);
|
||||
html += '</div>';
|
||||
|
||||
// Loading address
|
||||
html += '<div class="summary-section">';
|
||||
html += '<h3>' + escHtml(l10n.summaryLoading || 'Loading Address') + '</h3>';
|
||||
html += summaryRow('Name', getFieldVal('bName'));
|
||||
html += summaryRow('Street', getFieldVal('bStrasse'));
|
||||
html += summaryRow('ZIP/City', getFieldVal('bort'));
|
||||
html += summaryHeading(l10n.summaryLoading || 'Loading Address', 1);
|
||||
html += summaryRow(l10n.summaryName || 'Name', getFieldVal('bName'));
|
||||
html += summaryRow(l10n.summaryStreet || 'Street', getFieldVal('bStrasse'));
|
||||
html += summaryRow(l10n.summaryZipCity || 'ZIP/City', getFieldVal('bort'));
|
||||
var bGeschoss = getFieldVal('info[bGeschoss]');
|
||||
if (bGeschoss) html += summaryRow('Floor', bGeschoss);
|
||||
html += summaryRow('Elevator', getRadioVal('info[bLift]'));
|
||||
html += summaryRow('Phone', getFieldVal('bTelefon'));
|
||||
if (bGeschoss) html += summaryRow(l10n.summaryFloor || 'Floor', bGeschoss);
|
||||
html += summaryRow(l10n.summaryElevator || 'Elevator', getRadioVal('info[bLift]'));
|
||||
html += summaryRow(l10n.summaryPhone || 'Phone', getFieldVal('bTelefon'));
|
||||
var bFax = getFieldVal('info[bTelefax]');
|
||||
if (bFax) html += summaryRow('Fax', bFax);
|
||||
if (bFax) html += summaryRow(l10n.summaryFax || 'Fax', bFax);
|
||||
var bMobil = getFieldVal('info[bMobil]');
|
||||
if (bMobil) html += summaryRow('Mobile', bMobil);
|
||||
html += summaryRow('Email', getFieldVal('info[eE-Mail]'));
|
||||
if (bMobil) html += summaryRow(l10n.summaryMobile || 'Mobile', bMobil);
|
||||
html += summaryRow(l10n.summaryEmail || 'Email', getFieldVal('info[eE-Mail]'));
|
||||
html += '</div>';
|
||||
|
||||
// Unloading address
|
||||
html += '<div class="summary-section">';
|
||||
html += '<h3>' + escHtml(l10n.summaryUnloading || 'Unloading Address') + '</h3>';
|
||||
html += summaryRow('Name', getFieldVal('eName'));
|
||||
html += summaryRow('Street', getFieldVal('eStrasse'));
|
||||
html += summaryRow('ZIP/City', getFieldVal('eort'));
|
||||
html += summaryHeading(l10n.summaryUnloading || 'Unloading Address', 1);
|
||||
html += summaryRow(l10n.summaryName || 'Name', getFieldVal('eName'));
|
||||
html += summaryRow(l10n.summaryStreet || 'Street', getFieldVal('eStrasse'));
|
||||
html += summaryRow(l10n.summaryZipCity || 'ZIP/City', getFieldVal('eort'));
|
||||
var eGeschoss = getFieldVal('info[eGeschoss]');
|
||||
if (eGeschoss) html += summaryRow('Floor', eGeschoss);
|
||||
html += summaryRow('Elevator', getRadioVal('info[eLift]'));
|
||||
if (eGeschoss) html += summaryRow(l10n.summaryFloor || 'Floor', eGeschoss);
|
||||
html += summaryRow(l10n.summaryElevator || 'Elevator', getRadioVal('info[eLift]'));
|
||||
var eTel = getFieldVal('eTelefon');
|
||||
if (eTel) html += summaryRow('Phone', eTel);
|
||||
if (eTel) html += summaryRow(l10n.summaryPhone || 'Phone', eTel);
|
||||
var eFax = getFieldVal('info[eTelefax]');
|
||||
if (eFax) html += summaryRow('Fax', eFax);
|
||||
if (eFax) html += summaryRow(l10n.summaryFax || 'Fax', eFax);
|
||||
var eMobil = getFieldVal('info[eMobil]');
|
||||
if (eMobil) html += summaryRow('Mobile', eMobil);
|
||||
if (eMobil) html += summaryRow(l10n.summaryMobile || 'Mobile', eMobil);
|
||||
html += '</div>';
|
||||
|
||||
// Room summaries
|
||||
var roomMap = [
|
||||
{ key: 'wohnzimmer', name: 'Wohnzimmer' },
|
||||
{ key: 'schlafzimmer', name: 'Schlafzimmer' },
|
||||
{ key: 'arbeitszimmer', name: 'Arbeitszimmer' },
|
||||
{ key: 'bad', name: 'Bad' },
|
||||
{ key: 'kueche_esszimmer', name: 'Kueche_Esszimmer' },
|
||||
{ key: 'kinderzimmer', name: 'Kinderzimmer' },
|
||||
{ key: 'keller', name: 'Keller' }
|
||||
{ key: 'wohnzimmer', name: 'Wohnzimmer', step: 2 },
|
||||
{ key: 'schlafzimmer', name: 'Schlafzimmer', step: 3 },
|
||||
{ key: 'arbeitszimmer', name: 'Arbeitszimmer', step: 4 },
|
||||
{ key: 'bad', name: 'Bad', step: 5 },
|
||||
{ key: 'kueche_esszimmer', name: 'Kueche_Esszimmer', step: 5 },
|
||||
{ key: 'kinderzimmer', name: 'Kinderzimmer', step: 6 },
|
||||
{ key: 'keller', name: 'Keller', step: 7 }
|
||||
];
|
||||
|
||||
roomMap.forEach(function(room) {
|
||||
@@ -337,12 +354,12 @@
|
||||
|
||||
var total = calculateRoomTotal(room.key);
|
||||
html += '<div class="summary-section">';
|
||||
html += '<h3>' + escHtml(getRoomDisplayName(room.key)) + '</h3>';
|
||||
html += summaryHeading(getRoomDisplayName(room.key), room.step);
|
||||
roomItems.forEach(function(item) {
|
||||
html += '<div class="summary-item">';
|
||||
html += '<span class="summary-item-name">' + escHtml(item.name) + '</span>';
|
||||
html += '<span class="summary-item-qty">' + item.qty + '</span>';
|
||||
html += '<span class="summary-item-cbm">' + formatGermanDecimal(item.cbm) + '</span>';
|
||||
html += '<span class="summary-item-cbm">' + formatGermanDecimal(item.cbm) + ' ' + escHtml(l10n.summaryCbm || 'cbm') + '</span>';
|
||||
if (item.montage !== null) {
|
||||
html += '<span class="summary-item-montage">' + escHtml(item.montage === 'ja' ? (l10n.summaryYes || 'Yes') : (l10n.summaryNo || 'No')) + '</span>';
|
||||
}
|
||||
@@ -368,7 +385,7 @@
|
||||
var additionalHtml = getAdditionalWorkSummary();
|
||||
if (additionalHtml) {
|
||||
html += '<div class="summary-section">';
|
||||
html += '<h3>' + escHtml(l10n.summaryAdditional || 'Additional Work') + '</h3>';
|
||||
html += summaryHeading(l10n.summaryAdditional || 'Additional Work', 8);
|
||||
html += additionalHtml;
|
||||
html += '</div>';
|
||||
}
|
||||
@@ -377,7 +394,7 @@
|
||||
var sonstiges = getFieldVal('sonstiges');
|
||||
if (sonstiges) {
|
||||
html += '<div class="summary-section">';
|
||||
html += '<h3>' + escHtml(l10n.summaryOther || 'Other') + '</h3>';
|
||||
html += summaryHeading(l10n.summaryOther || 'Other', 8);
|
||||
html += '<p>' + escHtml(sonstiges) + '</p>';
|
||||
html += '</div>';
|
||||
}
|
||||
@@ -392,7 +409,7 @@
|
||||
function getFieldVal(name) {
|
||||
var el = qs('[name="' + name + '"]');
|
||||
if (!el) return '';
|
||||
if (el.tagName === 'SELECT') return el.options[el.selectedIndex].value;
|
||||
if (el.tagName === 'SELECT') return el.selectedIndex >= 0 ? el.options[el.selectedIndex].value : '';
|
||||
return el.value.trim();
|
||||
}
|
||||
|
||||
@@ -402,13 +419,19 @@
|
||||
}
|
||||
|
||||
function getRoomDisplayName(roomKey) {
|
||||
// Read from the step title or the furniture list heading
|
||||
var list = qs('.furniture-list[data-room="' + roomKey + '"]');
|
||||
if (list) {
|
||||
var card = list.closest('.step-card');
|
||||
if (card) {
|
||||
var h3 = qs('h3', card);
|
||||
if (h3) return h3.textContent;
|
||||
// For combined steps (step 5), use the h3 section heading
|
||||
var section = list.closest('.step-section');
|
||||
if (section) {
|
||||
var h3 = qs('h3', section);
|
||||
if (h3) return h3.textContent;
|
||||
}
|
||||
// For single-room steps, use the h2 step title
|
||||
var h2 = qs('h2.step-title', card);
|
||||
if (h2) return h2.textContent;
|
||||
}
|
||||
}
|
||||
return roomKey;
|
||||
@@ -526,15 +549,47 @@
|
||||
document.addEventListener('input', function(e) {
|
||||
if (e.target.classList.contains('quantity-input')) {
|
||||
handleQuantityChange();
|
||||
var hasQty = parseGermanDecimal(e.target.value) > 0;
|
||||
// Toggle has-value class
|
||||
if (parseGermanDecimal(e.target.value) > 0) {
|
||||
if (hasQty) {
|
||||
e.target.classList.add('has-value');
|
||||
} else {
|
||||
e.target.classList.remove('has-value');
|
||||
}
|
||||
// Toggle has-quantity on parent row for dimming/montage visibility
|
||||
var row = e.target.closest('.furniture-item');
|
||||
if (row) row.classList.toggle('has-quantity', hasQty);
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-check checkbox when qty-small gets a value
|
||||
document.addEventListener('input', function(e) {
|
||||
if (!e.target.classList.contains('qty-small')) return;
|
||||
var field = e.target.closest('.additional-field');
|
||||
if (!field) return;
|
||||
var cb = qs('input[type="checkbox"]', field);
|
||||
if (cb) cb.checked = e.target.value.trim() !== '';
|
||||
});
|
||||
|
||||
// Stepper button click handlers
|
||||
document.addEventListener('click', function(e) {
|
||||
var btn = e.target.closest('.qty-btn');
|
||||
if (!btn) return;
|
||||
var input = btn.parentNode.querySelector('.quantity-input');
|
||||
if (!input) return;
|
||||
var val = parseGermanDecimal(input.value);
|
||||
if (btn.classList.contains('qty-plus')) val++;
|
||||
else if (btn.classList.contains('qty-minus') && val > 0) val--;
|
||||
input.value = val > 0 ? val : '';
|
||||
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
});
|
||||
|
||||
// Summary edit link click handler
|
||||
document.addEventListener('click', function(e) {
|
||||
var el = e.target.closest('.summary-edit');
|
||||
if (el) showStep(parseInt(el.dataset.goto, 10));
|
||||
});
|
||||
|
||||
// Clear field errors on input
|
||||
document.addEventListener('input', function(e) {
|
||||
if (e.target.classList.contains('field-error')) {
|
||||
|
||||
@@ -69,6 +69,16 @@ class Umzugsliste_Admin_Menu {
|
||||
array( $this, 'settings_page' ) // Callback
|
||||
);
|
||||
|
||||
// Add Test Email submenu
|
||||
add_submenu_page(
|
||||
'umzugsliste',
|
||||
'Test Email',
|
||||
'Test Email',
|
||||
'manage_options',
|
||||
'umzugsliste-test-email',
|
||||
array( Umzugsliste_Test_Email::get_instance(), 'render_admin_page' )
|
||||
);
|
||||
|
||||
// Remove duplicate top-level menu item
|
||||
remove_submenu_page( 'umzugsliste', 'umzugsliste' );
|
||||
}
|
||||
|
||||
@@ -35,20 +35,17 @@ class Umzugsliste_Email_Generator {
|
||||
// Customer info
|
||||
$content .= self::generate_customer_info_section( $data );
|
||||
|
||||
// All rooms
|
||||
// All rooms with grand totals in last room's table
|
||||
$content .= self::generate_all_rooms( $data );
|
||||
|
||||
// Additional work sections
|
||||
$content .= self::generate_additional_work_sections( $data );
|
||||
// Weitere Arbeiten (single 3-column table)
|
||||
$content .= self::generate_weitere_arbeiten( $data );
|
||||
|
||||
// Sonstiges
|
||||
if ( ! empty( $data['sonstiges'] ) ) {
|
||||
$content .= self::generate_sonstiges_section( $data['sonstiges'] );
|
||||
}
|
||||
|
||||
// Grand totals
|
||||
$content .= self::generate_grand_totals( $data );
|
||||
|
||||
// Wrap in HTML document
|
||||
return self::wrap_html( $content );
|
||||
}
|
||||
@@ -135,7 +132,7 @@ class Umzugsliste_Email_Generator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate all room sections
|
||||
* Generate all room sections with grand totals in last room's table
|
||||
*
|
||||
* @param array $data Form data
|
||||
* @return string HTML
|
||||
@@ -144,22 +141,66 @@ class Umzugsliste_Email_Generator {
|
||||
$html = '';
|
||||
$rooms = Umzugsliste_Furniture_Data::get_rooms();
|
||||
|
||||
// Collect rooms that have items
|
||||
$rooms_with_items = array();
|
||||
foreach ( $rooms as $room_key => $room_label ) {
|
||||
// Get post array name for this room
|
||||
$post_array_name = ucfirst( $room_key );
|
||||
if ( 'kueche_esszimmer' === $room_key ) {
|
||||
$post_array_name = 'Kueche_Esszimmer';
|
||||
}
|
||||
|
||||
// Get room data from submission
|
||||
$room_data = $data[ $post_array_name ] ?? array();
|
||||
|
||||
// Only include room if it has items with quantities
|
||||
if ( self::has_items_with_quantities( $room_data ) ) {
|
||||
$html .= self::generate_room_section( $room_key, $room_label, $room_data );
|
||||
$rooms_with_items[ $room_key ] = array(
|
||||
'label' => $room_label,
|
||||
'data' => $room_data,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $rooms_with_items ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$room_keys = array_keys( $rooms_with_items );
|
||||
$last_room_key = end( $room_keys );
|
||||
|
||||
// Grand totals accumulators
|
||||
$grand_total_quantity = 0;
|
||||
$grand_total_cbm = 0;
|
||||
|
||||
foreach ( $rooms_with_items as $room_key => $room_info ) {
|
||||
$is_last = ( $room_key === $last_room_key );
|
||||
$html .= self::generate_room_section(
|
||||
$room_key,
|
||||
$room_info['label'],
|
||||
$room_info['data'],
|
||||
$is_last
|
||||
);
|
||||
|
||||
// Accumulate grand totals
|
||||
foreach ( $room_info['data'] as $key => $value ) {
|
||||
if ( substr( $key, 0, 1 ) === 'v' && ! empty( $value ) && floatval( $value ) > 0 ) {
|
||||
$item_name = substr( $key, 1 );
|
||||
$quantity = floatval( str_replace( ',', '.', trim( $value ) ) );
|
||||
$cbm = isset( $room_info['data'][ 'q' . $item_name ] ) ? floatval( $room_info['data'][ 'q' . $item_name ] ) : 0;
|
||||
$grand_total_quantity += $quantity;
|
||||
$grand_total_cbm += ( $quantity * $cbm );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Grand totals row inside last room's table
|
||||
$grand_total_display = str_replace( '.', ',', number_format( $grand_total_cbm, 2, '.', '' ) );
|
||||
|
||||
$html .= "<tr><th> </th></tr>
|
||||
<tr>
|
||||
<th bgcolor='CCCCCC' align='right'>" . $grand_total_quantity . "</th>
|
||||
<th bgcolor='CCCCCC' align='left'>Gesamtsummen</th>
|
||||
<th bgcolor='CCCCCC' colspan='2' align='right'>" . esc_html( $grand_total_display ) . "</th>
|
||||
<th bgcolor='CCCCCC'> </th>
|
||||
</tr></tbody></table></div></div>";
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
@@ -184,9 +225,10 @@ class Umzugsliste_Email_Generator {
|
||||
* @param string $room_key Room key
|
||||
* @param string $room_label Room label
|
||||
* @param array $room_data Room submission data
|
||||
* @param bool $is_last Whether this is the last room (keeps table open for grand totals)
|
||||
* @return string HTML
|
||||
*/
|
||||
private static function generate_room_section( $room_key, $room_label, $room_data ) {
|
||||
private static function generate_room_section( $room_key, $room_label, $room_data, $is_last = false ) {
|
||||
$html = "<div class='row'>
|
||||
<div class='large-12 columns' style='margin: 10px 0px; overflow-x: auto;'>
|
||||
<table width='100%'>
|
||||
@@ -212,9 +254,6 @@ class Umzugsliste_Email_Generator {
|
||||
$room_total_quantity = 0;
|
||||
$room_total_cbm = 0;
|
||||
|
||||
// Process items in groups of v, q, m
|
||||
$processed_items = array();
|
||||
|
||||
foreach ( $room_data as $key => $value ) {
|
||||
if ( substr( $key, 0, 1 ) === 'v' ) {
|
||||
$item_name = substr( $key, 1 );
|
||||
@@ -238,8 +277,6 @@ class Umzugsliste_Email_Generator {
|
||||
$html .= "<td align='right'>" . esc_html( $total_display ) . '</td>';
|
||||
$html .= '<td> ' . esc_html( $montage ) . '</td>';
|
||||
$html .= '</tr>';
|
||||
|
||||
$processed_items[] = $item_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -254,165 +291,353 @@ class Umzugsliste_Email_Generator {
|
||||
<th bgcolor='CCCCCC'> </th>
|
||||
</tr>";
|
||||
|
||||
// Only close table if NOT the last room (last room stays open for grand totals)
|
||||
if ( ! $is_last ) {
|
||||
$html .= '</tbody></table></div></div>';
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get field key matching how form handler stores it
|
||||
*
|
||||
* @param array $field Field definition
|
||||
* @return string Field key
|
||||
*/
|
||||
private static function get_field_key( $field ) {
|
||||
if ( ! empty( $field['key'] ) ) {
|
||||
return sanitize_key( $field['key'] );
|
||||
}
|
||||
return sanitize_title( $field['name'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get field value from submitted data
|
||||
*
|
||||
* @param array $section_data Submitted data for section
|
||||
* @param array $field Field definition
|
||||
* @return string Field value
|
||||
*/
|
||||
private static function get_field_value( $section_data, $field ) {
|
||||
$key = self::get_field_key( $field );
|
||||
return $section_data[ $key ] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get _anzahl value for checkbox_anzahl fields
|
||||
*
|
||||
* @param array $section_data Submitted data for section
|
||||
* @param array $field Field definition
|
||||
* @return string Anzahl value
|
||||
*/
|
||||
private static function get_field_anzahl( $section_data, $field ) {
|
||||
$key = self::get_field_key( $field );
|
||||
return $section_data[ $key . '_anzahl' ] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Weitere Arbeiten section as single 3-column table
|
||||
*
|
||||
* @param array $data Form data
|
||||
* @return string HTML
|
||||
*/
|
||||
private static function generate_weitere_arbeiten( $data ) {
|
||||
$additional_data = $data['additional_work'] ?? array();
|
||||
$sections = Umzugsliste_Furniture_Data::get_additional_work();
|
||||
|
||||
$html = "<div class='row'>
|
||||
<div class='large-12 columns' style='margin: 10px 0px; overflow-x: auto;'>
|
||||
<table width='100%'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th align='left' bgcolor='#CCCCCC'>Weitere Arbeiten (bitte ankreuzen)</th>
|
||||
<th bgcolor='#CCCCCC'> </th>
|
||||
<th bgcolor='#CCCCCC'> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>";
|
||||
|
||||
$html .= self::generate_montage_rows( $additional_data['montage'] ?? array(), $sections['montage'] );
|
||||
$html .= self::generate_schrank_rows( $additional_data['schrank'] ?? array(), $sections['schrank'] );
|
||||
$html .= self::generate_elektriker_rows( $additional_data['elektriker'] ?? array(), $sections['elektriker'] );
|
||||
$html .= self::generate_duebelarbeiten_rows( $additional_data['duebelarbeiten'] ?? array(), $sections['duebelarbeiten'] );
|
||||
$html .= self::generate_packarbeiten_rows( $additional_data['packarbeiten'] ?? array(), $sections['packarbeiten'] );
|
||||
$html .= self::generate_anfahrt_rows( $additional_data['anfahrt'] ?? array(), $sections['anfahrt'] );
|
||||
|
||||
// anfahrt_rows closes the table
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Montagearbeiten rows
|
||||
*
|
||||
* @param array $section_data Submitted data
|
||||
* @param array $section_def Section field definitions
|
||||
* @return string HTML rows
|
||||
*/
|
||||
private static function generate_montage_rows( $section_data, $section_def ) {
|
||||
$html = "<tr>
|
||||
<th bgcolor='#CCCCCC' colspan='3'>Montagearbeiten</th>
|
||||
</tr>";
|
||||
|
||||
foreach ( $section_def['fields'] as $field ) {
|
||||
$value = self::get_field_value( $section_data, $field );
|
||||
$checked = ( 'ja' === $value ) ? 'X' : ' ';
|
||||
|
||||
$html .= '<tr>';
|
||||
$html .= '<td>' . esc_html( $field['name'] ) . '</td>';
|
||||
$html .= '<td>' . $checked . '</td>';
|
||||
$html .= '<td> </td>';
|
||||
$html .= '</tr>';
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Schrank rows with Abbau/Aufbau columns
|
||||
*
|
||||
* @param array $section_data Submitted data
|
||||
* @param array $section_def Section field definitions
|
||||
* @return string HTML rows
|
||||
*/
|
||||
private static function generate_schrank_rows( $section_data, $section_def ) {
|
||||
$html = "<tr>
|
||||
<th bgcolor='#CCCCCC'>Schrank</th>
|
||||
<th bgcolor='#CCCCCC'>Abbau</th>
|
||||
<th bgcolor='#CCCCCC'>Aufbau</th>
|
||||
</tr>";
|
||||
|
||||
foreach ( $section_def['fields'] as $field ) {
|
||||
$value = self::get_field_value( $section_data, $field );
|
||||
|
||||
$abbau = ' ';
|
||||
$aufbau = ' ';
|
||||
|
||||
if ( 'Abbau' === $value ) {
|
||||
$abbau = 'X';
|
||||
} elseif ( 'Aufbau' === $value ) {
|
||||
$aufbau = 'X';
|
||||
} elseif ( 'Beides' === $value ) {
|
||||
$abbau = 'X';
|
||||
$aufbau = 'X';
|
||||
}
|
||||
|
||||
$html .= '<tr>';
|
||||
$html .= '<td>' . esc_html( $field['name'] ) . '</td>';
|
||||
$html .= '<td>' . $abbau . '</td>';
|
||||
$html .= '<td>' . $aufbau . '</td>';
|
||||
$html .= '</tr>';
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Elektriker/Installateur rows
|
||||
*
|
||||
* @param array $section_data Submitted data
|
||||
* @param array $section_def Section field definitions
|
||||
* @return string HTML rows
|
||||
*/
|
||||
private static function generate_elektriker_rows( $section_data, $section_def ) {
|
||||
$html = "<tr>
|
||||
<th bgcolor='#CCCCCC'>Elektriker/Installateur</th>
|
||||
<th bgcolor='#CCCCCC'> </th>
|
||||
<th bgcolor='#CCCCCC'>Anzahl</th>
|
||||
</tr>";
|
||||
|
||||
foreach ( $section_def['fields'] as $field ) {
|
||||
$value = self::get_field_value( $section_data, $field );
|
||||
$anzahl = self::get_field_anzahl( $section_data, $field );
|
||||
|
||||
$checked = ( 'ja' === $value ) ? 'X' : ' ';
|
||||
$anzahl_display = ! empty( $anzahl ) ? esc_html( $anzahl ) : ' ';
|
||||
|
||||
$html .= '<tr>';
|
||||
$html .= '<td>' . esc_html( $field['name'] ) . '</td>';
|
||||
$html .= '<td>' . $checked . '</td>';
|
||||
$html .= '<td>' . $anzahl_display . '</td>';
|
||||
$html .= '</tr>';
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Dübelarbeiten rows
|
||||
*
|
||||
* @param array $section_data Submitted data
|
||||
* @param array $section_def Section field definitions
|
||||
* @return string HTML rows
|
||||
*/
|
||||
private static function generate_duebelarbeiten_rows( $section_data, $section_def ) {
|
||||
$html = "<tr>
|
||||
<th bgcolor='#CCCCCC'>Dübelarbeiten</th>
|
||||
<th bgcolor='#CCCCCC'> </th>
|
||||
<th bgcolor='#CCCCCC'>Anzahl</th>
|
||||
</tr>";
|
||||
|
||||
foreach ( $section_def['fields'] as $field ) {
|
||||
$value = self::get_field_value( $section_data, $field );
|
||||
$anzahl = self::get_field_anzahl( $section_data, $field );
|
||||
|
||||
$checked = ( 'ja' === $value ) ? 'X' : ' ';
|
||||
$anzahl_display = ! empty( $anzahl ) ? esc_html( $anzahl ) : ' ';
|
||||
|
||||
$html .= '<tr>';
|
||||
$html .= '<td>' . esc_html( $field['name'] ) . '</td>';
|
||||
$html .= '<td>' . $checked . '</td>';
|
||||
$html .= '<td>' . $anzahl_display . '</td>';
|
||||
$html .= '</tr>';
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Packarbeiten rows with sub-headers
|
||||
*
|
||||
* @param array $section_data Submitted data
|
||||
* @param array $section_def Section field definitions
|
||||
* @return string HTML rows
|
||||
*/
|
||||
private static function generate_packarbeiten_rows( $section_data, $section_def ) {
|
||||
$fields = $section_def['fields'];
|
||||
|
||||
// Packarbeiten header + first 2 checkbox rows
|
||||
$html = "<tr>
|
||||
<th bgcolor='#CCCCCC' colspan='3'>Packarbeiten</th>
|
||||
</tr>";
|
||||
|
||||
for ( $i = 0; $i < 2 && $i < count( $fields ); $i++ ) {
|
||||
$value = self::get_field_value( $section_data, $fields[ $i ] );
|
||||
$checked = ( 'ja' === $value ) ? 'X' : ' ';
|
||||
|
||||
$html .= '<tr>';
|
||||
$html .= '<td>' . esc_html( $fields[ $i ]['name'] ) . '</td>';
|
||||
$html .= '<td>' . $checked . '</td>';
|
||||
$html .= '<td> </td>';
|
||||
$html .= '</tr>';
|
||||
}
|
||||
|
||||
// "Wir haben spezielle Packwünsche:" sub-header + next 2 checkbox rows
|
||||
$html .= "<tr>
|
||||
<th bgcolor='#CCCCCC' colspan='3'>Wir haben spezielle Packwünsche:</th>
|
||||
</tr>";
|
||||
|
||||
for ( $i = 2; $i < 4 && $i < count( $fields ); $i++ ) {
|
||||
$value = self::get_field_value( $section_data, $fields[ $i ] );
|
||||
$checked = ( 'ja' === $value ) ? 'X' : ' ';
|
||||
|
||||
$html .= '<tr>';
|
||||
$html .= '<td>' . esc_html( $fields[ $i ]['name'] ) . '</td>';
|
||||
$html .= '<td>' . $checked . '</td>';
|
||||
$html .= '<td> </td>';
|
||||
$html .= '</tr>';
|
||||
}
|
||||
|
||||
// "Packmaterial" sub-header + 2 text quantity rows
|
||||
$html .= "<tr>
|
||||
<th bgcolor='#CCCCCC' colspan='3'>Packmaterial</th>
|
||||
</tr>";
|
||||
|
||||
for ( $i = 4; $i < 6 && $i < count( $fields ); $i++ ) {
|
||||
$value = self::get_field_value( $section_data, $fields[ $i ] );
|
||||
$value_display = ! empty( $value ) ? esc_html( $value ) : ' ';
|
||||
|
||||
$html .= '<tr>';
|
||||
$html .= '<td>' . esc_html( $fields[ $i ]['name'] ) . '</td>';
|
||||
$html .= '<td> </td>';
|
||||
$html .= '<td>' . $value_display . '</td>';
|
||||
$html .= '</tr>';
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Anfahrt rows with nested sub-headers (also closes the table)
|
||||
*
|
||||
* @param array $section_data Submitted data
|
||||
* @param array $section_def Section field definitions
|
||||
* @return string HTML rows including table close
|
||||
*/
|
||||
private static function generate_anfahrt_rows( $section_data, $section_def ) {
|
||||
$fields = $section_def['fields'];
|
||||
|
||||
$html = "<tr>
|
||||
<th bgcolor='#CCCCCC' colspan='3'>Anfahrt</th>
|
||||
</tr>";
|
||||
|
||||
// "LKW kann direkt vor den Eingang fahren" sub-header
|
||||
$html .= "<tr>
|
||||
<th bgcolor='#CCCCCC' colspan='3'>LKW kann direkt vor den Eingang fahren</th>
|
||||
</tr>";
|
||||
|
||||
// Beladestelle (field index 0)
|
||||
$value = self::get_field_value( $section_data, $fields[0] );
|
||||
$checked = ( 'ja' === $value ) ? 'X' : ' ';
|
||||
$html .= '<tr><td>Beladestelle</td><td>' . $checked . '</td><td> </td></tr>';
|
||||
|
||||
// Entladestelle (field index 1)
|
||||
$value = self::get_field_value( $section_data, $fields[1] );
|
||||
$checked = ( 'ja' === $value ) ? 'X' : ' ';
|
||||
$html .= '<tr><td>Entladestelle</td><td>' . $checked . '</td><td> </td></tr>';
|
||||
|
||||
// "Parkverbotsschilder aufstellen" sub-header
|
||||
$html .= "<tr>
|
||||
<th bgcolor='#CCCCCC' colspan='3'>Parkverbotsschilder aufstellen</th>
|
||||
</tr>";
|
||||
|
||||
// Beladestelle (field index 2)
|
||||
$value = self::get_field_value( $section_data, $fields[2] );
|
||||
$checked = ( 'ja' === $value ) ? 'X' : ' ';
|
||||
$html .= '<tr><td>Beladestelle</td><td>' . $checked . '</td><td> </td></tr>';
|
||||
|
||||
// Entladestelle (field index 3)
|
||||
$value = self::get_field_value( $section_data, $fields[3] );
|
||||
$checked = ( 'ja' === $value ) ? 'X' : ' ';
|
||||
$html .= '<tr><td>Entladestelle</td><td>' . $checked . '</td><td> </td></tr>';
|
||||
|
||||
// "Die Anfahrt ist eng bzw. nicht möglich" sub-header
|
||||
$html .= "<tr>
|
||||
<th bgcolor='#CCCCCC' colspan='3'>Die Anfahrt ist eng bzw. nicht möglich</th>
|
||||
</tr>";
|
||||
|
||||
// Beladestelle (field index 4)
|
||||
$value = self::get_field_value( $section_data, $fields[4] );
|
||||
$checked = ( 'ja' === $value ) ? 'X' : ' ';
|
||||
$html .= '<tr><td>Beladestelle</td><td>' . $checked . '</td><td> </td></tr>';
|
||||
|
||||
// Entladestelle (field index 5)
|
||||
$value = self::get_field_value( $section_data, $fields[5] );
|
||||
$checked = ( 'ja' === $value ) ? 'X' : ' ';
|
||||
$html .= '<tr><td>Entladestelle</td><td>' . $checked . '</td><td> </td></tr>';
|
||||
|
||||
// "Abtrageweg" sub-header
|
||||
$html .= "<tr>
|
||||
<th bgcolor='#CCCCCC' colspan='3'>Abtrageweg</th>
|
||||
</tr>";
|
||||
|
||||
// Beladestelle distance (field index 6) - value in col3
|
||||
$value = self::get_field_value( $section_data, $fields[6] );
|
||||
$value_display = ! empty( $value ) ? esc_html( $value ) : ' ';
|
||||
$html .= '<tr><td>Beladestelle Wegstrecke Haus-LKW in Meter</td><td> </td><td>' . $value_display . '</td></tr>';
|
||||
|
||||
// Entladestelle distance (field index 7) - value in col3, also closes table
|
||||
$value = self::get_field_value( $section_data, $fields[7] );
|
||||
$value_display = ! empty( $value ) ? esc_html( $value ) : ' ';
|
||||
$html .= '<tr><td>Entladestelle Wegstrecke LKW-Haus in Meter</td><td> </td><td>' . $value_display . '</td></tr>';
|
||||
|
||||
// Close the Weitere Arbeiten table
|
||||
$html .= '</tbody></table></div></div>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate grand totals section
|
||||
*
|
||||
* @param array $data Form data
|
||||
* @return string HTML
|
||||
*/
|
||||
private static function generate_grand_totals( $data ) {
|
||||
$grand_total_quantity = 0;
|
||||
$grand_total_cbm = 0;
|
||||
|
||||
$rooms = Umzugsliste_Furniture_Data::get_rooms();
|
||||
|
||||
foreach ( $rooms as $room_key => $room_label ) {
|
||||
$post_array_name = ucfirst( $room_key );
|
||||
if ( 'kueche_esszimmer' === $room_key ) {
|
||||
$post_array_name = 'Kueche_Esszimmer';
|
||||
}
|
||||
|
||||
$room_data = $data[ $post_array_name ] ?? array();
|
||||
|
||||
foreach ( $room_data as $key => $value ) {
|
||||
if ( substr( $key, 0, 1 ) === 'v' && ! empty( $value ) && floatval( $value ) > 0 ) {
|
||||
$item_name = substr( $key, 1 );
|
||||
$quantity = floatval( str_replace( ',', '.', trim( $value ) ) );
|
||||
$cbm = isset( $room_data[ 'q' . $item_name ] ) ? floatval( $room_data[ 'q' . $item_name ] ) : 0;
|
||||
|
||||
$grand_total_quantity += $quantity;
|
||||
$grand_total_cbm += ( $quantity * $cbm );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$grand_total_display = str_replace( '.', ',', number_format( $grand_total_cbm, 2, '.', '' ) );
|
||||
|
||||
return "<tr><th> </th></tr>
|
||||
<tr>
|
||||
<th bgcolor='CCCCCC' align='right'>" . $grand_total_quantity . "</th>
|
||||
<th bgcolor='CCCCCC' align='left'>Gesamtsummen</th>
|
||||
<th bgcolor='CCCCCC' colspan='2' align='right'>" . esc_html( $grand_total_display ) . "</th>
|
||||
<th bgcolor='CCCCCC'> </th>
|
||||
</tr></tbody></table></div></div>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate additional work sections
|
||||
*
|
||||
* @param array $data Form data
|
||||
* @return string HTML
|
||||
*/
|
||||
private static function generate_additional_work_sections( $data ) {
|
||||
$html = '';
|
||||
$sections = Umzugsliste_Furniture_Data::get_additional_work();
|
||||
|
||||
foreach ( $sections as $section_key => $section_data ) {
|
||||
// Only include section if it has data
|
||||
if ( self::has_additional_work_data( $data, $section_key ) ) {
|
||||
$html .= "<div class='row'>
|
||||
<div class='large-12 columns' style='margin: 10px 0px; overflow-x: auto;'>
|
||||
<table width='100%'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th align='left' bgcolor='#CCCCCC' colspan='2'>" . esc_html( $section_data['label'] ) . "</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>";
|
||||
|
||||
$section_submitted_data = $data['additional_work'][ $section_key ] ?? array();
|
||||
|
||||
foreach ( $section_data['fields'] as $field ) {
|
||||
// Get field key
|
||||
$field_key = ! empty( $field['key'] ) ? $field['key'] : sanitize_title( $field['name'] );
|
||||
|
||||
// Get field value
|
||||
$field_value = $section_submitted_data[ $field_key ] ?? '';
|
||||
|
||||
// Render based on field type
|
||||
switch ( $field['type'] ) {
|
||||
case 'checkbox':
|
||||
if ( 'ja' === $field_value ) {
|
||||
$html .= '<tr>';
|
||||
$html .= '<td>' . esc_html( $field['name'] ) . '</td>';
|
||||
$html .= '<td>Ja</td>';
|
||||
$html .= '</tr>';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'abbau_aufbau':
|
||||
if ( ! empty( $field_value ) ) {
|
||||
$html .= '<tr>';
|
||||
$html .= '<td>' . esc_html( $field['name'] ) . '</td>';
|
||||
$html .= '<td>' . esc_html( $field_value ) . '</td>';
|
||||
$html .= '</tr>';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'checkbox_anzahl':
|
||||
if ( 'ja' === $field_value ) {
|
||||
$anzahl_value = $section_submitted_data[ $field_key . '_anzahl' ] ?? '';
|
||||
$display_value = 'Ja';
|
||||
if ( ! empty( $anzahl_value ) ) {
|
||||
$display_value .= ' (Anzahl: ' . esc_html( $anzahl_value ) . ')';
|
||||
}
|
||||
$html .= '<tr>';
|
||||
$html .= '<td>' . esc_html( $field['name'] ) . '</td>';
|
||||
$html .= '<td>' . $display_value . '</td>';
|
||||
$html .= '</tr>';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'text':
|
||||
if ( ! empty( $field_value ) ) {
|
||||
$html .= '<tr>';
|
||||
$html .= '<td>' . esc_html( $field['name'] ) . '</td>';
|
||||
$html .= '<td>' . esc_html( $field_value ) . '</td>';
|
||||
$html .= '</tr>';
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$html .= '</tbody></table></div></div>';
|
||||
}
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if section has any data
|
||||
*
|
||||
* @param array $data Form data
|
||||
* @param string $section_key Section key
|
||||
* @return bool True if has data
|
||||
*/
|
||||
private static function has_additional_work_data( $data, $section_key ) {
|
||||
if ( empty( $data['additional_work'][ $section_key ] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$section_data = $data['additional_work'][ $section_key ];
|
||||
if ( ! is_array( $section_data ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if any value is non-empty
|
||||
foreach ( $section_data as $value ) {
|
||||
if ( ! empty( trim( $value ) ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Sonstiges section
|
||||
*
|
||||
@@ -448,7 +673,7 @@ class Umzugsliste_Email_Generator {
|
||||
return "<!DOCTYPE html PUBLIC '-//W3C//DTD HTML 4.0 Transitional//EN'>
|
||||
<html>
|
||||
<head>
|
||||
<title>Siegel Umzüge - Internetanfrage</title>
|
||||
<title>Siegel-Umzug</title>
|
||||
<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>
|
||||
</head>
|
||||
<body>" . $content . "</body>
|
||||
|
||||
@@ -46,7 +46,7 @@ class Umzugsliste_Form_Renderer {
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="umzugsliste-wizard">
|
||||
<div class="umzugsliste-wizard palette-b">
|
||||
<?php self::render_validation_errors(); ?>
|
||||
<?php self::render_progress_bar( $steps ); ?>
|
||||
<div class="running-totals" id="running-totals">
|
||||
@@ -82,6 +82,20 @@ class Umzugsliste_Form_Renderer {
|
||||
<button type="submit" class="wizard-btn wizard-btn-submit" id="wizard-submit" style="display:none;"><?php echo esc_html__( 'Submit Request', 'siegel-umzugsliste' ); ?></button>
|
||||
</div>
|
||||
</form>
|
||||
<?php if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) : ?>
|
||||
<button type="button" id="dev-palette-switch" class="dev-palette-btn">🎨 B</button>
|
||||
<script>
|
||||
document.getElementById('dev-palette-switch').addEventListener('click', function() {
|
||||
var w = document.querySelector('.umzugsliste-wizard');
|
||||
var p = ['palette-a', 'palette-b', 'palette-c'];
|
||||
var c = p.findIndex(function(x) { return w.classList.contains(x); });
|
||||
var n = (c + 1) % p.length;
|
||||
w.classList.remove(p[c]);
|
||||
w.classList.add(p[n]);
|
||||
this.textContent = '\u{1F3A8} ' + p[n].split('-')[1].toUpperCase();
|
||||
});
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
@@ -134,6 +148,7 @@ class Umzugsliste_Form_Renderer {
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress-counter" id="progress-counter"></div>
|
||||
<?php
|
||||
}
|
||||
|
||||
@@ -143,9 +158,8 @@ class Umzugsliste_Form_Renderer {
|
||||
private static function render_step_1() {
|
||||
?>
|
||||
<div class="wizard-step active" data-step="1">
|
||||
<h2 class="step-title"><?php echo esc_html__( 'Moving Date & Addresses', 'siegel-umzugsliste' ); ?></h2>
|
||||
|
||||
<div class="step-card">
|
||||
<h2 class="step-title"><?php echo esc_html__( 'Moving Date & Addresses', 'siegel-umzugsliste' ); ?></h2>
|
||||
<h3><?php echo esc_html__( 'Expected Moving Date', 'siegel-umzugsliste' ); ?></h3>
|
||||
<div class="date-selector">
|
||||
<?php
|
||||
@@ -160,38 +174,134 @@ class Umzugsliste_Form_Renderer {
|
||||
'<a href="http://siegel-umzug.de/datenschutz.html" target="_blank" rel="noopener">' . esc_html__( 'Privacy Policy', 'siegel-umzugsliste' ) . '</a>'
|
||||
);
|
||||
?></p>
|
||||
<div class="address-grid">
|
||||
<div class="address-section">
|
||||
<h3><?php echo esc_html__( 'Loading Address', 'siegel-umzugsliste' ); ?></h3>
|
||||
<?php
|
||||
self::render_address_field( __( 'Name', 'siegel-umzugsliste' ), 'bName', true );
|
||||
self::render_address_field( __( 'Street', 'siegel-umzugsliste' ), 'bStrasse', true );
|
||||
self::render_address_field( __( 'ZIP/City', 'siegel-umzugsliste' ), 'bort', true );
|
||||
self::render_address_field( __( 'Floor', 'siegel-umzugsliste' ), 'info[bGeschoss]' );
|
||||
self::render_lift_field( 'info[bLift]' );
|
||||
self::render_address_field( __( 'Phone', 'siegel-umzugsliste' ), 'bTelefon', true );
|
||||
self::render_address_field( __( 'Fax', 'siegel-umzugsliste' ), 'info[bTelefax]' );
|
||||
self::render_address_field( __( 'Mobile', 'siegel-umzugsliste' ), 'info[bMobil]' );
|
||||
self::render_address_field( __( 'Email', 'siegel-umzugsliste' ), 'info[eE-Mail]', true, 'email' );
|
||||
?>
|
||||
</div>
|
||||
<div class="address-section">
|
||||
<h3><?php echo esc_html__( 'Unloading Address', 'siegel-umzugsliste' ); ?></h3>
|
||||
<?php
|
||||
self::render_address_field( __( 'Name', 'siegel-umzugsliste' ), 'eName', true );
|
||||
self::render_address_field( __( 'Street', 'siegel-umzugsliste' ), 'eStrasse', true );
|
||||
self::render_address_field( __( 'ZIP/City', 'siegel-umzugsliste' ), 'eort', true );
|
||||
self::render_address_field( __( 'Floor', 'siegel-umzugsliste' ), 'info[eGeschoss]' );
|
||||
self::render_lift_field( 'info[eLift]' );
|
||||
self::render_address_field( __( 'Phone', 'siegel-umzugsliste' ), 'eTelefon' );
|
||||
self::render_address_field( __( 'Fax', 'siegel-umzugsliste' ), 'info[eTelefax]' );
|
||||
self::render_address_field( __( 'Mobile', 'siegel-umzugsliste' ), 'info[eMobil]' );
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
<p class="required-note"><?php echo esc_html__( '* Required fields', 'siegel-umzugsliste' ); ?></p>
|
||||
</div>
|
||||
<?php if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) : ?>
|
||||
<button type="button" id="dev-autofill" class="dev-autofill-btn">⚙ Fill All</button>
|
||||
<script>
|
||||
document.getElementById('dev-autofill').addEventListener('click', function() {
|
||||
function setField(name, val) {
|
||||
var el = document.querySelector('[name="'+name+'"]');
|
||||
if (el) { el.value = val; el.dispatchEvent(new Event('input',{bubbles:true})); }
|
||||
}
|
||||
function setRadio(name, val) {
|
||||
var el = document.querySelector('[name="'+name+'"][value="'+val+'"]');
|
||||
if (el) el.checked = true;
|
||||
}
|
||||
|
||||
<div class="address-grid">
|
||||
<div class="step-card">
|
||||
<h3><?php echo esc_html__( 'Loading Address', 'siegel-umzugsliste' ); ?></h3>
|
||||
<?php
|
||||
self::render_address_field( __( 'Name', 'siegel-umzugsliste' ), 'bName', true );
|
||||
self::render_address_field( __( 'Street', 'siegel-umzugsliste' ), 'bStrasse', true );
|
||||
self::render_address_field( __( 'ZIP/City', 'siegel-umzugsliste' ), 'bort', true );
|
||||
self::render_address_field( __( 'Floor', 'siegel-umzugsliste' ), 'info[bGeschoss]' );
|
||||
self::render_lift_field( 'info[bLift]' );
|
||||
self::render_address_field( __( 'Phone', 'siegel-umzugsliste' ), 'bTelefon', true );
|
||||
self::render_address_field( __( 'Fax', 'siegel-umzugsliste' ), 'info[bTelefax]' );
|
||||
self::render_address_field( __( 'Mobile', 'siegel-umzugsliste' ), 'info[bMobil]' );
|
||||
self::render_address_field( __( 'Email', 'siegel-umzugsliste' ), 'info[eE-Mail]', true, 'email' );
|
||||
?>
|
||||
</div>
|
||||
<div class="step-card">
|
||||
<h3><?php echo esc_html__( 'Unloading Address', 'siegel-umzugsliste' ); ?></h3>
|
||||
<?php
|
||||
self::render_address_field( __( 'Name', 'siegel-umzugsliste' ), 'eName', true );
|
||||
self::render_address_field( __( 'Street', 'siegel-umzugsliste' ), 'eStrasse', true );
|
||||
self::render_address_field( __( 'ZIP/City', 'siegel-umzugsliste' ), 'eort', true );
|
||||
self::render_address_field( __( 'Floor', 'siegel-umzugsliste' ), 'info[eGeschoss]' );
|
||||
self::render_lift_field( 'info[eLift]' );
|
||||
self::render_address_field( __( 'Phone', 'siegel-umzugsliste' ), 'eTelefon' );
|
||||
self::render_address_field( __( 'Fax', 'siegel-umzugsliste' ), 'info[eTelefax]' );
|
||||
self::render_address_field( __( 'Mobile', 'siegel-umzugsliste' ), 'info[eMobil]' );
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
<p class="required-note"><?php echo esc_html__( '* Required fields', 'siegel-umzugsliste' ); ?></p>
|
||||
/* Step 1: Date — option values are plain numbers (no zero-pad) */
|
||||
var d = new Date();
|
||||
setField('umzug_day', String(d.getDate()));
|
||||
setField('umzug_month', String(d.getMonth()+1));
|
||||
setField('umzug_year', String(d.getFullYear()));
|
||||
|
||||
/* Step 1: Addresses */
|
||||
var addr = {
|
||||
'bName':'Max Mustermann','bStrasse':'Musterstraße 42',
|
||||
'bort':'65197 Wiesbaden','bTelefon':'0611 123456',
|
||||
'eName':'Erika Musterfrau','eStrasse':'Beispielweg 7',
|
||||
'eort':'55116 Mainz','eTelefon':'06131 654321',
|
||||
'info[bGeschoss]':'2. OG','info[eGeschoss]':'EG',
|
||||
'info[bTelefax]':'0611 123457','info[eTelefax]':'06131 654322',
|
||||
'info[bMobil]':'0170 1234567','info[eMobil]':'0171 7654321',
|
||||
'info[eE-Mail]':'test@example.com'
|
||||
};
|
||||
for (var n in addr) setField(n, addr[n]);
|
||||
setRadio('info[bLift]','ja');
|
||||
|
||||
/* Steps 2-7: Furniture — ALL items in every room */
|
||||
document.querySelectorAll('.furniture-list').forEach(function(list) {
|
||||
var items = list.querySelectorAll('.furniture-item');
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
var qty = (i % 3) + 1;
|
||||
var inp = items[i].querySelector('.quantity-input');
|
||||
if (inp) {
|
||||
inp.value = String(qty);
|
||||
inp.classList.add('has-value');
|
||||
items[i].classList.add('has-quantity');
|
||||
inp.dispatchEvent(new Event('input',{bubbles:true}));
|
||||
}
|
||||
if (i % 2 === 0) {
|
||||
var mj = items[i].querySelector('.montage-toggle input[value="ja"]');
|
||||
if (mj) mj.checked = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* Step 8: Additional work — Montage checkboxes */
|
||||
document.querySelectorAll('[data-section="montage"] input[type="checkbox"]').forEach(function(cb) { cb.checked = true; });
|
||||
|
||||
/* Schrank radios — cycle Abbau/Aufbau/Beides */
|
||||
var abbauVals = ['Abbau','Aufbau','Beides'], ai = 0;
|
||||
document.querySelectorAll('[data-section="schrank"] .additional-field-abbau').forEach(function(f) {
|
||||
var r = f.querySelector('input[value="'+abbauVals[ai%3]+'"]');
|
||||
if (r) r.checked = true;
|
||||
ai++;
|
||||
});
|
||||
|
||||
/* Elektriker — check + anzahl */
|
||||
var ez = 1;
|
||||
document.querySelectorAll('[data-section="elektriker"] .additional-field-qty').forEach(function(f) {
|
||||
var cb = f.querySelector('input[type="checkbox"]'); if (cb) cb.checked = true;
|
||||
var q = f.querySelector('.qty-small'); if (q) q.value = String(ez++);
|
||||
});
|
||||
|
||||
/* Dübelarbeiten — check + anzahl */
|
||||
var dz = 2;
|
||||
document.querySelectorAll('[data-section="duebelarbeiten"] .additional-field-qty').forEach(function(f) {
|
||||
var cb = f.querySelector('input[type="checkbox"]'); if (cb) cb.checked = true;
|
||||
var q = f.querySelector('.qty-small'); if (q) { q.value = String(dz); dz += 2; }
|
||||
});
|
||||
|
||||
/* Packarbeiten — all checkboxes + text quantities */
|
||||
document.querySelectorAll('[data-section="packarbeiten"] input[type="checkbox"]').forEach(function(cb) { cb.checked = true; });
|
||||
var pv = [25, 5], pi = 0;
|
||||
document.querySelectorAll('[data-section="packarbeiten"] .additional-field-text .qty-small').forEach(function(inp) {
|
||||
inp.value = String(pv[pi] || 5); pi++;
|
||||
});
|
||||
|
||||
/* Anfahrt — all checkboxes + distances */
|
||||
document.querySelectorAll('[data-section="anfahrt"] input[type="checkbox"]').forEach(function(cb) { cb.checked = true; });
|
||||
var av = [15, 25], avi = 0;
|
||||
document.querySelectorAll('[data-section="anfahrt"] .additional-field-text .qty-small').forEach(function(inp) {
|
||||
inp.value = String(av[avi] || 25); avi++;
|
||||
});
|
||||
|
||||
/* Sonstiges */
|
||||
var s = document.querySelector('[name="sonstiges"]');
|
||||
if (s) s.value = 'Bitte vorsichtig mit dem antiken Schrank im Wohnzimmer.\nDas Klavier muss besonders geschützt werden.';
|
||||
});
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
@@ -213,8 +323,8 @@ class Umzugsliste_Form_Renderer {
|
||||
}
|
||||
?>
|
||||
<div class="wizard-step" data-step="<?php echo esc_attr( $step_num ); ?>">
|
||||
<h2 class="step-title"><?php echo esc_html( $room_label ); ?></h2>
|
||||
<div class="step-card">
|
||||
<h2 class="step-title"><?php echo esc_html( $room_label ); ?></h2>
|
||||
<div class="furniture-list" data-room="<?php echo esc_attr( $room_key ); ?>">
|
||||
<?php
|
||||
foreach ( $items as $item ) {
|
||||
@@ -240,10 +350,10 @@ class Umzugsliste_Form_Renderer {
|
||||
$rooms = Umzugsliste_Furniture_Data::get_rooms();
|
||||
?>
|
||||
<div class="wizard-step" data-step="5">
|
||||
<h2 class="step-title"><?php echo esc_html( $rooms['bad'] ); ?> & <?php echo esc_html( $rooms['kueche_esszimmer'] ); ?></h2>
|
||||
|
||||
<div class="step-card">
|
||||
<h3><?php echo esc_html( $rooms['bad'] ); ?></h3>
|
||||
<h2 class="step-title"><?php echo esc_html( $rooms['bad'] ); ?> & <?php echo esc_html( $rooms['kueche_esszimmer'] ); ?></h2>
|
||||
<div class="step-section">
|
||||
<h3><?php echo esc_html( $rooms['bad'] ); ?></h3>
|
||||
<div class="furniture-list" data-room="bad">
|
||||
<?php
|
||||
$bad_items = Umzugsliste_Furniture_Data::get_furniture_items( 'bad' );
|
||||
@@ -257,23 +367,23 @@ class Umzugsliste_Form_Renderer {
|
||||
<span class="room-totals-sep">·</span>
|
||||
<span class="room-total-cbm">0,00</span> <?php echo esc_html__( 'cbm', 'siegel-umzugsliste' ); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="step-card">
|
||||
<h3><?php echo esc_html( $rooms['kueche_esszimmer'] ); ?></h3>
|
||||
<div class="furniture-list" data-room="kueche_esszimmer">
|
||||
<?php
|
||||
$kueche_items = Umzugsliste_Furniture_Data::get_furniture_items( 'kueche_esszimmer' );
|
||||
foreach ( $kueche_items as $item ) {
|
||||
self::render_furniture_item( 'Kueche_Esszimmer', 'kueche_esszimmer', $item );
|
||||
}
|
||||
?>
|
||||
<div class="room-totals" data-room="kueche_esszimmer">
|
||||
<span class="room-total-label"><?php echo esc_html__( 'Total', 'siegel-umzugsliste' ) . ' ' . esc_html( $rooms['kueche_esszimmer'] ); ?>:</span>
|
||||
<span class="room-total-quantity">0</span> <?php echo esc_html__( 'Items', 'siegel-umzugsliste' ); ?>
|
||||
<span class="room-totals-sep">·</span>
|
||||
<span class="room-total-cbm">0,00</span> <?php echo esc_html__( 'cbm', 'siegel-umzugsliste' ); ?>
|
||||
<div class="step-section">
|
||||
<h3><?php echo esc_html( $rooms['kueche_esszimmer'] ); ?></h3>
|
||||
<div class="furniture-list" data-room="kueche_esszimmer">
|
||||
<?php
|
||||
$kueche_items = Umzugsliste_Furniture_Data::get_furniture_items( 'kueche_esszimmer' );
|
||||
foreach ( $kueche_items as $item ) {
|
||||
self::render_furniture_item( 'Kueche_Esszimmer', 'kueche_esszimmer', $item );
|
||||
}
|
||||
?>
|
||||
<div class="room-totals" data-room="kueche_esszimmer">
|
||||
<span class="room-total-label"><?php echo esc_html__( 'Total', 'siegel-umzugsliste' ) . ' ' . esc_html( $rooms['kueche_esszimmer'] ); ?>:</span>
|
||||
<span class="room-total-quantity">0</span> <?php echo esc_html__( 'Items', 'siegel-umzugsliste' ); ?>
|
||||
<span class="room-totals-sep">·</span>
|
||||
<span class="room-total-cbm">0,00</span> <?php echo esc_html__( 'cbm', 'siegel-umzugsliste' ); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -288,27 +398,29 @@ class Umzugsliste_Form_Renderer {
|
||||
$sections = Umzugsliste_Furniture_Data::get_additional_work();
|
||||
?>
|
||||
<div class="wizard-step" data-step="8">
|
||||
<h2 class="step-title"><?php echo esc_html__( 'Additional Work', 'siegel-umzugsliste' ); ?></h2>
|
||||
|
||||
<?php foreach ( $sections as $section_key => $section_data ) : ?>
|
||||
<div class="step-card">
|
||||
<h3><?php echo esc_html( $section_data['label'] ); ?></h3>
|
||||
<div class="additional-work-section" data-section="<?php echo esc_attr( $section_key ); ?>">
|
||||
<?php
|
||||
foreach ( $section_data['fields'] as $field ) {
|
||||
$field_key = self::get_field_key( $field );
|
||||
$field_name = 'additional_work[' . $section_key . '][' . $field_key . ']';
|
||||
self::render_additional_field( $field, $field_name, $field_key );
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<div class="step-card">
|
||||
<h3><?php echo esc_html__( 'Other', 'siegel-umzugsliste' ); ?></h3>
|
||||
<label for="sonstiges"><?php echo esc_html__( 'Additional notes or requests:', 'siegel-umzugsliste' ); ?></label>
|
||||
<textarea name="sonstiges" id="sonstiges" rows="5" class="sonstiges-textarea" placeholder="<?php echo esc_attr__( 'Additional notes or requests...', 'siegel-umzugsliste' ); ?>"></textarea>
|
||||
<h2 class="step-title"><?php echo esc_html__( 'Additional Work', 'siegel-umzugsliste' ); ?></h2>
|
||||
|
||||
<?php foreach ( $sections as $section_key => $section_data ) : ?>
|
||||
<div class="step-section">
|
||||
<h3><?php echo esc_html( $section_data['label'] ); ?></h3>
|
||||
<div class="additional-work-section" data-section="<?php echo esc_attr( $section_key ); ?>">
|
||||
<?php
|
||||
foreach ( $section_data['fields'] as $field ) {
|
||||
$field_key = self::get_field_key( $field );
|
||||
$field_name = 'additional_work[' . $section_key . '][' . $field_key . ']';
|
||||
self::render_additional_field( $field, $field_name, $field_key );
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<div class="step-section">
|
||||
<h3><?php echo esc_html__( 'Other', 'siegel-umzugsliste' ); ?></h3>
|
||||
<label for="sonstiges"><?php echo esc_html__( 'Additional notes or requests:', 'siegel-umzugsliste' ); ?></label>
|
||||
<textarea name="sonstiges" id="sonstiges" rows="5" class="sonstiges-textarea"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
@@ -323,15 +435,17 @@ class Umzugsliste_Form_Renderer {
|
||||
$captcha = Umzugsliste_Captcha::get_instance();
|
||||
?>
|
||||
<div class="wizard-step" data-step="9">
|
||||
<h2 class="step-title"><?php echo esc_html__( 'Summary', 'siegel-umzugsliste' ); ?></h2>
|
||||
<div id="wizard-summary"></div>
|
||||
<?php
|
||||
if ( $captcha->is_enabled() ) {
|
||||
echo '<div class="step-card">';
|
||||
echo $captcha->render_widget();
|
||||
echo '</div>';
|
||||
}
|
||||
?>
|
||||
<div class="step-card">
|
||||
<h2 class="step-title"><?php echo esc_html__( 'Summary', 'siegel-umzugsliste' ); ?></h2>
|
||||
<div id="wizard-summary"></div>
|
||||
<?php
|
||||
if ( $captcha->is_enabled() ) {
|
||||
echo '<div class="captcha-section">';
|
||||
echo $captcha->render_widget();
|
||||
echo '</div>';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<?php wp_nonce_field( 'umzugsliste_submit', 'umzugsliste_nonce' ); ?>
|
||||
<input type="hidden" name="umzugsliste_submit" value="1">
|
||||
<input type="hidden" name="umzugsliste_form_id" value="<?php echo esc_attr( $form_id ); ?>">
|
||||
@@ -391,12 +505,17 @@ class Umzugsliste_Form_Renderer {
|
||||
$montage_name = $room_name . '[m' . $item_name . ']';
|
||||
?>
|
||||
<div class="furniture-item" data-room="<?php echo esc_attr( $room_key ); ?>" data-cbm="<?php echo esc_attr( $cbm ); ?>">
|
||||
<input type="text" name="<?php echo esc_attr( $quantity_name ); ?>" class="quantity-input" inputmode="decimal" placeholder="0" maxlength="3">
|
||||
<div class="quantity-stepper">
|
||||
<button type="button" class="qty-btn qty-minus" aria-label="<?php echo esc_attr__( 'Decrease', 'siegel-umzugsliste' ); ?>">-</button>
|
||||
<input type="text" name="<?php echo esc_attr( $quantity_name ); ?>" class="quantity-input" inputmode="numeric" placeholder="0" maxlength="3">
|
||||
<button type="button" class="qty-btn qty-plus" aria-label="<?php echo esc_attr__( 'Increase', 'siegel-umzugsliste' ); ?>">+</button>
|
||||
</div>
|
||||
<span class="item-name"><?php echo esc_html( $item_name ); ?></span>
|
||||
<span class="item-cbm"><?php echo esc_html( str_replace( '.', ',', (string) $cbm ) ); ?></span>
|
||||
<span class="item-cbm"><?php echo esc_html( str_replace( '.', ',', (string) $cbm ) ); ?> <?php echo esc_html__( 'cbm', 'siegel-umzugsliste' ); ?></span>
|
||||
<input type="hidden" name="<?php echo esc_attr( $cbm_name ); ?>" value="<?php echo esc_attr( $cbm ); ?>">
|
||||
<?php if ( $has_montage ) : ?>
|
||||
<div class="montage-toggle">
|
||||
<span class="montage-label"><?php echo esc_html__( 'Montage?', 'siegel-umzugsliste' ); ?></span>
|
||||
<label class="radio-label"><input type="radio" name="<?php echo esc_attr( $montage_name ); ?>" value="nein" checked> <?php echo esc_html__( 'No', 'siegel-umzugsliste' ); ?></label>
|
||||
<label class="radio-label"><input type="radio" name="<?php echo esc_attr( $montage_name ); ?>" value="ja"> <?php echo esc_html__( 'Yes', 'siegel-umzugsliste' ); ?></label>
|
||||
</div>
|
||||
@@ -445,7 +564,7 @@ class Umzugsliste_Form_Renderer {
|
||||
<input type="checkbox" name="<?php echo esc_attr( $field_name ); ?>" value="ja">
|
||||
<?php echo esc_html( $field['name'] ); ?>
|
||||
</label>
|
||||
<input type="text" name="<?php echo esc_attr( $field_name . '_anzahl' ); ?>" class="qty-small" placeholder="<?php echo esc_attr__( 'Qty.', 'siegel-umzugsliste' ); ?>">
|
||||
<input type="text" name="<?php echo esc_attr( substr( $field_name, 0, -1 ) . '_anzahl]' ); ?>" class="qty-small" placeholder="<?php echo esc_attr__( 'Qty.', 'siegel-umzugsliste' ); ?>">
|
||||
</div>
|
||||
<?php
|
||||
break;
|
||||
|
||||
@@ -123,6 +123,18 @@ class Umzugsliste_Shortcode {
|
||||
'grandTotalLabel' => __( 'Grand Total', 'siegel-umzugsliste' ),
|
||||
'quantityLabel' => __( 'Qty', 'siegel-umzugsliste' ),
|
||||
'cbmLabel' => __( 'cbm', 'siegel-umzugsliste' ),
|
||||
'summaryEdit' => __( 'Edit', 'siegel-umzugsliste' ),
|
||||
'summaryName' => __( 'Name', 'siegel-umzugsliste' ),
|
||||
'summaryStreet' => __( 'Street', 'siegel-umzugsliste' ),
|
||||
'summaryZipCity' => __( 'ZIP/City', 'siegel-umzugsliste' ),
|
||||
'summaryFloor' => __( 'Floor', 'siegel-umzugsliste' ),
|
||||
'summaryElevator' => __( 'Elevator', 'siegel-umzugsliste' ),
|
||||
'summaryPhone' => __( 'Phone', 'siegel-umzugsliste' ),
|
||||
'summaryFax' => __( 'Fax', 'siegel-umzugsliste' ),
|
||||
'summaryMobile' => __( 'Mobile', 'siegel-umzugsliste' ),
|
||||
'summaryEmail' => __( 'Email', 'siegel-umzugsliste' ),
|
||||
'stepLabel' => __( 'Step', 'siegel-umzugsliste' ),
|
||||
'stepOf' => __( 'of', 'siegel-umzugsliste' ),
|
||||
) );
|
||||
}
|
||||
}
|
||||
|
||||
306
includes/class-test-email.php
Normal file
306
includes/class-test-email.php
Normal file
@@ -0,0 +1,306 @@
|
||||
<?php
|
||||
/**
|
||||
* Test Email
|
||||
*
|
||||
* Admin tool for generating and previewing test emails with all fields populated
|
||||
*
|
||||
* @package Umzugsliste
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test email class
|
||||
*/
|
||||
class Umzugsliste_Test_Email {
|
||||
|
||||
/**
|
||||
* Single instance
|
||||
*
|
||||
* @var Umzugsliste_Test_Email
|
||||
*/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Get instance
|
||||
*
|
||||
* @return Umzugsliste_Test_Email
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( null === self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
private function __construct() {
|
||||
add_action( 'admin_init', array( $this, 'maybe_output_preview' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercept preview request before admin page header is output
|
||||
*/
|
||||
public function maybe_output_preview() {
|
||||
if ( ! isset( $_GET['page'] ) || 'umzugsliste-test-email' !== $_GET['page'] ) {
|
||||
return;
|
||||
}
|
||||
if ( ! isset( $_GET['action'] ) || 'preview' !== $_GET['action'] ) {
|
||||
return;
|
||||
}
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
wp_die( 'Unauthorized' );
|
||||
}
|
||||
|
||||
switch_to_locale( 'de_DE' );
|
||||
$data = self::generate_test_data();
|
||||
$html = Umzugsliste_Email_Generator::generate( $data );
|
||||
restore_previous_locale();
|
||||
|
||||
echo $html;
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate comprehensive test data array with every field populated
|
||||
*
|
||||
* Must be called within de_DE locale context so __() returns German strings
|
||||
*
|
||||
* @return array Complete form submission data
|
||||
*/
|
||||
public static function generate_test_data() {
|
||||
$data = array();
|
||||
|
||||
// Date - today
|
||||
$data['umzug_day'] = date( 'd' );
|
||||
$data['umzug_month'] = date( 'm' );
|
||||
$data['umzug_year'] = date( 'Y' );
|
||||
|
||||
// Address fields - realistic German test values
|
||||
$data['bName'] = 'Max Mustermann';
|
||||
$data['eName'] = 'Erika Musterfrau';
|
||||
$data['bStrasse'] = 'Musterstraße 42';
|
||||
$data['eStrasse'] = 'Beispielweg 7';
|
||||
$data['bort'] = '65197 Wiesbaden';
|
||||
$data['eort'] = '55116 Mainz';
|
||||
$data['bTelefon'] = '0611 123456';
|
||||
$data['eTelefon'] = '06131 654321';
|
||||
|
||||
// Info array
|
||||
$data['info'] = array(
|
||||
'bGeschoss' => '2. OG',
|
||||
'eGeschoss' => 'EG',
|
||||
'bLift' => 'ja',
|
||||
'eLift' => 'nein',
|
||||
'bTelefax' => '0611 123457',
|
||||
'eTelefax' => '06131 654322',
|
||||
'bMobil' => '0170 1234567',
|
||||
'eMobil' => '0171 7654321',
|
||||
'eE-Mail' => 'test@example.com',
|
||||
);
|
||||
|
||||
// Room data - pick 2-3 items per room with fixed quantities
|
||||
$room_picks = array(
|
||||
'wohnzimmer' => array( array( 0, 2 ), array( 4, 4 ), array( 8, 1 ) ),
|
||||
'schlafzimmer' => array( array( 2, 1 ), array( 3, 2 ), array( 5, 2 ) ),
|
||||
'arbeitszimmer' => array( array( 1, 1 ), array( 3, 2 ), array( 8, 1 ) ),
|
||||
'bad' => array( array( 0, 1 ), array( 1, 1 ) ),
|
||||
'kueche_esszimmer' => array( array( 4, 1 ), array( 7, 4 ), array( 9, 1 ) ),
|
||||
'kinderzimmer' => array( array( 2, 1 ), array( 3, 1 ), array( 6, 1 ) ),
|
||||
'keller' => array( array( 0, 2 ), array( 4, 4 ), array( 8, 1 ) ),
|
||||
);
|
||||
|
||||
$rooms = Umzugsliste_Furniture_Data::get_rooms();
|
||||
|
||||
foreach ( $rooms as $room_key => $room_label ) {
|
||||
$post_array_name = ucfirst( $room_key );
|
||||
if ( 'kueche_esszimmer' === $room_key ) {
|
||||
$post_array_name = 'Kueche_Esszimmer';
|
||||
}
|
||||
|
||||
$furniture_items = Umzugsliste_Furniture_Data::get_furniture_items( $room_key );
|
||||
$picks = $room_picks[ $room_key ];
|
||||
$room_data = array();
|
||||
|
||||
foreach ( $picks as $pick ) {
|
||||
$idx = $pick[0];
|
||||
$quantity = $pick[1];
|
||||
|
||||
if ( isset( $furniture_items[ $idx ] ) ) {
|
||||
$item = $furniture_items[ $idx ];
|
||||
$name = $item['name'];
|
||||
|
||||
$room_data[ 'v' . $name ] = (string) $quantity;
|
||||
$room_data[ 'q' . $name ] = (string) $item['cbm'];
|
||||
$room_data[ 'm' . $name ] = ( $quantity > 2 ) ? 'ja' : 'nein';
|
||||
}
|
||||
}
|
||||
|
||||
$data[ $post_array_name ] = $room_data;
|
||||
}
|
||||
|
||||
// Additional work
|
||||
$sections = Umzugsliste_Furniture_Data::get_additional_work();
|
||||
$additional_work = array();
|
||||
|
||||
// Montage - both checkboxes checked
|
||||
$montage_data = array();
|
||||
foreach ( $sections['montage']['fields'] as $field ) {
|
||||
$key = self::get_field_key( $field );
|
||||
$montage_data[ $key ] = 'ja';
|
||||
}
|
||||
$additional_work['montage'] = $montage_data;
|
||||
|
||||
// Schrank - mix of Abbau, Aufbau, Beides
|
||||
$schrank_values = array( 'Abbau', 'Aufbau', 'Beides', 'Abbau', 'Aufbau', 'Beides' );
|
||||
$schrank_data = array();
|
||||
$i = 0;
|
||||
foreach ( $sections['schrank']['fields'] as $field ) {
|
||||
$key = self::get_field_key( $field );
|
||||
$schrank_data[ $key ] = $schrank_values[ $i % count( $schrank_values ) ];
|
||||
$i++;
|
||||
}
|
||||
$additional_work['schrank'] = $schrank_data;
|
||||
|
||||
// Elektriker - all checked with varied _anzahl values
|
||||
$elektriker_data = array();
|
||||
$anzahl = 1;
|
||||
foreach ( $sections['elektriker']['fields'] as $field ) {
|
||||
$key = self::get_field_key( $field );
|
||||
$elektriker_data[ $key ] = 'ja';
|
||||
$elektriker_data[ $key . '_anzahl' ] = (string) $anzahl;
|
||||
$anzahl++;
|
||||
}
|
||||
$additional_work['elektriker'] = $elektriker_data;
|
||||
|
||||
// Duebelarbeiten - all checked with varied _anzahl values
|
||||
$duebel_data = array();
|
||||
$anzahl = 2;
|
||||
foreach ( $sections['duebelarbeiten']['fields'] as $field ) {
|
||||
$key = self::get_field_key( $field );
|
||||
$duebel_data[ $key ] = 'ja';
|
||||
$duebel_data[ $key . '_anzahl' ] = (string) $anzahl;
|
||||
$anzahl += 2;
|
||||
}
|
||||
$additional_work['duebelarbeiten'] = $duebel_data;
|
||||
|
||||
// Packarbeiten - all 4 checkboxes checked, text quantities filled
|
||||
$pack_data = array();
|
||||
$pack_fields = $sections['packarbeiten']['fields'];
|
||||
for ( $i = 0; $i < 4 && $i < count( $pack_fields ); $i++ ) {
|
||||
$key = self::get_field_key( $pack_fields[ $i ] );
|
||||
$pack_data[ $key ] = 'ja';
|
||||
}
|
||||
for ( $i = 4; $i < 6 && $i < count( $pack_fields ); $i++ ) {
|
||||
$key = self::get_field_key( $pack_fields[ $i ] );
|
||||
$pack_data[ $key ] = ( $i === 4 ) ? '25' : '5';
|
||||
}
|
||||
$additional_work['packarbeiten'] = $pack_data;
|
||||
|
||||
// Anfahrt - all checkboxes checked + distance text values
|
||||
$anfahrt_data = array();
|
||||
$anfahrt_fields = $sections['anfahrt']['fields'];
|
||||
foreach ( $anfahrt_fields as $i => $field ) {
|
||||
$key = self::get_field_key( $field );
|
||||
if ( 'checkbox' === $field['type'] ) {
|
||||
$anfahrt_data[ $key ] = 'ja';
|
||||
} else {
|
||||
$anfahrt_data[ $key ] = ( $i === 6 ) ? '15' : '25';
|
||||
}
|
||||
}
|
||||
$additional_work['anfahrt'] = $anfahrt_data;
|
||||
|
||||
$data['additional_work'] = $additional_work;
|
||||
|
||||
// Sonstiges
|
||||
$data['sonstiges'] = "Bitte vorsichtig mit dem antiken Schrank im Wohnzimmer.\nDas Klavier muss besonders geschützt werden.";
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get field key matching email generator logic
|
||||
*
|
||||
* @param array $field Field definition
|
||||
* @return string Field key
|
||||
*/
|
||||
private static function get_field_key( $field ) {
|
||||
if ( ! empty( $field['key'] ) ) {
|
||||
return sanitize_key( $field['key'] );
|
||||
}
|
||||
return sanitize_title( $field['name'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render admin page
|
||||
*/
|
||||
public function render_admin_page() {
|
||||
// Handle send test email
|
||||
$notice = '';
|
||||
if ( isset( $_POST['send_test_email'] ) && check_admin_referer( 'umzugsliste_send_test_email' ) ) {
|
||||
$notice = $this->send_test_email();
|
||||
}
|
||||
|
||||
$preview_url = add_query_arg(
|
||||
array(
|
||||
'page' => 'umzugsliste-test-email',
|
||||
'action' => 'preview',
|
||||
),
|
||||
admin_url( 'admin.php' )
|
||||
);
|
||||
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1>Test Email</h1>
|
||||
|
||||
<?php if ( $notice ) : ?>
|
||||
<?php echo $notice; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" style="margin-bottom: 20px;">
|
||||
<?php wp_nonce_field( 'umzugsliste_send_test_email' ); ?>
|
||||
<p>
|
||||
<?php $to = get_option( 'umzugsliste_receiver_email', get_option( 'admin_email' ) ); ?>
|
||||
<strong>Recipient:</strong> <?php echo esc_html( $to ); ?>
|
||||
</p>
|
||||
<p>
|
||||
<?php submit_button( 'Send Test Email', 'primary', 'send_test_email', false ); ?>
|
||||
</p>
|
||||
</form>
|
||||
|
||||
<h2>Email Preview</h2>
|
||||
<iframe
|
||||
src="<?php echo esc_url( $preview_url ); ?>"
|
||||
style="width: 100%; height: 800px; border: 1px solid #ccd0d4; background: #fff;"
|
||||
></iframe>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Send test email
|
||||
*
|
||||
* @return string Notice HTML
|
||||
*/
|
||||
private function send_test_email() {
|
||||
switch_to_locale( 'de_DE' );
|
||||
$data = self::generate_test_data();
|
||||
$html = Umzugsliste_Email_Generator::generate( $data );
|
||||
restore_previous_locale();
|
||||
|
||||
$to = get_option( 'umzugsliste_receiver_email', get_option( 'admin_email' ) );
|
||||
$subject = 'TEST - Internetanfrage - Anfrage vom ' . date( 'd.m.Y H:i' );
|
||||
$headers = array( 'Content-Type: text/html; charset=UTF-8' );
|
||||
|
||||
$sent = wp_mail( $to, $subject, $html, $headers );
|
||||
|
||||
if ( $sent ) {
|
||||
return '<div class="notice notice-success is-dismissible"><p>Test email sent to <strong>' . esc_html( $to ) . '</strong>.</p></div>';
|
||||
}
|
||||
return '<div class="notice notice-error is-dismissible"><p>Failed to send test email. Check your mail configuration.</p></div>';
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -1137,3 +1137,22 @@ msgstr "Mobil (Beladeadresse)"
|
||||
#: includes/class-cpt.php
|
||||
msgid "Mobile (Unloading)"
|
||||
msgstr "Mobil (Entladeadresse)"
|
||||
|
||||
#: includes/class-shortcode.php
|
||||
#: templates/form-page.php
|
||||
msgid "Step"
|
||||
msgstr "Schritt"
|
||||
|
||||
#: includes/class-shortcode.php
|
||||
#: templates/form-page.php
|
||||
msgid "of"
|
||||
msgstr "von"
|
||||
|
||||
#: includes/class-shortcode.php
|
||||
#: templates/form-page.php
|
||||
msgid "Edit"
|
||||
msgstr "Bearbeiten"
|
||||
|
||||
#: includes/class-form-renderer.php
|
||||
msgid "Montage?"
|
||||
msgstr "Montage?"
|
||||
|
||||
@@ -40,6 +40,18 @@ $l10n_data = array(
|
||||
'grandTotalLabel' => __( 'Grand Total', 'siegel-umzugsliste' ),
|
||||
'quantityLabel' => __( 'Qty', 'siegel-umzugsliste' ),
|
||||
'cbmLabel' => __( 'cbm', 'siegel-umzugsliste' ),
|
||||
'summaryEdit' => __( 'Edit', 'siegel-umzugsliste' ),
|
||||
'summaryName' => __( 'Name', 'siegel-umzugsliste' ),
|
||||
'summaryStreet' => __( 'Street', 'siegel-umzugsliste' ),
|
||||
'summaryZipCity' => __( 'ZIP/City', 'siegel-umzugsliste' ),
|
||||
'summaryFloor' => __( 'Floor', 'siegel-umzugsliste' ),
|
||||
'summaryElevator' => __( 'Elevator', 'siegel-umzugsliste' ),
|
||||
'summaryPhone' => __( 'Phone', 'siegel-umzugsliste' ),
|
||||
'summaryFax' => __( 'Fax', 'siegel-umzugsliste' ),
|
||||
'summaryMobile' => __( 'Mobile', 'siegel-umzugsliste' ),
|
||||
'summaryEmail' => __( 'Email', 'siegel-umzugsliste' ),
|
||||
'stepLabel' => __( 'Step', 'siegel-umzugsliste' ),
|
||||
'stepOf' => __( 'of', 'siegel-umzugsliste' ),
|
||||
'nonce' => wp_create_nonce( 'umzugsliste_submit' ),
|
||||
);
|
||||
?>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Plugin Name: Umzugsliste
|
||||
* Description: Email-basiertes Möbelauswahlsystem für Siegel Umzüge
|
||||
* Version: 1.0.0
|
||||
* Author: Siegel Umzüge
|
||||
* Author: Viktor Miller
|
||||
* Text Domain: siegel-umzugsliste
|
||||
* Domain Path: /languages
|
||||
*/
|
||||
@@ -84,6 +84,7 @@ class Umzugsliste {
|
||||
require_once UMZUGSLISTE_PLUGIN_DIR . 'includes/class-form-renderer.php';
|
||||
require_once UMZUGSLISTE_PLUGIN_DIR . 'includes/class-shortcode.php';
|
||||
require_once UMZUGSLISTE_PLUGIN_DIR . 'includes/class-email-generator.php';
|
||||
require_once UMZUGSLISTE_PLUGIN_DIR . 'includes/class-test-email.php';
|
||||
require_once UMZUGSLISTE_PLUGIN_DIR . 'includes/class-form-handler.php';
|
||||
}
|
||||
|
||||
@@ -152,9 +153,10 @@ class Umzugsliste {
|
||||
$cpt = Umzugsliste_CPT::get_instance();
|
||||
$cpt->register_post_type();
|
||||
|
||||
// Initialize admin menu
|
||||
// Initialize admin menu and test email
|
||||
if ( is_admin() ) {
|
||||
Umzugsliste_Admin_Menu::get_instance();
|
||||
Umzugsliste_Test_Email::get_instance();
|
||||
}
|
||||
|
||||
// Initialize settings
|
||||
|
||||
Reference in New Issue
Block a user