The Roster System is a comprehensive Google Apps Script web application designed for modern staff roster management. Built by Backpack Software House, this free and open-source starter kit provides teams with powerful tools to manage rosters, users, and settings through an intuitive, responsive interface.
| Feature | Description | Status |
|---|---|---|
| Dashboard Analytics | Visual overview with key metrics | Complete |
| User Management | Comprehensive access control | Complete |
| Roster Management | Full CRUD operations | Complete |
| Settings Control | Admin panel for configuration | Complete |
| Responsive Design | Mobile-first interface | Optimized |
| Google Integration | Seamless Sheets connectivity | Active |
| Access Control | Role-based permissions | Secure |
| Modern UI | Custom theming system | Beautiful |
Prerequisites
- Google account with Apps Script access
- Google Sheets for data storage
- Basic understanding of web applications
- Admin privileges for initial setup
Installation Steps
1. Create Google Apps Script Project
# Navigate to Google Apps Script
https://script.google.com
# Create new project
# Copy all project files2. Set up Google Sheets Structure
# Create sheets with these tabs:
- Settings
- Rosters
- Users Rosters
- Raw dashboard3. Configure Project Manifest
{
"timeZone": "Asia/Singapore",
"dependencies": {
"enabledAdvancedServices": [
{
"userSymbol": "Calendar",
"version": "v3",
"serviceId": "calendar"
}
]
},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"webapp": {
"executeAs": "USER_ACCESSING",
"access": "ANYONE"
}
}4. Deploy as Web Application
# Deploy > New deployment
# Type: Web app
# Execute as: User accessing the web app
# Access: Anyonegraph TB
A[User Access] --> B{Authorization Check}
B -->|Authorized| C[Main Application]
B -->|Unauthorized| D[Error Page]
C --> E[Dashboard]
C --> F[Settings Panel]
E --> G[Roster Analytics]
E --> H[User Statistics]
E --> I[Recent Activity]
F --> J[User Management]
F --> K[Admin Control]
F --> L[Roster Types]
F --> M[Status Management]
G --> N[Google Sheets]
H --> N
I --> N
J --> N
K --> N
L --> N
M --> N
Application Layer
| Component | Purpose | Key Features |
|---|---|---|
| RoosterApp.html | Main interface | Responsive layout, navigation, theming |
| Rooster App.js | Core logic | HTTP handling, CRUD operations, access control |
| Unauthorized.html | Error handling | Professional error page for unauthorized access |
Dashboard System
| Component | Purpose | Key Features |
|---|---|---|
| CRUDDashboard.html | Analytics interface | Metrics cards, recent activity, user stats |
| CRUD Dashboard.js | Dashboard logic | Data processing, statistics calculation |
Settings Management
| Component | Purpose | Key Features |
|---|---|---|
| CRUDSettings.html | Admin interface | User management, roster configuration |
| CRUD Settings.js | Settings logic | Admin operations, data validation |
Data Layer
| Sheet | Purpose | Key Fields |
|---|---|---|
| Settings | User access control | email, role, permissions, status |
| Rosters | Roster definitions | name, type, status, assignments |
| Users Rosters | User assignments | user_id, roster_id, role, dates |
| Raw dashboard | Analytics data | metrics, counts, activity logs |
| Feature | Implementation | Benefit |
|---|---|---|
| Email Verification | Settings sheet validation | Prevents unauthorized access |
| Role-based Access | Admin/User permissions | Granular control |
| Session Management | Google OAuth integration | Secure authentication |
| Error Handling | Graceful failure modes | Professional user experience |
|
Key Metrics
|
Recent Activity
|
|
User Statistics
|
Visual Analytics
|
Comprehensive administrative control panel
| User Management | System Configuration | Roster Control |
|---|---|---|
|
|
|
User Authentication
function checkUserAccess() {
try {
const userEmail = Session.getActiveUser().getEmail();
const settingsSheet = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName('Settings');
const data = settingsSheet.getDataRange().getValues();
const userRow = data.find(row => row[0] === userEmail);
return {
hasAccess: !!userRow,
isAdmin: userRow && userRow[1] === 'Admin',
userInfo: userRow
};
} catch (error) {
console.error('Access check failed:', error);
return { hasAccess: false, isAdmin: false };
}
}Dashboard Data Processing
function getDashboardMetrics() {
try {
const sheets = {
rosters: SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Rosters'),
users: SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Users Rosters'),
dashboard: SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Raw dashboard')
};
const currentMonth = new Date().getMonth();
const rostersData = sheets.rosters.getDataRange().getValues();
return {
monthlyRosters: rostersData.filter(row =>
new Date(row[2]).getMonth() === currentMonth
).length,
totalAssignments: sheets.users.getLastRow() - 1,
activeUsers: new Set(sheets.users.getRange('A:A').getValues().flat()).size,
recentActivity: getRecentActivity(sheets.dashboard)
};
} catch (error) {
console.error('Dashboard metrics failed:', error);
return {};
}
}CRUD Operations
function createRoster(rosterData) {
try {
const sheet = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName('Rosters');
const newRow = [
rosterData.name,
rosterData.type,
rosterData.status,
new Date(),
Session.getActiveUser().getEmail()
];
sheet.appendRow(newRow);
return {
success: true,
message: 'Roster created successfully',
rosterId: sheet.getLastRow()
};
} catch (error) {
console.error('Create roster failed:', error);
return {
success: false,
message: 'Failed to create roster'
};
}
}Batch Operations
function batchUpdateRosters(updates) {
try {
const sheet = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName('Rosters');
updates.forEach(update => {
const range = sheet.getRange(update.row, update.column);
range.setValue(update.value);
});
return {
success: true,
updated: updates.length,
timestamp: new Date()
};
} catch (error) {
console.error('Batch update failed:', error);
return { success: false, error: error.message };
}
}The application uses a sophisticated color palette that can be customized:
:root {
--dark-teal: #001219; /* Primary dark backgrounds */
--teal: #005F73; /* Navigation and headers */
--turquoise: #0A9396; /* Interactive elements */
--light-teal: #94D2BD; /* Subtle backgrounds */
--cream: #E9D8A6; /* Content backgrounds */
--orange: #EE9B00; /* Warning states */
--burnt-orange: #CA6702; /* Active states */
--rust: #BB3E03; /* Error states */
--red: #AE2012; /* Critical alerts */
--dark-red: #9B2226; /* Danger zones */
}Custom Dashboard Widgets
function addCustomWidget(widgetConfig) {
const widget = {
title: widgetConfig.title,
data: widgetConfig.dataSource(),
template: widgetConfig.template,
refreshInterval: widgetConfig.refresh || 300000
};
// Add to dashboard rendering
return widget;
}New Settings Categories
function addSettingsCategory(category) {
const settingsConfig = {
name: category.name,
fields: category.fields,
validation: category.validation,
permissions: category.permissions
};
// Register with settings system
return settingsConfig;
}// Batch operations for better performance
function optimizedDataFetch() {
const sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
const data = {};
sheets.forEach(sheet => {
data[sheet.getName()] = sheet.getDataRange().getValues();
});
return data;
}
// Caching for frequently accessed data
const cache = CacheService.getScriptCache();
function getCachedData(key, fetchFunction, ttl = 300) {
let data = cache.get(key);
if (!data) {
data = JSON.stringify(fetchFunction());
cache.put(key, data, ttl);
}
return JSON.parse(data);
}Step-by-step deployment process
|
Preparation
|
Deployment
|
|
Verification
|
Monitoring
|
// Regular maintenance functions
function performMaintenance() {
// Clean old logs
cleanupLogs();
// Optimize sheets
optimizeSheetStructure();
// Update cache
refreshSystemCache();
// Generate reports
generateMaintenanceReport();
}
function cleanupLogs() {
const logSheet = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName('System Logs');
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - 30);
// Remove logs older than 30 days
const data = logSheet.getDataRange().getValues();
const filteredData = data.filter(row =>
new Date(row[0]) > cutoffDate
);
logSheet.clear();
logSheet.getRange(1, 1, filteredData.length, filteredData[0].length)
.setValues(filteredData);
}Access Denied Errors
Problem: Users cannot access the application
Solutions:
- Verify user email in Settings sheet
- Check deployment permissions
- Confirm OAuth scopes
- Review sharing settings
// Debug access issues
function debugUserAccess(userEmail) {
const settingsSheet = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName('Settings');
const users = settingsSheet.getDataRange().getValues();
const userFound = users.find(row => row[0] === userEmail);
console.log('User lookup result:', {
email: userEmail,
found: !!userFound,
details: userFound
});
}Performance Issues
Problem: Slow loading or timeouts
Solutions:
- Implement data caching
- Optimize sheet queries
- Reduce data transfer
- Use batch operations
// Performance monitoring
function monitorPerformance() {
const startTime = new Date();
// Your operation here
performOperation();
const endTime = new Date();
const duration = endTime - startTime;
console.log(`Operation took \${duration}ms`);
if (duration > 5000) {
console.warn('Performance issue detected');
}
}Data Synchronization
Problem: Data inconsistencies between sheets
Solutions:
- Implement data validation
- Add referential integrity checks
- Use transaction-like operations
- Regular data audits
// Data integrity check
function validateDataIntegrity() {
const issues = [];
// Check for orphaned records
const rosters = getRosterIds();
const assignments = getAssignmentRosterIds();
assignments.forEach(id => {
if (!rosters.includes(id)) {
issues.push(`Orphaned assignment for roster \${id}`);
}
});
return issues;
}Join our community of contributors!
|
Report Issues Found a bug or have suggestions? Create detailed issue reports |
Contribute Code Fork, develop, and submit PRs Follow our coding standards |
Improve Docs Help make documentation better Add examples and tutorials |
Share Ideas Propose new features Join community discussions |
# 1. Fork the repository
git clone https://github.com/yourusername/rooster-system.git
cd rooster-system
# 2. Create feature branch
git checkout -b feature/amazing-feature
# 3. Make changes and test
# Test in Google Apps Script environment
# Verify all functionality works
# 4. Commit with clear messages
git add .
git commit -m "feat: add user bulk import functionality"
# 5. Push and create pull request
git push origin feature/amazing-feature- JavaScript Style: Use ES6+ features, consistent naming
- HTML Structure: Semantic markup, accessibility considerations
- CSS Organization: Custom properties, responsive design
- Documentation: Clear comments, updated README
- Testing: Thorough testing in Apps Script environment
Modern software house dedicated to accessible, beautiful, and practical tools
|
Our Mission Building tools that empower teams and make work more efficient |
Our Values Open source, community-driven accessible to everyone |
Our Team Passionate developers creating solutions that matter |
Our Commitment Free, high-quality tools for the global community |
![]() Lead Developer @Darkguyaiman |
![]() Co-Developer @Ak-ko |
Crafted with care for the community.
Need Help?
- Check our documentation
- Browse existing issues
- Create new issue reports
- Join community discussions
Star this repository if you find it useful!
Built with care by Backpack Software House

