Sync app/scripts.html
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user