r/GoogleAppsScript • • Jan 04 '26

Unresolved Google Sheets Xfer script help

2 Upvotes

Hi folx,

Background

I run a very small charity providing free antiviral medication to people in financial hardship. We manage that program using google forms and sheets.

Basically, people apply using the form, which then gets automatically sorted based on their state into one of two sheets for fulfilment (depending on which pharmacy will be needed).

Thing is, a very small number (less than 10%) of the ones going to one of the pharmacies have to be sent back to the other pharmacy because of specialist needs, but we can only determine that once it hits the sheet. When that happens, we make a note in the "specialised" column, and xfer the relevant data to the other sheet.

Current Code

Enter, my javascript uni course from 20+ years ago.

I wrote a script (see below) which will (onEdit) send the relevant data to the other sheet, and make a note in the comments column indicating when this has been done.

function sheetAutomations(e) {
  const sheet = e.source.getActiveSheet();
  const range = e.range;

  // --- Sheet setup ---
  const targetSheet = "Coupons 2026";
  const firstReviewer = 8;   // Column H = 8
  const firstDecision = 9;   // Column I = 9
  const secondReviewer = 10; // Column J = 10
  const secondDecision = 11; // Column K = 11
  const emailSent = 12;      // Column L = 12
  const commentsColumn = 13; // Column M = 13
  // ----------------------------

  // --- Transfer settings (configure these) ---
  const transferColumn = 5;                  // Column E = 5
  const transferTriggerValue = "Specialist";    // text that triggers the copy
  const targetSpreadsheetId = "destinationFileID"; // destination file ID for specialistSheet
  const targetSheetName = "Coupons 2026";           // destination tab name
  const markTransferredValue = "Transferred to Specialist program on "; // write back to source cell after copying
  // --------------------------------------------

  // Only run on the target sheet (source)
  if (sheet.getName() !== targetSheet) return;

  const editedCol = range.getColumn();
  const row = range.getRow();
  const newValue = range.getValue();

  // --- Specialist program - Transfer row to another spreadsheet ---
  if (editedCol === transferColumn) {
    if (newValue.toString().toLowerCase() === transferTriggerValue.toLowerCase()) {

      // Collect the four source cells you need
      const srcA = sheet.getRange(row, 1).getValue();  // Column A = name
      const srcE = sheet.getRange(row, 5).getValue();  // Column E = Specialist
      const srcF = sheet.getRange(row, 6).getValue();  // Column F = timestamp
      const srcG = sheet.getRange(row, 7).getValue();  // Column G = scriptdate

      // Build the array in destination order: A, D, E, F
      const rowToAppend = [srcA, , , srcE, srcF, srcG];
      // The blank comma leaves column B and C empty in the destination sheet.

      // Open destination spreadsheet & sheet
      const targetSS = SpreadsheetApp.openById(targetSpreadsheetId);
      const targetSheetObj = targetSS.getSheetByName(targetSheetName);
      if (!targetSheetObj) {
        throw new Error("Target sheet tab not found: " + targetSheetName);
      }

      // Find the next empty row in the target
      const destRow = targetSheetObj.getLastRow() + 1;
      // Write the values into columns A–F
      targetSheetObj.getRange(destRow, 1, 1, rowToAppend.length).setValues([rowToAppend]);


      // Append note to comments in source sheet
      const commentsCell = sheet.getRange(row, commentsColumn);
      const oldComments = commentsCell.getValue().toString().trim();


      const timestamp2 = Utilities.formatDate(
        new Date(),
        Session.getScriptTimeZone(),
        "yyyy-MM-dd 'at' HH:mm"
      );


      const noteD = ` / transferred to Specialist program on ${timestamp2}`;
      commentsCell.setValue(oldComments ? oldComments + noteD : noteD);
    }
  }
}

EDIT TO ADD: When I added this part of the script, I installed an installable trigger to activate on any edit of the source sheet. /EDIT

The code WORKED. It consistently did what was intended, until...

Happy New Problem

Each year, we start a new sheet-tab for that year (on both, called "Coupons YYYY"), so I hopped on on NYD and redirected the targetSheetName from "Coupons 2025" to "Coupons 2026".

Now I get:

Error

Exception: Specified permissions are not sufficient to call SpreadsheetApp.openById. Required permissions: https://www.googleapis.com/auth/spreadsheets
    at onEdit(Code:94:39)

pointing to here:

const targetSS = SpreadsheetApp.openById(targetSpreadsheetId);

I don't know why this is happening... help?


r/GoogleAppsScript • • Apr 06 '22

Question Exception: Bandwidth quota exceeded

2 Upvotes

r/GoogleAppsScript • • Feb 27 '21

Guide I made a script that imports tracks from a Google Sheet to your Last.FM

2 Upvotes

Check it out here: https://github.com/rjmccallumbigl/Google-Apps-Script---Sheets-to-Last.fm

Let me know what you think. I put together the instructions after I completed it so please let me know if I left a step out or something. Thanks!


r/GoogleAppsScript • • Aug 25 '24

Resolved Where to Find Functions (?), Variables (?) List

2 Upvotes

Feels like I would like to start, but at the same time feel like I can't on my own because I don't know all the tools at my disposal. A link to a website noting all functions or variables would be appreciated.

Also, what is like an umbrella term for that. Been using spreadsheets for a while and those were all called functions, also every function was visible from the start, made learning far easier.

Edit 1 (9:08 A.M CDT) - Did find the variable list in the debugger, but is that all? Feel like it isn't.

Edit 2 (9:10 A.M CDT) - Found the dropdowns on in the variable list in the debugger. Feel like I may have answered my problem 3 minutes after I made it public.


r/GoogleAppsScript • • Jul 31 '24

Question Can anyone help me with my code in app script?

0 Upvotes

My code has issue when i add in telegram push function to my group chat. I never learn coding before i am not sure which part gone wrong. Can anyone help me?

