Sync app/scripts.html

This commit is contained in:
2026-04-24 23:44:53 +00:00
parent dbb2c8891b
commit 8301255dd6
+507
View File
@@ -0,0 +1,507 @@
<script src="https://cdn.jsdelivr.net/npm/framework7@8/framework7-bundle.min.js"></script>
<script>
function appLauncher() {
return {
// --- Framework7 Instance ---
f7: null,
// --- Core State ---
loading: true,
user: null,
apps: [],
error: null,
isAdmin: false,
authenticated: false,
// --- Toast System ---
toast: {
open: false,
type: 'success', // 'success' | 'warning' | 'danger'
title: '',
message: ''
},
toastTimer: null,
showToast(type, title, message, ms = 2500) {
this.toast.type = type || 'success';
this.toast.title = title || '';
this.toast.message = message || '';
this.toast.open = true;
if (this.toastTimer) clearTimeout(this.toastTimer);
this.toastTimer = setTimeout(() => {
this.toast.open = false;
this.toastTimer = null;
}, ms);
},
// --- Setup & Auth State ---
isSystemEmpty: false,
needsAuth: false,
userEmail: '',
setupName: '',
loginMethod: 'google',
loginLoading: false,
authPhone: '',
authCode: '',
otpSent: false,
// --- Admin View States ---
view: 'home',
loadingProgress: 0,
loadingStage: 0,
loadingMessage: 'Initializing...',
fetchProgress: 0,
layoutMode: 'grid',
adminApps: [],
adminUsers: [],
loadingApps: false,
loadingUsers: false,
userCategory: 'all',
// --- Modal States ---
modalOpen: false, // App Modal
userModalOpen: false, // User Modal
accessModalOpen: false, // Access Modal
confirmModalOpen: false, // Confirm Modal
alertModalOpen: false, // Alert Modal
saving: false,
// --- Form Data ---
appForm: {
isNew: true,
appId: '',
appName: '',
appUrl: '',
sortOrder: 0,
active: 'TRUE'
},
userForm: {
isNew: true,
email: '',
name: '',
unit: '',
status: 'active',
isAdmin: 'FALSE'
},
// --- Modal Content ---
confirmData: { title: '', message: '' },
confirmAction: null,
alertData: { title: '', message: '' },
accessUser: null,
userAccessList: [],
availableApps: [],
// --- Computed ---
get filteredUsers() {
if (this.userCategory === 'all') return this.adminUsers;
if (this.userCategory === 'admin') return this.adminUsers.filter(u => u.isAdmin === 'TRUE' || u.isAdmin === true);
if (this.userCategory === 'active') return this.adminUsers.filter(u => String(u.status || '').toLowerCase() === 'active');
if (this.userCategory === 'inactive') return this.adminUsers.filter(u => String(u.status || '').toLowerCase() !== 'active');
return this.adminUsers;
},
// --- Initialization ---
init() {
this.fetchData();
},
initF7() {
if (this.f7) return;
this.$nextTick(() => {
const appEl = document.getElementById('app');
if (!appEl) return;
// Double check if F7 has already attached to the element to prevent "already initialized" error
if (appEl.classList.contains('framework7-initialized')) return;
try {
this.f7 = new Framework7({
el: '#app',
name: 'SchoolHub',
theme: 'ios',
touch: { tapHold: true }
});
appEl.classList.add('framework7-initialized');
console.log('Framework7 initialized successfully');
} catch (e) {
console.error('F7 Init Error:', e);
}
});
},
// --- Authentication Logic ---
async doGoogleLogin() {
console.log('Attempting Google Login...');
this.loginLoading = true;
google.script.run
.withSuccessHandler((res) => {
console.log('Google Login Response:', res);
this.loginLoading = false;
if (res && res.ok) {
this.completeLogin(res.data);
} else {
const msg = (res && res.message) ? res.message : 'Email tidak terdeteksi.';
this.showToast('danger', 'Gagal', msg);
}
})
.withFailureHandler((err) => {
console.error('Google Login Failure:', err);
this.loginLoading = false;
this.showToast('danger', 'Error', 'Koneksi server gagal');
})
.loginWithGoogle();
},
sendOTP() {
if (!this.authPhone) return this.showToast('warning', 'Peringatan', 'Masukkan nomor WA');
this.loginLoading = true;
google.script.run
.withSuccessHandler((res) => {
this.loginLoading = false;
if (res.ok) {
this.otpSent = true;
this.showToast('success', 'Terkirim', res.message);
} else {
this.showToast('danger', 'Gagal', res.message);
}
})
.withFailureHandler((err) => {
this.loginLoading = false;
this.showToast('danger', 'Error', 'Koneksi gagal');
})
.sendOTP(this.authPhone);
},
verifyOTP() {
if (this.authCode.length < 6) return this.showToast('warning', 'Peringatan', 'Kode OTP harus 6 digit');
this.loginLoading = true;
google.script.run
.withSuccessHandler((res) => {
this.loginLoading = false;
if (res.ok) {
this.completeLogin(res.data);
} else {
this.showToast('danger', 'Gagal', res.message);
}
})
.withFailureHandler((err) => {
this.loginLoading = false;
this.showToast('danger', 'Error', 'Koneksi gagal');
})
.verifyOTP(this.authPhone, this.authCode);
},
completeLogin(data) {
this.authenticated = true;
localStorage.setItem('schoolhub_sid', data.sessionId);
this.fetchData();
this.$nextTick(() => this.initF7());
this.showToast('success', 'Berhasil', 'Selamat datang kembali!');
},
exitApp() {
this.confirmData = {
title: 'Keluar Aplikasi?',
message: 'Apakah Anda yakin ingin keluar dari SchoolHub Launcher?'
};
this.confirmAction = () => {
localStorage.removeItem('schoolhub_sid');
this.authenticated = false;
this.user = null;
this.apps = [];
this.showToast('success', 'Logged Out', 'Anda telah keluar dari aplikasi');
};
this.confirmModalOpen = true;
},
// --- Data Orchestration ---
fetchData() {
const sid = localStorage.getItem('schoolhub_sid');
if (!sid && !this.authenticated) {
this.authenticated = false;
return;
}
this.loading = true;
this.error = null;
this.isSystemEmpty = false;
this.needsAuth = false;
this.user = null;
this.apps = [];
this.isAdmin = false;
this.loadingProgress = 10;
this.loadingStage = 1;
this.loadingMessage = 'Connecting to SchoolHub...';
google.script.run
.withSuccessHandler((res) => {
if (res && res.ok) {
this.loadingProgress = 50;
this.loadingStage = 3;
this.loadingMessage = 'Loading your dashboard...';
if (res.data.isSystemEmpty) {
this.isSystemEmpty = true;
this.userEmail = res.data.userEmail || '';
} else if (res.data.needsAuth) {
this.needsAuth = true;
} else {
this.user = res.data.user || null;
this.apps = res.data.apps || [];
this.isAdmin = res.data.isAdmin || false;
}
} else {
this.error = (res && res.message) ? res.message : 'Access denied.';
}
this.loadingProgress = 100;
this.loadingStage = 6;
this.loading = false;
})
.withFailureHandler((err) => {
this.error = 'A system error occurred. Please try again later.';
this.loading = false;
})
.getInitialData();
},
// --- Admin: App Management ---
fetchAdminApps() {
this.loadingApps = true;
google.script.run
.withSuccessHandler((res) => {
this.loadingApps = false;
if (res && res.ok) this.adminApps = res.data || [];
else this.showToast('danger', 'Error', res.message);
})
.withFailureHandler((err) => {
this.loadingApps = false;
this.showToast('danger', 'Error', 'Koneksi gagal');
})
.getAdminApps();
},
openAppModal(app = null) {
if (app) {
this.appForm = { ...app, isNew: false };
this.appForm.active = (app.active === true || app.active === 'TRUE') ? 'TRUE' : 'FALSE';
} else {
this.appForm = {
isNew: true,
appId: '',
appName: '',
appUrl: '',
sortOrder: this.adminApps.length + 1,
active: 'TRUE'
};
}
this.modalOpen = true;
},
saveApp() {
if (!this.appForm.appId || !this.appForm.appName || !this.appForm.appUrl) {
return this.showToast('warning', 'Peringatan', 'Mohon lengkapi data aplikasi.');
}
this.saving = true;
google.script.run
.withSuccessHandler((res) => {
this.saving = false;
if (res && res.ok) {
this.showToast('success', 'Berhasil', 'Aplikasi berhasil disimpan.');
this.modalOpen = false;
this.fetchAdminApps();
this.fetchData();
} else {
this.showToast('danger', 'Gagal', res.message);
}
})
.withFailureHandler((err) => {
this.saving = false;
this.showToast('danger', 'Error', 'Koneksi gagal');
})
.saveApp(this.appForm);
},
confirmDelete(app) {
this.confirmData = {
title: 'Hapus Aplikasi?',
message: `Apakah Anda yakin ingin menghapus ${app.appName}?`
};
this.confirmAction = () => {
this.saving = true;
google.script.run
.withSuccessHandler((res) => {
this.saving = false;
if (res.ok) {
this.showToast('success', 'Berhasil', 'Aplikasi dihapus.');
this.fetchAdminApps();
this.fetchData();
}
})
.deleteApp(app.appId);
};
this.confirmModalOpen = true;
},
// --- Admin: User Management ---
fetchAdminUsers() {
this.loadingUsers = true;
google.script.run
.withSuccessHandler((res) => {
this.loadingUsers = false;
if (res && res.ok) this.adminUsers = res.data || [];
else this.showToast('danger', 'Error', res.message);
})
.withFailureHandler((err) => {
this.loadingUsers = false;
this.showToast('danger', 'Error', 'Koneksi gagal');
})
.getAdminUsers();
},
openUserModal(user = null) {
if (user) {
this.userForm = { ...user, isNew: false };
this.userForm.isAdmin = (user.isAdmin === true || user.isAdmin === 'TRUE') ? 'TRUE' : 'FALSE';
} else {
this.userForm = { isNew: true, email: '', name: '', unit: '', status: 'active', isAdmin: 'FALSE' };
}
this.userModalOpen = true;
},
saveUser() {
if (!this.userForm.email || !this.userForm.name) {
return this.showToast('warning', 'Peringatan', 'Email dan nama wajib diisi.');
}
this.saving = true;
google.script.run
.withSuccessHandler((res) => {
this.saving = false;
if (res && res.ok) {
this.showToast('success', 'Berhasil', 'User berhasil disimpan.');
this.userModalOpen = false;
this.fetchAdminUsers();
} else {
this.showToast('danger', 'Gagal', res.message);
}
})
.withFailureHandler((err) => {
this.saving = false;
this.showToast('danger', 'Error', 'Koneksi gagal');
})
.saveUser(this.userForm);
},
confirmDeleteUser(user) {
this.confirmData = {
title: 'Hapus Pengguna?',
message: `Apakah Anda yakin ingin menghapus ${user.name}?`
};
this.confirmAction = () => {
this.saving = true;
google.script.run
.withSuccessHandler((res) => {
this.saving = false;
if (res.ok) {
this.userModalOpen = false;
this.showToast('success', 'Berhasil', 'User dihapus.');
this.fetchAdminUsers();
}
})
.deleteUser(user.email);
};
this.confirmModalOpen = true;
},
// --- Admin: Access Management ---
async openAccessModal(user) {
this.accessUser = user;
this.accessModalOpen = true;
google.script.run
.withSuccessHandler((res) => {
if (res && res.ok) {
this.availableApps = res.data.availableApps;
this.userAccessList = res.data.userAccess;
}
})
.getAccessForUser(user.email);
},
isAppAssigned(appId) {
return this.userAccessList.some(a => a.appId === appId);
},
getAppRole(appId) {
const access = this.userAccessList.find(a => a.appId === appId);
return access ? (access.roles || 'viewer').split(',')[0] : 'viewer';
},
async toggleAppAccess(app) {
const isAssigned = this.isAppAssigned(app.appId);
if (isAssigned) {
google.script.run
.withSuccessHandler((res) => {
if (res.ok) {
this.userAccessList = this.userAccessList.filter(a => a.appId !== app.appId);
this.showToast('success', 'Akses Dicabut', `Akses ${app.appName} dihapus`);
}
})
.deleteAccess(this.accessUser.email, app.appId);
} else {
const data = { email: this.accessUser.email, appId: app.appId, roles: 'viewer', active: 'TRUE' };
google.script.run
.withSuccessHandler((res) => {
if (res.ok) {
this.userAccessList.push({ appId: app.appId, roles: 'viewer', active: 'TRUE' });
this.showToast('success', 'Akses Diberikan', `Akses ${app.appName} diberikan`);
}
})
.saveAccess(data);
}
},
async updateAppRole(appId, newRole) {
const data = { email: this.accessUser.email, appId: appId, roles: newRole, active: 'TRUE' };
google.script.run
.withSuccessHandler((res) => {
if (res.ok) {
const idx = this.userAccessList.findIndex(a => a.appId === appId);
if (idx !== -1) this.userAccessList[idx].roles = newRole;
this.showToast('success', 'Role Updated', 'Role berhasil diperbarui');
}
})
.saveAccess(data);
},
// --- System Setup ---
registerFirstAdmin() {
if (!this.setupName) return alert('Nama admin wajib diisi.');
this.saving = true;
google.script.run
.withSuccessHandler((res) => {
this.saving = false;
if (res && res.ok) this.fetchData();
else alert(res.message);
})
.registerFirstAdmin(this.setupName);
},
// --- General Helpers ---
handleConfirm() {
if (this.confirmAction) {
this.confirmAction();
this.confirmAction = null;
}
this.confirmModalOpen = false;
},
openApp(url) {
if (url) window.open(url, '_blank', 'noopener,noreferrer');
}
};
}
</script>