Add Filaments module with Seeder, update Calculator UI for filament selection, and upgrade dependencies for Livewire Flux
This commit is contained in:
@@ -8,16 +8,71 @@ use App\Models\Calculationpreset as Model;
|
|||||||
use Livewire\WithPagination;
|
use Livewire\WithPagination;
|
||||||
use Livewire\Attributes\title;
|
use Livewire\Attributes\title;
|
||||||
|
|
||||||
|
use App\Models\Filament;
|
||||||
|
use App\Models\FilamentType;
|
||||||
|
use App\Models\Manufacturer;
|
||||||
|
use App\Models\Color;
|
||||||
|
|
||||||
#[title('Calculator')]
|
#[title('Calculator')]
|
||||||
class Calculator extends Component
|
class Calculator extends Component
|
||||||
{
|
{
|
||||||
use WithPagination;
|
use WithPagination;
|
||||||
|
|
||||||
public Model $calculationpreset ;
|
public string $selection = '1';
|
||||||
|
public float $time = 0;
|
||||||
|
public string $timeunit = 'h';
|
||||||
|
public float $additionalcost = 0;
|
||||||
|
public string $additionalcostunit = 'per Piece';
|
||||||
|
public int $countonplate = 0;
|
||||||
|
public int $selcountonplate = 0;
|
||||||
|
public string $search_type = '';
|
||||||
|
public string $search_manufacturer = '';
|
||||||
|
public string $search_name = '';
|
||||||
|
public string $search_color = '';
|
||||||
|
|
||||||
|
public array $selectedFilaments = []; // [filament_id => amount_g]
|
||||||
|
|
||||||
|
public function toggleFilament($filamentId)
|
||||||
|
{
|
||||||
|
if (isset($this->selectedFilaments[$filamentId])) {
|
||||||
|
unset($this->selectedFilaments[$filamentId]);
|
||||||
|
} else {
|
||||||
|
$this->selectedFilaments[$filamentId] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
|
$filaments = Filament::query()
|
||||||
|
->where(function ($query) {
|
||||||
|
$query->when($this->search_type, function ($query) {
|
||||||
|
$query->whereHas('type', function ($q) {
|
||||||
|
$q->where('name', $this->search_type);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
->when($this->search_manufacturer, function ($query) {
|
||||||
|
$query->whereHas('manufacturer', function ($q) {
|
||||||
|
$q->where('name', 'like', '%' . $this->search_manufacturer . '%');
|
||||||
|
});
|
||||||
|
})
|
||||||
|
->when($this->search_name, function ($query) {
|
||||||
|
$query->where('name', 'like', '%' . $this->search_name . '%');
|
||||||
|
})
|
||||||
|
->when($this->search_color, function ($query) {
|
||||||
|
$query->whereHas('color', function ($q) {
|
||||||
|
$q->where('name', 'like', '%' . $this->search_color . '%');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
->orWhereIn('id', array_keys($this->selectedFilaments))
|
||||||
|
->with(['type', 'manufacturer', 'color'])
|
||||||
|
->get();
|
||||||
|
|
||||||
return view('livewire.calculator', [
|
return view('livewire.calculator', [
|
||||||
|
'presets' => \App\Models\Calculationpreset::all(),
|
||||||
|
'calculationpreset' => \App\Models\Calculationpreset::find($this->selection),
|
||||||
|
'filaments' => $filaments,
|
||||||
|
'filamenttypes' => FilamentType::all(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class Filament extends Model
|
|||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'name',
|
'name',
|
||||||
'description',
|
'description',
|
||||||
'type_id',
|
'filament_type_id',
|
||||||
'manufacturer_id',
|
'manufacturer_id',
|
||||||
'color_id',
|
'color_id',
|
||||||
'weight',
|
'weight',
|
||||||
@@ -31,19 +31,19 @@ class Filament extends Model
|
|||||||
// Filament belongs to a type
|
// Filament belongs to a type
|
||||||
public function type()
|
public function type()
|
||||||
{
|
{
|
||||||
return $this->hasOne(FilamentType::class);
|
return $this->belongsTo(FilamentType::class, 'filament_type_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filament belongs to a manufacturer
|
// Filament belongs to a manufacturer
|
||||||
public function manufacturer()
|
public function manufacturer()
|
||||||
{
|
{
|
||||||
return $this->hasOne(Manufacturer::class);
|
return $this->belongsTo(Manufacturer::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filament belongs to a color
|
// Filament belongs to a color
|
||||||
public function color()
|
public function color()
|
||||||
{
|
{
|
||||||
return $this->hasOne(Color::class);
|
return $this->belongsTo(Color::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+7
-7
@@ -2318,16 +2318,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "livewire/flux",
|
"name": "livewire/flux",
|
||||||
"version": "v2.10.2",
|
"version": "v2.11.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/livewire/flux.git",
|
"url": "https://github.com/livewire/flux.git",
|
||||||
"reference": "e7a93989788429bb6c0a908a056d22ea3a6c7975"
|
"reference": "3ada3b2644215fd1ccb7003ce8e6bc185c22e70a"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/livewire/flux/zipball/e7a93989788429bb6c0a908a056d22ea3a6c7975",
|
"url": "https://api.github.com/repos/livewire/flux/zipball/3ada3b2644215fd1ccb7003ce8e6bc185c22e70a",
|
||||||
"reference": "e7a93989788429bb6c0a908a056d22ea3a6c7975",
|
"reference": "3ada3b2644215fd1ccb7003ce8e6bc185c22e70a",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -2335,7 +2335,7 @@
|
|||||||
"illuminate/support": "^10.0|^11.0|^12.0",
|
"illuminate/support": "^10.0|^11.0|^12.0",
|
||||||
"illuminate/view": "^10.0|^11.0|^12.0",
|
"illuminate/view": "^10.0|^11.0|^12.0",
|
||||||
"laravel/prompts": "^0.1|^0.2|^0.3",
|
"laravel/prompts": "^0.1|^0.2|^0.3",
|
||||||
"livewire/livewire": "^3.7.3|^4.0",
|
"livewire/livewire": "^3.7.4|^4.0",
|
||||||
"php": "^8.1",
|
"php": "^8.1",
|
||||||
"symfony/console": "^6.0|^7.0"
|
"symfony/console": "^6.0|^7.0"
|
||||||
},
|
},
|
||||||
@@ -2378,9 +2378,9 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/livewire/flux/issues",
|
"issues": "https://github.com/livewire/flux/issues",
|
||||||
"source": "https://github.com/livewire/flux/tree/v2.10.2"
|
"source": "https://github.com/livewire/flux/tree/v2.11.1"
|
||||||
},
|
},
|
||||||
"time": "2025-12-19T02:11:45+00:00"
|
"time": "2026-01-21T17:09:56+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "livewire/livewire",
|
"name": "livewire/livewire",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class DatabaseSeeder extends Seeder
|
|||||||
BuildplateSeeder::class,
|
BuildplateSeeder::class,
|
||||||
CalculationpresetSeeder::class,
|
CalculationpresetSeeder::class,
|
||||||
PrintertypeSeeder::class,
|
PrintertypeSeeder::class,
|
||||||
|
FilamentSeeder::class,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
|
||||||
|
class FilamentSeeder extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the database seeds.
|
||||||
|
*/
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
// Seed predefined Filaments
|
||||||
|
$filaments = [
|
||||||
|
['name' => 'Bambu PLA Basic', 'manufacturer_id' => 1, 'filament_type_id' => 1, 'color_id' => 10, 'temp_max' => 230, 'temp_min' => 180, 'price' => 19.99, 'weight' => 1000, 'diameter' => '1.75'],
|
||||||
|
['name' => 'Bambu PLA Basic', 'manufacturer_id' => 1, 'filament_type_id' => 1, 'color_id' => 11, 'temp_max' => 230, 'temp_min' => 180, 'price' => 19.99, 'weight' => 1000, 'diameter' => '1.75'],
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($filaments as $filament) {
|
||||||
|
\App\Models\Filament::create($filament);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,15 +3,165 @@
|
|||||||
{{ __('Calculator') }}
|
{{ __('Calculator') }}
|
||||||
|
|
||||||
<x-slot name="headerleft">
|
<x-slot name="headerleft">
|
||||||
<flux:select wire:model.live="calculationpreset" placeholder="Select a preset">
|
<flux:select wire:model.live="selection" placeholder="Select a preset">
|
||||||
@foreach ( \App\Models\Calculationpreset::all() as $preset)
|
@foreach ( $presets as $preset)
|
||||||
<flux:select.option value="{{ $preset->id }}">{{ $preset->name }}</flux:select.option>
|
<flux:select.option value="{{ $preset->id }}">{{ $preset->name }}</flux:select.option>
|
||||||
@endforeach
|
@endforeach
|
||||||
</flux:select>
|
</flux:select>
|
||||||
</x-slot>
|
</x-slot>
|
||||||
</x-header>
|
</x-header>
|
||||||
|
|
||||||
<div class="container m-auto my-8 bg-elephant border border-elephant-900 rounded-lg shadow p-6 text-gray-50">
|
@if($calculationpreset)
|
||||||
{{ $preset->name }} : {{ $preset->price_kwh }}€
|
<div class="container m-auto">
|
||||||
</div>
|
<div class="grid grid-cols-3 gap-4">
|
||||||
|
<div class="container m-auto my-8 bg-elephant border border-elephant-900 rounded-lg shadow p-6 text-gray-50 col-span-2">
|
||||||
|
<div class="pr-4 flex flex-col gap-4">
|
||||||
|
<div class="border-b border-elephant-900 pb-4">
|
||||||
|
<flux:heading>Inputs</flux:heading>
|
||||||
|
<flux:text class="mt-2">Enter your calculation data.</flux:text>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2 pb-4 border-b border-elephant-900">
|
||||||
|
|
||||||
|
<flux:field>
|
||||||
|
<flux:label>Time</flux:label>
|
||||||
|
<flux:input.group>
|
||||||
|
<flux:input wire:model.live.debounce="time" type="number"/>
|
||||||
|
<flux:select class="max-w-fit" wire:model.lazy="timeunit">
|
||||||
|
<flux:select.option selected>h</flux:select.option>
|
||||||
|
<flux:select.option>min</flux:select.option>
|
||||||
|
</flux:select>
|
||||||
|
</flux:input.group>
|
||||||
|
</flux:field>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2 pb-4 border-b border-elephant-900 flex-col">
|
||||||
|
<div>
|
||||||
|
<flux:heading>Count on Plate</flux:heading>
|
||||||
|
<flux:text class="mt-2">More Objects per Plate?</flux:text>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<flux:input.group class="flex gap-2">
|
||||||
|
<flux:label>Enable</flux:label>
|
||||||
|
<flux:checkbox wire:model.lazy="selcountonplate" />
|
||||||
|
</flux:input.group>
|
||||||
|
</div>
|
||||||
|
@if($selcountonplate)
|
||||||
|
<div class="flex gap-2 flex-col px-8">
|
||||||
|
<flux:field>
|
||||||
|
<flux:label>Count on Plate</flux:label>
|
||||||
|
<flux:input wire:model.lazy="countonplate" type="number"/>
|
||||||
|
</flux:field>
|
||||||
|
|
||||||
|
<flux:field>
|
||||||
|
<flux:label>Additional Cost</flux:label>
|
||||||
|
<flux:input.group>
|
||||||
|
<flux:input wire:model.live.debounce="additionalcost" type="number"/>
|
||||||
|
<flux:select class="max-w-fit" wire:model.lazy="additionalcostunit">
|
||||||
|
<flux:select.option selected>per Piece</flux:select.option>
|
||||||
|
<flux:select.option>per Plate</flux:select.option>
|
||||||
|
</flux:select>
|
||||||
|
</flux:input.group>
|
||||||
|
</flux:field>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<flux:heading>Filament Selection</flux:heading>
|
||||||
|
<flux:text class="my-2">Filter by manufacturer, filament type and color.</flux:text>
|
||||||
|
|
||||||
|
<div class="my-2">
|
||||||
|
<flux:table>
|
||||||
|
<flux:table.columns>
|
||||||
|
<flux:table.column> Select </flux:table.column>
|
||||||
|
<flux:table.column> Type </flux:table.column>
|
||||||
|
<flux:table.column> Manufacturer </flux:table.column>
|
||||||
|
<flux:table.column> Name </flux:table.column>
|
||||||
|
<flux:table.column> Color </flux:table.column>
|
||||||
|
<flux:table.column> Amount (g) </flux:table.column>
|
||||||
|
</flux:table.columns>
|
||||||
|
|
||||||
|
<flux:table.rows class="bg-black/50">
|
||||||
|
<flux:table.row>
|
||||||
|
<flux:table.cell class="w-5"></flux:table.cell>
|
||||||
|
<flux:table.cell>
|
||||||
|
<flux:input wire:model.live="search_type" placeholder="Type" />
|
||||||
|
</flux:table.cell>
|
||||||
|
<flux:table.cell>
|
||||||
|
<flux:input wire:model.live.debounce="search_manufacturer" placeholder="Manufacturer" />
|
||||||
|
</flux:table.cell>
|
||||||
|
<flux:table.cell>
|
||||||
|
<flux:input wire:model.live.debounce="search_name" placeholder="Name" />
|
||||||
|
</flux:table.cell>
|
||||||
|
<flux:table.cell>
|
||||||
|
<flux:input wire:model.live.debounce="search_color" placeholder="Color" />
|
||||||
|
</flux:table.cell>
|
||||||
|
<flux:table.cell></flux:table.cell>
|
||||||
|
</flux:table.row>
|
||||||
|
@foreach ($filaments as $filament)
|
||||||
|
<flux:table.row :key="$filament->id" :class="isset($selectedFilaments[$filament->id]) ? 'bg-elephant-900' : ''">
|
||||||
|
<flux:table.cell class="w-5">
|
||||||
|
<flux:checkbox wire:click="toggleFilament({{ $filament->id }})" :checked="isset($selectedFilaments[$filament->id])" />
|
||||||
|
</flux:table.cell>
|
||||||
|
<flux:table.cell>{{ $filament->type?->name ?? 'N/A' }}</flux:table.cell>
|
||||||
|
<flux:table.cell>{{ $filament->manufacturer?->name ?? 'N/A' }}</flux:table.cell>
|
||||||
|
<flux:table.cell>{{ $filament->name }}</flux:table.cell>
|
||||||
|
<flux:table.cell>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<div class="w-4 h-4 rounded-full border border-gray-500" style="background-color: {{ '#' . $filament->color->hex ?? 'transparent' }}"></div>
|
||||||
|
{{ $filament->color?->name ?? 'N/A' }}
|
||||||
|
</div>
|
||||||
|
</flux:table.cell>
|
||||||
|
<flux:table.cell class="w-7">
|
||||||
|
@if(isset($selectedFilaments[$filament->id]))
|
||||||
|
<flux:input type="number" wire:model.live="selectedFilaments.{{ $filament->id }}"/>
|
||||||
|
@endif
|
||||||
|
</flux:table.cell>
|
||||||
|
</flux:table.row>
|
||||||
|
@endforeach
|
||||||
|
</flux:table.rows>
|
||||||
|
</flux:table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container m-auto my-8 bg-gold-drop-800 border border-gold-drop-600 rounded-lg shadow p-6 text-gray-50">
|
||||||
|
<div>
|
||||||
|
<div class="border-b border-gold-drop-600 pb-4">
|
||||||
|
<flux:heading>Outputs</flux:heading>
|
||||||
|
<flux:text class="mt-2">Get your ready for use Prices.</flux:text>
|
||||||
|
</div>
|
||||||
|
@php
|
||||||
|
$totalFilamentCost = 0;
|
||||||
|
foreach ($selectedFilaments as $id => $amount) {
|
||||||
|
$f = $filaments->firstWhere('id', $id);
|
||||||
|
if ($f && $f->price && $f->weight) {
|
||||||
|
$totalFilamentCost += ($f->price / $f->weight) * $amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$energyCost = ($calculationpreset->price_kwh ?? 0) * 0.25 * ($timeunit == 'h' ? $time : $time / 60);
|
||||||
|
$printerCost = ($calculationpreset->factor_printer ?? 0) * ($timeunit == 'h' ? $time : $time / 60);
|
||||||
|
$maintenanceCost = ($calculationpreset->factor_maintenance ?? 0) * ($timeunit == 'h' ? $time : $time / 60);
|
||||||
|
$totalCost = $totalFilamentCost + $energyCost + $printerCost + $maintenanceCost + ($additionalcostunit == 'per Piece' ? $additionalcost : $additionalcost / max(1, $countonplate ?? 1));
|
||||||
|
@endphp
|
||||||
|
<div class="mt-4 space-y-2">
|
||||||
|
<p>Time: {{ $time }}{{ $timeunit }}</p>
|
||||||
|
<p>Filament Cost: {{ number_format($totalFilamentCost, 2) }}€</p>
|
||||||
|
<p>Energy Cost: {{ number_format($energyCost, 2) }}€</p>
|
||||||
|
<p>Printer Cost: {{ number_format($printerCost, 2) }}€</p>
|
||||||
|
<p>Maintenance Cost: {{ number_format($maintenanceCost, 2) }}€</p>
|
||||||
|
|
||||||
|
@if($countonplate > 0 )
|
||||||
|
<div class="border-t border-gold-drop-600 flex flex-col gap-2 py-4">
|
||||||
|
<p>Additional Cost: {{ $additionalcost }}€ ({{ $additionalcostunit }})</p>
|
||||||
|
<flux:heading size="lg">Per Item Cost: {{ number_format($totalCost / $countonplate, 2) }}€ | {{ number_format(($totalCost * ($calculationpreset->factor_price / 100)/ $countonplate), 2) }}€</flux:heading>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
<div class="border-t border-gold-drop-600 flex flex-col gap-2 py-4">
|
||||||
|
<flux:heading size="lg" >Total Cost: {{ number_format($totalCost, 2) }}€ | {{ number_format($totalCost * ($calculationpreset->factor_price / 100), 2) }}€</flux:heading>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user