fix structure product categories table and crud product

This commit is contained in:
2025-06-02 16:21:33 +07:00
parent 59e23ae535
commit 6bf8bc4965
21 changed files with 573 additions and 141 deletions

View File

@@ -24,11 +24,14 @@ class ProductCategoriesController extends Controller
$data = ProductCategory::query(); $data = ProductCategory::query();
return DataTables::of($data) return DataTables::of($data)
->addIndexColumn() ->addIndexColumn()
->addColumn('parent', function ($row) {
return $row->parent ? $row->parent->name : '-';
})
->addColumn('action', function ($row) use ($menu) { ->addColumn('action', function ($row) use ($menu) {
$btn = ''; $btn = '';
if (Auth::user()->can('delete', $menu)) { if (Auth::user()->can('delete', $menu)) {
$btn .= '<button class="btn btn-danger btn-sm btn-destroy-product-category" data-action="' . route('product_categories.destroy', $row->id) . '" data-id="' . $row->id . '">Hapus</button>'; $btn .= '<button style="margin-right: 8px;" class="btn btn-danger btn-sm btn-destroy-product-category" data-action="' . route('product_categories.destroy', $row->id) . '" data-id="' . $row->id . '">Hapus</button>';
} }
if (Auth::user()->can('update', $menu)) { if (Auth::user()->can('update', $menu)) {
@@ -63,6 +66,7 @@ class ProductCategoriesController extends Controller
{ {
$validated = $request->validate([ $validated = $request->validate([
'name' => 'required|string|max:255', 'name' => 'required|string|max:255',
'parent_id' => 'nullable|exists:product_categories,id',
]); ]);
ProductCategory::create($validated); ProductCategory::create($validated);
return response()->json(['success' => true, 'message' => 'Kategori berhasil ditambahkan.']); return response()->json(['success' => true, 'message' => 'Kategori berhasil ditambahkan.']);
@@ -121,4 +125,10 @@ class ProductCategoriesController extends Controller
ProductCategory::findOrFail($id)->delete(); ProductCategory::findOrFail($id)->delete();
return response()->json(['success' => true, 'message' => 'Kategorii berhasil dihapus.']); return response()->json(['success' => true, 'message' => 'Kategorii berhasil dihapus.']);
} }
public function getParents(Request $request)
{
$parents = ProductCategory::whereNull('parent_id')->get(['id', 'name']);
return response()->json($parents);
}
} }

View File

@@ -3,6 +3,7 @@
namespace App\Http\Controllers\WarehouseManagement; namespace App\Http\Controllers\WarehouseManagement;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Dealer;
use App\Models\Menu; use App\Models\Menu;
use App\Models\Product; use App\Models\Product;
use App\Models\ProductCategory; use App\Models\ProductCategory;
@@ -34,16 +35,18 @@ class ProductsController extends Controller
}); });
}) })
->addColumn('action', function ($row) use ($menu) { ->addColumn('action', function ($row) use ($menu) {
$btn = ''; $btn = '<div class="d-flex">';
if (Auth::user()->can('delete', $menu)) { if (Auth::user()->can('delete', $menu)) {
$btn .= '<button class="btn btn-danger btn-sm btn-destroy-product" data-action="' . route('products.destroy', $row->id) . '" data-id="' . $row->id . '">Hapus</button>'; $btn .= '<button style="margin-right: 8px;" class="btn btn-danger btn-sm btn-destroy-product" data-action="' . route('products.destroy', $row->id) . '" data-id="' . $row->id . '">Hapus</button>';
} }
if (Auth::user()->can('update', $menu)) { if (Auth::user()->can('update', $menu)) {
$btn .= '<button class="btn btn-warning btn-sm btn-edit-product" data-url="' . route('products.edit', $row->id) . '" data-action="' . route('products.update', $row->id) . '" data-id="' . $row->id . '">Edit</button>'; $btn .= '<a href="' . route('products.edit', $row->id) . '" class="btn btn-warning btn-sm">Edit</a>';
} }
$btn .= '</div>';
return $btn; return $btn;
}) })
->rawColumns(['action']) ->rawColumns(['action'])
@@ -59,7 +62,9 @@ class ProductsController extends Controller
*/ */
public function create() public function create()
{ {
// $categories = ProductCategory::with('children')->whereNull('parent_id')->get();
$dealers = Dealer::all();
return view('warehouse_management.products.create', compact('categories', 'dealers'));
} }
/** /**
@@ -70,7 +75,38 @@ class ProductsController extends Controller
*/ */
public function store(Request $request) public function store(Request $request)
{ {
// $request->validate([
'code' => 'required|string|unique:products,code',
'name' => 'required|string',
'description' => 'nullable|string',
'product_category_id' => 'required|exists:product_categories,id',
'dealer_stock' => 'nullable|array',
'dealer_stock.*.dealer_id' => 'required|exists:dealers,id',
'dealer_stock.*.quantity' => 'required|integer|min:0',
]);
// Create product
$product = Product::create([
'code' => $request->code,
'name' => $request->name,
'description' => $request->description,
'product_category_id' => $request->product_category_id,
]);
// Prepare dealer stock for pivot
$pivotData = [];
if ($request->has('dealer_stock')) {
foreach ($request->dealer_stock as $stockData) {
if (empty($stockData['dealer_id']) || !isset($stockData['quantity'])) continue;
$pivotData[$stockData['dealer_id']] = ['quantity' => $stockData['quantity']];
}
// Attach dealer stock using pivot table
$product->dealers()->attach($pivotData);
}
return redirect()->route('products.index')->with('success', 'Produk berhasil ditambahkan.');
} }
/** /**
@@ -92,7 +128,12 @@ class ProductsController extends Controller
*/ */
public function edit($id) public function edit($id)
{ {
// $product = Product::findOrFail($id);
return view('warehouse_management.products.edit', [
'product' => $product->load('dealers'),
'dealers' => Dealer::all(),
'categories' => ProductCategory::with('children')->get(),
]);
} }
/** /**
@@ -102,9 +143,32 @@ class ProductsController extends Controller
* @param int $id * @param int $id
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function update(Request $request, $id) public function update(Request $request, Product $product)
{ {
// $request->validate([
'code' => 'required|string|unique:products,code,' . $product->id,
'name' => 'required|string',
'description' => 'nullable|string',
'product_category_id' => 'required|exists:product_categories,id',
'dealer_stock' => 'nullable|array',
'dealer_stock.*.dealer_id' => 'required|exists:dealers,id',
'dealer_stock.*.quantity' => 'required|integer|min:0',
]);
$product->update($request->only(['code', 'name', 'description', 'product_category_id']));
// Prepare pivot sync data
$syncData = [];
if ($request->has('dealer_stock')) {
foreach ($request->dealer_stock as $item) {
$syncData[$item['dealer_id']] = ['quantity' => $item['quantity']];
}
}
// Sync with pivot table
$product->dealers()->sync($syncData);
return redirect()->route('products.index')->with('success', 'Produk berhasil diperbarui.');
} }
/** /**
@@ -113,8 +177,14 @@ class ProductsController extends Controller
* @param int $id * @param int $id
* @return \Illuminate\Http\Response * @return \Illuminate\Http\Response
*/ */
public function destroy($id) public function destroy(Product $product)
{ {
// // Detach all dealer relationships (optional if using cascade on delete)
$product->dealers()->detach();
// Delete the product
$product->delete();
return response()->json(['success' => true, 'message' => 'Produk berhasil dihapus.']);
} }
} }