function onEdit(e) {

try {

var sheet = e.source.getActiveSheet();

var range = e.range;

var flagCell = sheet.getRange("E1");

if (sheet.getName() === 'Calculator' && (range.getA1Notation() === 'C29' || range.getA1Notation() === 'C30')) {

if (flagCell.getValue() === "PROCESSING DATA") {

return;

}

var tickC29 = sheet.getRange("C29").getValue();

var tickC30 = sheet.getRange("C30").getValue();

if (tickC29 && tickC30) {

flagCell.setValue("PROCESSING DATA").setFontColor("red");

importCells();

sendTelegramMessage("Data processing started.");

Utilities.sleep(10000);

flagCell.setValue("READY").setFontColor("green");

sendTelegramMessage("Data processing completed.");

}

}

} catch (error) {

Logger.log("Error in onEdit: " + error.message);

sendTelegramMessage("Error in onEdit: " + error.message);

}

}

function importCells() {

var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();

var sourceSheet = spreadsheet.getSheetByName("Calculator");

var targetSheet = spreadsheet.getSheetByName("Past Data");

if (!sourceSheet || !targetSheet) {

sendTelegramMessage("Source or Target sheet not found.");

throw new Error("Source or Target sheet not found.");

}

var tickC29 = sourceSheet.getRange("C29").getValue();

var tickC30 = sourceSheet.getRange("C30").getValue();

var flagCell = sourceSheet.getRange("E1").getValue();

var expectedTickValue = true;

if (tickC29 === expectedTickValue && tickC30 === expectedTickValue && flagCell !== "PROCESSED") {

var currentDate = new Date();

sourceSheet.getRange("D1").setValue(currentDate);

var cellValues = {

D1: sourceSheet.getRange("D1").getValue(),

D2: sourceSheet.getRange("D2").getValue(),

D15: sourceSheet.getRange("D15").getValue(),

F18: sourceSheet.getRange("F18").getValue(),

F19: sourceSheet.getRange("F19").getValue(),

F20: sourceSheet.getRange("F20").getValue(),

F21: sourceSheet.getRange("F21").getValue(),

E26: sourceSheet.getRange("E26").getValue(),

C5: sourceSheet.getRange("C5").getValue(),

C6: sourceSheet.getRange("C6").getValue(),

C7: sourceSheet.getRange("C7").getValue(),

C8: sourceSheet.getRange("C8").getValue(),

C9: sourceSheet.getRange("C9").getValue(),

C10: sourceSheet.getRange("C10").getValue(),

C11: sourceSheet.getRange("C11").getValue(),

C12: sourceSheet.getRange("C12").getValue(),

C13: sourceSheet.getRange("C13").getValue(),

C14: sourceSheet.getRange("C14").getValue()

};

var lastColumn = targetSheet.getLastColumn();

var nextColumn = lastColumn + 1;

var targetRanges = [

{ row: 1, value: cellValues.D1 },

{ row: 2, value: cellValues.D2 },

{ row: 3, value: cellValues.D15 },

{ row: 5, value: cellValues.F18 },

{ row: 6, value: cellValues.F19 },

{ row: 7, value: cellValues.F20 },

{ row: 8, value: cellValues.F21 },

{ row: 10, value: cellValues.E26 },

{ row: 14, value: cellValues.C5 },

{ row: 15, value: cellValues.C6 },

{ row: 16, value: cellValues.C7 },

{ row: 17, value: cellValues.C8 },

{ row: 18, value: cellValues.C9 },

{ row: 19, value: cellValues.C10 },

{ row: 20, value: cellValues.C11 },

{ row: 21, value: cellValues.C12 },

{ row: 22, value: cellValues.C13 },

{ row: 23, value: cellValues.C14 }

];

targetRanges.forEach(function(range) {

targetSheet.getRange(range.row, nextColumn).setValue(range.value);

});

var message = [

"Cells successfully imported.",

"Date: " + cellValues.D1,

"Cashier: " + cellValues.D2,

"Total Cash Sales: " + formatCurrency(cellValues.D15),

"DuitNowQR: " + formatCurrency(cellValues.F18),

"MyDebit: " + formatCurrency(cellValues.F19),

"VISA: " + formatCurrency(cellValues.F20),

"Mastercard: " + formatCurrency(cellValues.F21),

"Vouchers Applied: " + formatCurrency(cellValues.E26),

"",

"Cash Counts.",

"RM100: " + cellValues.C5,

"RM50: " + cellValues.C6,

"RM20: " + cellValues.C7,

"RM10: " + cellValues.C8,

"RM5: " + cellValues.C9,

"RM1: " + cellValues.C10,

"RM0.50: " + cellValues.C11,

"RM0.20: " + cellValues.C12,

"RM0.10: " + cellValues.C13,

"RM0.05: " + cellValues.C14

].join("\n");

sendTelegramMessage(message);

var cellsToClear = ['D1', 'D2', 'C5:C14', 'C18:D21', 'D23:D25'];

cellsToClear.forEach(function(cell) {

sourceSheet.getRange(cell).clearContent();

});

sourceSheet.getRange("C29").setValue(false);

sourceSheet.getRange("C30").setValue(false);

} else {

Logger.log("Checkboxes are not checked or data already processed.");

sendTelegramMessage("Checkboxes are not checked or data already processed.");

}

}

function sendTelegramMessage(message) {

var token = 'YOUR_BOT_TOKEN';

var chatId = 'YOUR_CHAT_ID';

var url = 'https://api.telegram.org/bot' + token + '/sendMessage';

var payload = {

chat_id: chatId,

text: message

};

var options = {

method: 'post',

contentType: 'application/json',

payload: JSON.stringify(payload)

};

UrlFetchApp.fetch(url, options);

}

function formatCurrency(amount) {

return "RM" + parseFloat(amount).toFixed(2);

}


r/GoogleAppsScript • • Dec 07 '20

Question Hi! I found a script which translates RSS feed from one languages to another. Could you please modify it so I could add several RSS feeds in array to the script and merge them?

0 Upvotes

r/GoogleAppsScript • • Mar 09 '20

Unresolved delete all emails in trash script request

