ferrero-ac-creator/index.html
2026-03-31 07:19:23 +00:00

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()">&times;</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()">&times;</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()">&times;</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;">&#10003;</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,'&amp;').replace(/"/g,'&quot;').replace(/</g,'&lt;'); }
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>