View File

@@ -10,9 +10,17 @@ class ProductCategory extends Model
{ {
use HasFactory, SoftDeletes; use HasFactory, SoftDeletes;
protected $fillable = ['name']; protected $fillable = ['name','parent_id'];
public function products(){ public function products(){
return $this->hasMany(Product::class, 'product_category_id'); return $this->hasMany(Product::class, 'product_category_id');
} }
public function parent(){
return $this->belongsTo(ProductCategory::class, 'parent_id');
}
public function children(){
return $this->hasMany(ProductCategory::class,'parent_id');
}
} }

27
app/Models/Stock.php Normal file
View File

@@ -0,0 +1,27 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Stock extends Model
{
use HasFactory;
protected $table = 'stock';
protected $fillable = [
'product_id',
'dealer_id',
'quantity',
];
public function product()
{
return $this->belongsTo(Product::class);
}
public function dealer()
{
return $this->belongsTo(Dealer::class);
}
}

View File

@@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddParentIdToProductCategoriesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('product_categories', function (Blueprint $table) {
$table->foreignId('parent_id')->nullable()->after('name')->constrained('product_categories')->nullOnDelete();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('product_categories', function (Blueprint $table) {
$table->dropForeign(['parent_id']);
$table->dropColumn('parent_id');
});
}
}

