refactor: remove image handling from Cutting components; simplify CuttingPosForm, CuttingPosSummaryPanel, and related files to enhance user experience and streamline product creation process
This commit is contained in:
parent
e4c36c8759
commit
58e676e2e1
@ -23,7 +23,6 @@ const props = defineProps<{
|
||||
|
||||
const initialData = computed(() => ({
|
||||
description: props.cutting.description ?? '',
|
||||
images: props.cutting.images ?? [],
|
||||
sewing_cost: String(props.cutting.sewing_cost ?? 0),
|
||||
other_cost: String(props.cutting.other_cost ?? 0),
|
||||
materials: props.cutting.materials.map((item) => ({
|
||||
|
||||
@ -143,15 +143,7 @@ const groupedResults = computed<GroupedResults[]>(() => {
|
||||
<p v-if="cutting.description" class="mt-3 text-sm">
|
||||
{{ cutting.description }}
|
||||
</p>
|
||||
<div
|
||||
v-if="cutting.images && cutting.images.length > 0"
|
||||
class="mt-3 flex flex-wrap gap-2"
|
||||
>
|
||||
<MediaThumbnailCell
|
||||
:items="cutting.images"
|
||||
:max-visible="10"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Results Section -->
|
||||
|
||||
@ -9,12 +9,7 @@ import type {
|
||||
CuttingRawMaterialCatalogItem,
|
||||
CuttingResultCartItem,
|
||||
} from '@/types/cutting';
|
||||
import {
|
||||
appendRootPhotosToFormData,
|
||||
createMediaUploadState,
|
||||
} from '@/types/media';
|
||||
import type { MediaItem } from '@/types/media';
|
||||
import type { MediaUploadState } from '@/types/media';
|
||||
import type { CategoryOption } from '@/types/product';
|
||||
import type { EnumOption } from '@/types/raw-material';
|
||||
import CuttingPosCartDetailDialog from './CuttingPosCartDetailDialog.vue';
|
||||
@ -46,8 +41,7 @@ const props = defineProps<{
|
||||
const isCreateMode = computed(() => props.method === 'post');
|
||||
const cartDetailOpen = ref(false);
|
||||
|
||||
const imageState = ref<MediaUploadState>(createMediaUploadState());
|
||||
const isUploading = computed(() => imageState.value.pendingUploads > 0);
|
||||
// No image upload state needed
|
||||
|
||||
// Make catalogs mutable so we can add new items from quick-create dialogs
|
||||
const rawMaterialCatalogState = ref<CuttingRawMaterialCatalogItem[]>([
|
||||
@ -109,15 +103,12 @@ const {
|
||||
|
||||
function populateForm() {
|
||||
if (!props.initialData) {
|
||||
imageState.value = createMediaUploadState();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
form.description = props.initialData.description;
|
||||
form.sewing_cost = props.initialData.sewing_cost ?? '0';
|
||||
form.other_cost = props.initialData.other_cost ?? '0';
|
||||
imageState.value = createMediaUploadState(props.initialData.images ?? []);
|
||||
setCarts(props.initialData.materials, props.initialData.results);
|
||||
}
|
||||
|
||||
@ -141,7 +132,7 @@ function buildFormData(): FormData {
|
||||
String(Number.parseInt(parseRupiah(form.other_cost), 10) || 0),
|
||||
);
|
||||
|
||||
appendRootPhotosToFormData(formData, imageState.value);
|
||||
// No images to append
|
||||
|
||||
if (props.method === 'put') {
|
||||
// Build combination results map
|
||||
@ -286,9 +277,9 @@ function onProductCreated(product: CuttingProductCatalogItem) {
|
||||
:categories="categories" :material-cart="materialCart" @decrease-result-qty="decreaseResultQty" @product-created="onProductCreated" />
|
||||
</div>
|
||||
|
||||
<CuttingPosSummaryPanel v-model:image-state="imageState" :form="form" :material-cart="materialCart"
|
||||
<CuttingPosSummaryPanel :form="form" :material-cart="materialCart"
|
||||
:result-cart="resultCart" :total-result-pieces="totalResultPieces" :submit-label="submitLabel"
|
||||
@submit="submit" :is-create-mode="isCreateMode" :is-uploading="isUploading"
|
||||
@submit="submit" :is-create-mode="isCreateMode"
|
||||
@open-detail="cartDetailOpen = true" @remove-material="removeMaterial"
|
||||
@sync-material-field="syncMaterialField" @remove-result="removeResult"
|
||||
@sync-result-totals="syncResultTotals" @sync-result-field="syncResultField"
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { Save, Scissors } from '@lucide/vue';
|
||||
import MediaDropzone from '@/components/media/MediaDropzone.vue';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
@ -20,7 +19,6 @@ import type {
|
||||
CuttingMaterialCartItem,
|
||||
CuttingResultCartItem,
|
||||
} from '@/types/cutting';
|
||||
import type { MediaUploadState } from '@/types/media';
|
||||
import CuttingPosMaterialSummaryItems from './CuttingPosMaterialSummaryItems.vue';
|
||||
import CuttingPosResultSummaryItems from './CuttingPosResultSummaryItems.vue';
|
||||
|
||||
@ -31,7 +29,6 @@ defineProps<{
|
||||
totalResultPieces: number;
|
||||
submitLabel: string;
|
||||
isCreateMode: boolean;
|
||||
isUploading?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
@ -45,10 +42,6 @@ const emit = defineEmits<{
|
||||
'remove-combination': [combinationId: number];
|
||||
'sync-combination-result': [combinationId: number, result: number | null];
|
||||
}>();
|
||||
|
||||
const imageState = defineModel<MediaUploadState>('imageState', {
|
||||
required: true,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -83,9 +76,6 @@ const imageState = defineModel<MediaUploadState>('imageState', {
|
||||
<FieldError :errors="formErrors(form, 'description')" />
|
||||
</Field>
|
||||
|
||||
<MediaDropzone id="cutting-images" v-model="imageState" label="Foto Cutting" :max-files="10"
|
||||
:errors="formErrors(form, 'images')" />
|
||||
|
||||
<CuttingPosMaterialSummaryItems :form="form" :material-cart="materialCart"
|
||||
@remove="emit('remove-material', $event)" @sync-field="emit('sync-material-field', $event)"
|
||||
@remove-combination="emit('remove-combination', $event)"
|
||||
@ -99,18 +89,11 @@ const imageState = defineModel<MediaUploadState>('imageState', {
|
||||
@sync-field="emit('sync-result-field', $event)" />
|
||||
|
||||
<Button type="submit" class="w-full" :disabled="form.processing ||
|
||||
isUploading ||
|
||||
materialCart.length === 0 ||
|
||||
resultCart.length === 0
|
||||
">
|
||||
<Save class="size-4" />
|
||||
{{
|
||||
isUploading
|
||||
? 'Mengunggah...'
|
||||
: form.processing
|
||||
? 'Menyimpan...'
|
||||
: submitLabel
|
||||
}}
|
||||
{{ form.processing ? 'Menyimpan...' : submitLabel }}
|
||||
</Button>
|
||||
</FieldSet>
|
||||
</FieldGroup>
|
||||
|
||||
@ -182,11 +182,7 @@ function getGroupedResults(results: any[]): GroupedCuttingResults[] {
|
||||
<p v-if="cutting.description" class="text-sm text-muted-foreground">
|
||||
{{ cutting.description }}
|
||||
</p>
|
||||
<div v-if="
|
||||
cutting.images && cutting.images.length > 0
|
||||
" class="flex flex-wrap gap-2 pt-1">
|
||||
<MediaThumbnailCell :items="cutting.images" :max-visible="10" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-x-4 gap-y-1 text-sm">
|
||||
<span>Total Hasil Cutting
|
||||
<strong class="text-primary">{{
|
||||
|
||||
@ -687,6 +687,119 @@ function setupDraftItems(User $user): array
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Quick Create Product ─────────────────────────────────
|
||||
|
||||
describe('Quick Create Product', function () {
|
||||
test('authenticated user with permission can quick create product without prices', function () {
|
||||
$user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_CREATE);
|
||||
|
||||
$this->actingAs($user)
|
||||
->postJson('/admin/manage/cuttings/quick-create-product', [
|
||||
'name' => 'Test Product',
|
||||
'description' => 'Test Description',
|
||||
'variants' => [
|
||||
[
|
||||
'name' => 'Varian 1',
|
||||
'stock' => 10,
|
||||
'retail_stock' => 5,
|
||||
],
|
||||
],
|
||||
])
|
||||
->assertOk()
|
||||
->assertJsonStructure(['product' => ['id', 'name']]);
|
||||
|
||||
$this->assertDatabaseHas('products', ['name' => 'Test Product']);
|
||||
$this->assertDatabaseHas('product_variants', ['name' => 'Varian 1', 'stock' => 10]);
|
||||
});
|
||||
|
||||
test('quick create product without images is allowed', function () {
|
||||
$user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_CREATE);
|
||||
|
||||
$this->actingAs($user)
|
||||
->postJson('/admin/manage/cuttings/quick-create-product', [
|
||||
'name' => 'Product No Image',
|
||||
'variants' => [
|
||||
[
|
||||
'name' => 'Varian 1',
|
||||
'stock' => 5,
|
||||
'retail_stock' => 2,
|
||||
],
|
||||
],
|
||||
])
|
||||
->assertOk();
|
||||
|
||||
$this->assertDatabaseHas('products', ['name' => 'Product No Image']);
|
||||
});
|
||||
|
||||
test('quick create product requires name', function () {
|
||||
$user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_CREATE);
|
||||
|
||||
$this->actingAs($user)
|
||||
->postJson('/admin/manage/cuttings/quick-create-product', [
|
||||
'variants' => [
|
||||
[
|
||||
'name' => 'Varian 1',
|
||||
'stock' => 5,
|
||||
'retail_stock' => 2,
|
||||
],
|
||||
],
|
||||
])
|
||||
->assertUnprocessable();
|
||||
});
|
||||
|
||||
test('quick create product requires at least one variant', function () {
|
||||
$user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_CREATE);
|
||||
|
||||
$this->actingAs($user)
|
||||
->postJson('/admin/manage/cuttings/quick-create-product', [
|
||||
'name' => 'Test Product',
|
||||
'variants' => [],
|
||||
])
|
||||
->assertUnprocessable();
|
||||
});
|
||||
|
||||
test('quick create product with categories', function () {
|
||||
$user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_CREATE);
|
||||
|
||||
$category = \App\Models\Category::factory()->create();
|
||||
|
||||
$this->actingAs($user)
|
||||
->postJson('/admin/manage/cuttings/quick-create-product', [
|
||||
'name' => 'Product With Category',
|
||||
'category_ids' => [$category->id],
|
||||
'variants' => [
|
||||
[
|
||||
'name' => 'Varian 1',
|
||||
'stock' => 5,
|
||||
'retail_stock' => 2,
|
||||
],
|
||||
],
|
||||
])
|
||||
->assertOk();
|
||||
|
||||
$product = \App\Models\Product::where('name', 'Product With Category')->first();
|
||||
expect($product)->not->toBeNull();
|
||||
expect($product->categories)->toHaveCount(1);
|
||||
});
|
||||
|
||||
test('user without permission cannot quick create product', function () {
|
||||
$user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW);
|
||||
|
||||
$this->actingAs($user)
|
||||
->postJson('/admin/manage/cuttings/quick-create-product', [
|
||||
'name' => 'Test Product',
|
||||
'variants' => [
|
||||
[
|
||||
'name' => 'Varian 1',
|
||||
'stock' => 5,
|
||||
'retail_stock' => 2,
|
||||
],
|
||||
],
|
||||
])
|
||||
->assertForbidden();
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Store with Combination ─────────────────────────────────
|
||||
|
||||
describe('Cutting Store with Combination', function () {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user