feat: enhance raw material filtering with name selection and add distinct name retrieval
This commit is contained in:
parent
d0482c03db
commit
ef987360f4
@ -22,9 +22,10 @@ public function index(PaginatedRequest $request): Response
|
|||||||
return Inertia::render('admin/master/raw-material/index', [
|
return Inertia::render('admin/master/raw-material/index', [
|
||||||
'rawMaterials' => $this->service->paginated(
|
'rawMaterials' => $this->service->paginated(
|
||||||
...$request->validatedWithDefaults(),
|
...$request->validatedWithDefaults(),
|
||||||
filters: $request->only(['is_active', 'stock']),
|
filters: $request->only(['is_active', 'stock', 'name']),
|
||||||
),
|
),
|
||||||
'filters' => $request->only(['is_active', 'stock']),
|
'rawMaterialNames' => $this->service->getNames(),
|
||||||
|
'filters' => $request->only(['is_active', 'stock', 'name']),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
use App\Services\Concerns\RegistersMedia;
|
use App\Services\Concerns\RegistersMedia;
|
||||||
use App\Services\S3PresignedService;
|
use App\Services\S3PresignedService;
|
||||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class RawMaterialService
|
class RawMaterialService
|
||||||
@ -17,6 +18,13 @@ public function __construct(
|
|||||||
private S3PresignedService $s3Service,
|
private S3PresignedService $s3Service,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
public function getNames(): Collection
|
||||||
|
{
|
||||||
|
return RawMaterial::select('name')
|
||||||
|
->distinct()
|
||||||
|
->pluck('name');
|
||||||
|
}
|
||||||
|
|
||||||
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = []): LengthAwarePaginator
|
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', array $filters = []): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
$paginator = RawMaterial::query()
|
$paginator = RawMaterial::query()
|
||||||
@ -25,6 +33,7 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
|
|||||||
'rawMaterialPrices:id,raw_material_id,variant,price,stock',
|
'rawMaterialPrices:id,raw_material_id,variant,price,stock',
|
||||||
])
|
])
|
||||||
->when($search, fn ($q) => $q->where('name', 'like', "%{$search}%"))
|
->when($search, fn ($q) => $q->where('name', 'like', "%{$search}%"))
|
||||||
|
->when($filters['name'] ?? null, fn ($q, $name) => $q->where('name', 'like', "%{$name}%"))
|
||||||
->when(($filters['is_active'] ?? null) !== null && ($filters['is_active'] ?? null) !== '', function ($q) use ($filters) {
|
->when(($filters['is_active'] ?? null) !== null && ($filters['is_active'] ?? null) !== '', function ($q) use ($filters) {
|
||||||
$q->where('is_active', $filters['is_active'] === 'true');
|
$q->where('is_active', $filters['is_active'] === 'true');
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,12 +1,20 @@
|
|||||||
import { Head, Link, router } from '@inertiajs/react';
|
import { Head, Link, router } from '@inertiajs/react';
|
||||||
import { Plus } from 'lucide-react';
|
import { Plus } from 'lucide-react';
|
||||||
import { useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { CardTable } from '@/components/data-display';
|
import { CardTable } from '@/components/data-display';
|
||||||
import { DeleteConfirmDialog } from '@/components/dialogs';
|
import { DeleteConfirmDialog } from '@/components/dialogs';
|
||||||
import { FilterPopover } from '@/components/data-display';
|
import { FilterPopover } from '@/components/data-display';
|
||||||
import { useCardTableExpand } from '@/components/hooks/use-card-table-expand';
|
import { useCardTableExpand } from '@/components/hooks/use-card-table-expand';
|
||||||
import { PageHeader } from '@/components/layout';
|
import { PageHeader } from '@/components/layout';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
import {
|
||||||
|
Combobox,
|
||||||
|
ComboboxContent,
|
||||||
|
ComboboxEmpty,
|
||||||
|
ComboboxInput,
|
||||||
|
ComboboxItem,
|
||||||
|
ComboboxList,
|
||||||
|
} from '@/components/ui/combobox';
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@ -38,13 +46,15 @@ type Props = {
|
|||||||
per_page: number;
|
per_page: number;
|
||||||
total: number;
|
total: number;
|
||||||
};
|
};
|
||||||
|
rawMaterialNames: string[];
|
||||||
filters: {
|
filters: {
|
||||||
is_active?: string;
|
is_active?: string;
|
||||||
|
name?: string;
|
||||||
stock?: string;
|
stock?: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RawMaterialIndex({ rawMaterials, filters }: Props) {
|
export default function RawMaterialIndex({ rawMaterials, rawMaterialNames, filters }: Props) {
|
||||||
const { can } = useCan();
|
const { can } = useCan();
|
||||||
const [deleting, setDeleting] = useState<RawMaterial | null>(null);
|
const [deleting, setDeleting] = useState<RawMaterial | null>(null);
|
||||||
const [deletingVariant, setDeletingVariant] = useState<{
|
const [deletingVariant, setDeletingVariant] = useState<{
|
||||||
@ -76,6 +86,11 @@ export default function RawMaterialIndex({ rawMaterials, filters }: Props) {
|
|||||||
filterWithParams: false,
|
filterWithParams: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const sortedRawMaterialNames = useMemo(
|
||||||
|
() => [...rawMaterialNames].sort(),
|
||||||
|
[rawMaterialNames],
|
||||||
|
);
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
if (!deleting) {
|
if (!deleting) {
|
||||||
return;
|
return;
|
||||||
@ -107,9 +122,37 @@ export default function RawMaterialIndex({ rawMaterials, filters }: Props) {
|
|||||||
open={filterOpen}
|
open={filterOpen}
|
||||||
onOpenChange={setFilterOpen}
|
onOpenChange={setFilterOpen}
|
||||||
filters={filters}
|
filters={filters}
|
||||||
hasActiveFilters={Boolean(filters.is_active || filters.stock)}
|
hasActiveFilters={Boolean(filters.is_active || filters.name || filters.stock)}
|
||||||
onClear={clearFilters}
|
onClear={clearFilters}
|
||||||
>
|
>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="text-xs text-muted-foreground">
|
||||||
|
Nama Bahan Baku
|
||||||
|
</label>
|
||||||
|
<Combobox
|
||||||
|
items={sortedRawMaterialNames}
|
||||||
|
value={filters.name ?? ''}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
applyFilter('name', (value as string) ?? '')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ComboboxInput
|
||||||
|
placeholder="Pilih bahan baku..."
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
<ComboboxContent>
|
||||||
|
<ComboboxEmpty>
|
||||||
|
Tidak ada bahan baku ditemukan.
|
||||||
|
</ComboboxEmpty>
|
||||||
|
<ComboboxList>
|
||||||
|
{(name) => (
|
||||||
|
<ComboboxItem key={name} value={name}>{name}</ComboboxItem>
|
||||||
|
)}
|
||||||
|
</ComboboxList>
|
||||||
|
</ComboboxContent>
|
||||||
|
</Combobox>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<label className="text-xs text-muted-foreground">Status</label>
|
<label className="text-xs text-muted-foreground">Status</label>
|
||||||
<Select
|
<Select
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user