View File

@@ -17,33 +17,40 @@ class ProductAndCategorySeeder extends Seeder
{ {
$categories = [ $categories = [
'Oli & Pelumas' => [ 'Oli & Pelumas' => [
['code' => 'OLI001', 'name' => 'Oli Mesin 10W-40'], ['code' => 'OLI001', 'name' => 'Oli Mesin 10W-40', 'subcategory' => 'Oli Mesin'],
['code' => 'OLI002', 'name' => 'Oli Gardan'], ['code' => 'OLI002', 'name' => 'Oli Gardan', 'subcategory' => 'Oli Gardan'],
], ],
'Aki & Kelistrikan' => [ 'Aki & Kelistrikan' => [
['code' => 'AKI001', 'name' => 'Aki Kering 12V'], ['code' => 'AKI001', 'name' => 'Aki Kering 12V', 'subcategory' => 'Aki'],
['code' => 'AKI002', 'name' => 'Regulator Rectifier'], ['code' => 'AKI002', 'name' => 'Regulator Rectifier', 'subcategory' => 'Kelistrikan'],
], ],
'Rem' => [ 'Rem' => [
['code' => 'REM001', 'name' => 'Kampas Rem Belakang'], ['code' => 'REM001', 'name' => 'Kampas Rem Belakang', 'subcategory' => 'Kampas Rem'],
['code' => 'REM002', 'name' => 'Cakram Depan'], ['code' => 'REM002', 'name' => 'Cakram Depan', 'subcategory' => 'Cakram'],
], ],
]; ];
foreach ($categories as $parentName => $products) {
foreach ($categories as $categoryName => $products) { // Create parent category
$category = ProductCategory::firstOrCreate( $parent = ProductCategory::firstOrCreate(
['name' => $categoryName], ['name' => $parentName],
['created_at' => now(), 'updated_at' => now()] ['created_at' => now(), 'updated_at' => now()]
); );
foreach ($products as $product) { foreach ($products as $product) {
// Create child category (sub-category)
$child = ProductCategory::firstOrCreate(
['name' => $product['subcategory'], 'parent_id' => $parent->id],
['created_at' => now(), 'updated_at' => now()]
);
// Create product in the child category
Product::updateOrCreate( Product::updateOrCreate(
['code' => $product['code']], ['code' => $product['code']],
[ [
'name' => $product['name'], 'name' => $product['name'],
'description' => $product['name'], 'description' => $product['name'],
'product_category_id' => $category->id 'product_category_id' => $child->id
] ]
); );
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -2,5 +2,7 @@
"/js/app.js": "/js/app.js", "/js/app.js": "/js/app.js",
"/js/warehouse_management/product_categories/index.js": "/js/warehouse_management/product_categories/index.js", "/js/warehouse_management/product_categories/index.js": "/js/warehouse_management/product_categories/index.js",
"/js/warehouse_management/products/index.js": "/js/warehouse_management/products/index.js", "/js/warehouse_management/products/index.js": "/js/warehouse_management/products/index.js",
"/js/warehouse_management/products/create.js": "/js/warehouse_management/products/create.js",
"/js/warehouse_management/products/edit.js": "/js/warehouse_management/products/edit.js",
"/css/app.css": "/css/app.css" "/css/app.css": "/css/app.css"
} }

View File

@@ -10,13 +10,8 @@ let table = $("#product-categories-table").DataTable({
serverSide: true, serverSide: true,
ajax: url, ajax: url,
columns: [ columns: [
{
data: "DT_RowIndex",
name: "DT_RowIndex",
orderable: false,
searchable: false,
},
{ data: "name", name: "name" }, { data: "name", name: "name" },
{ data: "parent", name: "parent" },
{ data: "action", name: "action", orderable: false, searchable: false }, { data: "action", name: "action", orderable: false, searchable: false },
], ],
}); });
@@ -27,6 +22,7 @@ $(document).ready(function () {
$("#category_id").val(""); $("#category_id").val("");
$("#modalTitle").text("Tambah Kategori"); $("#modalTitle").text("Tambah Kategori");
$("#productCategoryModal").modal("show"); $("#productCategoryModal").modal("show");
loadParentCategories();
}); });
// Submit form (baik tambah maupun edit) // Submit form (baik tambah maupun edit)
@@ -67,6 +63,26 @@ $(document).on("click", ".btn-edit-product-category", function () {
success: function (response) { success: function (response) {
$("#category_id").val(response.id); $("#category_id").val(response.id);
$("#name").val(response.name); $("#name").val(response.name);
// Get parent categories and populate select
$.ajax({
url: "/warehouse/categories/parents", // Adjust to match your route
method: "GET",
success: function (parents) {
let options =
'<option value="">-- Tidak ada (Parent)</option>';
parents.forEach(function (parent) {
// Avoid self-select
if (parent.id !== response.id) {
options += `<option value="${parent.id}" ${
response.parent_id === parent.id
? "selected"
: ""
}>${parent.name}</option>`;
}
});
$("#parent_id").html(options);
},
});
$("#modalTitle").text("Edit Kategori"); $("#modalTitle").text("Edit Kategori");
$("#productCategoryModal").modal("show"); $("#productCategoryModal").modal("show");
}, },
@@ -107,3 +123,24 @@ $(document).on("click", ".btn-destroy-product-category", function () {
} }
}); });
}); });
function loadParentCategories(selectedId = null) {
$.ajax({
url: "/warehouse/categories/parents", // create this route
type: "GET",
success: function (data) {
$("#parent_id")
.empty()
.append(
'<option value="">-- Tidak ada (Kategori Utama) --</option>'
);
data.forEach(function (category) {
$("#parent_id").append(
`<option value="${category.id}" ${
selectedId == category.id ? "selected" : ""
}>${category.name}</option>`
);
});
},
});
}

