feat: Implement Feed Purchase management module with resource, pages, and cart functionality
This commit is contained in:
parent
6e3f685f06
commit
56038a3bf7
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Manage\FeedPurchases;
|
||||
|
||||
use App\Filament\Resources\Manage\FeedPurchases\Pages\CreateFeedPurchase;
|
||||
use App\Filament\Resources\Manage\FeedPurchases\Pages\ListFeedPurchases;
|
||||
use App\Filament\Resources\Manage\FeedPurchases\Schemas\FeedPurchaseForm;
|
||||
use App\Filament\Resources\Manage\FeedPurchases\Tables\FeedPurchasesTable;
|
||||
use App\Models\FeedPurchase;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
use UnitEnum;
|
||||
|
||||
class FeedPurchaseResource extends Resource
|
||||
{
|
||||
protected static ?string $model = FeedPurchase::class;
|
||||
|
||||
protected static string|UnitEnum|null $navigationGroup = 'Kelola';
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::Banknotes;
|
||||
|
||||
protected static ?string $navigationLabel = 'Belanja Pakan';
|
||||
|
||||
protected static ?int $navigationSort = 1;
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'reference_number';
|
||||
|
||||
protected static ?string $slug = 'manage/feed-purchases';
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return FeedPurchaseForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return FeedPurchasesTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListFeedPurchases::route('/'),
|
||||
'create' => CreateFeedPurchase::route('/create'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Manage\FeedPurchases\Pages;
|
||||
|
||||
use App\Filament\Actions\BackAction;
|
||||
use App\Filament\Resources\Manage\FeedPurchases\FeedPurchaseResource;
|
||||
use App\Filament\Resources\Manage\FeedPurchases\Traits\HasCart;
|
||||
use App\Models\FeedPurchaseItem;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Livewire\Attributes\On;
|
||||
|
||||
class CreateFeedPurchase extends CreateRecord
|
||||
{
|
||||
use HasCart;
|
||||
|
||||
protected static string $resource = FeedPurchaseResource::class;
|
||||
|
||||
protected ?string $heading = 'Tambah Belanja Pakan';
|
||||
|
||||
protected static ?string $title = 'Tambah Belanja Pakan';
|
||||
|
||||
#[On('cart-updated')]
|
||||
public function onCartUpdated(): void
|
||||
{
|
||||
$this->syncTotalPriceFromCart();
|
||||
}
|
||||
|
||||
protected function syncTotalPriceFromCart(): void
|
||||
{
|
||||
$total = (int) FeedPurchaseItem::whereNull('feed_purchase_id')->sum('subtotal');
|
||||
|
||||
$this->form->fill([
|
||||
...$this->form->getState(),
|
||||
'total_price' => $total,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
BackAction::make()
|
||||
->url(ListFeedPurchases::getUrl()),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getRedirectUrl(): string
|
||||
{
|
||||
return $this->getResource()::getUrl('index');
|
||||
}
|
||||
|
||||
protected function mutateFormDataBeforeCreate(array $data): array
|
||||
{
|
||||
$total = FeedPurchaseItem::whereNull('feed_purchase_id')->sum('subtotal');
|
||||
$data['total_price'] = $total;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function afterCreate(): void
|
||||
{
|
||||
FeedPurchaseItem::whereNull('feed_purchase_id')
|
||||
->update(['feed_purchase_id' => $this->record->id]);
|
||||
|
||||
$this->record->updateFeedStock();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Manage\FeedPurchases\Pages;
|
||||
|
||||
use App\Filament\Resources\Manage\FeedPurchases\FeedPurchaseResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListFeedPurchases extends ListRecords
|
||||
{
|
||||
protected static string $resource = FeedPurchaseResource::class;
|
||||
|
||||
protected static ?string $title = 'Belanja Pakan';
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->label('Tambah'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Manage\FeedPurchases\Schemas;
|
||||
|
||||
use App\Models\Feed;
|
||||
use App\Models\FeedPurchaseItem;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\View;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class FeedPurchaseForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
$feeds = Feed::with('unit')
|
||||
->latest()
|
||||
->get()
|
||||
->map(function ($feed) {
|
||||
$feed->image = $feed->getFirstMediaUrl('feeds');
|
||||
|
||||
return $feed;
|
||||
});
|
||||
|
||||
$cartItems = FeedPurchaseItem::with('feed')
|
||||
->whereNull('feed_purchase_id')
|
||||
->latest()
|
||||
->get()
|
||||
->map(function ($cartItem) {
|
||||
$cartItem->subtotal = $cartItem->unit_price * $cartItem->quantity;
|
||||
$cartItem->feedImage = $cartItem->feed?->getFirstMediaUrl('feeds');
|
||||
|
||||
return $cartItem;
|
||||
});
|
||||
|
||||
return $schema
|
||||
->components([
|
||||
Section::make('Informasi Belanja')
|
||||
->schema([
|
||||
Grid::make(3)
|
||||
->schema([
|
||||
TextInput::make('reference_number')
|
||||
->label('Nomor Referensi')
|
||||
->placeholder('...')
|
||||
->nullable()
|
||||
->autocomplete(false)
|
||||
->autofocus()
|
||||
->helperText('Silakan masukan nomor belanja, invoice atau lainnya.'),
|
||||
|
||||
DatePicker::make('purchase_date')
|
||||
->label('Tanggal Belanja')
|
||||
->default(now())
|
||||
->required()
|
||||
->native(false)
|
||||
->displayFormat('l, d F Y'),
|
||||
|
||||
TextInput::make('total_price')
|
||||
->label('Total Harga')
|
||||
->placeholder('0')
|
||||
->readOnly()
|
||||
->live()
|
||||
->currencyMask(thousandSeparator: '.', decimalSeparator: ',', precision: 0)
|
||||
->prefix('Rp')
|
||||
->required()
|
||||
->dehydrateStateUsing(fn ($state) => (int) str()->replace(',', '', (string) ($state ?? 0)))
|
||||
->default(fn () => (int) FeedPurchaseItem::whereNull('feed_purchase_id')->sum('subtotal')),
|
||||
|
||||
Textarea::make('notes')
|
||||
->label('Catatan')
|
||||
->placeholder('...')
|
||||
->columnSpanFull(),
|
||||
]),
|
||||
])
|
||||
->collapsible(),
|
||||
|
||||
Grid::make([
|
||||
'default' => 1,
|
||||
'lg' => 3,
|
||||
])
|
||||
->schema([
|
||||
Section::make('Daftar Pakan')
|
||||
->schema([
|
||||
View::make('filament.resources.manage.feed-purchases.feed-items-grid')
|
||||
->viewData([
|
||||
'feeds' => $feeds,
|
||||
]),
|
||||
])
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'lg' => 2,
|
||||
]),
|
||||
|
||||
Section::make('Keranjang')
|
||||
->schema([
|
||||
View::make('filament.resources.manage.feed-purchases.cart')
|
||||
->viewData([
|
||||
'cartItems' => $cartItems,
|
||||
]),
|
||||
])
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'lg' => 1,
|
||||
]),
|
||||
]),
|
||||
])
|
||||
->columns(1);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Manage\FeedPurchases\Tables;
|
||||
|
||||
use App\Filament\Actions\Cheerful\DeleteAction;
|
||||
use App\Filament\Actions\Cheerful\ForceDeleteAction;
|
||||
use App\Filament\Actions\Cheerful\RestoreAction;
|
||||
use App\Filament\Actions\DefaultBulkActions;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\TrashedFilter;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class FeedPurchasesTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('reference_number')
|
||||
->label('No. Referensi')
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('purchase_date')
|
||||
->label('Tanggal Belanja')
|
||||
->date('l, d F Y')
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('items_summary')
|
||||
->label('Item Pakan')
|
||||
->getStateUsing(function ($record): array {
|
||||
return $record->items
|
||||
->map(function ($item) {
|
||||
$feedName = $item->feed?->name ?? '-';
|
||||
$feedUnit = $item->feed?->unit?->name ?? '-';
|
||||
$quantity = number_format($item->quantity, 0, ',', '.');
|
||||
$subtotal = $item->formatted_subtotal;
|
||||
|
||||
return sprintf(
|
||||
'<div class="flex items-center justify-between gap-1 p-1">
|
||||
<div class="flex flex-col">
|
||||
<span class="font-semibold text-gray-900 dark:text-gray-100">
|
||||
%s
|
||||
</span>
|
||||
<span class="text-xs text-gray-500">
|
||||
x%s %s
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="text-sm whitespace-nowrap">
|
||||
Rp %s
|
||||
</div>
|
||||
</div>',
|
||||
e($feedName),
|
||||
$quantity,
|
||||
$feedUnit,
|
||||
$subtotal,
|
||||
);
|
||||
})
|
||||
->toArray();
|
||||
})
|
||||
->listWithLineBreaks()
|
||||
->html()
|
||||
->toggleable(),
|
||||
|
||||
TextColumn::make('total_price')
|
||||
->label('Total Harga')
|
||||
->money('IDR', decimalPlaces: 0)
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('created_at')
|
||||
->label('Dibuat')
|
||||
->dateTime('d/m/Y H:i')
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
TrashedFilter::make()
|
||||
->native(false),
|
||||
])
|
||||
->recordActions([
|
||||
DeleteAction::make(),
|
||||
|
||||
ForceDeleteAction::make(),
|
||||
|
||||
RestoreAction::make(),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
...DefaultBulkActions::make('Belanja Pakan'),
|
||||
]),
|
||||
])
|
||||
->emptyStateIcon(Heroicon::Banknotes)
|
||||
->emptyStateDescription('Setelah Anda membuat data pertama, maka akan muncul disini.')
|
||||
->defaultSort('created_at', 'desc')
|
||||
->deferFilters(false);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Manage\FeedPurchases\Traits;
|
||||
|
||||
use App\Models\Feed;
|
||||
use App\Models\FeedPurchaseItem;
|
||||
|
||||
trait HasCart
|
||||
{
|
||||
public array $cart = [];
|
||||
|
||||
public function loadCartFromDb(): void
|
||||
{
|
||||
$this->cart = FeedPurchaseItem::whereNull('feed_purchase_id')
|
||||
->get()
|
||||
->pluck('quantity', 'feed_id')
|
||||
->map(fn ($qty) => ['qty' => (int) $qty])
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function addFeed(int $feedId): void
|
||||
{
|
||||
$feed = Feed::find($feedId);
|
||||
if (! $feed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$item = FeedPurchaseItem::whereNull('feed_purchase_id')
|
||||
->where('feed_id', $feedId)
|
||||
->first();
|
||||
|
||||
if ($item) {
|
||||
$item->increment('quantity', 1);
|
||||
$item->update(['subtotal' => $item->quantity * $item->unit_price]);
|
||||
} else {
|
||||
FeedPurchaseItem::create([
|
||||
'feed_purchase_id' => null,
|
||||
'feed_id' => $feedId,
|
||||
'quantity' => 1,
|
||||
'unit_price' => $feed->price,
|
||||
'subtotal' => $feed->price,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->loadCartFromDb();
|
||||
$this->dispatch('cart-updated');
|
||||
}
|
||||
|
||||
public function decreaseQty(int $feedId): void
|
||||
{
|
||||
$item = FeedPurchaseItem::whereNull('feed_purchase_id')
|
||||
->where('feed_id', $feedId)
|
||||
->first();
|
||||
|
||||
if ($item) {
|
||||
if ($item->quantity > 1) {
|
||||
$item->decrement('quantity', 1);
|
||||
$item->update(['subtotal' => $item->quantity * $item->unit_price]);
|
||||
} else {
|
||||
$item->delete();
|
||||
}
|
||||
|
||||
$this->loadCartFromDb();
|
||||
$this->dispatch('cart-updated');
|
||||
}
|
||||
}
|
||||
|
||||
public function removeCartItem(int $itemId): void
|
||||
{
|
||||
FeedPurchaseItem::where('id', $itemId)->whereNull('feed_purchase_id')->delete();
|
||||
$this->loadCartFromDb();
|
||||
$this->dispatch('cart-updated');
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,7 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\FeedStatus;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
@ -25,6 +26,13 @@ protected function casts(): array
|
||||
];
|
||||
}
|
||||
|
||||
protected function formattedPrice(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => number_format($this->price, 0, ',', '.'),
|
||||
);
|
||||
}
|
||||
|
||||
public function unit(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Unit::class);
|
||||
|
||||
61
app/Models/FeedPurchase.php
Normal file
61
app/Models/FeedPurchase.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class FeedPurchase extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $guarded = ['id'];
|
||||
|
||||
protected $casts = [
|
||||
'purchase_date' => 'date',
|
||||
'total_price' => 'integer',
|
||||
];
|
||||
|
||||
protected static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::deleted(function ($feedPurchase) {
|
||||
$feedPurchase->revertFeedStock();
|
||||
});
|
||||
|
||||
static::forceDeleted(function ($feedPurchase) {
|
||||
$feedPurchase->revertFeedStock();
|
||||
});
|
||||
|
||||
static::restored(function ($feedPurchase) {
|
||||
$feedPurchase->updateFeedStock();
|
||||
});
|
||||
}
|
||||
|
||||
public function items(): HasMany
|
||||
{
|
||||
return $this->hasMany(FeedPurchaseItem::class);
|
||||
}
|
||||
|
||||
public function updateFeedStock(): void
|
||||
{
|
||||
foreach ($this->items as $item) {
|
||||
$feed = $item->feed;
|
||||
if ($feed) {
|
||||
$feed->increment('stock', $item->quantity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function revertFeedStock(): void
|
||||
{
|
||||
foreach ($this->items as $item) {
|
||||
$feed = $item->feed;
|
||||
if ($feed) {
|
||||
$feed->decrement('stock', $item->quantity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
45
app/Models/FeedPurchaseItem.php
Normal file
45
app/Models/FeedPurchaseItem.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class FeedPurchaseItem extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $guarded = ['id'];
|
||||
|
||||
protected $casts = [
|
||||
'quantity' => 'integer',
|
||||
'unit_price' => 'integer',
|
||||
'subtotal' => 'integer',
|
||||
];
|
||||
|
||||
protected function formattedUnitPrice(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => number_format($this->unit_price, 0, ',', '.'),
|
||||
);
|
||||
}
|
||||
|
||||
protected function formattedSubtotal(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => number_format($this->subtotal, 0, ',', '.'),
|
||||
);
|
||||
}
|
||||
|
||||
public function purchase(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(FeedPurchase::class, 'feed_purchase_id');
|
||||
}
|
||||
|
||||
public function feed(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Feed::class);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('feed_purchases', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('reference_number', 50)->nullable();
|
||||
$table->date('purchase_date');
|
||||
$table->unsignedInteger('total_price');
|
||||
$table->text('notes')->nullable();
|
||||
$table->timestamp('created_at')->useCurrent();
|
||||
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('feed_purchases');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('feed_purchase_items', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('feed_purchase_id')->nullable()->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('feed_id')->constrained()->cascadeOnDelete();
|
||||
$table->unsignedInteger('quantity');
|
||||
$table->unsignedInteger('unit_price');
|
||||
$table->unsignedInteger('subtotal');
|
||||
$table->timestamp('created_at')->useCurrent();
|
||||
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('feed_purchase_items');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,69 @@
|
||||
<div>
|
||||
|
||||
<!-- Cart Items List -->
|
||||
<div class="flex-1 overflow-y-auto space-y-3 custom-scrollbar" style="max-height: 500px; min-height: 300px;">
|
||||
@forelse ($cartItems as $cartItem)
|
||||
<div
|
||||
class="group relative flex flex-col gap-3 p-4 bg-white dark:bg-gray-800 rounded-xl border border-gray-100 dark:border-gray-700 shadow-sm hover:shadow-md hover:border-primary-100 dark:hover:border-primary-900/50 transition-all duration-300">
|
||||
|
||||
<div class="flex justify-between items-start gap-4">
|
||||
<div class="flex items-start gap-3">
|
||||
<!-- Icon/Image Placeholder -->
|
||||
<div
|
||||
class="flex-shrink-0 w-10 h-10 flex items-center justify-center bg-gray-50 dark:bg-gray-700 text-gray-400 dark:text-gray-500 rounded-lg group-hover:bg-primary-50 group-hover:text-primary-500 dark:group-hover:bg-primary-900/20 transition-colors">
|
||||
<img src="{{ $cartItem->feedImage }}" alt="{{ $cartItem->feed?->name }}"
|
||||
class="w-full h-full object-cover rounded-lg">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3
|
||||
class="font-bold text-gray-900 dark:text-white text-sm leading-tight group-hover:text-primary-600 transition-colors">
|
||||
{{ $cartItem->feed?->name }}
|
||||
</h3>
|
||||
<div class="flex items-center gap-2 mt-1">
|
||||
<span
|
||||
class="text-xs text-gray-500 dark:text-gray-400 bg-gray-100 dark:bg-gray-700 px-1.5 py-0.5 rounded">
|
||||
Rp {{ $cartItem->formatted_unit_price }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button wire:click="removeCartItem({{ $cartItem['id'] }})" type="button"
|
||||
class="text-gray-400 hover:text-red-500 bg-transparent hover:bg-red-50 dark:hover:bg-red-900/20 p-2 rounded-lg transition-all duration-200"
|
||||
title="Hapus Item">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Footer: Calculation & Subtotal -->
|
||||
<div
|
||||
class="flex items-center justify-between pt-3 border-t border-dashed border-gray-100 dark:border-gray-700 mt-1">
|
||||
<div class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
||||
<span class="text-gray-800 dark:text-gray-200 font-bold">{{ $cartItem->quantity }}</span>
|
||||
{{ $cartItem->feed?->unit?->alias ?? 'Unit' }}
|
||||
</div>
|
||||
|
||||
<div class="text-primary-600 dark:text-primary-400 font-bold text-sm">
|
||||
Rp {{ $cartItem->formatted_subtotal }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="flex flex-col items-center justify-center h-full py-12 text-center opacity-60">
|
||||
<div class="w-20 h-20 bg-gray-100 dark:bg-gray-800 rounded-full flex items-center justify-center mb-4">
|
||||
<svg class="w-10 h-10 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-gray-900 dark:text-white font-semibold text-base">Keranjang Kosong</h3>
|
||||
<p class="text-gray-500 dark:text-gray-400 text-sm mt-1 px-8">Pilih pakan di sebelah kiri untuk
|
||||
menambahkan ke keranjang.</p>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,44 @@
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-5">
|
||||
|
||||
@foreach ($feeds as $feed)
|
||||
<div class="border rounded-xl bg-white shadow-sm hover:shadow-md transition overflow-hidden flex flex-col">
|
||||
|
||||
<img src="{{ $feed->image }}" class="w-full h-40 object-cover">
|
||||
|
||||
<div class="p-4 flex flex-col flex-1">
|
||||
|
||||
<div class="text-xs text-gray-400">
|
||||
{{ $feed->unit?->name ?? '-' }}
|
||||
</div>
|
||||
|
||||
<div class="font-semibold">
|
||||
{{ $feed->name }}
|
||||
</div>
|
||||
|
||||
<div class="font-bold text-lg mt-1">
|
||||
Rp {{ $feed->formatted_price }}
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-3 mt-auto">
|
||||
|
||||
<a href="javascript:void(0)" wire:click="decreaseQty({{ $feed->id }})"
|
||||
class="w-8 h-8 rounded-full border border-gray-200 flex items-center justify-center text-lg hover:bg-gray-50 transition-colors">
|
||||
-
|
||||
</a>
|
||||
|
||||
<span class="text-sm font-bold w-6 text-center">
|
||||
{{ $this->cart[$feed->id]['qty'] ?? 0 }}
|
||||
</span>
|
||||
|
||||
<a href="javascript:void(0)" wire:click="addFeed({{ $feed->id }})"
|
||||
class="w-8 h-8 rounded-full flex items-center justify-center text-white text-lg transition-colors bg-teal-500 hover:bg-teal-600">
|
||||
+
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
</div>
|
||||
Loading…
Reference in New Issue
Block a user