1230 lines
55 KiB
HTML
1230 lines
55 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Ferrero - AC Booking Tool</title>
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #f5f0eb; color: #333; }
|
|
|
|
/* Header */
|
|
.header {
|
|
background: linear-gradient(135deg, #4a2c0f 0%, #6b3a1f 100%);
|
|
padding: 16px 32px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
.header-left { display: flex; align-items: center; gap: 16px; }
|
|
.logo-text { font-size: 28px; font-weight: 700; color: #fff; letter-spacing: 1px; }
|
|
.logo-badge { background: #c8942a; color: #fff; font-size: 11px; font-weight: 700; padding: 3px 8px; border-radius: 4px; }
|
|
.header-subtitle { color: #d4b88c; font-size: 13px; margin-left: 8px; }
|
|
.header-right { color: #d4b88c; font-size: 13px; }
|
|
|
|
/* Info Banner */
|
|
.info-banner {
|
|
background: #fff8e1;
|
|
border: 1px solid #c8942a;
|
|
border-radius: 6px;
|
|
padding: 10px 20px;
|
|
margin: 16px 32px 0;
|
|
font-size: 13px;
|
|
color: #6b3a1f;
|
|
}
|
|
|
|
/* Tabs */
|
|
.tabs { display: flex; margin: 20px 32px 0; gap: 2px; }
|
|
.tab {
|
|
padding: 10px 24px;
|
|
background: #6b3a1f;
|
|
color: #fff;
|
|
border: none;
|
|
border-radius: 8px 8px 0 0;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Main Content */
|
|
.content {
|
|
background: #fff;
|
|
margin: 0 32px 32px;
|
|
border-radius: 0 8px 8px 8px;
|
|
padding: 24px;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
|
min-height: 500px;
|
|
}
|
|
.content h2 { font-size: 20px; color: #4a2c0f; margin-bottom: 16px; }
|
|
.content p.desc { font-size: 13px; color: #777; margin-bottom: 20px; }
|
|
|
|
/* Toolbar */
|
|
.toolbar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
margin-bottom: 16px;
|
|
flex-wrap: wrap;
|
|
}
|
|
.toolbar .btn {
|
|
padding: 8px 20px;
|
|
border-radius: 6px;
|
|
border: 1px solid #ccc;
|
|
background: #f5f5f5;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
.toolbar .btn:hover:not(:disabled) { background: #e8e8e8; }
|
|
.toolbar .btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
.toolbar .btn-primary { background: #6b3a1f; color: #fff; border-color: #6b3a1f; }
|
|
.toolbar .btn-primary:hover:not(:disabled) { background: #4a2c0f; }
|
|
.toolbar .btn-duplicate { background: #c8942a; color: #fff; border-color: #c8942a; }
|
|
.toolbar .btn-duplicate:hover:not(:disabled) { background: #a87a1f; }
|
|
.toolbar .btn-analyse { background: #2a7bc8; color: #fff; border-color: #2a7bc8; }
|
|
.toolbar .btn-analyse:hover:not(:disabled) { background: #1f5fa8; }
|
|
.toolbar .btn-danger { background: #c0392b; color: #fff; border-color: #c0392b; }
|
|
.toolbar .btn-danger:hover:not(:disabled) { background: #a93226; }
|
|
.toolbar .btn-accept { background: #27ae60; color: #fff; border-color: #27ae60; }
|
|
.toolbar .btn-accept:hover:not(:disabled) { background: #1e8449; }
|
|
.toolbar .btn-back { background: #7f8c8d; color: #fff; border-color: #7f8c8d; }
|
|
.toolbar .btn-back:hover:not(:disabled) { background: #636e72; }
|
|
.toolbar .spacer { flex: 1; }
|
|
.toolbar .selected-count { font-size: 13px; color: #6b3a1f; font-weight: 600; }
|
|
|
|
/* Table */
|
|
.table-wrapper { overflow-x: auto; border: 1px solid #e0d6c8; border-radius: 8px; }
|
|
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
thead th {
|
|
background: #6b3a1f;
|
|
color: #fff;
|
|
padding: 10px 12px;
|
|
text-align: left;
|
|
font-weight: 600;
|
|
white-space: nowrap;
|
|
position: sticky;
|
|
top: 0;
|
|
}
|
|
thead th:first-child { width: 40px; text-align: center; }
|
|
thead th .select-all-wrap { display: flex; align-items: center; justify-content: center; }
|
|
/* Input table column widths */
|
|
#dataTable th:nth-child(3) { min-width: 340px; } /* Master File Name - wide */
|
|
#dataTable th:nth-child(4) { min-width: 120px; } /* Local Meta ID - narrower */
|
|
#dataTable th:nth-child(5),
|
|
#dataTable th:nth-child(6),
|
|
#dataTable th:nth-child(7),
|
|
#dataTable th:nth-child(8) { min-width: 100px; } /* Dropdowns - narrower */
|
|
#dataTable td:nth-child(3) input { min-width: 320px; }
|
|
#dataTable td:nth-child(4) input { min-width: 100px; }
|
|
#dataTable td:nth-child(5) select,
|
|
#dataTable td:nth-child(6) select,
|
|
#dataTable td:nth-child(7) select,
|
|
#dataTable td:nth-child(8) select { min-width: 90px; font-size: 12px; }
|
|
tbody tr { border-bottom: 1px solid #f0e8de; transition: background 0.15s; }
|
|
tbody tr:hover { background: #faf6f0; }
|
|
tbody tr.selected { background: #fff3d6; }
|
|
tbody td { padding: 6px 12px; vertical-align: middle; }
|
|
tbody td:first-child { text-align: center; }
|
|
|
|
/* Inline editable cells */
|
|
td input[type="text"], .output-table td input[type="text"] {
|
|
width: 100%; min-width: 140px;
|
|
border: 1px solid transparent; background: transparent;
|
|
padding: 5px 8px; font-size: 13px; border-radius: 4px;
|
|
transition: border-color 0.2s;
|
|
text-overflow: ellipsis; overflow: hidden;
|
|
}
|
|
td input[type="text"]:hover { border-color: #d4c4b0; }
|
|
td input[type="text"]:focus { border-color: #c8942a; outline: none; background: #fff; }
|
|
td select {
|
|
width: 100%; min-width: 90px;
|
|
border: 1px solid transparent; background: transparent;
|
|
padding: 5px 6px; font-size: 13px; border-radius: 4px;
|
|
cursor: pointer; transition: border-color 0.2s;
|
|
}
|
|
td select:hover { border-color: #d4c4b0; }
|
|
td select:focus { border-color: #c8942a; outline: none; background: #fff; }
|
|
td input[type="date"] {
|
|
border: 1px solid transparent; background: transparent;
|
|
padding: 5px 8px; font-size: 13px; border-radius: 4px;
|
|
cursor: pointer; transition: border-color 0.2s;
|
|
}
|
|
td input[type="date"]:hover { border-color: #d4c4b0; }
|
|
td input[type="date"]:focus { border-color: #c8942a; outline: none; background: #fff; }
|
|
input[type="checkbox"] { width: 16px; height: 16px; cursor: pointer; accent-color: #c8942a; }
|
|
|
|
/* Modal */
|
|
.modal-overlay {
|
|
display: none; position: fixed; inset: 0;
|
|
background: rgba(0,0,0,0.5); z-index: 1000;
|
|
align-items: center; justify-content: center;
|
|
}
|
|
.modal-overlay.active { display: flex; }
|
|
.modal {
|
|
background: #fff; border-radius: 12px; width: 560px; max-width: 95vw;
|
|
box-shadow: 0 8px 32px rgba(0,0,0,0.2); overflow: hidden;
|
|
}
|
|
.modal-header {
|
|
background: #6b3a1f; color: #fff; padding: 16px 24px;
|
|
font-size: 16px; font-weight: 600;
|
|
display: flex; justify-content: space-between; align-items: center;
|
|
}
|
|
.modal-header .close-btn { background: none; border: none; color: #fff; font-size: 22px; cursor: pointer; padding: 0 4px; }
|
|
.modal-body { padding: 24px; max-height: 60vh; overflow-y: auto; }
|
|
.modal-body .field-group { margin-bottom: 16px; }
|
|
.modal-body label {
|
|
display: block; font-size: 12px; font-weight: 600; color: #6b3a1f;
|
|
margin-bottom: 4px; text-transform: uppercase; letter-spacing: 0.5px;
|
|
}
|
|
.modal-body select, .modal-body input[type="date"], .modal-body input[type="text"] {
|
|
width: 100%; padding: 10px 12px; border: 1px solid #d4c4b0;
|
|
border-radius: 6px; font-size: 14px; background: #fff;
|
|
}
|
|
.modal-body select:focus, .modal-body input[type="date"]:focus, .modal-body input[type="text"]:focus {
|
|
border-color: #c8942a; outline: none;
|
|
}
|
|
.modal-body .hint { font-size: 11px; color: #999; margin-top: 3px; }
|
|
.modal-footer {
|
|
padding: 16px 24px; background: #faf6f0;
|
|
display: flex; justify-content: flex-end; gap: 10px;
|
|
}
|
|
.modal-footer .btn {
|
|
padding: 10px 24px; border-radius: 6px; border: 1px solid #ccc;
|
|
font-size: 13px; font-weight: 600; cursor: pointer;
|
|
}
|
|
.modal-footer .btn-cancel { background: #f5f5f5; }
|
|
.modal-footer .btn-cancel:hover { background: #e8e8e8; }
|
|
.modal-footer .btn-apply { background: #c8942a; color: #fff; border-color: #c8942a; }
|
|
.modal-footer .btn-apply:hover { background: #a87a1f; }
|
|
|
|
.row-num { color: #999; font-size: 11px; font-weight: 600; min-width: 24px; text-align: center; }
|
|
|
|
/* Output section */
|
|
.output-section { display: none; margin-top: 24px; }
|
|
.output-section.visible { display: block; }
|
|
.output-section h2 { margin-top: 0; }
|
|
.section-divider { border: none; border-top: 2px solid #e0d6c8; margin: 24px 0; }
|
|
|
|
/* Output table cell text (non-editable) */
|
|
.output-table td.cell-text {
|
|
padding: 8px 12px;
|
|
font-size: 13px;
|
|
white-space: nowrap;
|
|
}
|
|
.output-table td.cell-text.status-booked {
|
|
color: #27ae60;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Tooltip on hover for truncated cells */
|
|
td { position: relative; }
|
|
td input[type="text"]:hover, td select:hover { z-index: 1; }
|
|
.cell-tooltip {
|
|
position: absolute;
|
|
left: 0; top: 100%;
|
|
background: #333; color: #fff;
|
|
padding: 6px 10px; border-radius: 4px;
|
|
font-size: 12px; white-space: nowrap;
|
|
z-index: 100; pointer-events: none;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|
max-width: 500px; overflow: hidden; text-overflow: ellipsis;
|
|
}
|
|
|
|
/* Campaign modal */
|
|
.campaign-input {
|
|
width: 100%; padding: 12px; border: 2px solid #d4c4b0;
|
|
border-radius: 6px; font-size: 18px; text-align: center;
|
|
letter-spacing: 4px; font-weight: 600;
|
|
}
|
|
.campaign-input:focus { border-color: #c8942a; outline: none; }
|
|
.campaign-error { color: #c0392b; font-size: 12px; margin-top: 6px; display: none; }
|
|
|
|
@media (max-width: 768px) {
|
|
.header { padding: 12px 16px; }
|
|
.content { margin: 0 12px 16px; padding: 16px; }
|
|
.tabs { margin: 16px 12px 0; }
|
|
.info-banner { margin: 12px 16px 0; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- Header -->
|
|
<div class="header">
|
|
<div class="header-left">
|
|
<span class="logo-text">Ferrero</span>
|
|
<span class="logo-badge">V3</span>
|
|
<span class="header-subtitle">Communication Assets - Naming Convention Tool with Tracking IDs</span>
|
|
</div>
|
|
<div class="header-right">Inclusive of: Meta Platforms <span class="logo-badge" style="margin-left:6px;">NEW</span></div>
|
|
</div>
|
|
|
|
<!-- Info Banner -->
|
|
<div class="info-banner">
|
|
Enter asset metadata below. Use <strong>Duplicate</strong> to copy selected rows. Click <strong>Analyse</strong> to generate the output table with computed fields.
|
|
</div>
|
|
|
|
<!-- Tab -->
|
|
<div class="tabs">
|
|
<div class="tab">AC Generator</div>
|
|
</div>
|
|
|
|
<!-- Main Content -->
|
|
<div class="content">
|
|
|
|
<!-- ========== INPUT SECTION ========== -->
|
|
<div id="inputSection">
|
|
<h2>AC Generator - Input Data</h2>
|
|
<p class="desc">Enter or paste asset booking data. Select rows and click Duplicate to copy them. Click Analyse when ready.</p>
|
|
|
|
<div class="toolbar">
|
|
<button class="btn btn-primary" onclick="addRow()">+ Add Row</button>
|
|
<button class="btn btn-duplicate" id="duplicateBtn" disabled onclick="duplicateSelected()">Duplicate</button>
|
|
<button class="btn" id="editBtn" disabled onclick="openInputEditModal()" style="background:#c8942a;color:#fff;border-color:#c8942a;">Edit Selected</button>
|
|
<button class="btn btn-analyse" id="analyseBtn" onclick="runAnalyse()">Analyse</button>
|
|
<span class="selected-count" id="selectedCount"></span>
|
|
<span class="spacer"></span>
|
|
<button class="btn btn-danger" id="deleteBtn" disabled onclick="deleteSelected()">Delete Selected</button>
|
|
</div>
|
|
|
|
<div class="table-wrapper">
|
|
<table id="dataTable">
|
|
<thead>
|
|
<tr>
|
|
<th><div class="select-all-wrap"><input type="checkbox" id="selectAll" onchange="toggleSelectAll(this)"></div></th>
|
|
<th>#</th>
|
|
<th>Master File Name</th>
|
|
<th>Local Meta ID</th>
|
|
<th>Asset Type</th>
|
|
<th>Media Type</th>
|
|
<th>Aspect Ratio</th>
|
|
<th>ISO Code</th>
|
|
<th>Supply Date</th>
|
|
<th>Live Date</th>
|
|
<th>End Date</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="tableBody"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ========== OUTPUT SECTION ========== -->
|
|
<div id="outputSection" class="output-section">
|
|
<h2>AC Generator - Analysed Output</h2>
|
|
<p class="desc">Review the generated output below. Edit any cells as needed, then click Accept to export.</p>
|
|
|
|
<div class="toolbar">
|
|
<button class="btn btn-back" onclick="backToInput()">Back to Input</button>
|
|
<button class="btn btn-edit" id="outputEditBtn" disabled onclick="openOutputEditModal()" style="background:#c8942a;color:#fff;border-color:#c8942a;">Edit Selected</button>
|
|
<span class="selected-count" id="outputSelectedCount"></span>
|
|
<span class="spacer"></span>
|
|
<button class="btn btn-accept" onclick="openCampaignModal('send')">Send to OMG</button>
|
|
<button class="btn" onclick="openCampaignModal('download')" style="background:#6b3a1f;color:#fff;border-color:#6b3a1f;">Download CSV</button>
|
|
</div>
|
|
|
|
<div class="table-wrapper">
|
|
<table id="outputTable" class="output-table">
|
|
<thead>
|
|
<tr>
|
|
<th><div class="select-all-wrap"><input type="checkbox" id="outputSelectAll" onchange="toggleOutputSelectAll(this)"></div></th>
|
|
<th>#</th>
|
|
<th>Title</th>
|
|
<th>Status</th>
|
|
<th>Category</th>
|
|
<th>Media</th>
|
|
<th>Sub Media</th>
|
|
<th>Destination</th>
|
|
<th>Format</th>
|
|
<th>Supply Date</th>
|
|
<th>Live Date</th>
|
|
<th>End Date</th>
|
|
<th>Language</th>
|
|
<th>Country</th>
|
|
<th>Creative Execution</th>
|
|
<th>Quantity</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="outputBody"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- Input Bulk Edit Modal -->
|
|
<div class="modal-overlay" id="inputEditModal">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<span>Bulk Edit Selected Rows</span>
|
|
<button class="close-btn" onclick="closeInputEditModal()">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p style="font-size:13px;color:#777;margin-bottom:16px;">Only fields you change will be applied. Leave as "No change" to keep existing values.</p>
|
|
<div class="field-group">
|
|
<label>Asset Type</label>
|
|
<select id="imAssetType"><option value="">-- No change --</option></select>
|
|
</div>
|
|
<div class="field-group">
|
|
<label>Media Type</label>
|
|
<select id="imMediaType"><option value="">-- No change --</option></select>
|
|
</div>
|
|
<div class="field-group">
|
|
<label>Aspect Ratio</label>
|
|
<select id="imAspectRatio"><option value="">-- No change --</option></select>
|
|
</div>
|
|
<div class="field-group">
|
|
<label>ISO Code</label>
|
|
<select id="imLangCode"><option value="">-- No change --</option></select>
|
|
</div>
|
|
<div class="field-group">
|
|
<label>Supply Date</label>
|
|
<input type="date" id="imSupplyDate">
|
|
</div>
|
|
<div class="field-group">
|
|
<label>Live Date</label>
|
|
<input type="date" id="imLiveDate">
|
|
</div>
|
|
<div class="field-group">
|
|
<label>End Date</label>
|
|
<input type="date" id="imEndDate">
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-cancel" onclick="closeInputEditModal()">Cancel</button>
|
|
<button class="btn btn-apply" onclick="applyInputEdit()">Apply Changes</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Output Edit Modal -->
|
|
<div class="modal-overlay" id="outputEditModal">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<span>Edit Selected Output Rows</span>
|
|
<button class="close-btn" onclick="closeOutputEditModal()">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p style="font-size:13px;color:#777;margin-bottom:16px;">Only fields you change will be applied. Leave as "No change" to keep existing values.</p>
|
|
<div class="field-group">
|
|
<label>Title</label>
|
|
<input type="text" id="omTitle" placeholder="-- No change --">
|
|
</div>
|
|
<div class="field-group">
|
|
<label>Status</label>
|
|
<input type="text" id="omStatus" placeholder="-- No change --">
|
|
</div>
|
|
<div class="field-group">
|
|
<label>Category</label>
|
|
<input type="text" id="omCategory" placeholder="-- No change --">
|
|
</div>
|
|
<div class="field-group">
|
|
<label>Media</label>
|
|
<input type="text" id="omMedia" placeholder="-- No change --">
|
|
</div>
|
|
<div class="field-group">
|
|
<label>Sub Media</label>
|
|
<input type="text" id="omSubMedia" placeholder="-- No change --">
|
|
</div>
|
|
<div class="field-group">
|
|
<label>Destination</label>
|
|
<input type="text" id="omDestination" placeholder="-- No change --">
|
|
</div>
|
|
<div class="field-group">
|
|
<label>Format</label>
|
|
<input type="text" id="omFormat" placeholder="-- No change --">
|
|
</div>
|
|
<div class="field-group">
|
|
<label>Supply Date</label>
|
|
<input type="date" id="omSupplyDate">
|
|
</div>
|
|
<div class="field-group">
|
|
<label>Live Date</label>
|
|
<input type="date" id="omLiveDate">
|
|
</div>
|
|
<div class="field-group">
|
|
<label>End Date</label>
|
|
<input type="date" id="omEndDate">
|
|
</div>
|
|
<div class="field-group">
|
|
<label>Language</label>
|
|
<input type="text" id="omLanguage" placeholder="-- No change --">
|
|
</div>
|
|
<div class="field-group">
|
|
<label>Country</label>
|
|
<input type="text" id="omCountry" placeholder="-- No change --">
|
|
</div>
|
|
<div class="field-group">
|
|
<label>Creative Execution</label>
|
|
<input type="text" id="omCreativeExec" placeholder="-- No change --">
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-cancel" onclick="closeOutputEditModal()">Cancel</button>
|
|
<button class="btn btn-apply" onclick="applyOutputEdit()">Apply Changes</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Campaign Number Modal -->
|
|
<div class="modal-overlay" id="campaignModal">
|
|
<div class="modal" style="width:400px;">
|
|
<div class="modal-header">
|
|
<span id="campaignModalTitle">Enter Campaign Number</span>
|
|
<button class="close-btn" onclick="closeCampaignModal()">×</button>
|
|
</div>
|
|
<div class="modal-body" style="text-align:center;">
|
|
<p style="font-size:13px;color:#777;margin-bottom:16px;">Please enter the 7-digit campaign number for the export file.</p>
|
|
<input type="text" class="campaign-input" id="campaignNumber" maxlength="7" placeholder="0000000" oninput="validateCampaign()">
|
|
<div class="campaign-error" id="campaignError">Campaign number must be exactly 7 digits.</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-cancel" onclick="closeCampaignModal()">Cancel</button>
|
|
<button class="btn btn-apply" id="campaignSubmitBtn" disabled onclick="submitCampaignExport()">Submit</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Success Modal -->
|
|
<div class="modal-overlay" id="successModal">
|
|
<div class="modal" style="width:400px;">
|
|
<div class="modal-header" style="background:#27ae60;">
|
|
<span>Success</span>
|
|
</div>
|
|
<div class="modal-body" style="text-align:center;padding:32px 24px;">
|
|
<div style="font-size:48px;margin-bottom:12px;">✓</div>
|
|
<p id="successMessage" style="font-size:15px;color:#333;font-weight:600;">CSV has been passed to OMG</p>
|
|
</div>
|
|
<div class="modal-footer" style="justify-content:center;">
|
|
<button class="btn btn-apply" onclick="closeSuccessModal()" style="background:#27ae60;border-color:#27ae60;min-width:100px;">OK</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// =====================================================================
|
|
// DROPDOWN DATA
|
|
// =====================================================================
|
|
const assetTypes = [
|
|
"Font","Gadget","Group Guidelines","Immagine Guida","Front of Pack Image",
|
|
"Local Guidelines","Logo","Marketing Leaflet","On Line Video","Pack Artworks",
|
|
"Pack Images","POS Material","Product Demo","Product Images","QR code","Sound",
|
|
"Styleguide Internal Properties","Styleguide Licenses","TVC",
|
|
"Visual Identity Elements","3D Real Toys","Applaydu Characters","Brand Book",
|
|
"Brand Character","Brand Signature","Campaign Key Visual","Creative Idea",
|
|
"Digital Assets/Toolkit","E-COMM: A+","E-COMM: Back Packshot",
|
|
"E-COMM: Beauty shot","E-COMM: Brand Store","E-COMM: E-Media",
|
|
"E-COMM: Hero Image","E-COMM: Ingredients List","E-COMM: Out Of Pack",
|
|
"E-COMM: UGC","E-COMM: Why Buy","Flyer Artworks"
|
|
];
|
|
|
|
const mediaTypes = [
|
|
"Amazon","DV360","FB - Biz Disco Feed","FB - Groups Feed","FB - Reels",
|
|
"FB - Reels Overlay","FB - Stories","FB - Feed","FB - Instant Article",
|
|
"FB - Instream Video","FB - Marketplace","FB - Profile Feed",
|
|
"FB - Right Hand Column","FB - Search","FB - Search Serp",
|
|
"FB - Suggested Video","FB - Unknown","FB - Video Feeds",
|
|
"Audience Network - An Classic","Audience Network - Instream Video",
|
|
"Audience Network - Rewarded Video","Messenger - Inbox","Messenger - Stories",
|
|
"Google","IG - Feed","IG - Explore","IG - Explore Grid Home","IG - IGTV",
|
|
"IG - Profile Feed","IG - Profile Reels","IG - Reels","IG - Overlay",
|
|
"IG - Search","IG - Shop","IG - Stories","Pinterest","Snap","TikTok",
|
|
"Twitter","YT - Youtube Bumper","YT - Youtube Instream","YT - Youtube Shorts",
|
|
"YT - Youtube Trueview","YT - Youtube Ad"
|
|
];
|
|
|
|
const aspectRatios = ["9x16","16x9","1x1"];
|
|
|
|
const languageCodes = [
|
|
"AT-de","BE-fr","BE-nl","BG-bg","CH-de","CH-fr","CH-it","CZ-cs","DE-de",
|
|
"DK-da","EE-et","ES-es","FI-fi","FR-fr","GB-en","GR-el","HR-hr","HU-hu",
|
|
"IE-en","IT-it","LT-lt","LU-fr","LV-lv","NL-nl","NO-no","PL-pl","PT-pt",
|
|
"RO-ro","RS-sr","SE-sv","SI-sl","SK-sk","TR-tr","UA-uk","US-en","AU-en",
|
|
"BR-pt","CA-en","CA-fr","CN-zh","HK-zh","ID-id","IN-en","IN-hi","JP-ja",
|
|
"KR-ko","MX-es","MY-ms","NZ-en","PH-en","RU-ru","SG-en","TH-th","TW-zh",
|
|
"VN-vi","ZA-en","AE-ar","EG-ar","IL-he","SA-ar","GLO-en"
|
|
];
|
|
|
|
// =====================================================================
|
|
// LOOKUP TABLES (from Excel Dropdowns sheet)
|
|
// =====================================================================
|
|
// Asset Name → Asset Code
|
|
const assetCodeMap = {
|
|
"Font":"FNT","Gadget":"GDT","Group Guidelines":"GRG","Immagine Guida":"IMG",
|
|
"Front of Pack Image":"FPO","Local Guidelines":"LGL","Logo":"LOG",
|
|
"Marketing Leaflet":"MLF","On Line Video":"OLV","Pack Artworks":"PAW",
|
|
"Pack Images":"PKI","POS Material":"POS","Product Demo":"PDM",
|
|
"Product Images":"PRI","QR code":"QRC","Sound":"SND",
|
|
"Styleguide Internal Properties":"SIP","Styleguide Licenses":"SGL","TVC":"TVC",
|
|
"Visual Identity Elements":"VIE","3D Real Toys":"3RT","Applaydu Characters":"APC",
|
|
"Brand Book":"BBK","Brand Character":"BRC","Brand Signature":"BSG",
|
|
"Campaign Key Visual":"CKV","Creative Idea":"CID","Digital Assets/Toolkit":"DAT",
|
|
"E-COMM: A+":"ECA","E-COMM: Back Packshot":"ECB","E-COMM: Beauty shot":"EBS",
|
|
"E-COMM: Brand Store":"EBR","E-COMM: E-Media":"EEM","E-COMM: Hero Image":"EHI",
|
|
"E-COMM: Ingredients List":"EIL","E-COMM: Out Of Pack":"EOP",
|
|
"E-COMM: UGC":"EUG","E-COMM: Why Buy":"EWB","Flyer Artworks":"FLA"
|
|
};
|
|
|
|
// Media Name → Media Code
|
|
const mediaCodeMap = {
|
|
"Amazon":"AMZ","DV360":"DV3","FB - Biz Disco Feed":"FBD","FB - Groups Feed":"FGF",
|
|
"FB - Reels":"FBR","FB - Reels Overlay":"FRO","FB - Stories":"FBS","FB - Feed":"FBF",
|
|
"FB - Instant Article":"FIA","FB - Instream Video":"FIV","FB - Marketplace":"FMP",
|
|
"FB - Profile Feed":"FPF","FB - Right Hand Column":"FRC","FB - Search":"FSE",
|
|
"FB - Search Serp":"FSS","FB - Suggested Video":"FSV","FB - Unknown":"FUK",
|
|
"FB - Video Feeds":"FVF","Audience Network - An Classic":"ANC",
|
|
"Audience Network - Instream Video":"ANI","Audience Network - Rewarded Video":"ANR",
|
|
"Messenger - Inbox":"MSI","Messenger - Stories":"MSS","Google":"GOO",
|
|
"IG - Feed":"IGF","IG - Explore":"IGE","IG - Explore Grid Home":"IGG",
|
|
"IG - IGTV":"IGT","IG - Profile Feed":"IPF","IG - Profile Reels":"IPR",
|
|
"IG - Reels":"IGR","IG - Overlay":"IGO","IG - Search":"IGS","IG - Shop":"ISH",
|
|
"IG - Stories":"IST","Pinterest":"PIN","Snap":"SNA","TikTok":"TIK",
|
|
"Twitter":"TWI","YT - Youtube Bumper":"YTB","YT - Youtube Instream":"YTI",
|
|
"YT - Youtube Shorts":"YTS","YT - Youtube Trueview":"YTT","YT - Youtube Ad":"YTA"
|
|
};
|
|
|
|
// ISO Code → { language, country }
|
|
// Format: CC-ll where CC = country, ll = language
|
|
const langCountryMap = {
|
|
"AT-de": { language: "German (de-at)", country: "Austria" },
|
|
"BE-fr": { language: "French (fr-be)", country: "Belgium" },
|
|
"BE-nl": { language: "Dutch (nl-be)", country: "Belgium" },
|
|
"BG-bg": { language: "Bulgarian (bg-bg)", country: "Bulgaria" },
|
|
"CH-de": { language: "German (de-ch)", country: "Switzerland" },
|
|
"CH-fr": { language: "French (fr-ch)", country: "Switzerland" },
|
|
"CH-it": { language: "Italian (it-ch)", country: "Switzerland" },
|
|
"CZ-cs": { language: "Czech (cs-cz)", country: "Czech Republic" },
|
|
"DE-de": { language: "German (de-de)", country: "Germany" },
|
|
"DK-da": { language: "Danish (da-dk)", country: "Denmark" },
|
|
"EE-et": { language: "Estonian (et-ee)", country: "Estonia" },
|
|
"ES-es": { language: "Spanish (es-es)", country: "Spain" },
|
|
"FI-fi": { language: "Finnish (fi-fi)", country: "Finland" },
|
|
"FR-fr": { language: "French (fr-fr)", country: "France" },
|
|
"GB-en": { language: "English (en-gb)", country: "United Kingdom" },
|
|
"GR-el": { language: "Greek (el-gr)", country: "Greece" },
|
|
"HR-hr": { language: "Croatian (hr-hr)", country: "Croatia" },
|
|
"HU-hu": { language: "Hungarian (hu-hu)", country: "Hungary" },
|
|
"IE-en": { language: "English (en-ie)", country: "Ireland" },
|
|
"IT-it": { language: "Italian (it-it)", country: "Italy" },
|
|
"LT-lt": { language: "Lithuanian (lt-lt)", country: "Lithuania" },
|
|
"LU-fr": { language: "French (fr-lu)", country: "Luxembourg" },
|
|
"LV-lv": { language: "Latvian (lv-lv)", country: "Latvia" },
|
|
"NL-nl": { language: "Dutch (nl-nl)", country: "Netherlands" },
|
|
"NO-no": { language: "Norwegian (no-no)", country: "Norway" },
|
|
"PL-pl": { language: "Polish (pl-pl)", country: "Poland" },
|
|
"PT-pt": { language: "Portuguese (pt-pt)", country: "Portugal" },
|
|
"RO-ro": { language: "Romanian (ro-ro)", country: "Romania" },
|
|
"RS-sr": { language: "Serbian (sr-rs)", country: "Serbia" },
|
|
"SE-sv": { language: "Swedish (sv-se)", country: "Sweden" },
|
|
"SI-sl": { language: "Slovenian (sl-si)", country: "Slovenia" },
|
|
"SK-sk": { language: "Slovak (sk-sk)", country: "Slovakia" },
|
|
"TR-tr": { language: "Turkish (tr-tr)", country: "Turkey" },
|
|
"UA-uk": { language: "Ukrainian (uk-ua)", country: "Ukraine" },
|
|
"US-en": { language: "English (en-us)", country: "United States" },
|
|
"AU-en": { language: "English (en-au)", country: "Australia" },
|
|
"BR-pt": { language: "Portuguese (pt-br)", country: "Brazil" },
|
|
"CA-en": { language: "English (en-ca)", country: "Canada" },
|
|
"CA-fr": { language: "French (fr-ca)", country: "Canada" },
|
|
"CN-zh": { language: "Chinese (zh-cn)", country: "China" },
|
|
"HK-zh": { language: "Chinese (zh-hk)", country: "Hong Kong" },
|
|
"ID-id": { language: "Indonesian (id-id)", country: "Indonesia" },
|
|
"IN-en": { language: "English (en-in)", country: "India" },
|
|
"IN-hi": { language: "Hindi (hi-in)", country: "India" },
|
|
"JP-ja": { language: "Japanese (ja-jp)", country: "Japan" },
|
|
"KR-ko": { language: "Korean (ko-kr)", country: "South Korea" },
|
|
"MX-es": { language: "Spanish (es-mx)", country: "Mexico" },
|
|
"MY-ms": { language: "Malay (ms-my)", country: "Malaysia" },
|
|
"NZ-en": { language: "English (en-nz)", country: "New Zealand" },
|
|
"PH-en": { language: "English (en-ph)", country: "Philippines" },
|
|
"RU-ru": { language: "Russian (ru-ru)", country: "Russia" },
|
|
"SG-en": { language: "English (en-sg)", country: "Singapore" },
|
|
"TH-th": { language: "Thai (th-th)", country: "Thailand" },
|
|
"TW-zh": { language: "Chinese (zh-tw)", country: "Taiwan" },
|
|
"VN-vi": { language: "Vietnamese (vi-vn)", country: "Vietnam" },
|
|
"ZA-en": { language: "English (en-za)", country: "South Africa" },
|
|
"AE-ar": { language: "Arabic (ar-ae)", country: "United Arab Emirates" },
|
|
"EG-ar": { language: "Arabic (ar-eg)", country: "Egypt" },
|
|
"IL-he": { language: "Hebrew (he-il)", country: "Israel" },
|
|
"SA-ar": { language: "Arabic (ar-sa)", country: "Saudi Arabia" },
|
|
"GLO-en": { language: "English (en)", country: "Global" }
|
|
};
|
|
|
|
// =====================================================================
|
|
// STATE
|
|
// =====================================================================
|
|
let rowIdCounter = 0;
|
|
let rows = [];
|
|
|
|
// =====================================================================
|
|
// BUILD OPTION HTML
|
|
// =====================================================================
|
|
function buildOptions(items) {
|
|
return '<option value="">--</option>' + items.map(i => `<option value="${esc(i)}">${esc(i)}</option>`).join('');
|
|
}
|
|
function esc(s) { return s.replace(/&/g,'&').replace(/"/g,'"').replace(/</g,'<'); }
|
|
|
|
const assetOpts = buildOptions(assetTypes);
|
|
const mediaOpts = buildOptions(mediaTypes);
|
|
const ratioOpts = buildOptions(aspectRatios);
|
|
const langOpts = buildOptions(languageCodes);
|
|
|
|
// =====================================================================
|
|
// INPUT TABLE — ADD ROW
|
|
// =====================================================================
|
|
function addRow(data) {
|
|
rowIdCounter++;
|
|
const id = rowIdCounter;
|
|
const d = data || {};
|
|
rows.push(id);
|
|
|
|
const tr = document.createElement('tr');
|
|
tr.id = `row-${id}`;
|
|
tr.innerHTML = `
|
|
<td><input type="checkbox" class="row-check" data-id="${id}" onchange="updateSelection()"></td>
|
|
<td class="row-num">${id}</td>
|
|
<td><input type="text" data-field="masterFileName" value="${esc(d.masterFileName||'')}" placeholder="Enter file name..." title="${esc(d.masterFileName||'')}"></td>
|
|
<td><input type="text" data-field="metadataId" value="${esc(d.metadataId||'')}" placeholder="Enter metadata ID..." title="${esc(d.metadataId||'')}"></td>
|
|
<td><select data-field="assetType">${assetOpts}</select></td>
|
|
<td><select data-field="mediaType">${mediaOpts}</select></td>
|
|
<td><select data-field="aspectRatio">${ratioOpts}</select></td>
|
|
<td><select data-field="langCode">${langOpts}</select></td>
|
|
<td><input type="date" data-field="supplyDate" value="${d.supplyDate||''}"></td>
|
|
<td><input type="date" data-field="liveDate" value="${d.liveDate||''}"></td>
|
|
<td><input type="date" data-field="endDate" value="${d.endDate||''}"></td>
|
|
`;
|
|
document.getElementById('tableBody').appendChild(tr);
|
|
|
|
if (d.assetType) tr.querySelector('[data-field="assetType"]').value = d.assetType;
|
|
if (d.mediaType) tr.querySelector('[data-field="mediaType"]').value = d.mediaType;
|
|
if (d.aspectRatio) tr.querySelector('[data-field="aspectRatio"]').value = d.aspectRatio;
|
|
if (d.langCode) tr.querySelector('[data-field="langCode"]').value = d.langCode;
|
|
}
|
|
|
|
// =====================================================================
|
|
// INPUT TABLE — SELECTION
|
|
// =====================================================================
|
|
function getSelectedIds() {
|
|
return [...document.querySelectorAll('.row-check:checked')].map(cb => parseInt(cb.dataset.id));
|
|
}
|
|
|
|
function updateSelection() {
|
|
const count = getSelectedIds().length;
|
|
document.getElementById('duplicateBtn').disabled = count === 0;
|
|
document.getElementById('editBtn').disabled = count < 2;
|
|
document.getElementById('deleteBtn').disabled = count === 0;
|
|
document.getElementById('selectedCount').textContent = count > 0 ? `${count} row${count>1?'s':''} selected` : '';
|
|
|
|
document.querySelectorAll('#tableBody tr').forEach(tr => {
|
|
const cb = tr.querySelector('.row-check');
|
|
tr.classList.toggle('selected', cb && cb.checked);
|
|
});
|
|
}
|
|
|
|
function toggleSelectAll(cb) {
|
|
document.querySelectorAll('.row-check').forEach(c => c.checked = cb.checked);
|
|
updateSelection();
|
|
}
|
|
|
|
// =====================================================================
|
|
// DUPLICATE
|
|
// =====================================================================
|
|
function duplicateSelected() {
|
|
const ids = getSelectedIds();
|
|
if (ids.length === 0) return;
|
|
|
|
ids.forEach(id => {
|
|
const tr = document.getElementById(`row-${id}`);
|
|
if (!tr) return;
|
|
const getVal = f => { const el = tr.querySelector(`[data-field="${f}"]`); return el ? el.value : ''; };
|
|
addRow({
|
|
masterFileName: getVal('masterFileName'),
|
|
metadataId: getVal('metadataId'),
|
|
assetType: getVal('assetType'),
|
|
mediaType: getVal('mediaType'),
|
|
aspectRatio: getVal('aspectRatio'),
|
|
langCode: getVal('langCode'),
|
|
supplyDate: getVal('supplyDate'),
|
|
liveDate: getVal('liveDate'),
|
|
endDate: getVal('endDate'),
|
|
});
|
|
});
|
|
|
|
// Flash new rows
|
|
const total = document.querySelectorAll('#tableBody tr');
|
|
for (let i = total.length - ids.length; i < total.length; i++) {
|
|
const tr = total[i];
|
|
tr.style.transition = 'background 0.3s';
|
|
tr.style.background = '#d4edda';
|
|
setTimeout(() => { tr.style.background = ''; }, 800);
|
|
}
|
|
}
|
|
|
|
// =====================================================================
|
|
// DELETE
|
|
// =====================================================================
|
|
function deleteSelected() {
|
|
const ids = getSelectedIds();
|
|
if (ids.length === 0) return;
|
|
if (!confirm(`Delete ${ids.length} selected row(s)?`)) return;
|
|
ids.forEach(id => {
|
|
const tr = document.getElementById(`row-${id}`);
|
|
if (tr) tr.remove();
|
|
rows = rows.filter(r => r !== id);
|
|
});
|
|
document.getElementById('selectAll').checked = false;
|
|
updateSelection();
|
|
}
|
|
|
|
// =====================================================================
|
|
// TITLE FORMULA (replicates Excel Column J)
|
|
//
|
|
// = textBetween1stAnd3rdUnderscore(masterFileName)
|
|
// & "_" & assetCode
|
|
// & "_" & 4thSegment(masterFileName)
|
|
// & "_" & aspectRatio
|
|
// & "_" & langCode
|
|
// & "_" & mediaCode
|
|
// & "_" & metadataId
|
|
// =====================================================================
|
|
function computeTitle(masterFileName, assetType, mediaType, aspectRatio, langCode, metadataId) {
|
|
const parts = masterFileName.split('_');
|
|
// Text between 1st and 3rd underscore = parts[1] + "_" + parts[2]
|
|
const segment1_2 = (parts.length >= 3) ? parts[1] + '_' + parts[2] : (parts[1] || '');
|
|
// 4th segment (index 3)
|
|
const segment3 = parts[3] || '';
|
|
const assetCode = assetCodeMap[assetType] || '';
|
|
const mediaCode = mediaCodeMap[mediaType] || '';
|
|
|
|
return [segment1_2, assetCode, segment3, aspectRatio, langCode, mediaCode, metadataId]
|
|
.filter(Boolean).join('_');
|
|
}
|
|
|
|
// =====================================================================
|
|
// LANGUAGE / COUNTRY RESOLVER
|
|
// =====================================================================
|
|
function resolveLangCountry(code) {
|
|
const entry = langCountryMap[code];
|
|
if (entry) return entry;
|
|
// Fallback: split and return raw
|
|
const [cc, ll] = (code || '').split('-');
|
|
return { language: ll || code, country: cc || code };
|
|
}
|
|
|
|
// =====================================================================
|
|
// INPUT TABLE — BULK EDIT MODAL
|
|
// =====================================================================
|
|
function populateInputModalDropdowns() {
|
|
const fill = (id, items) => {
|
|
const el = document.getElementById(id);
|
|
el.innerHTML = '<option value="">-- No change --</option>' + items.map(i => `<option value="${esc(i)}">${esc(i)}</option>`).join('');
|
|
};
|
|
fill('imAssetType', assetTypes);
|
|
fill('imMediaType', mediaTypes);
|
|
fill('imAspectRatio', aspectRatios);
|
|
fill('imLangCode', languageCodes);
|
|
}
|
|
populateInputModalDropdowns();
|
|
|
|
function openInputEditModal() {
|
|
if (getSelectedIds().length < 2) return;
|
|
['imAssetType','imMediaType','imAspectRatio','imLangCode'].forEach(id => document.getElementById(id).value = '');
|
|
['imSupplyDate','imLiveDate','imEndDate'].forEach(id => document.getElementById(id).value = '');
|
|
document.getElementById('inputEditModal').classList.add('active');
|
|
}
|
|
|
|
function closeInputEditModal() {
|
|
document.getElementById('inputEditModal').classList.remove('active');
|
|
}
|
|
|
|
function applyInputEdit() {
|
|
const ids = getSelectedIds();
|
|
const fieldMap = {
|
|
imAssetType: 'assetType', imMediaType: 'mediaType',
|
|
imAspectRatio: 'aspectRatio', imLangCode: 'langCode',
|
|
imSupplyDate: 'supplyDate', imLiveDate: 'liveDate', imEndDate: 'endDate'
|
|
};
|
|
const changes = {};
|
|
Object.entries(fieldMap).forEach(([modalId, field]) => {
|
|
const val = document.getElementById(modalId).value;
|
|
if (val) changes[field] = val;
|
|
});
|
|
if (Object.keys(changes).length === 0) { alert('No changes specified.'); return; }
|
|
|
|
ids.forEach(id => {
|
|
const tr = document.getElementById(`row-${id}`);
|
|
if (!tr) return;
|
|
Object.entries(changes).forEach(([field, val]) => {
|
|
const el = tr.querySelector(`[data-field="${field}"]`);
|
|
if (el) el.value = val;
|
|
});
|
|
});
|
|
closeInputEditModal();
|
|
flashRows(ids.map(id => document.getElementById(`row-${id}`)));
|
|
}
|
|
|
|
function flashRows(trs) {
|
|
trs.forEach(tr => {
|
|
if (!tr) return;
|
|
tr.style.transition = 'background 0.3s';
|
|
tr.style.background = '#d4edda';
|
|
setTimeout(() => { tr.style.background = ''; }, 800);
|
|
});
|
|
}
|
|
|
|
// =====================================================================
|
|
// CATEGORY / MEDIA / SUB MEDIA / DESTINATION / FORMAT LOGIC
|
|
// =====================================================================
|
|
function computeDerivedFields(mediaType, aspectRatio) {
|
|
const mt = mediaType || '';
|
|
let category = '', media = '', subMedia = '', destination = '', format = '';
|
|
|
|
// Check if media type contains these keywords
|
|
const isSocial = /\bFB\b|Amazon|DV360|\bIG\b|\bYT\b/i.test(mt);
|
|
|
|
if (isSocial) {
|
|
category = 'Social Media';
|
|
media = 'Social';
|
|
subMedia = 'Video';
|
|
}
|
|
|
|
// Destination
|
|
if (/\bFB\b/i.test(mt) || /\bIG\b/i.test(mt)) {
|
|
destination = '[Facebook - Instagram | Meta]';
|
|
} else if (/Amazon/i.test(mt)) {
|
|
destination = 'Amazon';
|
|
} else if (/DV360/i.test(mt)) {
|
|
destination = 'DV360';
|
|
} else if (/\bYT\b/i.test(mt)) {
|
|
destination = 'YouTube';
|
|
}
|
|
|
|
// Format: text after the hyphen + " | " + aspect ratio
|
|
// e.g. "FB - Feed" → "Feed | 16x9"
|
|
const hyphenIdx = mt.indexOf(' - ');
|
|
if (hyphenIdx !== -1) {
|
|
const afterHyphen = mt.substring(hyphenIdx + 3).trim();
|
|
format = afterHyphen + (aspectRatio ? ' | ' + aspectRatio : '');
|
|
} else if (aspectRatio) {
|
|
format = aspectRatio;
|
|
}
|
|
|
|
return { category, media, subMedia, destination, format };
|
|
}
|
|
|
|
// =====================================================================
|
|
// ANALYSE — build output table
|
|
// =====================================================================
|
|
let outputData = [];
|
|
|
|
function runAnalyse() {
|
|
const inputRows = document.querySelectorAll('#tableBody tr');
|
|
if (inputRows.length === 0) { alert('No rows to analyse. Add some data first.'); return; }
|
|
|
|
outputData = [];
|
|
inputRows.forEach((tr, idx) => {
|
|
const getVal = f => { const el = tr.querySelector(`[data-field="${f}"]`); return el ? el.value : ''; };
|
|
const masterFileName = getVal('masterFileName');
|
|
const metadataId = getVal('metadataId');
|
|
const assetType = getVal('assetType');
|
|
const mediaType = getVal('mediaType');
|
|
const aspectRatio = getVal('aspectRatio');
|
|
const langCode = getVal('langCode');
|
|
const supplyDate = getVal('supplyDate');
|
|
const liveDate = getVal('liveDate');
|
|
const endDate = getVal('endDate');
|
|
|
|
const title = computeTitle(masterFileName, assetType, mediaType, aspectRatio, langCode, metadataId);
|
|
const lc = resolveLangCountry(langCode);
|
|
const derived = computeDerivedFields(mediaType, aspectRatio);
|
|
|
|
outputData.push({
|
|
title,
|
|
status: 'Booked',
|
|
category: derived.category,
|
|
media: derived.media,
|
|
subMedia: derived.subMedia,
|
|
destination: derived.destination,
|
|
format: derived.format,
|
|
supplyDate,
|
|
liveDate,
|
|
endDate,
|
|
language: lc.language,
|
|
country: lc.country,
|
|
creativeExecution: masterFileName,
|
|
quantity: '1',
|
|
});
|
|
});
|
|
|
|
renderOutputTable();
|
|
document.getElementById('inputSection').style.display = 'none';
|
|
document.getElementById('outputSection').classList.add('visible');
|
|
}
|
|
|
|
function renderOutputTable() {
|
|
const tbody = document.getElementById('outputBody');
|
|
tbody.innerHTML = '';
|
|
|
|
outputData.forEach((row, idx) => {
|
|
const tr = document.createElement('tr');
|
|
tr.id = `out-${idx}`;
|
|
tr.innerHTML = `
|
|
<td><input type="checkbox" class="out-check" data-idx="${idx}" onchange="updateOutputSelection()"></td>
|
|
<td class="row-num">${idx + 1}</td>
|
|
<td><input type="text" data-field="title" value="${esc(row.title)}" title="${esc(row.title)}"></td>
|
|
<td class="cell-text status-booked">${esc(row.status)}</td>
|
|
<td><input type="text" data-field="category" value="${esc(row.category)}" title="${esc(row.category)}"></td>
|
|
<td><input type="text" data-field="media" value="${esc(row.media)}" title="${esc(row.media)}"></td>
|
|
<td><input type="text" data-field="subMedia" value="${esc(row.subMedia)}" title="${esc(row.subMedia)}"></td>
|
|
<td><input type="text" data-field="destination" value="${esc(row.destination)}" title="${esc(row.destination)}"></td>
|
|
<td><input type="text" data-field="format" value="${esc(row.format)}" title="${esc(row.format)}"></td>
|
|
<td><input type="date" data-field="supplyDate" value="${row.supplyDate}"></td>
|
|
<td><input type="date" data-field="liveDate" value="${row.liveDate}"></td>
|
|
<td><input type="date" data-field="endDate" value="${row.endDate}"></td>
|
|
<td><input type="text" data-field="language" value="${esc(row.language)}" title="${esc(row.language)}"></td>
|
|
<td><input type="text" data-field="country" value="${esc(row.country)}" title="${esc(row.country)}"></td>
|
|
<td><input type="text" data-field="creativeExecution" value="${esc(row.creativeExecution)}" title="${esc(row.creativeExecution)}"></td>
|
|
<td><input type="text" data-field="quantity" value="${esc(row.quantity)}" style="min-width:50px;text-align:center;"></td>
|
|
`;
|
|
tbody.appendChild(tr);
|
|
});
|
|
}
|
|
|
|
// =====================================================================
|
|
// OUTPUT TABLE — SELECTION & EDIT
|
|
// =====================================================================
|
|
function getOutputSelectedIdxs() {
|
|
return [...document.querySelectorAll('.out-check:checked')].map(cb => parseInt(cb.dataset.idx));
|
|
}
|
|
|
|
function updateOutputSelection() {
|
|
const count = getOutputSelectedIdxs().length;
|
|
document.getElementById('outputEditBtn').disabled = count < 2;
|
|
document.getElementById('outputSelectedCount').textContent = count > 0 ? `${count} row${count>1?'s':''} selected` : '';
|
|
document.querySelectorAll('#outputBody tr').forEach(tr => {
|
|
const cb = tr.querySelector('.out-check');
|
|
tr.classList.toggle('selected', cb && cb.checked);
|
|
});
|
|
}
|
|
|
|
function toggleOutputSelectAll(cb) {
|
|
document.querySelectorAll('.out-check').forEach(c => c.checked = cb.checked);
|
|
updateOutputSelection();
|
|
}
|
|
|
|
function openOutputEditModal() {
|
|
if (getOutputSelectedIdxs().length < 2) return;
|
|
['omTitle','omStatus','omCategory','omMedia','omSubMedia','omDestination','omFormat','omLanguage','omCountry','omCreativeExec'].forEach(id => document.getElementById(id).value = '');
|
|
['omSupplyDate','omLiveDate','omEndDate'].forEach(id => document.getElementById(id).value = '');
|
|
document.getElementById('outputEditModal').classList.add('active');
|
|
}
|
|
|
|
function closeOutputEditModal() {
|
|
document.getElementById('outputEditModal').classList.remove('active');
|
|
}
|
|
|
|
function applyOutputEdit() {
|
|
const idxs = getOutputSelectedIdxs();
|
|
const fieldMap = {
|
|
omTitle: 'title', omStatus: 'status', omCategory: 'category',
|
|
omMedia: 'media', omSubMedia: 'subMedia', omDestination: 'destination',
|
|
omFormat: 'format', omSupplyDate: 'supplyDate', omLiveDate: 'liveDate',
|
|
omEndDate: 'endDate', omLanguage: 'language', omCountry: 'country',
|
|
omCreativeExec: 'creativeExecution'
|
|
};
|
|
const changes = {};
|
|
Object.entries(fieldMap).forEach(([modalId, field]) => {
|
|
const val = document.getElementById(modalId).value;
|
|
if (val) changes[field] = val;
|
|
});
|
|
if (Object.keys(changes).length === 0) { alert('No changes specified.'); return; }
|
|
|
|
idxs.forEach(idx => {
|
|
const tr = document.getElementById(`out-${idx}`);
|
|
if (!tr) return;
|
|
Object.entries(changes).forEach(([field, val]) => {
|
|
const el = tr.querySelector(`[data-field="${field}"]`);
|
|
if (el) el.value = val;
|
|
});
|
|
});
|
|
closeOutputEditModal();
|
|
flashRows(idxs.map(idx => document.getElementById(`out-${idx}`)));
|
|
}
|
|
|
|
// =====================================================================
|
|
// CLOSE MODALS
|
|
// =====================================================================
|
|
['inputEditModal','outputEditModal','campaignModal','successModal'].forEach(id => {
|
|
document.getElementById(id).addEventListener('click', function(e) {
|
|
if (e.target === this) this.classList.remove('active');
|
|
});
|
|
});
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Escape') {
|
|
closeInputEditModal();
|
|
closeOutputEditModal();
|
|
closeCampaignModal();
|
|
closeSuccessModal();
|
|
}
|
|
});
|
|
|
|
// =====================================================================
|
|
// BACK TO INPUT
|
|
// =====================================================================
|
|
function backToInput() {
|
|
document.getElementById('outputSection').classList.remove('visible');
|
|
document.getElementById('inputSection').style.display = '';
|
|
}
|
|
|
|
// =====================================================================
|
|
// CAMPAIGN NUMBER MODAL & EXPORT
|
|
// =====================================================================
|
|
let campaignMode = 'send'; // 'send' or 'download'
|
|
|
|
function openCampaignModal(mode) {
|
|
campaignMode = mode || 'send';
|
|
document.getElementById('campaignNumber').value = '';
|
|
document.getElementById('campaignError').style.display = 'none';
|
|
document.getElementById('campaignSubmitBtn').disabled = true;
|
|
document.getElementById('campaignModalTitle').textContent = 'Enter Campaign Number';
|
|
document.getElementById('campaignSubmitBtn').textContent = (campaignMode === 'send') ? 'Send to OMG' : 'Download CSV';
|
|
document.getElementById('campaignModal').classList.add('active');
|
|
setTimeout(() => document.getElementById('campaignNumber').focus(), 100);
|
|
}
|
|
|
|
function closeCampaignModal() {
|
|
document.getElementById('campaignModal').classList.remove('active');
|
|
}
|
|
|
|
function closeSuccessModal() {
|
|
document.getElementById('successModal').classList.remove('active');
|
|
}
|
|
|
|
function validateCampaign() {
|
|
const val = document.getElementById('campaignNumber').value;
|
|
const valid = /^\d{7}$/.test(val);
|
|
document.getElementById('campaignSubmitBtn').disabled = !valid;
|
|
document.getElementById('campaignError').style.display = (val.length > 0 && !valid) ? 'block' : 'none';
|
|
}
|
|
|
|
// Convert YYYY-MM-DD (from date input) to DD/MM/YYYY 00:00 (OMG format)
|
|
function formatDateForExport(dateStr) {
|
|
if (!dateStr) return '';
|
|
const parts = dateStr.split('-');
|
|
if (parts.length !== 3) return dateStr;
|
|
return `${parts[2]}/${parts[1]}/${parts[0]} 00:00`;
|
|
}
|
|
|
|
function buildCSV() {
|
|
const headers = ['Title','Status','Category','Media','Sub media','Destination','Format','Supply date','Live date','End date','Special instructions','Language','Country','Creative Execution','Quantity'];
|
|
|
|
let csv = headers.join(',') + '\n';
|
|
|
|
document.querySelectorAll('#outputBody tr').forEach(tr => {
|
|
const getVal = (field) => {
|
|
const input = tr.querySelector(`[data-field="${field}"]`);
|
|
return input ? input.value : '';
|
|
};
|
|
const statusCell = tr.querySelector('.status-booked');
|
|
const status = statusCell ? statusCell.textContent : '';
|
|
|
|
const vals = [
|
|
getVal('title'),
|
|
status,
|
|
getVal('category'),
|
|
getVal('media'),
|
|
getVal('subMedia'),
|
|
getVal('destination'),
|
|
getVal('format'),
|
|
formatDateForExport(getVal('supplyDate')),
|
|
formatDateForExport(getVal('liveDate')),
|
|
formatDateForExport(getVal('endDate')),
|
|
'', // Special instructions
|
|
getVal('language'),
|
|
getVal('country'),
|
|
getVal('creativeExecution'),
|
|
getVal('quantity'),
|
|
];
|
|
|
|
csv += vals.map(v => v.includes(',') ? `"${v}"` : v).join(',') + '\n';
|
|
});
|
|
|
|
return csv;
|
|
}
|
|
|
|
function submitCampaignExport() {
|
|
const campaignNum = document.getElementById('campaignNumber').value;
|
|
if (!/^\d{7}$/.test(campaignNum)) return;
|
|
|
|
const csv = buildCSV();
|
|
const filename = `OMG${campaignNum}_ACINGEST.csv`;
|
|
|
|
if (campaignMode === 'download') {
|
|
// Browser download
|
|
const blob = new Blob([csv], { type: 'text/csv' });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = filename;
|
|
a.click();
|
|
URL.revokeObjectURL(url);
|
|
closeCampaignModal();
|
|
} else {
|
|
// Send to OMG via server
|
|
closeCampaignModal();
|
|
fetch('/api/send-to-omg', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ filename, csv })
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
document.getElementById('successMessage').textContent = 'CSV has been passed to OMG';
|
|
document.getElementById('successModal').classList.add('active');
|
|
} else {
|
|
alert('Error: ' + (data.error || 'Failed to save file'));
|
|
}
|
|
})
|
|
.catch(err => {
|
|
alert('Could not connect to the server. Make sure you started it with: node server.js');
|
|
});
|
|
}
|
|
}
|
|
|
|
// =====================================================================
|
|
// TOOLTIP ON HOVER (shows full value for truncated cells)
|
|
// =====================================================================
|
|
document.addEventListener('mouseover', function(e) {
|
|
const input = e.target;
|
|
if (input.tagName === 'INPUT' && input.type === 'text' && input.closest('td')) {
|
|
if (input.scrollWidth > input.clientWidth) {
|
|
input.title = input.value;
|
|
}
|
|
}
|
|
if (input.tagName === 'SELECT' && input.closest('td')) {
|
|
const opt = input.options[input.selectedIndex];
|
|
if (opt) input.title = opt.textContent;
|
|
}
|
|
});
|
|
|
|
// =====================================================================
|
|
// SAMPLE DATA (from Excel)
|
|
// =====================================================================
|
|
const sampleData = [
|
|
{ masterFileName:"1234567_ROC_FCT-GL-BUNNY-V1_OLV_15S_9x16_REF", metadataId:"IGR$VEElc9", assetType:"Gadget", mediaType:"FB - Profile Feed", aspectRatio:"1x1", langCode:"CZ-cs" },
|
|
{ masterFileName:"6601816_NUT_XMAS-SHARETHELOVE-GLAS_OLV_20S_16x9_REF", metadataId:"IGR$VEElc10", assetType:"On Line Video", mediaType:"FB - Feed", aspectRatio:"16x9", langCode:"CZ-cs" },
|
|
{ masterFileName:"6601834_NUT_XMAS-SHARETHELOVE-GLAS_OLV_15S_16x9_REF_YTA", metadataId:"IGR$VEElc11", assetType:"Gadget", mediaType:"FB - Profile Feed", aspectRatio:"16x9", langCode:"CZ-cs" },
|
|
{ masterFileName:"6601842_NUT_XMAS-SHARETHELOVE-GLAS_OLV_15S_9x16_REF_FBS", metadataId:"IGR$VEElc12", assetType:"Gadget", mediaType:"FB - Profile Feed", aspectRatio:"1x1", langCode:"CZ-cs" },
|
|
{ masterFileName:"6601844_NUT_XMAS-SHARETHELOVE-GLAS_OLV_15S_9x16_REF_FBR", metadataId:"IGR$VEElc13", assetType:"Gadget", mediaType:"FB - Profile Feed", aspectRatio:"1x1", langCode:"CZ-cs" },
|
|
{ masterFileName:"6601854_NUT_XMAS-SHARETHELOVE-GLAS_OLV_6S_9x16_REF_YTS", metadataId:"IGR$VEElc14", assetType:"Gadget", mediaType:"FB - Profile Feed", aspectRatio:"1x1", langCode:"CZ-cs" },
|
|
{ masterFileName:"6601814_NUT_XMAS-SHARETHELOVE-GLAS_TVC_20S_16x9_REF", metadataId:"IGR$VEElc15", assetType:"Gadget", mediaType:"FB - Profile Feed", aspectRatio:"1x1", langCode:"CZ-cs" },
|
|
];
|
|
|
|
sampleData.forEach(d => addRow(d));
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|