Files
hub/resources/js/app.js
kelly 496ca61489 feat: dashboard redesign and sidebar consolidation
- Redesign dashboard as daily briefing format with action-first layout
- Consolidate sidebar menu structure (Dashboard as single link)
- Fix CRM form styling to use consistent UI patterns
- Add PWA icons and push notification groundwork
- Update SuiteMenuResolver for cleaner navigation
2025-12-14 03:41:31 -07:00

135 lines
5.0 KiB
JavaScript

import './bootstrap';
import './push-notifications';
import Alpine from 'alpinejs';
import Precognition from 'laravel-precognition-alpine';
import persist from '@alpinejs/persist';
import morph from '@alpinejs/morph';
import intersect from '@alpinejs/intersect';
import collapse from '@alpinejs/collapse';
import Sortable from 'sortablejs';
import Choices from 'choices.js';
import 'choices.js/public/assets/styles/choices.min.css';
import ApexCharts from 'apexcharts';
import { animate } from 'animejs';
import CalHeatmap from 'cal-heatmap';
import 'cal-heatmap/cal-heatmap.css';
import Quill from 'quill';
import 'quill/dist/quill.snow.css';
window.Alpine = Alpine;
window.Sortable = Sortable;
window.Choices = Choices;
window.ApexCharts = ApexCharts;
window.anime = animate; // Anime.js v4 renamed anime() to animate()
window.CalHeatmap = CalHeatmap;
window.Quill = Quill;
// Register Alpine.js plugins
Alpine.plugin(persist);
Alpine.plugin(Precognition);
Alpine.plugin(morph);
Alpine.plugin(intersect);
Alpine.plugin(collapse);
// Import Password Meter component
import './components/password-meter.js';
// Import Phone Formatter component
import './components/phone-formatter.js';
// Import Search Select component
import './components/search-select.js';
// Route helper function for JavaScript
window.route = function(name, params = {}) {
// Simple route helper - in a real app you might use Laravel Ziggy
const routes = {
'business.setup': '/business/setup',
'business.setup.create': '/business/setup',
'dashboard': '/dashboard',
'profile.edit': '/profile',
};
return routes[name] || '#';
};
// Currency formatter for consistent display across the app
window.formatCurrency = function(amount) {
// Handle null, undefined, or NaN values
if (amount == null || isNaN(amount)) {
return '$0.00';
}
// Convert to number, format with 2 decimals, and add thousand separators
return '$' + Number(amount).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
};
Alpine.start();
// FilePond integration
import * as FilePond from 'filepond';
import FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type';
import FilePondPluginImagePreview from 'filepond-plugin-image-preview';
import 'filepond/dist/filepond.min.css';
import 'filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css';
import "cally";
document.addEventListener('DOMContentLoaded', () => {
FilePond.registerPlugin(FilePondPluginFileValidateType, FilePondPluginImagePreview);
document.querySelectorAll('form').forEach(form => {
form.addEventListener('submit', function(e) {
// Prevent submission if any FilePond instance is still uploading
const ponds = Array.from(form.querySelectorAll('input[type="file"].filepond')).map(el => el._pond).filter(Boolean);
if (ponds.some(pond => pond && pond.status !== 5)) { // 5 = FilePond.Status.IDLE
e.preventDefault();
// Optionally show a message to the user
alert('Please wait for all uploads to finish before submitting.');
}
});
});
document.querySelectorAll('input[type="file"].filepond').forEach(el => {
const hidden = document.getElementById(el.name + '_path');
let files = [];
if (hidden && hidden.value) {
files.push({ source: hidden.value, options: { type: 'local' } });
}
try {
const pond = FilePond.create(el, {
files,
maxFileSize: '5MB',
acceptedFileTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'application/pdf'],
server: {
url: '/filepond',
process: {
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
}
}
},
onprocessfile: (error, file) => {
if (!error && hidden) {
hidden.value = file.serverId;
// Also update filename and size
const nameInput = document.getElementById(el.name + '_name');
const sizeInput = document.getElementById(el.name + '_size');
if (nameInput) nameInput.value = file.file.name;
if (sizeInput) sizeInput.value = file.file.size;
} else if (error) {
console.error('FilePond upload error:', error, file);
}
},
onremovefile: () => {
if (hidden) {
hidden.value = '';
}
},
});
// Attach the pond instance to the input for form-level checks
el._pond = pond;
} catch (e) {
console.error('FilePond failed to initialize:', e, el);
}
});
});