πͺππ½πΎ π¨ππ π₯ππ πΎ ππΊππ.ππ
πͺππ½πΎ π¨ππ π₯ππ πΎ ππΊππ.ππ
π‘πΊπππ π»ππΊπππΊπ πππ½πΎ πππ πΏππ πΎ ππΊππ.ππ
FarmerSmartAI - File JavaScript Utama
Berikut kode lengkap untuk file main.js yang berisi fungsi-fungsi umum dan utilitas untuk aplikasi FarmerSmartAI:
```javascript
// main.js - JavaScript Utama FarmerSmartAI
class FarmerSmartAI {
constructor() {
this.currentUser = null;
this.settings = this.loadSettings();
this.init();
}
// ===== INITIALIZATION =====
init() {
this.setupEventListeners();
this.setupServiceWorker();
this.setupOfflineDetection();
this.loadUserData();
this.setupTheme();
this.setupLanguage();
}
// ===== USER MANAGEMENT =====
async loadUserData() {
try {
const userData = localStorage.getItem('farmerSmartAI_user');
if (userData) {
this.currentUser = JSON.parse(userData);
this.updateUIUserInfo();
}
} catch (error) {
console.error('Error loading user data:', error);
}
}
async login(email, password) {
try {
// Simulasi API call
const response = await this.mockAPICall('login', { email, password });
if (response.success) {
this.currentUser = response.user;
localStorage.setItem('farmerSmartAI_user', JSON.stringify(response.user));
this.updateUIUserInfo();
this.showNotification('Login berhasil!', 'success');
return true;
} else {
this.showNotification('Email atau password salah', 'error');
return false;
}
} catch (error) {
console.error('Login error:', error);
this.showNotification('Terjadi kesalahan saat login', 'error');
return false;
}
}
async register(userData) {
try {
const response = await this.mockAPICall('register', userData);
if (response.success) {
this.showNotification('Registrasi berhasil! Silakan login.', 'success');
return true;
} else {
this.showNotification('Registrasi gagal', 'error');
return false;
}
} catch (error) {
console.error('Registration error:', error);
this.showNotification('Terjadi kesalahan saat registrasi', 'error');
return false;
}
}
logout() {
this.currentUser = null;
localStorage.removeItem('farmerSmartAI_user');
window.location.href = 'index.html';
}
updateUIUserInfo() {
const userElements = document.querySelectorAll('.user-info, .user-avatar, .profile-info');
userElements.forEach(element => {
if (element.classList.contains('user-avatar') && this.currentUser) {
const names = this.currentUser.name.split(' ');
const initials = names.map(name => name[0]).join('').toUpperCase();
element.textContent = initials;
}
if (element.classList.contains('profile-info') && this.currentUser) {
const nameElement = element.querySelector('h3');
const emailElement = element.querySelector('.profile-email');
if (nameElement) nameElement.textContent = this.currentUser.name;
if (emailElement) emailElement.textContent = this.currentUser.email;
}
});
}
// ===== SETTINGS MANAGEMENT =====
loadSettings() {
const defaultSettings = {
theme: 'light',
language: 'id',
notifications: {
email: true,
browser: true,
sms: false
},
units: {
temperature: 'celsius',
distance: 'metric',
volume: 'liters'
}
};
try {
const savedSettings = localStorage.getItem('farmerSmartAI_settings');
return savedSettings ? { ...defaultSettings, ...JSON.parse(savedSettings) } : defaultSettings;
} catch (error) {
console.error('Error loading settings:', error);
return defaultSettings;
}
}
saveSettings(newSettings) {
this.settings = { ...this.settings, ...newSettings };
localStorage.setItem('farmerSmartAI_settings', JSON.stringify(this.settings));
this.applySettings();
}
applySettings() {
this.applyTheme();
this.applyLanguage();
}
// ===== THEME MANAGEMENT =====
setupTheme() {
const savedTheme = this.settings.theme;
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (savedTheme === 'auto') {
this.applyTheme(systemPrefersDark ? 'dark' : 'light');
} else {
this.applyTheme(savedTheme);
}
}
applyTheme(theme = this.settings.theme) {
const html = document.documentElement;
if (theme === 'dark') {
html.setAttribute('data-theme', 'dark');
html.classList.add('dark-mode');
} else {
html.removeAttribute('data-theme');
html.classList.remove('dark-mode');
}
}
// ===== LANGUAGE MANAGEMENT =====
setupLanguage() {
this.applyLanguage(this.settings.language);
}
applyLanguage(lang = 'id') {
document.documentElement.setAttribute('lang', lang);
// In a real app, you would load translation files here
}
// ===== NOTIFICATION SYSTEM =====
showNotification(message, type = 'info', duration = 5000) {
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.innerHTML = `
<div class="notification-content">
<i class="fas fa-${this.getNotificationIcon(type)}"></i>
<span>${message}</span>
</div>
<button class="notification-close" onclick="this.parentElement.remove()">
<i class="fas fa-times"></i>
</button>
`;
// Add styles if not already added
if (!document.querySelector('#notification-styles')) {
const styles = document.createElement('style');
styles.id = 'notification-styles';
styles.textContent = `
.notification {
position: fixed;
top: 20px;
right: 20px;
background: white;
border-radius: 8px;
padding: 15px 20px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
border-left: 4px solid #2196F3;
z-index: 10000;
max-width: 400px;
animation: slideInRight 0.3s ease;
}
.notification-success { border-left-color: #4CAF50; }
.notification-error { border-left-color: #f44336; }
.notification-warning { border-left-color: #ff9800; }
.notification-content {
display: flex;
align-items: center;
gap: 10px;
}
.notification-close {
background: none;
border: none;
cursor: pointer;
padding: 5px;
margin-left: 10px;
}
@keyframes slideInRight {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
`;
document.head.appendChild(styles);
}
document.body.appendChild(notification);
// Auto remove after duration
if (duration > 0) {
setTimeout(() => {
if (notification.parentElement) {
notification.remove();
}
}, duration);
}
return notification;
}
getNotificationIcon(type) {
const icons = {
success: 'check-circle',
error: 'exclamation-circle',
warning: 'exclamation-triangle',
info: 'info-circle'
};
return icons[type] || 'info-circle';
}
// ===== OFFLINE DETECTION =====
setupOfflineDetection() {
window.addEventListener('online', () => {
this.showNotification('Koneksi internet kembali', 'success');
this.syncOfflineData();
});
window.addEventListener('offline', () => {
this.showNotification('Anda sedang offline', 'warning', 0);
});
}
async syncOfflineData() {
// Sync any pending data when coming back online
const pendingActions = JSON.parse(localStorage.getItem('farmerSmartAI_pendingActions') || '[]');
for (const action of pendingActions) {
try {
await this.mockAPICall(action.type, action.data);
} catch (error) {
console.error('Failed to sync action:', action, error);
}
}
localStorage.removeItem('farmerSmartAI_pendingActions');
}
// ===== SERVICE WORKER =====
setupServiceWorker() {
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
const registration = await navigator.serviceWorker.register('/sw.js');
console.log('ServiceWorker registered:', registration);
} catch (error) {
console.log('ServiceWorker registration failed:', error);
}
});
}
}
// ===== DATA FORMATTING =====
formatNumber(number, decimals = 2) {
return new Intl.NumberFormat('id-ID', {
minimumFractionDigits: decimals,
maximumFractionDigits: decimals
}).format(number);
}
formatDate(date, options = {}) {
const defaultOptions = {
year: 'numeric',
month: 'long',
day: 'numeric'
};
return new Intl.DateTimeFormat('id-ID', { ...defaultOptions, ...options }).format(new Date(date));
}
formatCurrency(amount) {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0
}).format(amount);
}
formatPercentage(value, decimals = 1) {
return `${this.formatNumber(value, decimals)}%`;
}
// ===== SENSOR DATA SIMULATION =====
generateSensorData() {
return {
soil_moisture: this.randomInt(30, 80),
temperature: this.randomInt(20, 35) + Math.random(),
humidity: this.randomInt(40, 90),
ph_level: this.randomInt(55, 75) / 10,
nutrient_n: this.randomInt(20, 60),
nutrient_p: this.randomInt(15, 40),
nutrient_k: this.randomInt(10, 50),
timestamp: new Date().toISOString()
};
}
randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// ===== API SIMULATION =====
async mockAPICall(endpoint, data) {
// Simulate API delay
await this.delay(1000 + Math.random() * 1000);
const responses = {
login: () => {
if (data.email === 'demo@farmersmartai.com' && data.password === 'demo123') {
return {
success: true,
user: {
id: 1,
name: 'Petani Smart',
email: data.email,
farmName: 'Kebun Sejahtera',
farmSize: 5.2,
joinDate: '2023-01-15'
},
token: 'mock_jwt_token_' + Date.now()
};
}
return { success: false, message: 'Invalid credentials' };
},
register: () => {
return { success: true, message: 'User registered successfully' };
},
sensorData: () => {
return {
success: true,
data: Array.from({ length: 24 }, (_, i) => ({
...this.generateSensorData(),
timestamp: new Date(Date.now() - i * 3600000).toISOString()
}))
};
},
recommendations: () => {
return {
success: true,
recommendations: [
{
type: 'irrigation',
priority: 'high',
message: 'Kelembaban tanah rendah, perlu irigasi',
action: 'schedule_irrigation',
parameters: { duration: 30, area: 'north' }
},
{
type: 'fertilization',
priority: 'medium',
message: 'Level kalium rendah, pertimbangkan pemupukan',
action: 'apply_fertilizer',
parameters: { type: 'KCL', amount: 50 }
}
]
};
}
};
const handler = responses[endpoint];
return handler ? handler() : { success: false, message: 'Endpoint not found' };
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// ===== FORM HANDLING =====
setupFormValidation(formSelector) {
const forms = document.querySelectorAll(formSelector);
forms.forEach(form => {
form.addEventListener('submit', (e) => {
if (!this.validateForm(form)) {
e.preventDefault();
this.showNotification('Harap periksa form yang diisi', 'error');
}
});
// Real-time validation
const inputs = form.querySelectorAll('input[required], select[required], textarea[required]');
inputs.forEach(input => {
input.addEventListener('blur', () => this.validateField(input));
input.addEventListener('input', () => this.clearFieldError(input));
});
});
}
validateForm(form) {
let isValid = true;
const inputs = form.querySelectorAll('input[required], select[required], textarea[required]');
inputs.forEach(input => {
if (!this.validateField(input)) {
isValid = false;
}
});
return isValid;
}
validateField(field) {
this.clearFieldError(field);
let isValid = true;
let errorMessage = '';
// Check required fields
if (field.hasAttribute('required') && !field.value.trim()) {
isValid = false;
errorMessage = field.getAttribute('data-error-required') || 'Field ini wajib diisi';
}
// Email validation
if (field.type === 'email' && field.value) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(field.value)) {
isValid = false;
errorMessage = field.getAttribute('data-error-email') || 'Format email tidak valid';
}
}
// Password strength
if (field.type === 'password' && field.value) {
if (field.value.length < 8) {
isValid = false;
errorMessage = 'Password harus minimal 8 karakter';
}
}
// Phone number validation
if (field.type === 'tel' && field.value) {
const phoneRegex = /^[+]?[\d\s\-()]{10,}$/;
if (!phoneRegex.test(field.value)) {
isValid = false;
errorMessage = 'Format nomor telepon tidak valid';
}
}
if (!isValid) {
this.showFieldError(field, errorMessage);
}
return isValid;
}
showFieldError(field, message) {
field.classList.add('error');
let errorElement = field.parentNode.querySelector('.field-error');
if (!errorElement) {
errorElement = document.createElement('div');
errorElement.className = 'field-error';
field.parentNode.appendChild(errorElement);
}
errorElement.textContent = message;
}
clearFieldError(field) {
field.classList.remove('error');
const errorElement = field.parentNode.querySelector('.field-error');
if (errorElement) {
errorElement.remove();
}
}
// ===== CHART HELPERS =====
createChart(canvasId, config) {
const ctx = document.getElementById(canvasId)?.getContext('2d');
if (!ctx) {
console.error(`Canvas with id ${canvasId} not found`);
return null;
}
const defaultConfig = {
type: 'line',
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'top',
}
}
}
};
return new Chart(ctx, { ...defaultConfig, ...config });
}
// ===== EVENT LISTENERS SETUP =====
setupEventListeners() {
// Mobile menu toggle
this.setupMobileMenu();
// Logout buttons
this.setupLogoutHandlers();
// Theme toggle
this.setupThemeToggle();
// Form submissions
this.setupFormValidation('form[data-validate]');
// Tab navigation
this.setupTabNavigation();
// Modal handling
this.setupModalHandlers();
}
setupMobileMenu() {
const menuButtons = document.querySelectorAll('.mobile-menu-btn');
const sidebar = document.querySelector('.sidebar');
const overlay = document.createElement('div');
overlay.className = 'sidebar-overlay';
menuButtons.forEach(btn => {
btn.addEventListener('click', () => {
sidebar.classList.toggle('active');
if (sidebar.classList.contains('active')) {
document.body.appendChild(overlay);
document.body.style.overflow = 'hidden';
} else {
overlay.remove();
document.body.style.overflow = '';
}
});
});
// Close sidebar when clicking overlay
overlay.addEventListener('click', () => {
sidebar.classList.remove('active');
overlay.remove();
document.body.style.overflow = '';
});
}
setupLogoutHandlers() {
const logoutButtons = document.querySelectorAll('[data-action="logout"]');
logoutButtons.forEach(btn => {
btn.addEventListener('click', (e) => {
e.preventDefault();
if (confirm('Apakah Anda yakin ingin logout?')) {
this.logout();
}
});
});
}
setupThemeToggle() {
const themeToggles = document.querySelectorAll('[data-action="toggle-theme"]');
themeToggles.forEach(toggle => {
toggle.addEventListener('click', () => {
const newTheme = this.settings.theme === 'dark' ? 'light' : 'dark';
this.saveSettings({ theme: newTheme });
this.showNotification(`Tema diubah ke ${newTheme === 'dark' ? 'gelap' : 'terang'}`, 'success');
});
});
}
setupTabNavigation() {
const tabContainers = document.querySelectorAll('.tab-container, .settings-tabs');
tabContainers.forEach(container => {
const tabs = container.querySelectorAll('.tab');
const contents = container.querySelectorAll('.tab-content');
tabs.forEach(tab => {
tab.addEventListener('click', () => {
const target = tab.getAttribute('data-tab');
// Update active tab
tabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
// Show target content
contents.forEach(content => {
content.classList.remove('active');
if (content.id === `${target}-tab`) {
content.classList.add('active');
}
});
});
});
});
}
setupModalHandlers() {
const modalTriggers = document.querySelectorAll('[data-modal]');
const modals = document.querySelectorAll('.modal');
modalTriggers.forEach(trigger => {
trigger.addEventListener('click', () => {
const modalId = trigger.getAttribute('data-modal');
const modal = document.getElementById(modalId);
if (modal) {
modal.classList.add('active');
document.body.style.overflow = 'hidden';
}
});
});
// Close modals
modals.forEach(modal => {
const closeBtn = modal.querySelector('.modal-close');
if (closeBtn) {
closeBtn.addEventListener('click', () => {
modal.classList.remove('active');
document.body.style.overflow = '';
});
}
// Close on overlay click
modal.addEventListener('click', (e) => {
if (e.target === modal) {
modal.classList.remove('active');
document.body.style.overflow = '';
}
});
});
}
// ===== PERFORMANCE OPTIMIZATION =====
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// ===== EXPORT DATA =====
exportToCSV(data, filename) {
if (!data || !data.length) return;
const headers = Object.keys(data[0]);
const csvContent = [
headers.join(','),
...data.map(row => headers.map(header => JSON.stringify(row[header])).join(','))
].join('\n');
this.downloadFile(csvContent, filename, 'text/csv');
}
exportToJSON(data, filename) {
const jsonContent = JSON.stringify(data, null, 2);
this.downloadFile(jsonContent, filename, 'application/json');
}
downloadFile(content, filename, contentType) {
const blob = new Blob([content], { type: contentType });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
// ===== SENSOR DATA MANAGEMENT =====
async getSensorData(range = '24h') {
try {
const response = await this.mockAPICall('sensorData', { range });
return response.success ? response.data : [];
} catch (error) {
console.error('Error fetching sensor data:', error);
return [];
}
}
async getAIRecommendations() {
try {
const response = await this.mockAPICall('recommendations', {});
return response.success ? response.recommendations : [];
} catch (error) {
console.error('Error fetching recommendations:', error);
return [];
}
}
}
// ===== GLOBAL INITIALIZATION =====
let app;
document.addEventListener('DOMContentLoaded', function() {
app = new FarmerSmartAI();
// Make app globally available for HTML event handlers
window.FarmerSmartAI = app;
console.log('FarmerSmartAI initialized');
});
// ===== UTILITY FUNCTIONS (Global) =====
window.formatNumber = (number, decimals = 2) => {
return app ? app.formatNumber(number, decimals) : number.toFixed(decimals);
};
window.formatDate = (date, options) => {
return app ? app.formatDate(date, options) : new Date(date).toLocaleDateString('id-ID');
};
window.showNotification = (message, type, duration) => {
return app ? app.showNotification(message, type, duration) : console.log(`[${type}] ${message}`);
};
// ===== ERROR HANDLING =====
window.addEventListener('error', (event) => {
console.error('Global error:', event.error);
if (app) {
app.showNotification('Terjadi kesalahan sistem', 'error');
}
});
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled promise rejection:', event.reason);
event.preventDefault();
});
```
Cara Penggunaan:
1. Simpan file sebagai main.js di folder JS project Anda
2. Hubungkan ke HTML dengan menambahkan sebelum penutup </body>:
```html
<script src="js/main.js"></script>
```
Fitur Utama Main.js:
π️ Architecture & Initialization
· Class-based structure untuk organized code
· Auto-initialization pada DOM ready
· Comprehensive error handling
π€ User Management
· Login/logout functionality
· User session persistence
· Profile data management
⚙️ Settings System
· Theme management (light/dark/auto)
· Language support
· Persistent settings storage
π Notification System
· Toast notifications dengan berbagai types
· Auto-dismiss functionality
· Customizable duration
π± Responsive Behavior
· Mobile menu handling
· Touch event optimization
· Responsive utilities
π Data Management
· Sensor data simulation
· API call abstractions
· Offline data synchronization
π¨ UI Utilities
· Form validation system
· Modal management
· Tab navigation
· Chart helpers
π Export Features
· CSV export untuk data
· JSON export
· File download utilities
⚡ Performance
· Debounce dan throttle functions
· Efficient event handling
· Memory management
π§ Development Tools
· Mock API untuk development
· Debug logging
· Error tracking
File ini menyediakan foundation πang solid untuk seluruh aplikasi FarmerSmartAI dengan fungsi-fungsi yang reusable dan maintainable.
π‘πΎπππΊππ»πππ ππΎ: πͺππ½πΎ π¨ππ π₯ππ πΎ dashboard.js
Comments
Post a Comment