View File

@@ -0,0 +1,43 @@
document.addEventListener("DOMContentLoaded", function () {
const addBtn = document.getElementById("addDealerRow");
const dealerRows = document.getElementById("dynamicDealerRows");
// Get dealer data from Blade
const dealerDataDiv = document.getElementById("dealerData");
const dealers = JSON.parse(dealerDataDiv.getAttribute("data-dealers")); // 👈 this reads JSON from Blade
let rowCount = 0;
function createDealerRow(index) {
const dealerOptions = dealers
.map((d) => `<option value="${d.id}">${d.name}</option>`)
.join("");
return `
<div class="form-group row align-items-center dealer-row" data-index="${index}">
<div class="col-md-5">
<select name="dealer_stock[${index}][dealer_id]" class="form-control" required>
<option value="">-- Pilih Dealer --</option>
${dealerOptions}
</select>
</div>
<div class="col-md-4">
<input type="number" name="dealer_stock[${index}][quantity]" class="form-control" value="1" min="1" required>
</div>
<div class="col-md-3">
<button type="button" class="btn btn-danger btn-sm removeDealerRow">Hapus</button>
</div>
</div>
`;
}
addBtn.addEventListener("click", function () {
dealerRows.insertAdjacentHTML("beforeend", createDealerRow(rowCount));
rowCount++;
});
dealerRows.addEventListener("click", function (e) {
if (e.target.classList.contains("removeDealerRow")) {
e.target.closest(".dealer-row").remove();
}
});
});

View File

