diff --git a/app/Http/Controllers/Dashboard/Category/Controller.php b/app/Http/Controllers/Dashboard/Category/Controller.php index c0ea18d..29cbeb8 100755 --- a/app/Http/Controllers/Dashboard/Category/Controller.php +++ b/app/Http/Controllers/Dashboard/Category/Controller.php @@ -14,7 +14,9 @@ use App\Jobs\Dashboard\Category\Update as UpdateJob; use App\Models\Characteristic; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; +use Throwable; class Controller extends ExController { @@ -25,17 +27,46 @@ class Controller extends ExController public function index() { $this->authorize('view', 'categories'); - $categories = Category::select('id', 'name->ru as category', 'position', 'parent_id', 'image') - ->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(); + $categories = $this->categoryTree(); 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 * @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')); } - $category = $this->dispatchSync(new StoreJob($request)); + try { + $category = DB::transaction(function () use ($request) { + $category = $this->dispatchSync(new StoreJob($request)); - if (!empty($request->char)) { - foreach ($request->char as $char) { - Characteristic::create([ - 'name' => $char['name'], - 'type' => $char['type'], - 'category_id' => $category->id, - 'filter' => $char['filter'] == 'true' ? 1 : 0 - ]); - } + if (!empty($request->char)) { + foreach ($request->char as $char) { + Characteristic::create([ + 'name' => $char['name'] ?? ['ru' => '', 'uz' => ''], + 'type' => $char['type'] ?? 'text', + 'category_id' => $category->id, + '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([ 'status' => true @@ -103,39 +153,52 @@ class Controller extends ExController 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)) { - foreach ($request->char as $char) { - if ($char['id'] == null || $char['id'] == 'null') { - Characteristic::create([ - 'name' => $char['name'], - 'type' => $char['type'], - 'category_id' => $category->id, - 'filter' => $char['filter'] == 'true' ? 1 : 0 - ]); - } else { - Characteristic::where('id', $char['id'])->update([ - 'name' => $char['name'], - 'type' => $char['type'], - 'filter' => $char['filter'] == 'true' ? 1 : 0 - ]); + if (!empty($request->char)) { + foreach ($request->char as $char) { + if ($char['id'] == null || $char['id'] == 'null') { + Characteristic::create([ + 'name' => $char['name'] ?? ['ru' => '', 'uz' => ''], + 'type' => $char['type'] ?? 'text', + 'category_id' => $category->id, + 'filter' => filter_var($char['filter'] ?? false, FILTER_VALIDATE_BOOLEAN) + ]); + } else { + Characteristic::where('id', $char['id'])->update([ + 'name' => $char['name'] ?? ['ru' => '', 'uz' => ''], + 'type' => $char['type'] ?? 'text', + 'filter' => filter_var($char['filter'] ?? false, FILTER_VALIDATE_BOOLEAN) + ]); + } + } } - } - } - if (!empty($request->deletes['char'])) { - $chars = Characteristic::whereIn('id', $request->deletes['char'])->get(); + if (!empty($request->deletes['char'])) { + $chars = Characteristic::whereIn('id', $request->deletes['char'])->get(); - foreach ($chars as $char) { - $char->values()->detach(); - // foreach ($char->values as $value) { - // $value->delete(); - // } - $char->delete(); - } + foreach ($chars as $char) { + $char->values()->detach(); + $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')); @@ -145,7 +208,6 @@ class Controller extends ExController ]); } - /** * @param Category $category * @return \Illuminate\Http\RedirectResponse diff --git a/app/Http/Controllers/Dashboard/Product/Controller.php b/app/Http/Controllers/Dashboard/Product/Controller.php index 5104a8d..3dcb6e8 100755 --- a/app/Http/Controllers/Dashboard/Product/Controller.php +++ b/app/Http/Controllers/Dashboard/Product/Controller.php @@ -82,17 +82,7 @@ class Controller extends ExController { if ($request->isMethod('get')) { $this->authorize('create', 'products'); - $categories = $this->categories->select('id', 'name->ru as category') - ->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(); + $categories = $this->categoryTree(); $brands = $this->brands->get(); $colors = $this->colors->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 * @return array @@ -309,17 +324,7 @@ class Controller extends ExController } - $categories = $this->categories->select('id', 'name->ru as category') - ->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(); + $categories = $this->categoryTree(); $brands = $this->brands->get(); $measurement = Measurement::query()->get(); diff --git a/resources/js/components/Dashboard/Category/Store.vue b/resources/js/components/Dashboard/Category/Store.vue index 1154bce..67e15b1 100755 --- a/resources/js/components/Dashboard/Category/Store.vue +++ b/resources/js/components/Dashboard/Category/Store.vue @@ -793,7 +793,16 @@ export default { .catch((error) => { if (error.response) { 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", + ], + ]; } }); }, diff --git a/resources/js/components/Dashboard/Category/Update.vue b/resources/js/components/Dashboard/Category/Update.vue index 946a5e2..1ded507 100755 --- a/resources/js/components/Dashboard/Category/Update.vue +++ b/resources/js/components/Dashboard/Category/Update.vue @@ -804,7 +804,16 @@ export default { .catch((error) => { if (error.response) { 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", + ], + ]; } }); }, diff --git a/resources/js/components/ProductEdit.vue b/resources/js/components/ProductEdit.vue index 75d0d06..4607234 100755 --- a/resources/js/components/ProductEdit.vue +++ b/resources/js/components/ProductEdit.vue @@ -63,51 +63,28 @@ -
-
-
- -
-
- - -
@@ -1074,6 +1051,22 @@ export default { 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 () { @@ -1106,6 +1099,7 @@ export default { three: {}, two_view: false, three_view: false, + path: [], }, 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: { getPosterPreview() { if (this.products.poster instanceof File) { @@ -1145,35 +1155,58 @@ export default { }, setCategory() { if (this.products.categories[0]) { - if (this.products.categories[0].parent) { - if (this.products.categories[0].parent.parent) { - this.category.two_view = true; - this.category.three_view = true; - 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.products.category_id = this.products.categories[0].id; + this.category.path = this.findCategoryPath( + this.categories, + this.products.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() { this.category.two = { parents: [], diff --git a/resources/js/components/ProductStore.vue b/resources/js/components/ProductStore.vue index deed6db..239fa95 100755 --- a/resources/js/components/ProductStore.vue +++ b/resources/js/components/ProductStore.vue @@ -59,51 +59,28 @@ -
-
-
- -
-
- - -
@@ -1136,6 +1113,7 @@ export default { three: {}, two_view: false, three_view: false, + path: [], }, characteristic: false, @@ -1149,6 +1127,20 @@ export default { uploadDisabled() { 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: { @@ -1181,6 +1173,22 @@ export default { 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: { @@ -1312,11 +1320,37 @@ export default { .catch((error) => { if (error.response) { 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 () { if (this.product.short_body.ru.length > 300) { this.short_limit.ru = "Превышен лимит в 300 символов.";