-
Notifications
You must be signed in to change notification settings - Fork 0
Major Updates, Overhaul #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
adizaveri
wants to merge
15
commits into
main
Choose a base branch
from
major-updates
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 13 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
20f3e9c
Implement major updates
adizaveri bbc5a76
Implement new styling
adizaveri 9543802
Extract 24h formatting method, drastically simplify Student Name hand…
adizaveri 8da0723
Remove redundant grade changed check
adizaveri 88fef88
Add `.gitattributes` to control line ending styles
adizaveri 3cea1bc
Add name indicator & new student mode, with registration confirmation…
adizaveri fdfe5b2
Remove redundant JS function, fix cursor on enabled Grade radio button
adizaveri a788e22
Add time input validation, clean up `actuallySubmit`
adizaveri b3bcd1d
Implement empty time box failsafe, missing grade failsafe
adizaveri f089c5f
Extract Column Number Constant refactoring
adizaveri f697ce6
Clean up comments and formatting
adizaveri 8f0585c
Organize `Stylesheet`
adizaveri bd36563
Add keyboard navigation
adizaveri 0476181
Fix `timeString` bug
adizaveri 2fdf716
Fix "Invalid Grade" bug
adizaveri File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| *.gs text eol=lf | ||
| *.html text eol=lf | ||
| *.json text eol=lf | ||
| *.bat text eol=crlf |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,118 +1,171 @@ | ||
| function doGet() { | ||
| return HtmlService.createTemplateFromFile("Index") | ||
| .evaluate() | ||
| .setTitle("Robotics Club Ops") | ||
| .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL) | ||
| .addMetaTag("viewport", "width=device-width, initial-scale=1"); | ||
| return HtmlService.createTemplateFromFile("Index") | ||
| .evaluate() | ||
| .setTitle("Robotics Club Ops") | ||
| .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL) | ||
| .addMetaTag("viewport", "width=device-width, initial-scale=1"); | ||
| } | ||
|
|
||
| // Standard HTML Service include function | ||
| // This allows you to inject content from other files into Index.html | ||
| // This allows us to modularize our HTML/CSS/JS | ||
| function include(filename) { | ||
| return HtmlService.createHtmlOutputFromFile(filename).getContent(); | ||
| return HtmlService.createHtmlOutputFromFile(filename).getContent(); | ||
| } | ||
|
|
||
| /* 1. GET DATA FOR DROPDOWN */ | ||
| // SPREADSHEET COLUMN SETTINGS | ||
| // Update these numbers if columns are added or moved in the "Students" sheet (1 = A, 2 = B, ...) | ||
| // Note that Java constants are zero-indexed, while Google Sheets columns are 1-indexed. | ||
| // These are the 1-indexed column numbers. | ||
| const COL_NAME = 1; | ||
| const COL_GRADE = 2; | ||
| const COL_STATUS = 3; | ||
| const COL_LAST_IN = 4; | ||
| const COL_LAST_OUT = 5; | ||
| const COL_GRADE_CHANGED = 6; | ||
| const COL_DATE_ADDED = 7; | ||
|
|
||
| /** Get data for dropdown */ | ||
| function getStudentList() { | ||
| const ss = SpreadsheetApp.getActiveSpreadsheet(); | ||
| const sheet = ss.getSheetByName("Students"); | ||
| const ss = SpreadsheetApp.getActiveSpreadsheet(); | ||
| const sheet = ss.getSheetByName("Students"); | ||
|
|
||
| if (!sheet || sheet.getLastRow() <= 1) return {}; | ||
| const data = sheet.getDataRange().getValues(); | ||
|
|
||
| // Remove headers | ||
| if (data.length > 0) data.shift(); | ||
| let studentMap = {}; | ||
|
|
||
| // Build a map of normalized name -> { displayName, grade } | ||
| data.forEach((row) => { | ||
| const nameValue = row[COL_NAME - 1]; | ||
| const gradeValue = row[COL_GRADE - 1]; | ||
|
|
||
| // Skip empty rows | ||
| if (nameValue) { | ||
| const displayName = String(nameValue).trim(); | ||
| const normName = displayName.toLowerCase(); | ||
|
|
||
| studentMap[normName] = { | ||
| displayName: displayName, | ||
| grade: gradeValue | ||
| }; | ||
| } | ||
| }); | ||
|
|
||
| return studentMap; | ||
| } | ||
|
|
||
| // Handle empty sheet case | ||
| if (sheet.getLastRow() <= 1) return {}; | ||
| /** Handle form submission */ | ||
| function processForm(formObject) { | ||
| const ss = SpreadsheetApp.getActiveSpreadsheet(); | ||
| const studentSheet = ss.getSheetByName("Students"); | ||
| const logsSheet = ss.getSheetByName("Logs"); | ||
|
|
||
| const data = sheet.getDataRange().getValues(); | ||
| // Error handling for missing sheets | ||
| if (!studentSheet || !logsSheet) { | ||
| return { status: "ERROR", message: "Missing required sheet(s)." }; | ||
| } | ||
|
|
||
| // Remove headers | ||
| if (data.length > 0) data.shift(); | ||
| // Sanitize and normalize input | ||
| let name = String(formObject.studentName || "").trim(); | ||
| let normName = name.toLowerCase(); | ||
| let grade = String(formObject.grade || "").trim(); | ||
| const type = String(formObject.checkType || "").trim(); // "Check In" or "Check Out" | ||
| const notes = String(formObject.notes || "").trim(); | ||
| const timeString = String(formObject.manualTime || "").trim(); // HH:mm | ||
|
|
||
| // Validate Grade | ||
| if (!["9", "10", "11", "12"].includes(grade)) { | ||
| return { status: "ERROR", message: "Invalid grade." }; | ||
| } | ||
|
|
||
| let studentMap = {}; | ||
| data.forEach((row) => { | ||
| // Name is Col A (index 0), Grade is Col B (index 1) | ||
| if (row[0]) { | ||
| studentMap[row[0]] = row[1]; | ||
| // Validate Name | ||
| if (!name) { | ||
| return { status: "ERROR", message: "Name required." }; | ||
| } | ||
| }); | ||
|
|
||
| return studentMap; | ||
| } | ||
| // Validate Time format (HH:mm) | ||
| if (!/^\d{2}:\d{2}$/.test(timeString)) { | ||
| return { status: "ERROR", message: "Invalid time format." }; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Improve message to indicate expected format "Invalid time format, expected HH:MM" |
||
| } | ||
|
|
||
| /* 2. HANDLE FORM SUBMISSION */ | ||
| function processForm(formObject) { | ||
| const ss = SpreadsheetApp.getActiveSpreadsheet(); | ||
| const studentSheet = ss.getSheetByName("Students"); | ||
| const logsSheet = ss.getSheetByName("Logs"); | ||
|
|
||
| const name = formObject.studentName; | ||
| const grade = formObject.grade; | ||
| const type = formObject.checkType; // "Check In" or "Check Out" | ||
| const notes = formObject.notes || ""; | ||
| const timeString = formObject.manualTime; // HH:mm | ||
|
|
||
| const now = new Date(); | ||
| const dateString = Utilities.formatDate( | ||
| now, | ||
| Session.getScriptTimeZone(), | ||
| "yyyy-MM-dd", | ||
| ); | ||
| const timestamp = Utilities.formatDate( | ||
| now, | ||
| Session.getScriptTimeZone(), | ||
| "yyyy-MM-dd HH:mm:ss", | ||
| ); | ||
|
|
||
| // A. UPDATE STUDENT RECORD (Status, Last Seen, Grade, etc.) | ||
| updateStudentRecord(studentSheet, name, grade, type, timestamp); | ||
|
|
||
| // B. LOG DATA TO LOGS TAB | ||
| // Logs: Name, Grade, Type, Time, Date, Notes | ||
| logsSheet.appendRow([name, grade, type, timeString, dateString, notes]); | ||
|
|
||
| return { status: "SUCCESS", type: type }; | ||
| const now = new Date(); | ||
|
|
||
| // If manual time is provided, override the hours and minutes of 'now' | ||
| const dateString = Utilities.formatDate( | ||
| now, | ||
| Session.getScriptTimeZone(), | ||
| "yyyy-MM-dd", | ||
| ); | ||
|
|
||
| const timestamp = Utilities.formatDate( | ||
| now, | ||
| Session.getScriptTimeZone(), | ||
| "yyyy-MM-dd HH:mm:ss", | ||
| ); | ||
|
|
||
| // Update student record (Status, Last Seen, Grade, etc.) | ||
| try { | ||
| updateStudentRecord(studentSheet, name, grade, type, timestamp); | ||
| // Log data to Logs tab | ||
| logsSheet.appendRow([name, grade, type, timeString, dateString, notes]); | ||
| return { status: "SUCCESS", type: type }; | ||
| } catch (e) { | ||
| return { status: "ERROR", message: "Sheet operation failed: " + e.message }; | ||
| } | ||
| } | ||
|
|
||
| /* 3. CORE LOGIC FOR UPDATING STUDENTS SHEET */ | ||
| /** Core logic for updating Students sheet */ | ||
| function updateStudentRecord(sheet, name, newGrade, type, timestamp) { | ||
| const data = sheet.getDataRange().getValues(); | ||
| let rowIndex = -1; | ||
|
|
||
| // 1. FIND ROW | ||
| for (let i = 1; i < data.length; i++) { | ||
| if (data[i][0] === name) { | ||
| rowIndex = i + 1; // Convert 0-index to Sheet Row Number | ||
| break; | ||
| const data = sheet.getDataRange().getValues(); | ||
| let rowIndex = -1; | ||
| let normName = String(name).trim().toLowerCase(); | ||
|
|
||
| // Find row (case-insensitive, trim) | ||
| for (let i = 1; i < data.length; i++) { | ||
| if (String(data[i][COL_NAME - 1]).trim().toLowerCase() === normName) { | ||
| rowIndex = i + 1; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| // If new student, add them | ||
| if (rowIndex === -1) { | ||
| // Defend against missing columns by ensuring newRow has enough elements | ||
| let maxCol = Math.max(COL_DATE_ADDED, sheet.getLastColumn()); | ||
| let newRow = new Array(maxCol).fill(""); | ||
|
|
||
| newRow[COL_NAME - 1] = name; | ||
| newRow[COL_GRADE - 1] = newGrade; | ||
| newRow[COL_DATE_ADDED - 1] = timestamp; | ||
|
|
||
| sheet.appendRow(newRow); | ||
| rowIndex = sheet.getLastRow(); // Get the new row number | ||
| } | ||
|
|
||
| // Check for grade change | ||
| const gradeCell = sheet.getRange(rowIndex, COL_GRADE); | ||
| const currentGrade = gradeCell.getValue(); | ||
|
|
||
| // Only update if Grade is different | ||
| if (String(currentGrade) !== String(newGrade)) { | ||
| gradeCell.setValue(newGrade); | ||
| // Update "Grade Last Changed" | ||
| sheet.getRange(rowIndex, COL_GRADE_CHANGED).setValue(timestamp); | ||
| } | ||
|
|
||
| // Update Status and Last Seen | ||
| const statusCell = sheet.getRange(rowIndex, COL_STATUS); | ||
|
|
||
| if (type === "Check In") { | ||
| // Set Status text and color | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comment is good, a constant is better -- LIGHT_GREEN. |
||
| statusCell.setValue("Checked In").setBackground("#d9ead3"); // Light green | ||
| // Update Last Check In | ||
| sheet.getRange(rowIndex, COL_LAST_IN).setValue(timestamp); | ||
| } else { | ||
| // Set Status text and color | ||
| statusCell.setValue("Checked Out").setBackground("#f4cccc"); // Light red | ||
| // Update Last Check Out | ||
| sheet.getRange(rowIndex, COL_LAST_OUT).setValue(timestamp); | ||
| } | ||
| } | ||
|
|
||
| // 2. IF NEW STUDENT (Add them) | ||
| if (rowIndex === -1) { | ||
| // Append: Name, Grade, Status(empty), LastIn(empty), LastOut(empty), GradeChanged(empty), DateAdded | ||
| sheet.appendRow([name, newGrade, "", "", "", "", timestamp]); | ||
| rowIndex = sheet.getLastRow(); // Get the new row number | ||
| } | ||
|
|
||
| // 3. CHECK FOR GRADE CHANGE (Col B -> Column 2) | ||
| const gradeCell = sheet.getRange(rowIndex, 2); | ||
| const currentGrade = gradeCell.getValue(); | ||
|
|
||
| // Only update if grade is different | ||
| if (String(currentGrade) !== String(newGrade)) { | ||
| gradeCell.setValue(newGrade); | ||
| // Update "Grade Last Changed" (Col F -> Column 6) | ||
| sheet.getRange(rowIndex, 6).setValue(timestamp); | ||
| } | ||
|
|
||
| // 4. UPDATE STATUS & LAST SEEN | ||
| const statusCell = sheet.getRange(rowIndex, 3); // Col C | ||
|
|
||
| if (type === "Check In") { | ||
| // Set Status Text & Color | ||
| statusCell.setValue("Checked In").setBackground("#d9ead3"); // Light Green | ||
| // Update Last Check In (Col D -> Column 4) | ||
| sheet.getRange(rowIndex, 4).setValue(timestamp); | ||
| } else { | ||
| // Set Status Text & Color | ||
| statusCell.setValue("Checked Out").setBackground("#f4cccc"); // Light Red | ||
| // Update Last Check Out (Col E -> Column 5) | ||
| sheet.getRange(rowIndex, 5).setValue(timestamp); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider creating a function
safeTrim(..)to avoid repetition