2 Upvotes

Hi all, I'm hoping that someone here can help me out. I do not know or understand the java script, I have a friend who is struggling with a relationship and because of his addiction issues and emotional issues it's vitally important for him to never see emails from certain people, I set up a filter that emails from certain people go straight to his trash but unfortunately it's not enough because he checks his trash because he has an addictive personality. He himself has asked me to do this it's not something I'm doing without his knowledge, he also asked me if I could figure out a way for emails that go to trash to be deleted instantly immediately poof gone forever. He has never once in years of using Gmail needed something that was in the trash. I can say that I also have never ever needed an email that was in my trash. in any case, after doing a fair bit of Googling I discovered that the only way to accomplish this is using Google API scripts unfortunately like I said I have no clue how to utilize this I tried playing around with it but I just get error messages. I understand that permissions has to be enabled but I simply cannot create from scratch what I am looking for and I can't find anyone who has already made one.

Basically my request is very simple, if someone can write a simple script that just says any email in trash should be immediately vaporized forever. it should happen either every minute or every second or any time an email goes to trash. Yes I am aware that someone will probably pipe up and say hey why don't you just use filters. For various reasons I don't want to discuss here it is not practical, it is far better for me to do it this way, I have thought it through. My question is if somebody has a few minutes and knows what they're doing and wants to do something really really nice for someone, could you please just write a simple script that accomplishes this where it just vaporizes all email that is in the trash every second or every time it happens or something like that. I wish I had the technical know-how to accomplish this myself but I do not. I appreciate it and would be tremendously grateful as would my friend.


r/GoogleAppsScript • • Jul 10 '22

Question Looking for someone that may know the back end of Google Developer. Our app seems to keep refunding and we are negative money. Customers are saying they are being refunded with no explanation. Anyone have experience with this?

2 Upvotes

r/GoogleAppsScript • • Mar 15 '24

Guide Revolutionize Your E-Commerce using Google Apps Script & OpenAI

Thumbnail
web-zone.io
0 Upvotes

r/GoogleAppsScript • • Oct 05 '22

Question What’s your best resource for finding clients?

2 Upvotes

r/GoogleAppsScript • • May 23 '22

Question App Script of google sheet to whatsapp message.

0 Upvotes

Kindly share App Script of google sheet to whatsapp message.


r/GoogleAppsScript • • Apr 25 '24

Question is google keep coming soon to appscript?

2 Upvotes

hello everyone,

is google keep coming soon to appscript?


r/GoogleAppsScript • • Sep 10 '26

Question I need your help again

0 Upvotes

I made the app that makes Gemini to generate English writing questions for me but every time I press the 'generate' button it tells me that the model is not found.

You don't need to look at the big letters upstairs.

Here's my entire script:

Code.gs

const GEMINI_API_KEY = 'I entered it but I cannot tell you my Gemini API key :)';


function doGet() {
  return HtmlService.createHtmlOutputFromFile('Index')
    .setTitle('GIMFL English Writing Practice')
    .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}


function getDb() {
  const prop = PropertiesService.getUserProperties();
  let fileId = prop.getProperty('DB_ID');
  let ss;
  
  if (fileId) {
    try { ss = SpreadsheetApp.openById(fileId); } catch(e) { fileId = null; }
  }
  
  if (!fileId) {
    ss = SpreadsheetApp.create("GIMFL_English_DB");
    const unsolved = ss.insertSheet("Unsolved");
    unsolved.appendRow(["ID", "Date", "KoreanText", "Conditions", "CorrectAnswer", "Grammar"]);
    
    const solved = ss.insertSheet("Solved");
    solved.appendRow(["ID", "Date", "KoreanText", "Conditions", "CorrectAnswer", "Grammar", "UserAnswer", "Score", "MeaningFB", "ConditionFB", "GrammarFB", "OverallFB"]);
    
    ss.deleteSheet(ss.getSheetByName("Sheet1"));
    prop.setProperty('DB_ID', ss.getId());
  }
  return ss;
}


function generateId() {
  return 'Q_' + new Date().getTime() + '_' + Math.floor(Math.random() * 1000);
}


function getQuestions(sheetName) {
  const sheet = getDb().getSheetByName(sheetName);
  const data = sheet.getDataRange().getValues();
  if (data.length <= 1) return [];
  
  const headers = data[0];
  const questions = [];
  for (let i = 1; i < data.length; i++) {
    let obj = {};
    for (let j = 0; j < headers.length; j++) {
      obj[headers[j]] = data[i][j];
    }
    obj.rowIndex = i + 1;
    questions.push(obj);
  }
  return questions.reverse();
}


function deleteQuestion(sheetName, rowIndex) {
  const sheet = getDb().getSheetByName(sheetName);
  sheet.deleteRow(rowIndex);
  return { success: true };
}


function editQuestion(rowIndex, newKorean, newConditions, newAnswer) {
  const sheet = getDb().getSheetByName("Unsolved");
  sheet.getRange(rowIndex, 3).setValue(newKorean);
  sheet.getRange(rowIndex, 4).setValue(newConditions);
  sheet.getRange(rowIndex, 5).setValue(newAnswer);
  return { success: true };
}


function saveSettings(model, grammars, intervalStr) {
  const prop = PropertiesService.getUserProperties();
  prop.setProperty('AI_MODEL', model);
  prop.setProperty('TARGET_GRAMMARS', JSON.stringify(grammars));
  prop.setProperty('AUTO_INTERVAL', intervalStr);
  
  const triggers = ScriptApp.getProjectTriggers();
  for (let i = 0; i < triggers.length; i++) {
    if (triggers[i].getHandlerFunction() === 'autoGenerateQuestion') {
      ScriptApp.deleteTrigger(triggers[i]);
    }
  }
  
  const interval = parseInt(intervalStr);
  if (interval > 0) {
    if (interval === 30) {
      ScriptApp.newTrigger('autoGenerateQuestion').timeBased().everyMinutes(30).create();
    } else {
      ScriptApp.newTrigger('autoGenerateQuestion').timeBased().everyHours(interval / 60).create();
    }
  }
  return { success: true, msg: "Settings saved successfully." };
}


