category qosishsh to'g'irlandi

This commit is contained in:
2026-04-29 13:40:10 +05:00
parent 0254b616a9
commit e1f79a58ec
6 changed files with 312 additions and 160 deletions

View File

@@ -14,7 +14,9 @@ use App\Jobs\Dashboard\Category\Update as UpdateJob;
use App\Models\Characteristic; use App\Models\Characteristic;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Throwable;
class Controller extends ExController class Controller extends ExController
{ {
@@ -25,17 +27,46 @@ class Controller extends ExController
public function index() public function index()
{ {
$this->authorize('view', 'categories'); $this->authorize('view', 'categories');
$categories = Category::select('id', 'name->ru as category', 'position', 'parent_id', 'image') $categories = $this->categoryTree();
->where('parent_id', null)
->with(['children' => function ($parent) {
return $parent->select('id', 'name->ru as category', 'parent_id', 'position', 'image')->orderBy('position', 'asc')->with(['children' => function ($parent) {
return $parent->select('id', 'name->ru as category', 'parent_id', 'position', 'image')->orderBy('position', 'asc');
}]);
}])->orderBy('position', 'asc')->get();
return view('dashboard.category.index', compact('categories')); return view('dashboard.category.index', compact('categories'));
} }
private function categoryTree($parentId = null, array $visited = [])
{
return Category::select('id', 'name', 'position', 'parent_id', 'image')
->when($parentId === null, function ($query) {
$query->whereNull('parent_id');
}, function ($query) use ($parentId) {
$query->where('parent_id', $parentId);
})
->orderBy('position', 'asc')
->get()
->map(function (Category $category) use ($visited) {
if (in_array($category->id, $visited, true)) {
return [
'id' => $category->id,
'category' => $category->name['ru'] ?? $category->name['uz'] ?? '',
'position' => $category->position,
'parent_id' => $category->parent_id,
'image' => $category->image,
'image_url' => $category->image_url,
'children' => [],
];
}
return [
'id' => $category->id,
'category' => $category->name['ru'] ?? $category->name['uz'] ?? '',
'position' => $category->position,
'parent_id' => $category->parent_id,
'image' => $category->image,
'image_url' => $category->image_url,
'children' => $this->categoryTree($category->id, [...$visited, $category->id]),
];
});
}
/** /**
* @param StoreRequest $request * @param StoreRequest $request
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\JsonResponse|\Illuminate\View\View * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\JsonResponse|\Illuminate\View\View
@@ -55,20 +86,39 @@ class Controller extends ExController
return view('dashboard.category.store', compact('brands', 'parent_categories')); return view('dashboard.category.store', compact('brands', 'parent_categories'));
} }
$category = $this->dispatchSync(new StoreJob($request)); try {
$category = DB::transaction(function () use ($request) {
$category = $this->dispatchSync(new StoreJob($request));
if (!empty($request->char)) { if (!empty($request->char)) {
foreach ($request->char as $char) { foreach ($request->char as $char) {
Characteristic::create([ Characteristic::create([
'name' => $char['name'], 'name' => $char['name'] ?? ['ru' => '', 'uz' => ''],
'type' => $char['type'], 'type' => $char['type'] ?? 'text',
'category_id' => $category->id, 'category_id' => $category->id,
'filter' => $char['filter'] == 'true' ? 1 : 0 'filter' => filter_var($char['filter'] ?? false, FILTER_VALIDATE_BOOLEAN)
]); ]);
} }
}
return $category;
});
} catch (Throwable $exception) {
Log::error('Category store failed', [
'message' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
return response()->json([
'status' => false,
'message' => 'Category could not be saved.',
'error' => config('app.debug') ? $exception->getMessage() : null,
], 500);
} }
$this->success(trans('admin.messages.created')); if ($category) {
$this->success(trans('admin.messages.created'));
}
return response()->json([ return response()->json([
'status' => true 'status' => true
@@ -103,39 +153,52 @@ class Controller extends ExController
return view('dashboard.category.update', compact('parent_categories', 'category', 'brands')); return view('dashboard.category.update', compact('parent_categories', 'category', 'brands'));
} }
$image = $request->getImage($category); try {
$image = $request->getImage($category);
$this->dispatchSync(new UpdateJob($category, $request, $image)); DB::transaction(function () use ($category, $request, $image) {
$this->dispatchSync(new UpdateJob($category, $request, $image));
if (!empty($request->char)) { if (!empty($request->char)) {
foreach ($request->char as $char) { foreach ($request->char as $char) {
if ($char['id'] == null || $char['id'] == 'null') { if ($char['id'] == null || $char['id'] == 'null') {
Characteristic::create([ Characteristic::create([
'name' => $char['name'], 'name' => $char['name'] ?? ['ru' => '', 'uz' => ''],
'type' => $char['type'], 'type' => $char['type'] ?? 'text',
'category_id' => $category->id, 'category_id' => $category->id,
'filter' => $char['filter'] == 'true' ? 1 : 0 'filter' => filter_var($char['filter'] ?? false, FILTER_VALIDATE_BOOLEAN)
]); ]);
} else { } else {
Characteristic::where('id', $char['id'])->update([ Characteristic::where('id', $char['id'])->update([
'name' => $char['name'], 'name' => $char['name'] ?? ['ru' => '', 'uz' => ''],
'type' => $char['type'], 'type' => $char['type'] ?? 'text',
'filter' => $char['filter'] == 'true' ? 1 : 0 'filter' => filter_var($char['filter'] ?? false, FILTER_VALIDATE_BOOLEAN)
]); ]);
}
}
} }
}
}
if (!empty($request->deletes['char'])) { if (!empty($request->deletes['char'])) {
$chars = Characteristic::whereIn('id', $request->deletes['char'])->get(); $chars = Characteristic::whereIn('id', $request->deletes['char'])->get();
foreach ($chars as $char) { foreach ($chars as $char) {
$char->values()->detach(); $char->values()->detach();
// foreach ($char->values as $value) { $char->delete();
// $value->delete(); }
// } }
$char->delete(); });
} } catch (Throwable $exception) {
Log::error('Category update failed', [
'category_id' => $category->id,
'message' => $exception->getMessage(),
'trace' => $exception->getTraceAsString(),
]);
return response()->json([
'status' => false,
'message' => 'Category could not be saved.',
'error' => config('app.debug') ? $exception->getMessage() : null,
], 500);
} }
$this->success(trans('admin.messages.updated')); $this->success(trans('admin.messages.updated'));
@@ -145,7 +208,6 @@ class Controller extends ExController
]); ]);
} }
/** /**
* @param Category $category * @param Category $category
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse

View File

@@ -82,17 +82,7 @@ class Controller extends ExController
{ {
if ($request->isMethod('get')) { if ($request->isMethod('get')) {
$this->authorize('create', 'products'); $this->authorize('create', 'products');
$categories = $this->categories->select('id', 'name->ru as category') $categories = $this->categoryTree();
->where('parent_id', null)
->with([
'parents' => function ($parent) {
return $parent->select('id', 'name->ru as category', 'parent_id')->with([
'parents' => function ($parent) {
return $parent->select('id', 'name->ru as category', 'parent_id');
}
]);
}
])->get();
$brands = $this->brands->get(); $brands = $this->brands->get();
$colors = $this->colors->get(); $colors = $this->colors->get();
$measurement = Measurement::query()->get(); $measurement = Measurement::query()->get();
@@ -146,6 +136,31 @@ class Controller extends ExController
} }
} }
private function categoryTree($parentId = null, array $visited = [])
{
return Category::select('id', 'name', 'parent_id')
->when($parentId === null, function ($query) {
$query->whereNull('parent_id');
}, function ($query) use ($parentId) {
$query->where('parent_id', $parentId);
})
->orderBy('position', 'asc')
->get()
->map(function (Category $category) use ($visited) {
$children = in_array($category->id, $visited, true)
? collect()
: $this->categoryTree($category->id, [...$visited, $category->id]);
return [
'id' => $category->id,
'category' => $category->name['ru'] ?? $category->name['uz'] ?? '',
'parent_id' => $category->parent_id,
'parents' => $children,
'$isDisabled' => $children->isNotEmpty(),
];
});
}
/** /**
* @param $id * @param $id
* @return array * @return array
@@ -309,17 +324,7 @@ class Controller extends ExController
} }
$categories = $this->categories->select('id', 'name->ru as category') $categories = $this->categoryTree();
->where('parent_id', null)
->with([
'parents' => function ($parent) {
return $parent->select('id', 'name->ru as category', 'parent_id')->with([
'parents' => function ($parent) {
return $parent->select('id', 'name->ru as category', 'parent_id');
}
]);
}
])->get();
$brands = $this->brands->get(); $brands = $this->brands->get();
$measurement = Measurement::query()->get(); $measurement = Measurement::query()->get();

View File

@@ -793,7 +793,16 @@ export default {
.catch((error) => { .catch((error) => {
if (error.response) { if (error.response) {
this.error = true; this.error = true;
this.errors = error.response.data.errors; this.errors =
error.response.data.errors ||
error.response.data.details ||
[
[
error.response.data.error ||
error.response.data.message ||
"Server error",
],
];
} }
}); });
}, },

View File

@@ -804,7 +804,16 @@ export default {
.catch((error) => { .catch((error) => {
if (error.response) { if (error.response) {
this.error = true; this.error = true;
this.errors = error.response.data.errors; this.errors =
error.response.data.errors ||
error.response.data.details ||
[
[
error.response.data.error ||
error.response.data.message ||
"Server error",
],
];
} }
}); });
}, },

View File

@@ -63,51 +63,28 @@
<multiselect <multiselect
:options="categories" :options="categories"
v-model="category.first" v-model="category.path[0]"
label="category" label="category"
@change=" @input="selectCategoryLevel(0)"
DetectCategory($event) track-by="id"
"
track-by="category"
></multiselect> ></multiselect>
<!-- //@change="getCharacteristics($event)"-->
</div> </div>
</div> </div>
<div <div
class="col-4" class="col-4"
v-if="category.two_view" v-for="level in categoryLevels"
:key="'category-level-' + level"
> >
<div class="form-group"> <div class="form-group">
<label>Суб категория *</label> <label>Суб категория *</label>
<multiselect <multiselect
:options=" :options="categoryOptions(level)"
category.first.parents v-model="category.path[level]"
"
v-model="category.two"
label="category" label="category"
@change="DetectCategoryTwo" @input="selectCategoryLevel(level)"
track-by="category" track-by="id"
></multiselect>
</div>
</div>
<div
class="col-4"
v-if="category.three_view"
>
<div class="form-group">
<label>Под категория *</label>
<multiselect
:options="
category.two.parents
"
v-model="category.three"
label="category"
@change="DetectCategory"
track-by="category"
></multiselect> ></multiselect>
</div> </div>
</div> </div>
@@ -1074,6 +1051,22 @@ export default {
this.watch_count.three = 2; this.watch_count.three = 2;
}, },
"category.path": {
deep: true,
handler() {
const selected = this.lastSelectedCategory();
if (!selected) {
this.products.category_id = null;
this.characteristics = [];
return;
}
this.products.category_id = selected.id;
this.getCharacteristics(selected.id);
},
},
}, },
data: function () { data: function () {
@@ -1106,6 +1099,7 @@ export default {
three: {}, three: {},
two_view: false, two_view: false,
three_view: false, three_view: false,
path: [],
}, },
watch_count: { watch_count: {
@@ -1136,6 +1130,22 @@ export default {
} }
}, },
computed: {
categoryLevels() {
const levels = [];
let current = this.category.path[0];
let level = 1;
while (current && current.parents && current.parents.length > 0) {
levels.push(level);
current = this.category.path[level];
level++;
}
return levels;
},
},
methods: { methods: {
getPosterPreview() { getPosterPreview() {
if (this.products.poster instanceof File) { if (this.products.poster instanceof File) {
@@ -1145,35 +1155,58 @@ export default {
}, },
setCategory() { setCategory() {
if (this.products.categories[0]) { if (this.products.categories[0]) {
if (this.products.categories[0].parent) { this.products.category_id = this.products.categories[0].id;
if (this.products.categories[0].parent.parent) { this.category.path = this.findCategoryPath(
this.category.two_view = true; this.categories,
this.category.three_view = true; this.products.categories[0].id
this.products.category_id = );
this.products.categories[0].id;
this.category.first =
this.products.categories[0].parent.parent;
this.category.two = this.products.categories[0].parent;
this.category.three = this.products.categories[0];
} else {
this.products.category_id =
this.products.categories[0].id;
this.category.first =
this.products.categories[0].parent;
this.category.two = this.products.categories[0];
this.category.two_view = true;
}
} else {
this.products.category_id = this.products.categories[0].id;
this.category.first = this.products.categories[0];
}
this.getCharacteristics(this.product.categories[0].id); this.getCharacteristics(this.product.categories[0].id);
} }
}, },
findCategoryPath(categories, id, path = []) {
for (const category of categories || []) {
const currentPath = [...path, category];
if (category.id === id) {
return currentPath;
}
const childPath = this.findCategoryPath(
category.parents || [],
id,
currentPath
);
if (childPath.length > 0) {
return childPath;
}
}
return [];
},
categoryOptions(level) {
const parent = this.category.path[level - 1];
return parent && parent.parents ? parent.parents : [];
},
selectCategoryLevel(level) {
this.category.path.splice(level + 1);
},
lastSelectedCategory() {
for (let i = this.category.path.length - 1; i >= 0; i--) {
if (this.category.path[i] && this.category.path[i].id) {
return this.category.path[i];
}
}
return null;
},
DetectCategory() { DetectCategory() {
this.category.two = { this.category.two = {
parents: [], parents: [],

View File

@@ -59,51 +59,28 @@
<multiselect <multiselect
:options="categories" :options="categories"
v-model="category.first" v-model="category.path[0]"
label="category" label="category"
@change=" @input="selectCategoryLevel(0)"
DetectCategory($event) track-by="id"
"
track-by="category"
></multiselect> ></multiselect>
<!-- //@change="getCharacteristics($event)"-->
</div> </div>
</div> </div>
<div <div
class="col-4" class="col-4"
v-if="category.two_view" v-for="level in categoryLevels"
:key="'category-level-' + level"
> >
<div class="form-group"> <div class="form-group">
<label>Суб категория *</label> <label>Суб категория *</label>
<multiselect <multiselect
:options=" :options="categoryOptions(level)"
category.first.parents v-model="category.path[level]"
"
v-model="category.two"
label="category" label="category"
@change="DetectCategoryTwo" @input="selectCategoryLevel(level)"
track-by="category" track-by="id"
></multiselect>
</div>
</div>
<div
class="col-4"
v-if="category.three_view"
>
<div class="form-group">
<label>Под категория *</label>
<multiselect
:options="
category.two.parents
"
v-model="category.three"
label="category"
@change="DetectCategory"
track-by="category"
></multiselect> ></multiselect>
</div> </div>
</div> </div>
@@ -1136,6 +1113,7 @@ export default {
three: {}, three: {},
two_view: false, two_view: false,
three_view: false, three_view: false,
path: [],
}, },
characteristic: false, characteristic: false,
@@ -1149,6 +1127,20 @@ export default {
uploadDisabled() { uploadDisabled() {
return this.files.length === 0; return this.files.length === 0;
}, },
categoryLevels() {
const levels = [];
let current = this.category.path[0];
let level = 1;
while (current && current.parents && current.parents.length > 0) {
levels.push(level);
current = this.category.path[level];
level++;
}
return levels;
},
}, },
watch: { watch: {
@@ -1181,6 +1173,22 @@ export default {
this.DetectCategoryThree(); this.DetectCategoryThree();
}, },
"category.path": {
deep: true,
handler() {
const selected = this.lastSelectedCategory();
if (!selected) {
this.product.category_id = null;
this.characteristics = [];
return;
}
this.product.category_id = selected.id;
this.getCharacteristics(selected.id);
},
},
}, },
methods: { methods: {
@@ -1312,11 +1320,37 @@ export default {
.catch((error) => { .catch((error) => {
if (error.response) { if (error.response) {
this.error = true; this.error = true;
this.errors = error.response.data.errors; this.errors = error.response.data.errors || {
product: [
error.response.data.messages ||
error.response.data.message ||
"Ошибка при сохранении",
],
};
} }
}); });
}, },
categoryOptions(level) {
const parent = this.category.path[level - 1];
return parent && parent.parents ? parent.parents : [];
},
selectCategoryLevel(level) {
this.category.path.splice(level + 1);
},
lastSelectedCategory() {
for (let i = this.category.path.length - 1; i >= 0; i--) {
if (this.category.path[i] && this.category.path[i].id) {
return this.category.path[i];
}
}
return null;
},
remaincharRUCount: function () { remaincharRUCount: function () {
if (this.product.short_body.ru.length > 300) { if (this.product.short_body.ru.length > 300) {
this.short_limit.ru = "Превышен лимит в 300 символов."; this.short_limit.ru = "Превышен лимит в 300 символов.";