Make dashboard configurable.
This commit is contained in:
@@ -1,110 +1,236 @@
|
||||
@php
|
||||
$customers = \App\Models\Customer::doesntHave('address')->get();
|
||||
$created_invoices = \App\Models\Invoice::where('status', '=', 'created')->orderBy('created_at')->get();
|
||||
$sent_invoices = \App\Models\Invoice::where('status', '=', 'sent')->orderBy('created_at')->get();
|
||||
$unpaid_incoming = \App\Models\Incoming::where('pay_date', '=', null)->orderBy('due_date')->get();
|
||||
@endphp
|
||||
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
|
||||
{{ __('dashboard.Dashboard') }}
|
||||
</h2>
|
||||
<div class="relative" x-data="{open: false}">
|
||||
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
|
||||
{{ __('dashboard.Dashboard') }}
|
||||
</h2>
|
||||
<x-gear-icon class="absolute right-0 top-0 cursor-pointer" x-on:click="$dispatch('flash', {open: !open});"/>
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 grid grid-cols-3 gap-4">
|
||||
<div class="py-12" x-data="dashboardForm()" x-on:flash.window="change();">
|
||||
<div class="relative max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
|
||||
@if($sent_invoices->count() != 0)
|
||||
<!-- Invoices not paid -->
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg col-span-2">
|
||||
<div class="p-6 text-gray-900 dark:text-gray-100">
|
||||
<h2 class="mb-4 text-lg font-medium text-gray-900 dark:text-gray-100">{{ __('dashboard.Invoices not paid') }}</h2>
|
||||
@foreach($sent_invoices as $invoice)
|
||||
<a href="{{ route('payment.create') }}"
|
||||
class="flex max-w even:bg-gray-100 odd:bg-white">
|
||||
<div class="w-1/4">{{ $invoice->number }}</div>
|
||||
<div class="w-1/4">{{ $invoice->customer->name }}</div>
|
||||
<div
|
||||
class="w-1/4 text-right">{{ \Illuminate\Support\Number::currency($invoice->sum) }}</div>
|
||||
<div class="w-1/4 text-right">{{ $invoice->created }}</div>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($created_invoices->count() != 0)
|
||||
<!-- Invoices not sent -->
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6 text-gray-900 dark:text-gray-100">
|
||||
<h2 class="mb-4 text-lg font-medium text-gray-900 dark:text-gray-100">{{ __('dashboard.Invoices not sent') }}</h2>
|
||||
@foreach($created_invoices as $invoice)
|
||||
<a href="{{ route('invoice.edit', $invoice->id) }}"
|
||||
class="flex max-w even:bg-gray-100 odd:bg-white">
|
||||
<div class="w-1/2">{{ $invoice->number }}</div>
|
||||
<div class="w-1/2">{{ $invoice->customer->name }}</div>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($customers->count() != 0)
|
||||
<!-- Customers without address -->
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6 text-gray-900 dark:text-gray-100">
|
||||
<h2 class="mb-4 text-lg font-medium text-gray-900 dark:text-gray-100">{{ __('dashboard.Customers without address') }}</h2>
|
||||
@foreach($customers as $customer)
|
||||
<a href="{{ route('customer.edit', $customer->id) }}"
|
||||
class="flex max-w even:bg-gray-100 odd:bg-white">
|
||||
<div class="w-1/2">{{ $customer->name }}</div>
|
||||
<div class="w-1/2">{{ $customer->email }}</div>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($unpaid_incoming->count() != 0)
|
||||
<!-- Incoming invoices, that are not paid -->
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg col-span-2">
|
||||
<div class="p-6 text-gray-900 dark:text-gray-100">
|
||||
<h2 class="mb-4 text-lg font-medium text-gray-900 dark:text-gray-100">{{ __('dashboard.Incoming not paid') }}</h2>
|
||||
@foreach($unpaid_incoming as $incoming)
|
||||
<a href="{{ route('incoming.edit', $incoming->id) }}"
|
||||
class="flex max-w even:bg-gray-100 odd:bg-white">
|
||||
<div class="w-1/6">{{ $incoming->invoice_number }}</div>
|
||||
<div class="w-1/2">{{ $incoming->supplier->name }}</div>
|
||||
<div
|
||||
class="w-1/6 text-right">{{ \Illuminate\Support\Number::currency($incoming->gross) }}</div>
|
||||
<div class="w-1/6 text-right">{{ $incoming->due }}</div>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg col-span-2">
|
||||
<!-- Selection of available dashboard tiles -->
|
||||
<div id="settings"
|
||||
class="hidden absolute bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg w-full h-full z-10">
|
||||
<div class="p-6 text-gray-900 dark:text-gray-100">
|
||||
<x-graph-year/>
|
||||
<h2 class="mb-4 text-lg text-center font-medium text-gray-900 dark:text-gray-100">{{ __('dashboard.Available') }}</h2>
|
||||
|
||||
<div x-show="tiles.length === 0" class="w-full text-center">{{ __('dashboard.No data') }}</div>
|
||||
|
||||
<template x-data="tiles" x-for="tile in tiles">
|
||||
<div>
|
||||
<div class="cursor-pointer" x-text="tile.title" x-on:click="activate(tile);"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg col-span-2">
|
||||
|
||||
<!-- Configuration of available dashboard tiles -->
|
||||
<div id="configuration"
|
||||
class="hidden absolute bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg w-full h-full z-10">
|
||||
<div class="p-6 text-gray-900 dark:text-gray-100">
|
||||
<x-graph-month/>
|
||||
<h2 class="mb-4 text-lg text-center font-medium text-gray-900 dark:text-gray-100">{{ __('dashboard.Configuration') }}</h2>
|
||||
<form class="w-1/2" @submit.prevent="">
|
||||
<div class="flex flex-row items-center">
|
||||
<x-input-label class="w-1/3" for="title" :value="__('dashboard.Tile type')"/>
|
||||
|
||||
<x-text-input id="title" name="title" type="text"
|
||||
class="mt-1 w-2/3"
|
||||
autofocus disabled
|
||||
x-model="config_tile.title"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row items-center">
|
||||
<x-input-label class="w-1/3" for="width" :value="__('dashboard.Tile width')"/>
|
||||
|
||||
<x-text-input id="width" name="width" type="number"
|
||||
class="mt-1 w-2/3"
|
||||
autofocus min="1" max="4"
|
||||
x-model="config_tile.width"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row items-center">
|
||||
<x-input-label class="w-1/3" for="height" :value="__('dashboard.Tile height')"/>
|
||||
|
||||
<x-text-input id="height" name="height" type="number"
|
||||
class="mt-1 w-2/3"
|
||||
autofocus min="1" max="4"
|
||||
x-model="config_tile.height"/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row items-center" x-show="config_tile.name === 'dashboard_graph_month'">
|
||||
<x-input-label class="w-1/3" for="settings" :value="__('dashboard.Tile year')"/>
|
||||
|
||||
<x-text-input id="settings" name="settings" type="number"
|
||||
class="mt-1 w-2/3"
|
||||
autofocus min="2000" max="2099"
|
||||
x-model="config_tile.settings"/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<x-primary-button x-on:click="submit();" class="">{{ __('form.Save') }}</x-primary-button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sortable dashboard tiles -->
|
||||
<div class="grid grid-cols-4 gap-4" x-sort="sortItems">
|
||||
@php
|
||||
$i = 0;
|
||||
@endphp
|
||||
@foreach($tiles as $name => $tile)
|
||||
<div id="{{ $name }}" x-sort:item="{{ $name }}"
|
||||
class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg col-span-{{ $tile->width }} row-span-{{ $tile->height }}">
|
||||
<div class="p-6 text-gray-900 dark:text-gray-100">
|
||||
<div class="flex flex-row w-full relative z-1">
|
||||
<h2 x-sort:handle
|
||||
class="cursor-move w-full mb-4 text-lg text-center font-medium text-gray-900 dark:text-gray-100">{{ $tile->title }}</h2>
|
||||
<div class="flex flex-row absolute right-0 top-0">
|
||||
<x-gear-icon class="cursor-pointer" x-on:click="config({{ $tile }});"/>
|
||||
<x-close-icon class="cursor-pointer" x-on:click="remove({{ $tile }});"/>
|
||||
</div>
|
||||
</div>
|
||||
@switch($name)
|
||||
@case('dashboard_not_paid')
|
||||
<x-dashboard_not_paid/>
|
||||
@break
|
||||
@case('dashboard_not_sent')
|
||||
<x-dashboard_not_sent/>
|
||||
@break
|
||||
@case('dashboard_no_address')
|
||||
<x-dashboard_no_address/>
|
||||
@break
|
||||
@case('dashboard_incoming')
|
||||
<x-dashboard_incoming/>
|
||||
@break
|
||||
@case('dashboard_graph_year')
|
||||
<x-dashboard_graph_year/>
|
||||
@break
|
||||
@case('dashboard_graph_month')
|
||||
<x-dashboard_graph_month/>
|
||||
@break
|
||||
@endswitch
|
||||
</div>
|
||||
</div>
|
||||
@php($i++)
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hidden col-span-1 col-span-2 col-span-3 col-span-4 row-span-1 row-span-2 row-span-3 row-span-4"></div>
|
||||
</x-app-layout>
|
||||
|
||||
<script>
|
||||
let self;
|
||||
|
||||
function dashboardForm() {
|
||||
return {
|
||||
all_tiles: {!! $tiles !!},
|
||||
open: false,
|
||||
tiles: [],
|
||||
config_tile: {},
|
||||
|
||||
init() {
|
||||
self = this;
|
||||
},
|
||||
|
||||
change() {
|
||||
this.open = !this.open;
|
||||
document.querySelector('#settings').classList.toggle('hidden');
|
||||
this.getTiles();
|
||||
},
|
||||
|
||||
remove(tile) {
|
||||
axios.put('/dashboard/' + tile.id, {active: false})
|
||||
.then(function () {
|
||||
let el = document.querySelector('#' + tile.name);
|
||||
el.parentNode.removeChild(el);
|
||||
})
|
||||
},
|
||||
|
||||
activate(tile) {
|
||||
axios.put('/dashboard/' + tile.id, {active: true})
|
||||
.then(function () {
|
||||
window.location.reload();
|
||||
})
|
||||
},
|
||||
|
||||
config(tile) {
|
||||
document.querySelector('#configuration').classList.toggle('hidden');
|
||||
this.config_tile = tile;
|
||||
},
|
||||
|
||||
getTiles() {
|
||||
if (!this.open) {
|
||||
return;
|
||||
}
|
||||
let vm = this;
|
||||
vm.tiles = [];
|
||||
|
||||
axios.get('/dashboard')
|
||||
.then(function (response) {
|
||||
response.data.forEach((item) => {
|
||||
if (!item.active) {
|
||||
vm.tiles.push(item);
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log(error);
|
||||
})
|
||||
},
|
||||
|
||||
submit() {
|
||||
axios.put('/dashboard/' + this.config_tile.id, this.config_tile)
|
||||
.then(function () {
|
||||
window.location.reload();
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log(error);
|
||||
})
|
||||
},
|
||||
|
||||
sortItems(item, position) {
|
||||
if (position > self.all_tiles[item.id].sort) {
|
||||
for (const [key, tile] of Object.entries(self.all_tiles)) {
|
||||
|
||||
if (self.all_tiles[key].sort <= position && self.all_tiles[key].sort > self.all_tiles[item.id].sort) {
|
||||
--self.all_tiles[key].sort;
|
||||
axios.put('/dashboard/' + self.all_tiles[key].id, self.all_tiles[key])
|
||||
}
|
||||
}
|
||||
self.all_tiles[item.id].sort = position;
|
||||
axios.put('/dashboard/' + self.all_tiles[item.id].id, self.all_tiles[item.id])
|
||||
}
|
||||
|
||||
if (position < self.all_tiles[item.id].sort) {
|
||||
for (const [key, tile] of Object.entries(self.all_tiles)) {
|
||||
console.log(self.all_tiles[key]);
|
||||
if (self.all_tiles[key].sort >= position && self.all_tiles[key].sort < self.all_tiles[item.id].sort) {
|
||||
++self.all_tiles[key].sort;
|
||||
axios.put('/dashboard/' + self.all_tiles[key].id, self.all_tiles[key])
|
||||
}
|
||||
}
|
||||
self.all_tiles[item.id].sort = position;
|
||||
axios.put('/dashboard/' + self.all_tiles[item.id].id, self.all_tiles[item.id])
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = (event) => {
|
||||
if (typeof(drawMonth) != 'undefined') {
|
||||
if (typeof (drawMonth) != 'undefined') {
|
||||
drawMonth();
|
||||
}
|
||||
if (typeof(drawYear) != 'undefined') {
|
||||
if (typeof (drawYear) != 'undefined') {
|
||||
drawYear();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user