secutorpro's picture
réorganiser tout en pro achitecte pas un lambda ok tu es un boss
52fac7f verified
Raw
History Blame Contribute Delete
15.9 kB
```javascript
/**
* UTILITY SYSTEM
* Professional Helper Functions and Classes
*/
// Storage Manager
class StorageManager {
constructor(prefix = 'patrol_') {
this.prefix = prefix;
}
set(key, value, expiry = null) {
const item = {
value: value,
expiry: expiry ? Date.now() + expiry : null
};
try {
localStorage.setItem(this.prefix + key, JSON.stringify(item));
return true;
} catch (error) {
console.error('Storage set failed:', error);
return false;
}
}
get(key) {
try {
const itemStr = localStorage.getItem(this.prefix + key);
if (!itemStr) return null;
const item = JSON.parse(itemStr);
// Check expiry
if (item.expiry && Date.now() > item.expiry) {
this.remove(key);
return null;
}
return item.value;
} catch (error) {
console.error('Storage get failed:', error);
return null;
}
}
remove(key) {
try {
localStorage.removeItem(this.prefix + key);
return true;
} catch (error) {
console.error('Storage remove failed:', error);
return false;
}
}
clear() {
try {
const keys = Object.keys(localStorage);
keys.forEach(key => {
if (key.startsWith(this.prefix)) {
localStorage.removeItem(key);
}
});
return true;
} catch (error) {
console.error('Storage clear failed:', error);
return false;
}
}
getAll() {
const items = {};
try {
const keys = Object.keys(localStorage);
keys.forEach(key => {
if (key.startsWith(this.prefix)) {
const cleanKey = key.replace(this.prefix, '');
items[cleanKey] = this.get(cleanKey);
}
});
} catch (error) {
console.error('Storage getAll failed:', error);
}
return items;
}
}
// Event Bus
class EventBus {
constructor() {
this.events = new Map();
}
on(event, callback) {
if (!this.events.has(event)) {
this.events.set(event, new Set());
}
this.events.get(event).add(callback);
// Return unsubscribe function
return () => this.off(event, callback);
}
off(event, callback) {
const callbacks = this.events.get(event);
if (callbacks) {
callbacks.delete(callback);
if (callbacks.size === 0) {
this.events.delete(event);
}
}
}
emit(event, data = {}) {
const callbacks = this.events.get(event);
if (callbacks) {
callbacks.forEach(callback => {
try {
callback(data);
} catch (error) {
console.error(`Event callback error for "${event}":`, error);
}
});
}
}
once(event, callback) {
const onceCallback = (data) => {
callback(data);
this.off(event, onceCallback);
};
return this.on(event, onceCallback);
}
clear() {
this.events.clear();
}
}
// Notification Manager
class NotificationManager {
constructor() {
this.container = null;
this.notifications = new Map();
this.init();
}
init() {
this.container = document.createElement('div');
this.container.className = 'notification-container';
this.container.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
pointer-events: none;
`;
document.body.appendChild(this.container);
}
show(message, type = 'info', options = {}) {
const {
duration = 5000,
closable = true,
actions = []
} = options;
const id = Date.now() + Math.random();
const notification = this.createNotification(id, message, type, {
duration,
closable,
actions
});
this.notifications.set(id, notification);
this.container.appendChild(notification);
// Auto dismiss
if (duration > 0) {
setTimeout(() => this.dismiss(id), duration);
}
return id;
}
createNotification(id, message, type, options) {
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.style.cssText = `
background: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
margin-bottom: 12px;
padding: 16px;
min-width: 300px;
max-width: 500px;
transform: translateX(100%);
transition: transform 0.3s ease;
pointer-events: auto;
border-left: 4px solid ${this.getTypeColor(type)};
`;
const icon = this.getTypeIcon(type);
const content = `
<div style="display: flex; align-items: flex-start; gap: 12px;">
<div style="flex-shrink: 0; margin-top: 2px;">
${icon}
</div>
<div style="flex: 1;">
<div style="font-weight: 500; margin-bottom: 4px;">${this.getTypeTitle(type)}</div>
<div style="color: #666; line-height: 1.4;">${message}</div>
${options.actions.length > 0 ? this.createActions(options.actions, id) : ''}
</div>
${options.closable ? `
<button onclick="window.patrolSystem.notifications.dismiss('${id}')"
style="background: none; border: none; cursor: pointer; padding: 0; color: #999;">
×
</button>
` : ''}
</div>
`;
notification.innerHTML = content;
// Animate in
setTimeout(() => {
notification.style.transform = 'translateX(0)';
}, 100);
return notification;
}
createActions(actions, notificationId) {
const buttons = actions.map(action => `
<button onclick="${action.handler}(${notificationId})"
style="background: ${action.primary ? '#2563eb' : 'transparent'};
color: ${action.primary ? 'white' : '#2563eb'};
border: 1px solid #2563eb;
padding: 4px 12px;
border-radius: 4px;
margin-right: 8px;
cursor: pointer;
font-size: 12px;">
${action.label}
</button>
`).join('');
return `<div style="margin-top: 8px;">${buttons}</div>`;
}
dismiss(id) {
const notification = this.notifications.get(id);
if (notification) {
notification.style.transform = 'translateX(100%)';
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
this.notifications.delete(id);
}, 300);
}
}
success(message, options = {}) {
return this.show(message, 'success', options);
}
error(message, options = {}) {
return this.show(message, 'error', { ...options, duration: 8000 });
}
warning(message, options = {}) {
return this.show(message, 'warning', { ...options, duration: 6000 });
}
info(message, options = {}) {
return this.show(message, 'info', options);
}
getTypeColor(type) {
const colors = {
success: '#10b981',
error: '#ef4444',
warning: '#f59e0b',
info: '#3b82f6'
};
return colors[type] || colors.info;
}
getTypeIcon(type) {
const icons = {
success: '<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/><path d="M10.97 4.97a.235.235 0 0 0-.02.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.061L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-1.071-1.05z"/></svg>',
error: '<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/><path d="m7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z"/></svg>',
warning: '<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566ZM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/></svg>',
info: '<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/><path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"/></svg>'
};
return icons[type] || icons.info;
}
getTypeTitle(type) {
const titles = {
success: 'Succès',
error: 'Erreur',
warning: 'Attention',
info: 'Information'
};
return titles[type] || titles.info;
}
}
// Theme Manager
class ThemeManager {
constructor() {
this.currentTheme = 'light';
this.storage = new StorageManager();
this.init();
}
init() {
const saved = this.storage.get('theme');
if (saved) {
this.currentTheme = saved;
}
this.apply();
}
toggle() {
this.currentTheme = this.currentTheme === 'light' ? 'dark' : 'light';
this.apply();
this.storage.set('theme', this.currentTheme);
}
apply() {
const body = document.body;
body.className = body.className.replace(/theme-\w+/g, '');
body.classList.add(`theme-${this.currentTheme}`);
}
}
// Form Validator
class FormValidator {
constructor() {
this.rules = {
required: (value) => value !== null && value !== undefined && value.toString().trim() !== '',
email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
min: (value, min) => value.length >= min,
max: (value, max) => value.length <= max,
pattern: (value, pattern) => new RegExp(pattern).test(value),
phone: (value) => /^\+?[\d\s\-\(\)]+$/.test(value)
};
}
validate(formData, rules) {
const errors = {};
for (const field in rules) {
const fieldRules = rules[field];
const value = formData[field];
for (const rule of fieldRules) {
const { type, value: ruleValue, message } = rule;
const validator = this.rules[type];
if (validator && !validator(value, ruleValue)) {
if (!errors[field]) {
errors[field] = [];
}
errors[field].push(message || `Champ invalide: ${type}`);
}
}
}
return {
isValid: Object.keys(errors).length === 0,
errors
};
}
validateField(value, rules) {
for (const rule of rules) {
const { type, value: ruleValue, message } = rule;
const validator = this.rules[type];
if (validator && !validator(value, ruleValue)) {
return {
isValid: false,
message: message || `Champ invalide: ${type}`
};
}
}
return { isValid: true };
}
}
// Modal Manager
class ModalManager {
constructor() {
this.modals = new Map();
this.init();
}
init() {
this.container = document.createElement('div');
this.container.className = 'modal-container';
this.container.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
`;
document.body.appendChild(this.container);
}
open(id, options = {}) {
const modal = this.createModal(id, options);
this.modals.set(id, modal);
this.container.appendChild(modal);
setTimeout(() => {
modal.classList.add('active');
this.container.classList.add('active');
}, 10);
// Focus first input if exists
const firstInput = modal.querySelector('input, textarea, select');
if (firstInput) {
firstInput.focus();
}
}
close(id) {
const modal = this.modals.get(id);
if (modal) {
modal.classList.remove('active');
this.container.classList.remove('active');
setTimeout(() => {
if (modal.parentNode) {
modal.parentNode.removeChild(modal);
}
this.modals.delete(id);
}, 300);
}
}
createModal(id, options) {
const modal = document.createElement('div');
modal.className = 'modal';
modal.style.cssText = `
background: white;
border-radius: 8px;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
max-width: ${options.maxWidth || '500px'};
width: 90vw;
max-height: 90vh;
overflow: auto;
transform: translateY(-20px);
transition: transform 0.3s ease;
`;
const header = options.title ? `
<div class="modal-header" style="padding: 20px; border-bottom: 1px solid #e5e7eb;">
<h3 style="margin: 0; font-size: 18px; font-weight: 600;">${options.title}</h3>
<button onclick="window.patrolSystem.modalManager.close('${id}')"
style="position: absolute; top: 15px; right: 15px; background: none; border: none; cursor: pointer; font-size: 20px; color: #9ca3af;">
×
</button>
</div>
` : '';
const content = `
<div class="modal-content" style="padding: 20px;">
${options.content || ''}
</div>
`;
const footer = options.actions ? `
<div class="modal-footer" style="padding: 20px; border-top: 1px solid #e5e7eb; display: flex; justify-content: flex-end; gap: 12px;">
${options.actions.map(action => `
<button onclick="${action.handler}('${id}')"
style="padding: 8px 16px; border-radius: 4px; border: 1px solid ${action.primary ? '#2563eb' : '#d1d5db'};
background: ${action.primary ? '#2563eb' : 'white'};
color: ${action.primary ? 'white' : '#374151'};
cursor: pointer;">
${action.label}
</button>
`).join('')}
</div>
` : '';
modal.innerHTML = header + content + footer;
return modal;
}
}
// Utility Functions
const Utils = {
// Date formatting
formatDate(date, format = 'fr-FR') {
return new Intl.DateTimeFormat(format, {
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(new Date(date));
},
formatDateTime(date, format = 'fr-FR') {
return new Intl.DateTimeFormat(format, {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
}).format(new Date(date));
},
// Number formatting
formatNumber(number, decimals = 0) {
return new Intl.NumberFormat('fr-FR', {
minimumFractionDigits: decimals,
maximumFractionDigits: decimals
}).format(number);
},
// File utilities
formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
},
// String utilities
truncate(str, length = 50) {
return str.length > length ? str.substring(0, length) + '...' : str;
},
slugify(str) {
return str.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/[\s_-]+/g, '-')
.replace(/^-+|-+$/g, '');
},
// Array utilities
chunk(array, size) {
const chunks = [];
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
},
// Object utilities
deepClone(obj) {
return JSON.parse(JSON.stringify(obj));
},
isEmpty(obj) {
return Object.keys(obj).length ===
___METADATA_START___
{"repoId":"secutorpro/patrol-control-system","isNew":false,"userName":"secutorpro"}
___METADATA_END___