Vue.component('dashboard-location-selector', { template: `
`, props: { selectedNetworkOwner: {type: String, required: true}, selectedCampaign: {type: String, required: true}, selectedGemeinde: {type: String, required: true}, } , data() { return { showNetOwnerFilter: window.TT_CONFIG["IS_ADMIN"] === 'true', filterOptions: { netOwners: [], campaigns: [], gemeinden: [], }, }; }, async mounted() { await this.fetchNetOwnerFilterOptions(); await this.fetchCampaignFilterOptions(); await this.fetchCampaignGemeindeFilterOptions(); }, methods: { async fetchNetOwnerFilterOptions() { const response = await axios.get(`${window.TT_CONFIG['BASE_URL']}/getNetOwnerFilterOptions`); this.filterOptions.netOwners = [{value: 'all', text: 'Alle'}, ...response.data]; }, async fetchCampaignFilterOptions() { const response = await axios.post(`${window.TT_CONFIG['BASE_URL']}/getCampaignFilterOptions`, {netOwners: this.selectedNetworkOwner === 'all' ? undefined : this.selectedNetworkOwner}); this.filterOptions.campaigns = [{value: 'all', text: 'Alle'}, ...response.data]; }, async fetchCampaignGemeindeFilterOptions() { const response = await axios.post(`${window.TT_CONFIG['BASE_URL']}/getCampaignGemeindeFilterOptions`, {netOwners: this.selectedNetworkOwner === 'all' ? undefined : this.selectedNetworkOwner, campaigns: this.selectedCampaign === 'all' ? undefined : this.selectedCampaign}); this.filterOptions.gemeinden = [{value: 'all', text: 'Alle'}, ...response.data]; }, }, watch: { selectedNetworkOwner() { this.fetchCampaignFilterOptions(); this.fetchCampaignGemeindeFilterOptions(); }, selectedCampaign() { this.fetchCampaignGemeindeFilterOptions(); } }, }); Vue.component('tt-dashboard-display-card', { props: ['header', 'icon', 'text', 'subHeaders', 'color'], computed: { cardHeight() { return `${96 + 25 * this.subHeaders?.length ?? 0}px`; } }, template: `
{{ header }}

{{ text }}

{{ subHeader }}

` }); Vue.component('tt-timeline-chart', { props: { datasets: { type: Array, required: true }, label: { type: String, default: '' }, showGrid: { type: Boolean, default: true } }, template: '', data() { return { chart: null }; }, mounted() { this.renderChart(); }, methods: { renderChart() { if (this.chart) { this.chart.destroy(); } const ctx = this.$refs.chart.getContext('2d'); this.chart = new Chart(ctx, { type: 'line', data: { labels: this.datasets[0].data.map(item => item.date), datasets: this.datasets.map((dataset, index) => ({ label: dataset.label, data: dataset.data.map(item => item.value), borderColor: dataset.color, backgroundColor: dataset.fill ? dataset.color : 'transparent', tension: 0.1, radius: 5, hoverRadius: 8, fill: dataset.fill, yAxisID: dataset.yAxisID, order: dataset.order || index })) }, options: { responsive: true, maintainAspectRatio: false, scales: { x: { type: 'time', time: { unit: 'week', displayFormats: { week: 'DD.MM' } }, title: { display: true, text: 'Datum' }, grid: { display: this.showGrid }, ticks: { callback: (value, index, values) => { const date = moment(value); return this.label === 'KW' ? `KW ${date.isoWeek()}` : date.format('DD.MM'); } } }, y: { beginAtZero: true, title: { display: true, text: 'Anzahl' }, grid: { display: this.showGrid } } }, plugins: { tooltip: { callbacks: { title: (context) => { const date = moment(context[0].label); return this.label === 'KW' ? `KW ${date.isoWeek()}` : date.format('DD.MM.YYYY'); } } } } } }); } }, watch: { datasets: { deep: true, handler() { this.renderChart(); } } } }); Vue.component('dashboard-default', { props: ['dashboardData'], data() { return { selectedTimeframe: 'all', window: window, moment: moment } }, methods: { exportTimeline() { // we need all timelines with "ONT installiert", "Leerrohr", "Bestellungen" const data = this.dashboardData.timeline[0].map((item, index) => ({ date: item.date, bestellungen: item.value, leerrohr: this.dashboardData.timeline_leerrohr[0][index].value, ont_installiert: this.dashboardData.timeline_ont_installed[0][index].value })); // dont use any library for the csv const csv = [ ['Datum', 'Bestellungen', 'Leerrohr', 'ONT installiert'], ...data.map(item => [item.date, item.bestellungen, item.leerrohr, item.ont_installiert]) ].map(row => row.join(';')).join('\n'); const blob = new Blob([csv], {type: 'text/csv'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'timeline.csv'; a.click(); URL.revokeObjectURL(url); } }, template: `

Bestellungen


Baufortschritt


Installationsfortschritt


Bestellverlauf

` }) Vue.component('dashboard-rml', { props: ['dashboardData'], data() { return { selectedTimeframe: 'all', window: window, moment: moment } }, methods: { exportTimeline() { // we need all timelines with "ONT installiert", "Leerrohr", "Bestellungen" const data = this.dashboardData.timeline[0].map((item, index) => ({ date: item.date, bestellungen: item.value, leerrohr: this.dashboardData.timeline_leerrohr[0][index].value, ont_installiert: this.dashboardData.timeline_ont_installed[0][index].value })); // dont use any library for the csv const csv = [ ['Datum', 'Bestellungen', 'Leerrohr', 'ONT installiert'], ...data.map(item => [item.date, item.bestellungen, item.leerrohr, item.ont_installiert]) ].map(row => row.join(';')).join('\n'); const blob = new Blob([csv], {type: 'text/csv'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'timeline.csv'; a.click(); URL.revokeObjectURL(url); } }, template: `

Bestellungen


Baufortschritt


Installationsfortschritt Provider


Bestellverlauf

` }) Vue.component('dashboard-new', { template: `

`, data() { return { window, dashboardData: { type: 'unloaded', order_max_home_addrdb: 0, order_actual_order: "0", order_efh: "0", order_efh_vorsorge: "0", order_efh_vollanschluss: "0", order_mph: "0", order_mph_vorsorge: "0", order_mph_vollanschluss: "0", baufortschritt_140: "0", installationspaket_erhalten: "0", lehrrohr_im_haus: "0", inhouse_kabel_verlegt_efh: "0", inhouse_kabel_verlegt_mph: "0", installationsfortschritt_245: "0", ont_installiert_300: "0", vollanschluss_dokumentiert_350: "0", vorsorge_dokumentiert_351: "0", provider_bestellt_500: "0", timeline: [] }, selectedNetworkOwner: 'all', selectedCampaign: 'all', selectedGemeinde: 'all', isLoading: false }; }, async mounted() { await this.fetchDashboardData(); }, methods: { async fetchDashboardData() { this.isLoading = true; try { const response = await axios.post(`${window.TT_CONFIG['BASE_URL']}/getDashboardData`, { netOwners: this.selectedNetworkOwner === 'all' ? '' : this.selectedNetworkOwner, campaigns: this.selectedCampaign === 'all' ? '' : this.selectedCampaign, gemeinden: this.selectedGemeinde === 'all' ? '' : this.selectedGemeinde, }); console.log(response.data); this.dashboardData = response.data; } catch (error) { console.error('Error fetching dashboard data:', error); } finally { this.isLoading = false; } }, async csvExport() { this.isLoading = true; let objectUrl = null; try { const { data } = await axios.get(`${window.TT_CONFIG['BASE_URL']}/export`); if (!data?.length) return console.warn('No data available to export.'); const headers = Object.keys(data[0]); const csvContent = [ headers.join(';'), ...data.map(row => headers.map(header => { const cell = String(row[header] ?? ''); // Ensure value is a string, default to empty return /[";\n]/.test(cell) ? `"${cell.replace(/"/g, '""')}"` : cell; }).join(';')) ].join('\n'); const blob = new Blob(['\uFEFF' + csvContent], { type: 'text/csv;charset=utf-8;' }); objectUrl = URL.createObjectURL(blob); const link = Object.assign(document.createElement('a'), { href: objectUrl, download: `Statistik_${new Date().toISOString().slice(0, 10)}.csv` }); document.body.appendChild(link); link.click(); link.remove(); // More modern than removeChild(link) } catch (error) { console.error('Error exporting CSV:', error); } finally { this.isLoading = false; if (objectUrl) URL.revokeObjectURL(objectUrl); } } }, watch: { selectedNetworkOwner() { this.fetchDashboardData(); }, selectedCampaign() { this.fetchDashboardData(); }, selectedGemeinde() { this.fetchDashboardData(); } } });