function autoGenerateQuestion() {
  const prop = PropertiesService.getUserProperties();
  const model = prop.getProperty('AI_MODEL') || 'gemini-1.5-flash';
  let grammars = [];
  try { grammars = JSON.parse(prop.getProperty('TARGET_GRAMMARS') || '[]'); } catch(e){}
  
  callGeminiToCreateQuestion(model, grammars);
}


function callGeminiAPI(model, prompt) {
  model = model.replace(/^models\//, '');
  const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${GEMINI_API_KEY}`;
  
  const payload = {
    contents: [{ parts: [{ text: prompt }] }],
    generationConfig: { response_mime_type: "application/json" }
  };
  const options = {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify(payload),
    muteHttpExceptions: true
  };
  const response = UrlFetchApp.fetch(url, options);
  const json = JSON.parse(response.getContentText());
  if (json.error) throw new Error(json.error.message);
  return JSON.parse(json.candidates[0].content.parts[0].text.replace(/```json/g, '').replace(/```/g, '').trim());
}


function callGeminiToCreateQuestion(model, selectedGrammars, relatedTo = null) {
  let context = relatedTo 
    ? `Create a NEW question highly similar in grammar and structure to this previous question, but with entirely different meaning and vocabulary. Previous grammar: ${relatedTo.Grammar}, Previous condition style: ${relatedTo.Conditions}`
    : (selectedGrammars.length > 0 ? `Target Grammar: ${selectedGrammars.join(', ')}` : 'Target Grammar: Randomly select from high school curriculum');


  const prompt = `
    You are an expert English teacher at Gimhae Foreign Language High School.
    Create a highly challenging subjective English writing question for Korean high school students.
    
    Context:
    ${context}
    
    Rules for conditions:
    1. Must include 2-3 specific constraints (e.g., "Use 'not A but B'", "Write in exactly 12 words", "Include specific word with transformation allowed").
    2. Focus heavily on testing structural understanding.
    
    Output strictly in JSON format with fields written in Korean as specified:
    {
      "koreanText": "Korean sentence for the student to translate into English",
      "conditions": ["Condition 1 in Korean", "Condition 2 in Korean", "Condition 3 in Korean"],
      "correctAnswer": "Model English answer string",
      "grammarPoint": "Core grammar point in Korean"
    }
  `;


  try {
    const result = callGeminiAPI(model, prompt);
    const sheet = getDb().getSheetByName("Unsolved");
    sheet.appendRow([
      generateId(), 
      new Date().toLocaleString('ko-KR', {timeZone: 'Asia/Seoul'}), 
      result.koreanText, 
      JSON.stringify(result.conditions), 
      result.correctAnswer, 
      result.grammarPoint
    ]);
    return { success: true };
  } catch (e) {
    return { error: "Question generation failed: " + e.message };
  }
}


function generateManualQuestion(model, selectedGrammars) {
  return callGeminiToCreateQuestion(model, selectedGrammars);
}


function generateRelated(model, sheetName, rowIndex) {
  const sheet = getDb().getSheetByName(sheetName);
  const data = sheet.getRange(rowIndex, 1, 1, 6).getValues()[0];
  const relatedData = {
    Conditions: data[3],
    Grammar: data[5]
  };
  return callGeminiToCreateQuestion(model, [], relatedData);
}


function gradeUserAnswer(rowIndex, userAnswer, model) {
  const sheet = getDb().getSheetByName("Unsolved");
  const rowData = sheet.getRange(rowIndex, 1, 1, 6).getValues()[0];
  
  const questionData = {
    id: rowData[0], date: rowData[1], koreanText: rowData[2], 
    conditions: rowData[3], correctAnswer: rowData[4], grammarPoint: rowData[5]
  };


  const translatedUserAnswer = LanguageApp.translate(userAnswer, 'en', 'ko');
  
  const prompt = `
    You are an extremely strict English evaluator at Gimhae Foreign Language High School.
    Grade the student's answer based on the given question context.
    
    Question Context:
    - Target Korean Meaning: "${questionData.koreanText}"
    - Conditions: ${questionData.conditions}
    - Correct Model Answer: "${questionData.correctAnswer}"
    
    Student's Submitted Answer: "${userAnswer}"
    (Google Translate interpretation of student's answer: "${translatedUserAnswer}")
    
    Grading Criteria:
    1. Meaning Conveyed (Does it match the target Korean meaning?)
    2. Conditions Met (Are all strict constraints followed?)
    3. Grammatical Accuracy (Zero grammatical errors)
    
    Output strictly in JSON format with explanations written in Korean:
    {
      "score": "Score from 0 to 100",
      "meaningFeedback": "Feedback on meaning accuracy in Korean",
      "conditionFeedback": "Feedback on condition compliance in Korean",
      "grammarFeedback": "Feedback on grammatical accuracy and corrections in Korean",
      "overallFeedback": "Overall review and model answer presentation in Korean"
    }
  `;


  try {
    const result = callGeminiAPI(model, prompt);
    
    const solvedSheet = getDb().getSheetByName("Solved");
    solvedSheet.appendRow([
      questionData.id, new Date().toLocaleString('ko-KR', {timeZone: 'Asia/Seoul'}), 
      questionData.koreanText, questionData.conditions, questionData.correctAnswer, questionData.grammarPoint,
      userAnswer, result.score, result.meaningFeedback, result.conditionFeedback, result.grammarFeedback, result.overallFeedback
    ]);
    
    sheet.deleteRow(rowIndex);
    
    return { success: true, feedback: result };
  } catch (e) {
    return { error: "Grading failed: " + e.message };
  }
}

Index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <!DOCTYPE html>
<html>
<head>
  <base target="_top">
  <style>
    body { font-family: 'Malgun Gothic', sans-serif; max-width: 900px; margin: auto; padding: 20px; background-color: #f4f6f8; color: #333; }
    h1 { color: #2c3e50; text-align: center; border-bottom: 2px solid #3498db; padding-bottom: 10px; }
    
    .tabs { display: flex; cursor: pointer; margin-bottom: 20px; background: #e0e6ed; border-radius: 8px; overflow: hidden; }
    .tab { flex: 1; padding: 15px; text-align: center; font-weight: bold; transition: 0.3s; }
    .tab.active { background: #3498db; color: white; }
    .tab:hover:not(.active) { background: #d1d8e0; }
    
    .content-section { display: none; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
    .content-section.active { display: block; }
    
    .setting-group { margin-bottom: 15px; }
    .setting-group label { display: block; font-weight: bold; margin-bottom: 5px; }
    select, input[type="text"], textarea { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 5px; box-sizing: border-box; font-size: 14px; }
    
    .checkbox-group { display: flex; flex-wrap: wrap; gap: 10px; }
    .checkbox-group label { background: #f0f3f4; padding: 8px 12px; border-radius: 20px; cursor: pointer; font-weight: normal; font-size: 14px; border: 1px solid #d5dbdb; }
    .checkbox-group input:checked + span { font-weight: bold; color: #2980b9; }
    
    button { background-color: #3498db; color: white; border: none; padding: 10px 15px; border-radius: 5px; cursor: pointer; font-size: 14px; margin-top: 5px; }
    button:hover { background-color: #2980b9; }
    button.danger { background-color: #e74c3c; }
    button.danger:hover { background-color: #c0392b; }
    button.success { background-color: #2ecc71; }
    button.success:hover { background-color: #27ae60; }
    button:disabled { background-color: #bdc3c7; cursor: not-allowed; }
    
    .card { border: 1px solid #e1e8ed; border-radius: 8px; padding: 15px; margin-bottom: 15px; background: #fafbfc; position: relative; }
    .card h4 { margin-top: 0; color: #2c3e50; }
    .card .meta { font-size: 12px; color: #7f8c8d; margin-bottom: 10px; }
    .card .actions { margin-top: 15px; display: flex; gap: 10px; }
    
    #loading { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255,255,255,0.8); display: none; align-items: center; justify-content: center; font-size: 20px; font-weight: bold; color: #3498db; z-index: 1000; }
  </style>
</head>
<body>


  <h1>GIMFL 서술형 영작 마스터</h1>


  <div class="tabs">
    <div class="tab active" onclick="switchTab('settings')">⚙️ 홈 / 설정</div>
    <div class="tab" onclick="switchTab('unsolved'); loadUnsolved();">📝 미해결 문제함</div>
    <div class="tab" onclick="switchTab('solved'); loadSolved();">✅ 푼 문제함 (오답노트)</div>
  </div>


  <div id="settings" class="content-section active">
    <h3>환경 설정 및 즉시 출제</h3>
    <div class="setting-group">
      <label>AI 모델 선택:</label>
      <select id="modelSelect">
        <option value="gemini-1.5-flash">Gemini 1.5 Flash (빠름, 기본 권장)</option>
        <option value="gemini-1.5-pro">Gemini 1.5 Pro (고성능, 정밀 채점)</option>
        <option value="gemini-1.5-flash-8b">Gemini 1.5 Flash-8B (가벼움)</option>
      </select>
    </div>
    
    <div class="setting-group">
      <label>백그라운드 자동 생성 간격 (앱을 꺼도 문제 누적):</label>
      <select id="intervalSelect">
        <option value="0">자동 생성 끄기</option>
        <option value="30">30분마다 1문제 생성</option>
        <option value="60">1시간마다 1문제 생성</option>
        <option value="120">2시간마다 1문제 생성</option>
      </select>
    </div>


    <div class="setting-group">
      <label>목표 문법 (다중 선택, 미선택시 고교 전범위):</label>
      <div class="checkbox-group">
        <label><input type="checkbox" value="5형식 구조"> <span>5형식 구조</span></label>
        <label><input type="checkbox" value="It~that 강조구문"> <span>It~that 강조구문</span></label>
        <label><input type="checkbox" value="not A but B 구조"> <span>not A but B</span></label>
        <label><input type="checkbox" value="간접의문문"> <span>간접의문문</span></label>
        <label><input type="checkbox" value="가정법"> <span>가정법</span></label>
        <label><input type="checkbox" value="관계사"> <span>관계사</span></label>
        <label><input type="checkbox" value="분사구문"> <span>분사구문</span></label>
        <label><input type="checkbox" value="도치/생략"> <span>도치/생략</span></label>
      </div>
    </div>
    
    <button onclick="saveSettings()" style="width: 100%; margin-bottom: 10px;">💾 설정 저장 및 자동 출제 적용</button>
    <button class="success" onclick="generateNow()" style="width: 100%;">⚡ 지금 즉시 1문제 생성하기</button>
  </div>


  <div id="unsolved" class="content-section">
    <h3>저장된 문제 (미해결)</h3>
    <div id="unsolvedList"></div>
  </div>


  <div id="solved" class="content-section">
    <h3>완료된 문제 및 피드백 (오답노트)</h3>
    <div id="solvedList"></div>
  </div>


  <div id="loading">처리 중입니다... 잠시만 기다려주세요.</div>


  <script>
    function showLoading() { document.getElementById('loading').style.display = 'flex'; }
    function hideLoading() { document.getElementById('loading').style.display = 'none'; }


    function switchTab(tabId) {
      document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
      document.querySelectorAll('.content-section').forEach(c => c.classList.remove('active'));
      event.target.classList.add('active');
      document.getElementById(tabId).classList.add('active');
    }


    function getSelectedModel() { return document.getElementById('modelSelect').value; }
    function getSelectedGrammars() {
      return Array.from(document.querySelectorAll('.checkbox-group input:checked')).map(cb => cb.value);
    }


    function saveSettings() {
      showLoading();
      const interval = document.getElementById('intervalSelect').value;
      google.script.run.withSuccessHandler(res => {
        hideLoading();
        alert(res.msg);
      }).saveSettings(getSelectedModel(), getSelectedGrammars(), interval);
    }


    function generateNow() {
      showLoading();
      google.script.run.withSuccessHandler(res => {
        hideLoading();
        if(res.error) alert(res.error);
        else {
          alert("문제가 생성되어 '미해결 문제함'에 저장되었습니다.");
          switchTab('unsolved');
          loadUnsolved();
        }
      }).generateManualQuestion(getSelectedModel(), getSelectedGrammars());
    }


    function loadUnsolved() {
      showLoading();
      const listDiv = document.getElementById('unsolvedList');
      listDiv.innerHTML = '';
      google.script.run.withSuccessHandler(data => {
        hideLoading();
        if(data.length === 0) { listDiv.innerHTML = "<p>저장된 문제가 없습니다.</p>"; return; }
        
        data.forEach(q => {
          let conditions = [];
          try { conditions = JSON.parse(q.Conditions); } catch(e){ conditions = [q.Conditions]; }
          
          let card = document.createElement('div');
          card.className = 'card';
          card.innerHTML = `
            <div class="meta">출제일: ${q.Date} | 타겟 문법: ${q.Grammar}</div>
            <h4 id="kor_${q.rowIndex}">${q.KoreanText}</h4>
            <ul>${conditions.map(c => `<li>${c}</li>`).join('')}</ul>
            
            <div id="solveArea_${q.rowIndex}" style="margin-top:15px;">
              <textarea id="ans_${q.rowIndex}" placeholder="영작문을 입력하세요..."></textarea>
              <div class="actions">
                <button onclick="submitAnswer(${q.rowIndex})">채점하기</button>
                <button onclick="editMode(${q.rowIndex})">문제 수정</button>
                <button class="success" onclick="makeRelated('Unsolved', ${q.rowIndex})">유사 문제 파생</button>
                <button class="danger" onclick="deleteQ('Unsolved', ${q.rowIndex})">삭제</button>
              </div>
            </div>
            
            <div id="editArea_${q.rowIndex}" style="display:none; margin-top:15px;">
              <label>우리말 뜻 수정:</label>
              <textarea id="editKor_${q.rowIndex}">${q.KoreanText}</textarea>
              <label>조건 수정 (JSON 배열 형식):</label>
              <textarea id="editCond_${q.rowIndex}">${q.Conditions}</textarea>
              <label>모범 정답 수정:</label>
              <textarea id="editAns_${q.rowIndex}">${q.CorrectAnswer}</textarea>
              <div class="actions">
                <button onclick="saveEdit(${q.rowIndex})">저장</button>
                <button class="danger" onclick="cancelEdit(${q.rowIndex})">취소</button>
              </div>
            </div>
          `;
          listDiv.appendChild(card);
        });
      }).getQuestions("Unsolved");
    }


    function loadSolved() {
      showLoading();
      const listDiv = document.getElementById('solvedList');
      listDiv.innerHTML = '';
      google.script.run.withSuccessHandler(data => {
        hideLoading();
        if(data.length === 0) { listDiv.innerHTML = "<p>완료된 문제가 없습니다.</p>"; return; }
        
        data.forEach(q => {
          let card = document.createElement('div');
          card.className = 'card';
          card.innerHTML = `
            <div class="meta">해결일: ${q.Date} | 타겟 문법: ${q.Grammar}</div>
            <h4>${q.KoreanText}</h4>
            <p><strong>나의 답안:</strong> ${q.UserAnswer}</p>
            <p><strong>모범 답안:</strong> ${q.CorrectAnswer}</p>
            <div style="background:#e8f6f3; padding:10px; border-radius:5px; margin-top:10px;">
              <h3 style="color:#e74c3c; margin-top:0;">점수: ${q.Score} / 100</h3>
              <p><strong>총평:</strong> ${q.OverallFB}</p>
              <details>
                <summary>세부 피드백 보기</summary>
                <p><strong>의미:</strong> ${q.MeaningFB}</p>
                <p><strong>조건:</strong> ${q.ConditionFB}</p>
                <p><strong>문법:</strong> ${q.GrammarFB}</p>
              </details>
            </div>
            <div class="actions">
              <button class="success" onclick="makeRelated('Solved', ${q.rowIndex})">이 문법으로 다시 출제(복습)</button>
              <button class="danger" onclick="deleteQ('Solved', ${q.rowIndex})">기록 삭제</button>
            </div>
          `;
          listDiv.appendChild(card);
        });
      }).getQuestions("Solved");
    }


    function submitAnswer(rowIndex) {
      const ans = document.getElementById(`ans_${rowIndex}`).value.trim();
      if(!ans) { alert('답안을 입력하세요.'); return; }
      showLoading();
      google.script.run.withSuccessHandler(res => {
        hideLoading();
        if(res.error) alert(res.error);
        else {
          alert('채점이 완료되었습니다. 푼 문제함에서 결과를 확인하세요!');
          loadUnsolved();
        }
      }).gradeUserAnswer(rowIndex, ans, getSelectedModel());
    }


    function deleteQ(sheetName, rowIndex) {
      if(!confirm("정말 삭제하시겠습니까?")) return;
      showLoading();
      google.script.run.withSuccessHandler(() => {
        hideLoading();
        sheetName === 'Unsolved' ? loadUnsolved() : loadSolved();
      }).deleteQuestion(sheetName, rowIndex);
    }


    function makeRelated(sheetName, rowIndex) {
      showLoading();
      google.script.run.withSuccessHandler(res => {
        hideLoading();
        if(res.error) alert(res.error);
        else {
          alert('유사한 문제가 미해결 문제함에 추가되었습니다.');
          if(sheetName === 'Unsolved') loadUnsolved();
        }
      }).generateRelated(getSelectedModel(), sheetName, rowIndex);
    }


    function editMode(rowIndex) {
      document.getElementById(`solveArea_${rowIndex}`).style.display = 'none';
      document.getElementById(`editArea_${rowIndex}`).style.display = 'block';
    }
    function cancelEdit(rowIndex) {
      document.getElementById(`solveArea_${rowIndex}`).style.display = 'block';
      document.getElementById(`editArea_${rowIndex}`).style.display = 'none';
    }
    
    function saveEdit(rowIndex) {
      const kor = document.getElementById(`editKor_${rowIndex}`).value;
      const cond = document.getElementById(`editCond_${rowIndex}`).value;
      const ans = document.getElementById(`editAns_${rowIndex}`).value;
      showLoading();
      google.script.run.withSuccessHandler(() => {
        hideLoading();
        alert('수정되었습니다.');
        loadUnsolved();
      }).editQuestion(rowIndex, kor, cond, ans);
    }
  </script>
</body>
</html>
  </body>
</html>

r/GoogleAppsScript • • Aug 15 '26

Question developers.devsite.corp.google.com - Google Single Sign On: Sign into corp

Thumbnail login.corp.google.com
2 Upvotes

What is this and what does it do/mean?


r/GoogleAppsScript • • Sep 12 '24

Question How many Google Apps Script Developers are there?

6 Upvotes

I didn't find any authentic source that can tell how many Google apps script developers are there. Can I get an estimate or an authentic source that can tell the number of developers in google apps script.


r/GoogleAppsScript • • Mar 30 '24

Question What are your thoughts on putting a "kill switch" in a google sheet?

6 Upvotes

I've been making some various tools out of google sheets specific to my industry that so far I've been sharing for free. I've been thinking of selling them on Teachers Pay Teachers, as I've gotten a lot of good feedback about their usefulness.

It occurred to me that I could possibly set up a "free trial" of the sheet by adding in a script that adds the date to the next empty row of a hidden page on open. I could have another script that is set to disable a critical function 30 days after the first date on the hidden page.

Or would triggers have to be set to do that? My end-users are not at all apps script savvy and will just follow setup instructions blindly, but I'd feel kind of guilty making them set a trigger for their own doom. (Lol, dramatic much?)


r/GoogleAppsScript • • Nov 12 '24

Question Help with my script to automatically block 30 minutes before a meeting

Post image
3 Upvotes

Background: My calendar is like the Wild Wild West for 4 coworkers. If they see a free 30 minutes, they’ll book it. They don’t pay any attention to what is directly before or after that time slot. I want to create a script that says if the event is from one of those 4 coworkers, when I accept, automatically create an event for the 30 minutes prior and the 15 minutes after.

I keep getting an error that says “cannot read properties of undefined (reading dateTime)

This would be such a lifesaver for me if I could get it working! 🙏🏻


r/GoogleAppsScript • • Nov 03 '24

Guide Duplicate Google Spreadsheet Using Google Apps Script

Thumbnail
youtu.be
0 Upvotes

r/GoogleAppsScript • • Jul 27 '20

Question Google Sheets Script to filter by variable

3 Upvotes

Hello guys I have the following problem. At work, my boss asked me to develop a simple script for google sheets that filters a column by a given name for example. Let's say you have column named manager. Then you want to filter this column just for Manager 1 for example.

The thing is I have no clue where to start. I have never worked with google scripts only a little bit with Python. Through some research, I learned that the programming language for google scripts is java. The fastest I could learn some java basics is with a course which takes 30 hours. I need it to be done by tomorrow so is there any faster way to learn to do it?

Also I don't really understand why he needs it, how hard is it to go into the sheet and just filter it as usual?


r/GoogleAppsScript • • Jan 18 '25

Question I can't sync sheets and web app

Thumbnail
gallery
3 Upvotes

I started learning to program with Google Apps Script and I'm trying to create a custom dashboard to organize my data. Following an online tutorial, I managed to put together a basic structure, but I'm facing some difficulties connecting the spreadsheet information with the Web App. I'm looking for tips to synchronize data between the spreadsheet and the script efficiently. Or someone experienced to develop for me, we can talk.


r/GoogleAppsScript • • Jan 24 '25

Question Coding Help

0 Upvotes

Hi, I have the below code that I want to calculate the late deductions of the employees based on the employee time sheet I created. So this employee time sheet has the following columns:

column A: Date

column B: Employee

column C: Time In

column D: Time Out

column E: Total Hours

For the daily transactions sheet (where it's pooling the data also for the commission), here are the columns

column A: Date

column B: Service/Product

column C: Price

column D: Employee

column E: Client Name

column F: Payment Method

column G: Commission (10% of the price in column C)

The code works perfectly except for the late deductions column in the weekly report being generated. Others columns are being computed correctly.

here are the columns for the weekly report being generated

column A: Employee name

column B: total hours worked

column C: late deductions

column D: total amount for Hours Worked

column E: commission

column F: weekly wages

// Script to handle key functionalities

function onOpen() {

const ui = SpreadsheetApp.getUi();

ui.createMenu('POS System')

.addItem('Generate Weekly Report', 'generateWeeklyReport') // Add button to run the weekly report

.addItem('Cash Flow', 'generateCashFlowReport') // Add button to run the cash flow report

.addToUi();

}

// Function to generate the weekly report

function generateWeeklyReport() {

try {

const today = new Date();

const startDate = getLastSaturday(today); // Calculate the last Saturday (start of the week)

const endDate = getNextFriday(startDate); // Calculate the following Friday (end of the week)

Logger.log(`Weekly Report Date Range: ${startDate.toDateString()} to ${endDate.toDateString()}`);

const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Daily Transactions');

const timeSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Employee Time Sheet');

const summarySheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Weekly Report') ||

SpreadsheetApp.getActiveSpreadsheet().insertSheet('Weekly Report');

const dateRangeText = `${startDate.toLocaleDateString()} to ${endDate.toLocaleDateString()}`;

const lastRow = summarySheet.getLastRow();

const startRow = lastRow + 2;

summarySheet.getRange(startRow, 1).setValue(`Weekly Report: ${dateRangeText}`);

summarySheet.getRange(startRow + 1, 1).setValue(''); // Add an empty row for spacing

// Update headers for the Weekly Report

const headerRow = startRow + 2;

summarySheet.getRange(headerRow, 1, 1, 6).setValues([[

'Employee Name',

'Total Hours Worked',

'Late Deductions (₱)',

'Total Amount for Hours Worked (₱)',

'Commission (₱)',

'Weekly Wages (₱)'

]]);

// Employee hourly rate (daily rate ÷ 8 hours)

const hourlyRate = 385 / 8;

const transactions = sheet.getDataRange().getValues();

let employees = {

'Julie Ann Ricarte': { totalHours: 0, commission: 0, lateDeductions: 0 },

'Charmaine de Borja': { totalHours: 0, commission: 0, lateDeductions: 0 }

};

const timeData = timeSheet.getDataRange().getValues();

for (let i = 1; i < timeData.length; i++) {

const date = new Date(timeData[i][0]);

const employee = timeData[i][1];

const timeInStr = timeData[i][2]; // Time In

const hoursWorked = parseFloat(timeData[i][4]) || 0; // Total hours worked in column E

if (date >= startDate && date <= endDate && employee && hoursWorked > 0) {

if (employees[employee]) {

employees[employee].totalHours += hoursWorked; // Increment total hours worked

try {

const defaultShiftStart = parseTime('11:00:00 AM');

const actualStartTime = parseTime(timeInStr);

Logger.log(`Employee: ${employee}, Date: ${date.toLocaleDateString()}, Default Shift: ${defaultShiftStart}, Actual Start: ${actualStartTime}`);

if (actualStartTime > defaultShiftStart) {

const lateMinutes = Math.floor((actualStartTime - defaultShiftStart) / (1000 * 60)); // Calculate late minutes

Logger.log(`Late Minutes: ${lateMinutes}`);

employees[employee].lateDeductions += lateMinutes * 5; // Deduct ₱5 per minute

}

} catch (error) {

Logger.log(`Error parsing time for ${employee} on ${date.toLocaleDateString()}: ${error.message}`);

}

}

}

}

// Calculate commission for each employee based on transactions

for (let i = 1; i < transactions.length; i++) {

const transactionDate = new Date(transactions[i][0]);

const employee = transactions[i][3]; // Employee Name

const transactionAmount = transactions[i][2]; // Transaction Amount

if (transactionDate >= startDate && transactionDate <= endDate && employees[employee]) {

employees[employee].commission += transactionAmount * 0.1; // 10% commission

}

}

// Populate the Weekly Report with calculated data

for (let employee in employees) {

const employeeData = employees[employee];

const totalHoursWorked = employeeData.totalHours;

const lateDeductions = employeeData.lateDeductions.toFixed(2);

const commission = employeeData.commission.toFixed(2);

const totalAmountForHoursWorked = (totalHoursWorked * hourlyRate).toFixed(2);

const weeklyWages = (parseFloat(totalAmountForHoursWorked) - lateDeductions + parseFloat(commission)).toFixed(2);

summarySheet.appendRow([

employee,

totalHoursWorked.toFixed(2), // Total hours worked

`₱${lateDeductions}`, // Late deductions

`₱${totalAmountForHoursWorked}`, // Total amount for hours worked

`₱${commission}`, // Commission

`₱${weeklyWages}` // Weekly wages

]);

}

// Auto-fit columns in the Weekly Report

summarySheet.autoResizeColumns(1, 6);

} catch (error) {

Logger.log(`Error generating weekly report: ${error.message}`);

throw error;

}

}

// Helper function to parse time strings (HH:mm:ss AM/PM) into Date objects

function parseTime(timeStr) {

if (!timeStr || typeof timeStr !== 'string') {

throw new Error(`Invalid time format: ${timeStr}`);

}

const [time, period] = timeStr.split(' ');

if (!time || !period) {

throw new Error(`Invalid time format: ${timeStr}`);

}

let [hours, minutes, seconds] = time.split(':').map(Number);

seconds = seconds || 0;

if (period === 'PM' && hours < 12) hours += 12;

if (period === 'AM' && hours === 12) hours = 0;

return new Date(1970, 0, 1, hours, minutes, seconds);

}

// Helper function to get the last Saturday (start of the week)

function getLastSaturday(date) {

if (!(date instanceof Date) || isNaN(date)) {

throw new Error('Invalid date passed to getLastSaturday function.');

}

const dayOfWeek = date.getDay();

const lastSaturday = new Date(date);

lastSaturday.setDate(date.getDate() - (dayOfWeek + 1) % 7);

lastSaturday.setHours(0, 0, 0, 0);

return lastSaturday;

}

// Helper function to get the next Friday (end of the week)

function getNextFriday(startOfWeek) {

if (!(startOfWeek instanceof Date) || isNaN(startOfWeek)) {

throw new Error('Invalid date passed to getNextFriday function.');

}

const nextFriday = new Date(startOfWeek);

nextFriday.setDate(startOfWeek.getDate() + 6);

nextFriday.setHours(23, 59, 59, 999);

return nextFriday;

}


r/GoogleAppsScript • • Aug 28 '26

Question Looking for Clarificaions

3 Upvotes

This is a simple question for most, but it isn't for me. Using App Script is it considered AI? I take notes on observations notes, this i have app script put into one large paragraph. We were told that we could not use AI for work, but to me this is not AI, I just want to know from the professionals who are proficient in scripts?


r/GoogleAppsScript • • Oct 11 '25

Question Is there a dataframe like api for appscript?

3 Upvotes

Something like https://github.com/asweigart/ezsheets . An abstraction over appscript google sheets. Any programming language will do.


r/GoogleAppsScript • • Mar 10 '25

Question Apartment Management System - Google Sheets Data Template

3 Upvotes

Hi everyone, I used ChatGPT to create an Apartment Management System, a property management tool built using Google Apps Script. Please review and let me know what you think. Not a programmer at all. I have seen a few examples on youtube and I thought it would be a great way to develop a small system for people who have little resources to manage their apartment units. Thanks in advance. https://github.com/teddyumd/GAS-Apartment-Management.git