Make dashboard configurable.
This commit is contained in:
27
app/Http/Controllers/Api/DashboardController.php
Normal file
27
app/Http/Controllers/Api/DashboardController.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Dashboard;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class DashboardController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*/
|
||||||
|
public function index(): JsonResponse
|
||||||
|
{
|
||||||
|
return response()->json(Dashboard::all());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified resource in storage.
|
||||||
|
*/
|
||||||
|
public function update(Request $request, Dashboard $dashboard): JsonResponse
|
||||||
|
{
|
||||||
|
return response()->json($dashboard->update($request->all()));
|
||||||
|
}
|
||||||
|
}
|
||||||
54
app/Models/Dashboard.php
Normal file
54
app/Models/Dashboard.php
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Dashboard extends Model
|
||||||
|
{
|
||||||
|
public $timestamps = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'width',
|
||||||
|
'height',
|
||||||
|
'sort',
|
||||||
|
'active',
|
||||||
|
'settings'
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $appends = [
|
||||||
|
'title',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function getTitleAttribute()
|
||||||
|
{
|
||||||
|
return __('dashboard.title_' . $this->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function toObject()
|
||||||
|
{
|
||||||
|
$all = self::orderBy('sort')->get();
|
||||||
|
$tiles = new \stdClass();
|
||||||
|
foreach ($all as $tile) {
|
||||||
|
$key = $tile->name;
|
||||||
|
$tiles->$key = $tile;
|
||||||
|
}
|
||||||
|
return $tiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function activeToObject()
|
||||||
|
{
|
||||||
|
$all = self::where('active', true)->orderBy('sort')->get();
|
||||||
|
$tiles = new \stdClass();
|
||||||
|
foreach ($all as $tile) {
|
||||||
|
$key = $tile->name;
|
||||||
|
$tiles->$key = $tile;
|
||||||
|
}
|
||||||
|
return collect($tiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,8 +38,8 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
{
|
{
|
||||||
View::composer('components.tax-dropdown', TaxDropdown::class);
|
View::composer('components.tax-dropdown', TaxDropdown::class);
|
||||||
View::composer('components.company-logo', OptionLogo::class);
|
View::composer('components.company-logo', OptionLogo::class);
|
||||||
View::composer('components.graph-year', YearGraph::class);
|
View::composer('components.dashboard_graph_year', YearGraph::class);
|
||||||
View::composer('components.graph-month', MonthGraph::class);
|
View::composer('components.dashboard_graph_month', MonthGraph::class);
|
||||||
Number::useLocale(config('app.locale'));
|
Number::useLocale(config('app.locale'));
|
||||||
Number::useCurrency(config('app.currency'));
|
Number::useCurrency(config('app.currency'));
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\View\Composers;
|
namespace App\View\Composers;
|
||||||
|
|
||||||
|
use App\Models\Dashboard;
|
||||||
use App\Models\Incoming;
|
use App\Models\Incoming;
|
||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
@@ -11,7 +12,10 @@ class MonthGraph
|
|||||||
{
|
{
|
||||||
public function compose(View $view): void
|
public function compose(View $view): void
|
||||||
{
|
{
|
||||||
$monthly_invoices = Invoice::whereYear('created_at', '=', Carbon::now()->year)->get()
|
$config = Dashboard::where('id', '=', 6)->first();
|
||||||
|
$config_year = (is_null($config->settings)) ? Carbon::now()->year : intval($config->settings);
|
||||||
|
|
||||||
|
$monthly_invoices = Invoice::whereYear('created_at', '=', $config_year)->get()
|
||||||
->groupBy(function ($invoice) {
|
->groupBy(function ($invoice) {
|
||||||
return $invoice->created_at->format('n');
|
return $invoice->created_at->format('n');
|
||||||
})
|
})
|
||||||
@@ -19,7 +23,7 @@ class MonthGraph
|
|||||||
return $month->sum('sum');
|
return $month->sum('sum');
|
||||||
});
|
});
|
||||||
|
|
||||||
$monthly_incoming = Incoming::whereYear('issue_date', '=', Carbon::now()->year)->get()
|
$monthly_incoming = Incoming::whereYear('issue_date', '=', $config_year)->get()
|
||||||
->groupBy(function ($incoming) {
|
->groupBy(function ($incoming) {
|
||||||
return Carbon::parse($incoming->issue_date)->format('n');
|
return Carbon::parse($incoming->issue_date)->format('n');
|
||||||
})
|
})
|
||||||
@@ -36,6 +40,6 @@ class MonthGraph
|
|||||||
$monthly_invoices[$year] = 0;
|
$monthly_invoices[$year] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
$view->with(['monthly_invoices' => $monthly_invoices, 'monthly_incoming' => $monthly_incoming]);
|
$view->with(['monthly_invoices' => $monthly_invoices, 'monthly_incoming' => $monthly_incoming, 'year' => $config_year]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('dashboards', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('name');
|
||||||
|
$table->integer('width');
|
||||||
|
$table->integer('height');
|
||||||
|
$table->integer('sort');
|
||||||
|
$table->boolean('active');
|
||||||
|
$table->json('settings')->nullable();
|
||||||
|
});
|
||||||
|
|
||||||
|
DB::table('dashboards')->insert([
|
||||||
|
'name' => 'dashboard_not_paid',
|
||||||
|
'width' => 2,
|
||||||
|
'height' => 1,
|
||||||
|
'sort' => 0,
|
||||||
|
'active' => true,
|
||||||
|
]);
|
||||||
|
DB::table('dashboards')->insert([
|
||||||
|
'name' => 'dashboard_not_sent',
|
||||||
|
'width' => 1,
|
||||||
|
'height' => 2,
|
||||||
|
'sort' => 1,
|
||||||
|
'active' => true,
|
||||||
|
]);
|
||||||
|
DB::table('dashboards')->insert([
|
||||||
|
'name' => 'dashboard_no_address',
|
||||||
|
'width' => 1,
|
||||||
|
'height' => 1,
|
||||||
|
'sort' => 2,
|
||||||
|
'active' => true,
|
||||||
|
]);
|
||||||
|
DB::table('dashboards')->insert([
|
||||||
|
'name' => 'dashboard_incoming',
|
||||||
|
'width' => 1,
|
||||||
|
'height' => 1,
|
||||||
|
'sort' => 3,
|
||||||
|
'active' => true,
|
||||||
|
]);
|
||||||
|
DB::table('dashboards')->insert([
|
||||||
|
'name' => 'dashboard_graph_year',
|
||||||
|
'width' => 2,
|
||||||
|
'height' => 1,
|
||||||
|
'sort' => 4,
|
||||||
|
'active' => true,
|
||||||
|
]);
|
||||||
|
DB::table('dashboards')->insert([
|
||||||
|
'name' => 'dashboard_graph_month',
|
||||||
|
'width' => 4,
|
||||||
|
'height' => 1,
|
||||||
|
'sort' => 5,
|
||||||
|
'active' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('dashboards');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -13,10 +13,18 @@ return [
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
'Dashboard' => 'Dashboard',
|
'Dashboard' => 'Dashboard',
|
||||||
'Customers without address' => 'Kunden ohne Rechnungsadresse',
|
'title_dashboard_graph_month' => 'Monatliche Entwicklung',
|
||||||
'Invoices not sent' => 'Nicht versendete Rechnungen',
|
'title_dashboard_graph_year' => 'Jährliche Entwicklung',
|
||||||
'Invoices not paid' => 'Nicht bezahlte Rechnungen',
|
'title_dashboard_not_paid' => 'Nicht bezahlte Rechnungen',
|
||||||
'Incoming not paid' => 'Nicht bezahlte Eingangsrechnungen',
|
'title_dashboard_not_sent' => 'Nicht versendete Rechnungen',
|
||||||
'Incoming paid' => 'Bezahlte Eingangsrechnungen',
|
'title_dashboard_incoming' => 'Nicht bezahlte Eingangsrechnungen',
|
||||||
|
'title_dashboard_no_address' => 'Kunden ohne Rechnungsadresse',
|
||||||
|
'Available' => 'Verfügbare Dashboard Kacheln',
|
||||||
|
'Configuration' => 'Konfiguration',
|
||||||
|
'Tile type' => 'Kachel Typ',
|
||||||
|
'Tile width' => 'Kachel Breite',
|
||||||
|
'Tile height' => 'Kachel Höhe',
|
||||||
|
'Tile year' => 'Jahr der Daten',
|
||||||
|
'No data' => 'Keine Daten gefunden.',
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
3
resources/views/components/close-icon.blade.php
Normal file
3
resources/views/components/close-icon.blade.php
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" {{ $attributes->merge(['class' => 'size-8 p-1']) }}>
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 258 B |
40
resources/views/components/dashboard_graph_month.blade.php
Normal file
40
resources/views/components/dashboard_graph_month.blade.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
@if( $monthly_incoming->count() === 0 && $monthly_invoices->count() === 0)
|
||||||
|
<div class="w-full text-center">{{ __('dashboard.No data') }}</div>
|
||||||
|
@else
|
||||||
|
<div class="w-full">
|
||||||
|
<canvas id="monthly"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function drawMonth() {
|
||||||
|
let monthly_invoices = {!! $monthly_invoices !!};
|
||||||
|
let monthly_incoming = {!! $monthly_incoming !!};
|
||||||
|
let year = {!! $year !!};
|
||||||
|
|
||||||
|
let el = document.querySelector('#dashboard_graph_month h2');
|
||||||
|
el.innerHTML += ' ' + year;
|
||||||
|
|
||||||
|
new Chart(
|
||||||
|
document.getElementById('monthly'),
|
||||||
|
{
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: '{{ __('invoice.Invoices') }}',
|
||||||
|
data: monthly_invoices,
|
||||||
|
backgroundColor: 'rgb(25,48,90)',
|
||||||
|
borderColor: 'rgb(25,48,90)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '{{ __('incoming.Incoming') }}',
|
||||||
|
data: monthly_incoming,
|
||||||
|
backgroundColor: 'rgb(237,125,35)',
|
||||||
|
borderColor: 'rgb(237,125,35)',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
@endif
|
||||||
34
resources/views/components/dashboard_graph_year.blade.php
Normal file
34
resources/views/components/dashboard_graph_year.blade.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
@if( $yearly_incoming->count() === 0 && $yearly_invoices->count() === 0)
|
||||||
|
<div class="w-full text-center">{{ __('dashboard.No data') }}</div>
|
||||||
|
@else
|
||||||
|
<div class="w-full">
|
||||||
|
<canvas id="yearly"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function drawYear() {
|
||||||
|
let yearly_invoices = {!! $yearly_invoices !!};
|
||||||
|
let yearly_incoming = {!! $yearly_incoming !!};
|
||||||
|
|
||||||
|
new Chart(
|
||||||
|
document.getElementById('yearly'),
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: '{{ __('invoice.Invoices') }}',
|
||||||
|
data: yearly_invoices,
|
||||||
|
backgroundColor: 'rgb(25,48,90)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '{{ __('incoming.Incoming') }}',
|
||||||
|
data: yearly_incoming,
|
||||||
|
backgroundColor: 'rgb(237,125,35)',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
@endif
|
||||||
15
resources/views/components/dashboard_incoming.blade.php
Normal file
15
resources/views/components/dashboard_incoming.blade.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
@php
|
||||||
|
$unpaid_incoming = \App\Models\Incoming::where('pay_date', '=', null)->orderBy('due_date')->get();
|
||||||
|
@endphp
|
||||||
|
@if($unpaid_incoming->count() === 0)
|
||||||
|
<div class="w-full text-center">{{ __('dashboard.No data') }}</div>
|
||||||
|
@endif
|
||||||
|
@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
|
||||||
13
resources/views/components/dashboard_no_address.blade.php
Normal file
13
resources/views/components/dashboard_no_address.blade.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
@php
|
||||||
|
$customers = \App\Models\Customer::doesntHave('address')->get();
|
||||||
|
@endphp
|
||||||
|
@if($customers->count() === 0)
|
||||||
|
<div class="w-full text-center">{{ __('dashboard.No data') }}</div>
|
||||||
|
@endif
|
||||||
|
@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
|
||||||
15
resources/views/components/dashboard_not_paid.blade.php
Normal file
15
resources/views/components/dashboard_not_paid.blade.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
@php
|
||||||
|
$sent_invoices = \App\Models\Invoice::where('status', '=', 'sent')->orderBy('created_at')->get();
|
||||||
|
@endphp
|
||||||
|
@if($sent_invoices->count() === 0)
|
||||||
|
<div class="w-full text-center">{{ __('dashboard.No data') }}</div>
|
||||||
|
@endif
|
||||||
|
@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
|
||||||
13
resources/views/components/dashboard_not_sent.blade.php
Normal file
13
resources/views/components/dashboard_not_sent.blade.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
@php
|
||||||
|
$created_invoices = \App\Models\Invoice::where('status', '=', 'created')->orderBy('created_at')->get();
|
||||||
|
@endphp
|
||||||
|
@if($created_invoices->count() === 0)
|
||||||
|
<div class="w-full text-center">{{ __('dashboard.No data') }}</div>
|
||||||
|
@endif
|
||||||
|
@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
|
||||||
5
resources/views/components/gear-icon.blade.php
Normal file
5
resources/views/components/gear-icon.blade.php
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" {{ $attributes->merge(['class' => 'size-8 p-1']) }}>
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 0 1 1.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.559.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.894.149c-.424.07-.764.383-.929.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 0 1-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.398.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 0 1-.12-1.45l.527-.737c.25-.35.272-.806.108-1.204-.165-.397-.506-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.108-1.204l-.526-.738a1.125 1.125 0 0 1 .12-1.45l.773-.773a1.125 1.125 0 0 1 1.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894Z" />
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -1,30 +0,0 @@
|
|||||||
<div class="w-full">
|
|
||||||
<canvas id="monthly"></canvas>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
function drawMonth() {
|
|
||||||
let monthly_invoices = {!! $monthly_invoices !!};
|
|
||||||
let monthly_incoming = {!! $monthly_incoming !!};
|
|
||||||
|
|
||||||
new Chart(
|
|
||||||
document.getElementById('monthly'),
|
|
||||||
{
|
|
||||||
type: 'line',
|
|
||||||
data: {
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: '{{ __('invoice.Invoices') }}',
|
|
||||||
data: monthly_invoices,
|
|
||||||
backgroundColor: 'rgb(25,48,90)',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '{{ __('incoming.Incoming') }}',
|
|
||||||
data: monthly_incoming,
|
|
||||||
backgroundColor: 'rgb(237,125,35)',
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<div class="w-full">
|
|
||||||
<canvas id="yearly"></canvas>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
function drawYear() {
|
|
||||||
let yearly_invoices = {!! $yearly_invoices !!};
|
|
||||||
let yearly_incoming = {!! $yearly_incoming !!};
|
|
||||||
|
|
||||||
new Chart(
|
|
||||||
document.getElementById('yearly'),
|
|
||||||
{
|
|
||||||
type: 'bar',
|
|
||||||
data: {
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: '{{ __('invoice.Invoices') }}',
|
|
||||||
data: yearly_invoices,
|
|
||||||
backgroundColor: 'rgb(25,48,90)',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '{{ __('incoming.Incoming') }}',
|
|
||||||
data: yearly_incoming,
|
|
||||||
backgroundColor: 'rgb(237,125,35)',
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -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-app-layout>
|
||||||
<x-slot name="header">
|
<x-slot name="header">
|
||||||
|
<div class="relative" x-data="{open: false}">
|
||||||
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
|
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
|
||||||
{{ __('dashboard.Dashboard') }}
|
{{ __('dashboard.Dashboard') }}
|
||||||
</h2>
|
</h2>
|
||||||
|
<x-gear-icon class="absolute right-0 top-0 cursor-pointer" x-on:click="$dispatch('flash', {open: !open});"/>
|
||||||
|
</div>
|
||||||
</x-slot>
|
</x-slot>
|
||||||
|
|
||||||
<div class="py-12">
|
<div class="py-12" x-data="dashboardForm()" x-on:flash.window="change();">
|
||||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 grid grid-cols-3 gap-4">
|
<div class="relative max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||||
|
|
||||||
@if($sent_invoices->count() != 0)
|
<!-- Selection of available dashboard tiles -->
|
||||||
<!-- Invoices not paid -->
|
<div id="settings"
|
||||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg col-span-2">
|
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">
|
<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>
|
<h2 class="mb-4 text-lg text-center font-medium text-gray-900 dark:text-gray-100">{{ __('dashboard.Available') }}</h2>
|
||||||
@foreach($sent_invoices as $invoice)
|
|
||||||
<a href="{{ route('payment.create') }}"
|
<div x-show="tiles.length === 0" class="w-full text-center">{{ __('dashboard.No data') }}</div>
|
||||||
class="flex max-w even:bg-gray-100 odd:bg-white">
|
|
||||||
<div class="w-1/4">{{ $invoice->number }}</div>
|
<template x-data="tiles" x-for="tile in tiles">
|
||||||
<div class="w-1/4">{{ $invoice->customer->name }}</div>
|
<div>
|
||||||
<div
|
<div class="cursor-pointer" x-text="tile.title" x-on:click="activate(tile);"></div>
|
||||||
class="w-1/4 text-right">{{ \Illuminate\Support\Number::currency($invoice->sum) }}</div>
|
</div>
|
||||||
<div class="w-1/4 text-right">{{ $invoice->created }}</div>
|
</template>
|
||||||
</a>
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 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">
|
||||||
|
<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
|
@endforeach
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
|
||||||
|
|
||||||
@if($customers->count() != 0)
|
<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>
|
||||||
<!-- 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">
|
|
||||||
<div class="p-6 text-gray-900 dark:text-gray-100">
|
|
||||||
<x-graph-year/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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">
|
|
||||||
<x-graph-month/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</x-app-layout>
|
</x-app-layout>
|
||||||
|
|
||||||
<script>
|
<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) => {
|
window.onload = (event) => {
|
||||||
if (typeof(drawMonth) != 'undefined') {
|
if (typeof (drawMonth) != 'undefined') {
|
||||||
drawMonth();
|
drawMonth();
|
||||||
}
|
}
|
||||||
if (typeof(drawYear) != 'undefined') {
|
if (typeof (drawYear) != 'undefined') {
|
||||||
drawYear();
|
drawYear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
use App\Http\Controllers\Api\AddressController;
|
use App\Http\Controllers\Api\AddressController;
|
||||||
use App\Http\Controllers\Api\AuthController;
|
use App\Http\Controllers\Api\AuthController;
|
||||||
use App\Http\Controllers\Api\CustomerController;
|
use App\Http\Controllers\Api\CustomerController;
|
||||||
|
use App\Http\Controllers\Api\DashboardController;
|
||||||
use App\Http\Controllers\Api\EController;
|
use App\Http\Controllers\Api\EController;
|
||||||
use App\Http\Controllers\Api\ExcelController;
|
use App\Http\Controllers\Api\ExcelController;
|
||||||
use App\Http\Controllers\Api\IncomingController;
|
use App\Http\Controllers\Api\IncomingController;
|
||||||
@@ -49,6 +50,7 @@ Route::group(['as' => 'api.'], function () {
|
|||||||
Route::put('/incoming/{incoming}', [IncomingController::class, 'update'])->name('incoming.update');
|
Route::put('/incoming/{incoming}', [IncomingController::class, 'update'])->name('incoming.update');
|
||||||
Route::post('/incoming', [IncomingController::class, 'store'])->name('incoming.store');
|
Route::post('/incoming', [IncomingController::class, 'store'])->name('incoming.store');
|
||||||
Route::apiResource('/project', ProjectController::class);
|
Route::apiResource('/project', ProjectController::class);
|
||||||
|
Route::apiResource('/dashboard', DashboardController::class)->only(['index', 'update']);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use App\Http\Controllers\PdfController;
|
|||||||
use App\Http\Controllers\ProfileController;
|
use App\Http\Controllers\ProfileController;
|
||||||
use App\Http\Controllers\ProjectController;
|
use App\Http\Controllers\ProjectController;
|
||||||
use App\Http\Controllers\TaxrateController;
|
use App\Http\Controllers\TaxrateController;
|
||||||
|
use App\Models\Dashboard;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::get('/', function () {
|
Route::get('/', function () {
|
||||||
@@ -17,7 +18,7 @@ Route::get('/', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Route::get('/dashboard', function () {
|
Route::get('/dashboard', function () {
|
||||||
return view('dashboard');
|
return view('dashboard', ['tiles' => Dashboard::activeToObject()]);
|
||||||
})->middleware(['auth', 'verified'])->name('dashboard');
|
})->middleware(['auth', 'verified'])->name('dashboard');
|
||||||
|
|
||||||
Route::middleware('auth')->group(function () {
|
Route::middleware('auth')->group(function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user