@@ -0,0 +1,48 @@
document.addEventListener("DOMContentLoaded", function () {
let dealerRowContainer = document.getElementById("dynamicDealerRows");
let addDealerButton = document.getElementById("addDealerRow");
// Initial index from existing dealer rows
let rowIndex =
dealerRowContainer.querySelectorAll(".dealer-stock-row").length;
addDealerButton.addEventListener("click", function () {
const newRow = document.createElement("div");
newRow.className = "form-group row align-items-center dealer-stock-row";
newRow.innerHTML = `
<div class="col-md-6">
<select name="dealer_stock[${rowIndex}][dealer_id]" class="form-control">
<option value="">-- Pilih Dealer --</option>
${generateDealerOptions()}
</select>
</div>
<div class="col-md-4">
<input type="number" name="dealer_stock[${rowIndex}][quantity]" class="form-control" value="1" min="1" required placeholder="Jumlah Stok" />
</div>
<div class="col-md-2">
<button type="button" class="btn btn-danger btn-sm remove-dealer-row"> Hapus </button>
</div>
`;
dealerRowContainer.appendChild(newRow);
rowIndex++;
});
// Handle removal of dealer row
dealerRowContainer.addEventListener("click", function (event) {
if (
event.target &&
event.target.classList.contains("remove-dealer-row")
) {
event.target.closest(".dealer-stock-row").remove();
}
});
// Function to generate dealer <option> elements dynamically
function generateDealerOptions() {
const dealerDataDiv = document.getElementById("dealerData");
const dealers = JSON.parse(dealerDataDiv.getAttribute("data-dealers"));
return dealers
.map((d) => `<option value="${d.id}">${d.name}</option>`)
.join("");
}
});

View File

