// --- CONFIGURATION --- const FOLDER_ID = "1rLQ6UZ4GVYau6qW0yTAHbtiGyP81Qfgk"; const SPREADSHEET_ID = "1KioH_lA-6DXWgK6D2UhYvAi3WpgwfkLUlt2IeaVT3Bk"; function doGet(e) { const callback = (e && e.parameter && e.parameter.callback) || 'callback'; const action = e && e.parameter && e.parameter.action; let result = (action === 'getSubmissions') ? getSubmissionsData() : getCurriculumData(); return ContentService.createTextOutput(callback + "(" + JSON.stringify(result) + ");") .setMimeType(ContentService.MimeType.JAVASCRIPT); } function doPost(e) { try { const rawData = (e && e.postData && e.postData.contents) ? e.postData.contents : "{}"; const payload = JSON.parse(rawData); // Action 1: Upload a single file and return its Google Drive URL if (payload.action === "uploadFile") { const folder = DriveApp.getFolderById(FOLDER_ID.trim()); const splitData = payload.fileBase64.split(","); const contentType = splitData[0].match(/:(.*?);/)[1]; const bytes = Utilities.base64Decode(splitData[1]); const blob = Utilities.newBlob(bytes, contentType, payload.fileName); const file = folder.createFile(blob); file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW); return ContentService.createTextOutput(JSON.stringify({ success: true, fileUrl: file.getUrl() })).setMimeType(ContentService.MimeType.JSON); } // Action 2: Standard form submission (text + pre-uploaded file URLs) const result = submitPlaybook(payload); return ContentService.createTextOutput(JSON.stringify(result)) .setMimeType(ContentService.MimeType.JSON); } catch (err) { return ContentService.createTextOutput(JSON.stringify({ success: false, message: "Server error: " + err.toString() })).setMimeType(ContentService.MimeType.JSON); } } function getCurriculumData() { try { const ss = SpreadsheetApp.openById(SPREADSHEET_ID.trim()); const sheet = ss.getSheetByName('Curriculum'); if (!sheet) return { success: false, error: 'Curriculum tab not found.' }; const lastRow = sheet.getLastRow(); if (lastRow <= 1) return { success: false, error: 'No data rows in Curriculum.' }; const values = sheet.getRange(2, 1, lastRow - 1, 6).getValues(); const data = {}; values.forEach(function(row) { const topic = row[0] ? String(row[0]).trim() : ''; const title = row[1] ? String(row[1]).trim() : ''; if (!topic || !title) return; if (!data[topic]) data[topic] = []; data[topic].push({ title: title, desc: row[2] ? String(row[2]).trim() : '', visualUrl: row[3] ? String(row[3]).trim() : '', callHint: row[4] ? String(row[4]).trim() : 'Your 1-word call', sigHint: row[5] ? String(row[5]).trim() : 'Your hand/body signal' }); }); return { success: true, data: data }; } catch (err) { return { success: false, error: err.toString() }; } } function createSubmissionPdf(playerName, topic, answers) { const folder = DriveApp.getFolderById(FOLDER_ID.trim()); let cardsHtml = ''; const safeAnswers = Array.isArray(answers) ? answers : []; safeAnswers.forEach(function(ans, i) { const fileRow = (ans.fileUrl && ans.fileUrl !== 'No file attached') ? '
Attached File: View File
' : ''; cardsHtml += '
' + '
' + (i + 1) + '. ' + (ans.scenario || 'Scenario') + '
' + '
' + 'Question: ' + (ans.description || 'N/A') + '
' + '
' + '
' + '
Secret Call
' + '
' + (ans.call || '(None)') + '
' + '
' + '
' + '
Signal / Movement
' + '
' + (ans.signal || '(None)') + '
' + '
' + '
' + fileRow + '
'; }); const htmlBody = '' + '' + '' + '

Tactical Playbook Report

' + '
Player: ' + playerName + ' | Topic: ' + topic + ' | Date: ' + Utilities.formatDate(new Date(), Session.getScriptTimeZone(), 'dd/MM/yyyy HH:mm') + '
' + '

Decisions & Scenarios Breakdown

' + cardsHtml + ''; const blob = Utilities.newBlob(htmlBody, 'text/html', playerName + ' - ' + topic + ' Playbook.html'); const pdfFile = folder.createFile(blob.getAs('application/pdf')).setName(playerName + ' - ' + topic + ' Playbook.pdf'); pdfFile.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW); return pdfFile.getUrl(); } function submitPlaybook(data) { try { const playerName = data.playerName || 'Player'; const topic = data.topic || 'General'; const answers = Array.isArray(data.answers) ? data.answers : []; const ss = SpreadsheetApp.openById(SPREADSHEET_ID.trim()); const sheet = ss.getSheetByName('Submissions') || ss.getSheets()[0]; const pdfUrl = createSubmissionPdf(playerName, topic, answers); const timestamp = new Date(); if (answers.length > 0) { answers.forEach(function(ans) { sheet.appendRow([ timestamp, playerName, topic, ans.scenario || '', ans.description || '', ans.call || '', ans.signal || '', ans.fileUrl || 'No file attached', pdfUrl ]); }); } else { sheet.appendRow([timestamp, playerName, topic, 'No scenarios', '', '', '', 'No file attached', pdfUrl]); } return { success: true, message: "Saved successfully!" }; } catch (err) { return { success: false, message: "Execution error: " + err.toString() }; } } function getSubmissionsData() { try { const ss = SpreadsheetApp.openById(SPREADSHEET_ID.trim()); const sheet = ss.getSheetByName('Submissions') || ss.getSheets()[0]; const lastRow = sheet.getLastRow(); if (lastRow <= 1) return { success: true, rows: [] }; const values = sheet.getRange(2, 1, lastRow - 1, 9).getValues(); const grouped = {}; values.forEach(function(r) { const dateStr = Utilities.formatDate(new Date(r[0]), Session.getScriptTimeZone(), 'dd/MM/yyyy HH:mm'); const key = `${r[1]}_${r[2]}_${dateStr}`; if (!grouped[key]) { grouped[key] = { date: dateStr, playerName: r[1], topic: r[2], answers: [], pdfUrl: r[8] }; } grouped[key].answers.push({ scenario: r[3], description: r[4], call: r[5], signal: r[6], fileUrl: r[7] }); }); return { success: true, rows: Object.values(grouped).reverse() }; } catch (err) { return { success: false, error: err.toString() }; } }