508 lines
15 KiB
HTML
508 lines
15 KiB
HTML
<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>
|