@@ -10,12 +10,6 @@ let table = $("#products-table").DataTable({
serverSide: true, serverSide: true,
ajax: url, ajax: url,
columns: [ columns: [
{
data: "DT_RowIndex",
name: "DT_RowIndex",
orderable: false,
searchable: false,
},
{ data: "code", name: "code" }, { data: "code", name: "code" },
{ data: "name", name: "name" }, { data: "name", name: "name" },
{ data: "category_name", name: "category.name" }, { data: "category_name", name: "category.name" },
@@ -24,65 +18,9 @@ let table = $("#products-table").DataTable({
], ],
}); });
$(document).ready(function () { $(document).on("click", ".btn-destroy-product", function () {
$("#addProductCategory").click(function () {
$("#productCategoryForm")[0].reset();
$("#category_id").val("");
$("#modalTitle").text("Tambah Kategori");
$("#productCategoryModal").modal("show");
});
// Submit form (baik tambah maupun edit)
$("#productCategoryForm").submit(function (e) {
e.preventDefault();
let id = $("#category_id").val();
let url = id
? `/warehouse/product_categories/${id}`
: `/warehouse/product_categories`;
let method = id ? "PUT" : "POST";
$.ajax({
url: url,
method: method,
data: {
name: $("#name").val(),
_token: $('meta[name="csrf-token"]').attr("content"),
...(id && { _method: "PUT" }),
},
success: function () {
$("#productCategoryModal").modal("hide");
$("#product-categories-table").DataTable().ajax.reload();
},
error: function (xhr) {
alert("Gagal menyimpan data");
console.error(xhr.responseText);
},
});
});
});
$(document).on("click", ".btn-edit-product-category", function () {
const id = $(this).data("id");
const url = $(this).data("url");
$.ajax({
url: url,
method: "GET",
success: function (response) {
$("#category_id").val(response.id);
$("#name").val(response.name);
$("#modalTitle").text("Edit Kategori");
$("#productCategoryModal").modal("show");
},
error: function (xhr) {
alert("Gagal mengambil data");
console.error(xhr.responseText);
},
});
});
$(document).on("click", ".btn-destroy-product-category", function () {
Swal.fire({ Swal.fire({
title: "Hapus nama kategori?", title: "Hapus produk?",
text: "Anda tidak akan bisa mengembalikannya!", text: "Anda tidak akan bisa mengembalikannya!",
showCancelButton: true, showCancelButton: true,
confirmButtonColor: "#d33", confirmButtonColor: "#d33",
@@ -99,11 +37,11 @@ $(document).on("click", ".btn-destroy-product-category", function () {
_token: $('meta[name="csrf-token"]').attr("content"), _token: $('meta[name="csrf-token"]').attr("content"),
}, },
success: function () { success: function () {
alert("Kategori berhasil dihapus."); alert("Produk berhasil dihapus.");
$("#product-categories-table").DataTable().ajax.reload(); $("#products-table").DataTable().ajax.reload();
}, },
error: function (xhr) { error: function (xhr) {
alert("Gagal menghapus kategori."); alert("Gagal menghapus produk.");
console.error(xhr.responseText); console.error(xhr.responseText);
}, },
}); });

View File

@@ -8,7 +8,7 @@
<i class="kt-font-brand flaticon2-line-chart"></i> <i class="kt-font-brand flaticon2-line-chart"></i>
</span> </span>
<h3 class="kt-portlet__head-title"> <h3 class="kt-portlet__head-title">
Kategori Produk Tabel Kategori Produk
</h3> </h3>
</div> </div>
@can('create', $menus['product_categories.index']) @can('create', $menus['product_categories.index'])
@@ -28,8 +28,8 @@
<table class="table table-striped table-bordered table-hover" id="product-categories-table" data-url="{{ route("product_categories.index") }}"> <table class="table table-striped table-bordered table-hover" id="product-categories-table" data-url="{{ route("product_categories.index") }}">
<thead> <thead>
<tr> <tr>
<th>NO</th>
<th>Nama Kategori</th> <th>Nama Kategori</th>
<th>Parent</th>
<th>Aksi</th> <th>Aksi</th>
</tr> </tr>
</thead> </thead>
@@ -51,11 +51,18 @@
<button type="button" class="close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<input type="hidden" id="category_id"> <input type="hidden" id="category_id" name="id">
<div class="mb-3"> <div class="mb-3">
<label for="name" class="form-label">Nama Kategori</label> <label for="name" class="form-label">Nama Kategori</label>
<input type="text" class="form-control" id="name" name="name" required> <input type="text" class="form-control" id="name" name="name" required>
</div> </div>
<div class="mb-3">
<label for="parent_id" class="form-label">Induk Kategori (Opsional)</label>
<select class="form-control" id="parent_id" name="parent_id">
<option value="">-- Tidak ada (Kategori Utama) --</option>
<!-- will be filled by JavaScript -->
</select>
</div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="submit" class="btn btn-primary" id="saveBtn">Simpan</button> <button type="submit" class="btn btn-primary" id="saveBtn">Simpan</button>
@@ -64,7 +71,6 @@
</form> </form>
</div> </div>
</div> </div>
{{-- end modal --}} {{-- end modal --}}
@endsection @endsection

View File

@@ -1,37 +1,73 @@
@extends('layouts.backapp') @extends('layouts.backapp')
@section('content') @section('content')
<div class="kt-portlet kt-portlet--mobile" id="kt_blockui_datatable"> <div class="kt-portlet kt-portlet--mobile">
<div class="kt-portlet__head kt-portlet__head--lg"> <div class="kt-portlet__head kt-portlet__head--lg">
<div class="kt-portlet__head-label"> <div class="kt-portlet__head-label">
<span class="kt-portlet__head-icon"> <span class="kt-portlet__head-icon">
<i class="kt-font-brand flaticon2-line-chart"></i> <i class="kt-font-brand flaticon2-line-chart"></i>
</span> </span>
<h3 class="kt-portlet__head-title"> <h3 class="kt-portlet__head-title">Tambah Produk</h3>
Tambah Produk
</h3>
</div> </div>
</div> </div>
<div class="kt-portlet__body"> <div class="kt-portlet__body">
<div class=""> <form action="{{ route('products.store') }}" method="POST" id="productForm">
@csrf
{{-- Product Info --}}
<div class="form-group">
<label for="code"><strong>Kode Produk</strong></label>
<input type="text" name="code" id="code" class="form-control" required>
</div> </div>
<div class="table-responsive">
<!--begin: Datatable --> <div class="form-group">
<table class="table table-striped table-bordered table-hover" id="stock-dealers-table" data-url="{{ route("products.index") }}"> <label for="name"><strong>Nama Produk</strong></label>
<thead> <input type="text" name="name" id="name" class="form-control" required>
<tr>
<th>NO</th>
<th>Kode</th>
<th>Nama</th>
<th>Address</th>
<th>Stok</th>
</tr>
</thead>
</table>
<!--end: Datatable -->
</div> </div>
<div class="form-group">
<label for="description"><strong>Deskripsi</strong></label>
<textarea name="description" id="description" class="form-control" rows="3"></textarea>
</div>
{{-- Product Category --}}
<div class="form-group">
<label for="product_category_id"><strong>Kategori Produk</strong></label>
<select name="product_category_id" id="product_category_id" class="form-control" required>
<option value="">-- Pilih Kategori --</option>
@foreach ($categories as $category)
<option value="{{ $category->id }}" disabled style="background-color: #f0f0f0;">
{{ $category->name }}
</option>
@foreach ($category->children as $child)
<option value="{{ $child->id }}">&nbsp;&nbsp;&nbsp;{{ $child->name }}</option>
@endforeach
@endforeach
</select>
</div>
{{-- Dynamic Dealer Stock Section --}}
<div id="dealerData" data-dealers='@json($dealers)'></div>
<div class="form-group">
<label><strong>Stok per Dealer</strong></label>
<div id="dynamicDealerRows"></div>
<button type="button" id="addDealerRow" class="btn btn-outline-primary mt-2">
Tambah Dealer
</button>
</div>
{{-- Submit --}}
<div class="form-group mt-4">
<button type="submit" class="btn btn-primary">Simpan</button>
<a href="{{ route('products.index') }}" class="btn btn-secondary">Batal</a>
</div>
</form>
</div> </div>
</div> </div>
@endsection @endsection
@section('javascripts')
<script src="{{ mix('js/warehouse_management/products/create.js') }}"></script>
@endsection

View File

@@ -0,0 +1,95 @@
@extends('layouts.backapp')
@section('content')
<div class="kt-portlet kt-portlet--mobile">
<div class="kt-portlet__head kt-portlet__head--lg">
<div class="kt-portlet__head-label">
<span class="kt-portlet__head-icon">
<i class="kt-font-brand flaticon2-line-chart"></i>
</span>
<h3 class="kt-portlet__head-title">Tambah Produk</h3>
</div>
</div>
<div class="kt-portlet__body">
<form action="{{ route('products.update', $product->id) }}" method="POST" id="productForm">
@csrf
@method('PUT') {{-- Correctly spoof PUT method --}}
{{-- Product Info --}}
<div class="form-group">
<label for="code"><strong>Kode Produk</strong></label>
<input type="text" name="code" id="code" class="form-control" value="{{ old('code', $product->code) }}" required>
</div>
<div class="form-group">
<label for="name"><strong>Nama Produk</strong></label>
<input type="text" name="name" id="name" class="form-control" value="{{ old('name', $product->name) }}" required>
</div>
<div class="form-group">
<label for="description"><strong>Deskripsi</strong></label>
<textarea name="description" id="description" class="form-control" rows="3">{{ old('description', $product->description) }}</textarea>
</div>
{{-- Product Category --}}
<div class="form-group">
<label for="product_category_id"><strong>Kategori Produk</strong></label>
<select name="product_category_id" id="product_category_id" class="form-control" required>
<option value="">-- Pilih Kategori --</option>
@foreach ($categories as $category)
<option value="{{ $category->id }}" disabled style="background-color: #f0f0f0;">
{{ $category->name }}
</option>
@foreach ($category->children as $child)
<option value="{{ $child->id }}" {{ $child->id == old('product_category_id', $product->product_category_id) ? 'selected' : '' }}>
&nbsp;&nbsp;&nbsp;{{ $child->name }}
</option>
@endforeach
@endforeach
</select>
</div>
{{-- Stok per Dealer --}}
<div id="dealerData" data-dealers='@json($dealers)'></div>
<div class="form-group">
<label><strong>Stok per Dealer</strong></label>
<div id="dynamicDealerRows">
@foreach($product->dealers as $index => $dealer)
<div class="form-group row align-items-center dealer-stock-row">
<div class="col-md-6">
<select name="dealer_stock[{{ $index }}][dealer_id]" class="form-control">
<option value="">-- Pilih Dealer --</option>
@foreach ($dealers as $d)
<option value="{{ $d->id }}" {{ $d->id == $dealer->id ? 'selected' : '' }}>
{{ $d->name }}
</option>
@endforeach
</select>
</div>
<div class="col-md-4">
<input type="number" name="dealer_stock[{{ $index }}][quantity]" value="{{ $dealer->pivot->quantity }}" class="form-control" placeholder="Jumlah Stok" />
</div>
<div class="col-md-2">
<button type="button" class="btn btn-danger btn-sm remove-dealer-row">Hapus</button>
</div>
</div>
@endforeach
</div>
<button type="button" id="addDealerRow" class="btn btn-outline-primary mt-2"> Tambah Dealer</button>
</div>
{{-- Submit --}}
<div class="form-group mt-4">
<button type="submit" class="btn btn-primary">Simpan</button>
<a href="{{ route('products.index') }}" class="btn btn-secondary">Batal</a>
</div>
</form>
</div>
</div>
@endsection
@section('javascripts')
<script src="{{ mix('js/warehouse_management/products/edit.js') }}"></script>
@endsection

View File

@@ -8,14 +8,14 @@
<i class="kt-font-brand flaticon2-line-chart"></i> <i class="kt-font-brand flaticon2-line-chart"></i>
</span> </span>
<h3 class="kt-portlet__head-title"> <h3 class="kt-portlet__head-title">
Produk Tabel Produk
</h3> </h3>
</div> </div>
@can('create', $menus['product_categories.index']) @can('create', $menus['product_categories.index'])
<div class="kt-portlet__head-toolbar"> <div class="kt-portlet__head-toolbar">
<div class="kt-portlet__head-wrapper"> <div class="kt-portlet__head-wrapper">
<div class="kt-portlet__head-actions"> <div class="kt-portlet__head-actions">
<button type="button" class="btn btn-bold btn-label-brand btn-sm" id="addProductCategory"> Tambah </button> <a href="{{ route('products.create') }}" class="btn btn-bold btn-label-brand btn--sm">Tambah</a>
</div> </div>
</div> </div>
</div> </div>
@@ -28,7 +28,6 @@
<table class="table table-striped table-bordered table-hover" id="products-table" data-url="{{ route("products.index") }}"> <table class="table table-striped table-bordered table-hover" id="products-table" data-url="{{ route("products.index") }}">
<thead> <thead>
<tr> <tr>
<th>NO</th>
<th>Kode</th> <th>Kode</th>
<th>Nama</th> <th>Nama</th>
<th>Kategori</th> <th>Kategori</th>

View File

@@ -206,6 +206,7 @@ Route::group(['middleware' => 'auth'], function() {
Route::prefix('warehouse')->group(function (){ Route::prefix('warehouse')->group(function (){
Route::resource('products', ProductsController::class); Route::resource('products', ProductsController::class);
Route::resource('product_categories', ProductCategoriesController::class); Route::resource('product_categories', ProductCategoriesController::class);
Route::get('categories/parents', [ProductCategoriesController::class, 'getParents']);
}); });
}); });

View File

@@ -21,4 +21,12 @@ mix.js("resources/js/app.js", "public/js")
"resources/js/warehouse_management/products/index.js", "resources/js/warehouse_management/products/index.js",
"public/js/warehouse_management/products" "public/js/warehouse_management/products"
) )
.js(
"resources/js/warehouse_management/products/create.js",
"public/js/warehouse_management/products"
)
.js(
"resources/js/warehouse_management/products/edit.js",
"public/js/warehouse_management/products"
)
.sourceMaps(); .sourceMaps();