feat: enhance transaction management by adding cashier role checks and updating price type logic
This commit is contained in:
parent
85ba5631b3
commit
731c8aa6da
@ -5,6 +5,7 @@
|
||||
use App\Enums\OrderChannel;
|
||||
use App\Enums\PaymentType;
|
||||
use App\Enums\PriceType;
|
||||
use App\Enums\Role;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Admin\Manage\TransactionRequest;
|
||||
use App\Http\Requests\PaginatedRequest;
|
||||
@ -45,13 +46,20 @@ public function index(PaginatedRequest $request): Response
|
||||
|
||||
public function create(): Response
|
||||
{
|
||||
$user = auth()->user();
|
||||
$isCashier = $user->hasRole(Role::CASHIER);
|
||||
|
||||
return Inertia::render('admin/manage/transaction/create', [
|
||||
'products' => $this->productVariantService->getForTransaction(),
|
||||
'customers' => $this->customerService->getAll(),
|
||||
'employees' => $this->getEmployees(),
|
||||
'channelOptions' => OrderChannel::toSelect(),
|
||||
'paymentTypeOptions' => PaymentType::toSelect(),
|
||||
'priceTypeOptions' => PriceType::toSelect()->filter(fn ($p) => ! in_array($p['value'], [PriceType::CAPITAL->value, PriceType::REJECT_CAPITAL->value]))->values(),
|
||||
'priceTypeOptions' => PriceType::toSelect()
|
||||
->filter(fn ($p) => ! in_array($p['value'], [PriceType::CAPITAL->value, PriceType::REJECT_CAPITAL->value]))
|
||||
->when($isCashier, fn ($q) => $q->filter(fn ($p) => in_array($p['value'], [PriceType::RETAIL->value, PriceType::REJECT_SELLING->value])))
|
||||
->when(! $isCashier, fn ($q) => $q->filter(fn ($p) => $p['value'] !== PriceType::RETAIL->value))
|
||||
->values(),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -67,6 +75,9 @@ public function store(TransactionRequest $request): RedirectResponse
|
||||
|
||||
public function edit(Order $transaction): Response
|
||||
{
|
||||
$user = auth()->user();
|
||||
$isCashier = $user->hasRole(Role::CASHIER);
|
||||
|
||||
return Inertia::render('admin/manage/transaction/edit', [
|
||||
'transaction' => $this->service->getForEdit($transaction),
|
||||
'products' => $this->productVariantService->getForTransaction(),
|
||||
@ -74,7 +85,11 @@ public function edit(Order $transaction): Response
|
||||
'employees' => $this->getEmployees(),
|
||||
'channelOptions' => OrderChannel::toSelect(),
|
||||
'paymentTypeOptions' => PaymentType::toSelect(),
|
||||
'priceTypeOptions' => PriceType::toSelect()->filter(fn ($p) => ! in_array($p['value'], [PriceType::CAPITAL->value, PriceType::REJECT_CAPITAL->value]))->values(),
|
||||
'priceTypeOptions' => PriceType::toSelect()
|
||||
->filter(fn ($p) => ! in_array($p['value'], [PriceType::CAPITAL->value, PriceType::REJECT_CAPITAL->value]))
|
||||
->when($isCashier, fn ($q) => $q->filter(fn ($p) => in_array($p['value'], [PriceType::RETAIL->value, PriceType::REJECT_SELLING->value])))
|
||||
->when(! $isCashier, fn ($q) => $q->filter(fn ($p) => $p['value'] !== PriceType::RETAIL->value))
|
||||
->values(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@ -253,7 +253,7 @@ public function update(Order $order, array $data): Order
|
||||
$order = DB::transaction(function () use ($order, $data) {
|
||||
$order->load('orderItems');
|
||||
|
||||
$oldStockType = $order->orderItems->first()?->stock_type?->value ?? ProductStockQuality::GOOD->value;
|
||||
$oldStockType = $order->orderItems->first()?->stock_quality?->value ?? ProductStockQuality::GOOD->value;
|
||||
|
||||
$order->orderItems->each(function (OrderItem $item) use ($oldStockType) {
|
||||
$this->adjustVariantStock($item->product_variant_id, $item->quantity, 1, $oldStockType);
|
||||
@ -328,11 +328,13 @@ public function destroy(Order $order): bool
|
||||
$result = DB::transaction(function () use ($order) {
|
||||
$order->load('orderItems');
|
||||
|
||||
$stockType = $order->orderItems->first()?->stock_type?->value ?? ProductStockQuality::GOOD->value;
|
||||
if (! in_array($order->status, [OrderStatus::CANCELLED, OrderStatus::REFUNDED])) {
|
||||
$stockType = $order->orderItems->first()?->stock_quality?->value ?? ProductStockQuality::GOOD->value;
|
||||
|
||||
$order->orderItems->each(function (OrderItem $item) use ($stockType) {
|
||||
$this->adjustVariantStock($item->product_variant_id, $item->quantity, 1, $stockType);
|
||||
});
|
||||
$order->orderItems->each(function (OrderItem $item) use ($stockType) {
|
||||
$this->adjustVariantStock($item->product_variant_id, $item->quantity, 1, $stockType);
|
||||
});
|
||||
}
|
||||
|
||||
$order->orderItems()->delete();
|
||||
$order->delete();
|
||||
@ -352,8 +354,19 @@ public function destroy(Order $order): bool
|
||||
|
||||
public function updateStatus(Order $order, string $status): Order
|
||||
{
|
||||
$oldStatus = $order->status->value;
|
||||
|
||||
$order->update(['status' => $status]);
|
||||
|
||||
if (in_array($status, [OrderStatus::CANCELLED->value, OrderStatus::REFUNDED->value]) && ! in_array($oldStatus, [OrderStatus::CANCELLED->value, OrderStatus::REFUNDED->value])) {
|
||||
$order->load('orderItems');
|
||||
$stockType = $order->orderItems->first()?->stock_quality?->value ?? ProductStockQuality::GOOD->value;
|
||||
|
||||
$order->orderItems->each(function (OrderItem $item) use ($stockType) {
|
||||
$this->adjustVariantStock($item->product_variant_id, $item->quantity, 1, $stockType);
|
||||
});
|
||||
}
|
||||
|
||||
return $order;
|
||||
}
|
||||
|
||||
|
||||
@ -73,7 +73,7 @@ public function getForTransaction(): array
|
||||
$products = Product::query()
|
||||
->select(['id', 'name', 'status'])
|
||||
->with([
|
||||
'productVariants:id,product_id,name,stock,reject_stock',
|
||||
'productVariants:id,product_id,name,stock,reject_stock,retail_stock',
|
||||
'productVariants.productPrices:id,variant_id,type,price',
|
||||
])
|
||||
->active()
|
||||
|
||||
@ -13,6 +13,7 @@ trait HasStockAdjustment
|
||||
private const QUALITY_STOCK_MAP = [
|
||||
ProductStockQuality::GOOD->value => 'stock',
|
||||
ProductStockQuality::REJECT->value => 'reject_stock',
|
||||
ProductStockQuality::RETAIL->value => 'retail_stock',
|
||||
];
|
||||
|
||||
private function adjustStock(Model $model, string $field, int $quantity, int $sign): void
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
export type TransactionStockType = 'good' | 'reject';
|
||||
export type TransactionStockType = 'good' | 'reject' | 'retail';
|
||||
|
||||
export type TransactionChannel = 'offline' | 'online' | 'whatsapp' | 'shopee' | 'tiktok';
|
||||
|
||||
export type TransactionPriceType = 'distributor' | 'agent' | 'sub_agent' | 'wholesale' | 'retail' | 'tiktok' | 'shopee';
|
||||
export type TransactionPriceType = 'distributor' | 'agent' | 'sub_agent' | 'wholesale' | 'retail' | 'tiktok' | 'shopee' | 'reject_selling';
|
||||
|
||||
export type TransactionPaymentType = 'cash' | 'transfer' | 'marketplace' | 'qris';
|
||||
|
||||
@ -109,6 +109,7 @@ export type ProductForTransaction = {
|
||||
name: string;
|
||||
stock: number;
|
||||
reject_stock: number;
|
||||
retail_stock: number;
|
||||
photo_url: string | null;
|
||||
prices: Record<string, number>;
|
||||
}[];
|
||||
|
||||
@ -5,11 +5,11 @@ import { ArrowLeft, Minus, Plus, ShoppingCart, Trash2 } from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { ConfirmDialog } from '@/components/dialogs';
|
||||
import { FileUpload } from '@/components/inputs';
|
||||
import { ImagePreviewModal } from '@/components/dialogs';
|
||||
import { InputError } from '@/components/ui';
|
||||
import { FileUpload } from '@/components/inputs';
|
||||
import { NumberInput } from '@/components/inputs';
|
||||
import { RupiahInput } from '@/components/inputs';
|
||||
import { InputError } from '@/components/ui';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import {
|
||||
@ -39,6 +39,7 @@ import {
|
||||
} from '@/components/ui/sheet';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { useCan } from '@/hooks/use-can';
|
||||
import { useTransactionDraftSave } from '@/hooks/use-transaction-draft';
|
||||
import { formatNumber } from '@/lib/format';
|
||||
import { loadTransactionDraft } from '@/lib/transaction-draft';
|
||||
@ -80,6 +81,8 @@ export default function TransactionCreate({
|
||||
}: Props) {
|
||||
const { auth } = usePage().props as { auth: { user?: { id?: number } } };
|
||||
const userId = auth.user?.id;
|
||||
const { hasRole } = useCan();
|
||||
const isCashier = hasRole('cashier');
|
||||
|
||||
const draft = loadTransactionDraft('create', userId);
|
||||
|
||||
@ -87,7 +90,7 @@ export default function TransactionCreate({
|
||||
draft?.stockType === 'reject' ? 'reject' : 'good',
|
||||
);
|
||||
const [channel, setChannel] = useState(draft?.channel ?? 'store');
|
||||
const [priceType, setPriceType] = useState(draft?.priceType ?? 'retail');
|
||||
const [priceType, setPriceType] = useState(draft?.priceType ?? (isCashier ? 'retail' : 'retail'));
|
||||
const [paymentType, setPaymentType] = useState(draft?.paymentType ?? 'cash');
|
||||
const [customerId, setCustomerId] = useState<number | null>(draft?.customerId ?? null);
|
||||
const [marketingId, setMarketingId] = useState<number | null>(draft?.marketingId ?? null);
|
||||
@ -176,16 +179,20 @@ export default function TransactionCreate({
|
||||
} else if (channel === 'shopee') {
|
||||
setPriceType('shopee');
|
||||
setPaymentType('marketplace');
|
||||
} else if (isCashier) {
|
||||
setPriceType('retail');
|
||||
}
|
||||
}, [channel]);
|
||||
}, [channel, isCashier]);
|
||||
|
||||
useEffect(() => {
|
||||
if (stockType === 'reject') {
|
||||
setPriceType('reject_selling');
|
||||
} else if (isCashier) {
|
||||
setPriceType('retail');
|
||||
} else if (priceType === 'reject_selling') {
|
||||
setPriceType('retail');
|
||||
}
|
||||
}, [stockType]);
|
||||
}, [stockType, isCashier]);
|
||||
|
||||
const showPhoto = paymentType === 'transfer' || paymentType === 'qris';
|
||||
|
||||
@ -194,8 +201,12 @@ export default function TransactionCreate({
|
||||
return priceTypeOptions.filter((o) => o.value === 'reject_selling');
|
||||
}
|
||||
|
||||
return priceTypeOptions.filter((o) => SELLING_PRICE_TYPES.includes(o.value));
|
||||
}, [stockType, priceTypeOptions]);
|
||||
if (isCashier) {
|
||||
return priceTypeOptions.filter((o) => o.value === 'retail');
|
||||
}
|
||||
|
||||
return priceTypeOptions.filter((o) => SELLING_PRICE_TYPES.includes(o.value) && o.value !== 'retail');
|
||||
}, [stockType, priceTypeOptions, isCashier]);
|
||||
|
||||
const getUnitPrice = useCallback(
|
||||
(variantId: number) => {
|
||||
@ -209,9 +220,13 @@ export default function TransactionCreate({
|
||||
return variant.prices?.reject_selling ?? 0;
|
||||
}
|
||||
|
||||
if (isCashier) {
|
||||
return variant.prices?.retail ?? 0;
|
||||
}
|
||||
|
||||
return variant.prices?.[priceType] ?? 0;
|
||||
},
|
||||
[variantById, stockType, priceType],
|
||||
[variantById, stockType, priceType, isCashier],
|
||||
);
|
||||
|
||||
const subtotal = Object.entries(quantities).reduce(
|
||||
@ -236,8 +251,10 @@ export default function TransactionCreate({
|
||||
(variantId: number, amount: number) => {
|
||||
if (amount > 0 && getUnitPrice(variantId) <= 0) {
|
||||
toast.error('Harga produk ini belum diatur.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setQuantities((prev) => ({
|
||||
...prev,
|
||||
[variantId]: Math.max(0, (prev[variantId] ?? 0) + amount),
|
||||
@ -282,9 +299,9 @@ export default function TransactionCreate({
|
||||
|
||||
function getPayload() {
|
||||
return {
|
||||
stock_type: stockType,
|
||||
stock_type: stockType === 'good' && isCashier ? 'retail' : stockType,
|
||||
channel,
|
||||
price_type: stockType === 'reject' ? 'reject_selling' : priceType,
|
||||
price_type: stockType === 'reject' ? 'reject_selling' : (isCashier ? 'retail' : priceType),
|
||||
payment_type: paymentType,
|
||||
customer_id: customerId,
|
||||
marketing_id: marketingId,
|
||||
@ -391,9 +408,9 @@ export default function TransactionCreate({
|
||||
{selectedProduct.product_variants.map(
|
||||
(variant) => {
|
||||
const currentStock =
|
||||
stockType === 'good'
|
||||
? variant.stock
|
||||
: variant.reject_stock;
|
||||
stockType === 'reject'
|
||||
? variant.reject_stock
|
||||
: (isCashier ? variant.retail_stock : variant.stock);
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -444,7 +461,7 @@ export default function TransactionCreate({
|
||||
formatCurrency(
|
||||
stockType === 'reject'
|
||||
? (variant.prices?.reject_selling ?? 0)
|
||||
: (variant.prices?.[priceType] ?? 0),
|
||||
: (isCashier ? (variant.prices?.retail ?? 0) : (variant.prices?.[priceType] ?? 0)),
|
||||
)
|
||||
)}
|
||||
</p>
|
||||
@ -629,33 +646,35 @@ export default function TransactionCreate({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Label>Tipe Harga <span className="text-destructive">*</span></Label>
|
||||
<Select
|
||||
value={priceType}
|
||||
onValueChange={setPriceType}
|
||||
disabled={channel === 'tiktok' || channel === 'shopee'}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Pilih tipe harga" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{availablePriceTypes.map(
|
||||
(opt) => (
|
||||
<SelectItem
|
||||
key={opt.value}
|
||||
value={opt.value}
|
||||
>
|
||||
{opt.label}
|
||||
</SelectItem>
|
||||
),
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<InputError
|
||||
message={errors.price_type}
|
||||
/>
|
||||
</div>
|
||||
{!isCashier && (
|
||||
<div className="grid gap-2">
|
||||
<Label>Tipe Harga <span className="text-destructive">*</span></Label>
|
||||
<Select
|
||||
value={priceType}
|
||||
onValueChange={setPriceType}
|
||||
disabled={channel === 'tiktok' || channel === 'shopee'}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Pilih tipe harga" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{availablePriceTypes.map(
|
||||
(opt) => (
|
||||
<SelectItem
|
||||
key={opt.value}
|
||||
value={opt.value}
|
||||
>
|
||||
{opt.label}
|
||||
</SelectItem>
|
||||
),
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<InputError
|
||||
message={errors.price_type}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Label>Tipe Pembayaran <span className="text-destructive">*</span></Label>
|
||||
|
||||
@ -5,11 +5,11 @@ import { ArrowLeft, Minus, Plus, ShoppingCart, Trash2 } from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { ConfirmDialog } from '@/components/dialogs';
|
||||
import { FileUpload } from '@/components/inputs';
|
||||
import { ImagePreviewModal } from '@/components/dialogs';
|
||||
import { InputError } from '@/components/ui';
|
||||
import { FileUpload } from '@/components/inputs';
|
||||
import { NumberInput } from '@/components/inputs';
|
||||
import { RupiahInput } from '@/components/inputs';
|
||||
import { InputError } from '@/components/ui';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import {
|
||||
@ -39,6 +39,7 @@ import {
|
||||
} from '@/components/ui/sheet';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { useCan } from '@/hooks/use-can';
|
||||
import { formatNumber } from '@/lib/format';
|
||||
import { getTemporaryUrl } from '@/lib/upload';
|
||||
import { formatCurrency } from '@/lib/utils';
|
||||
@ -79,6 +80,9 @@ export default function TransactionEdit({
|
||||
priceTypeOptions,
|
||||
}: Props) {
|
||||
|
||||
const { hasRole } = useCan();
|
||||
const isCashier = hasRole('cashier');
|
||||
|
||||
const [stockType, setStockType] = useState<'good' | 'reject'>(
|
||||
transaction.stock_type === 'reject' ? 'reject' : 'good',
|
||||
);
|
||||
@ -129,16 +133,20 @@ export default function TransactionEdit({
|
||||
} else if (channel === 'shopee') {
|
||||
setPriceType('shopee');
|
||||
setPaymentType('marketplace');
|
||||
} else if (isCashier) {
|
||||
setPriceType('retail');
|
||||
}
|
||||
}, [channel]);
|
||||
}, [channel, isCashier]);
|
||||
|
||||
useEffect(() => {
|
||||
if (stockType === 'reject') {
|
||||
setPriceType('reject_selling');
|
||||
} else if (isCashier) {
|
||||
setPriceType('retail');
|
||||
} else if (priceType === 'reject_selling') {
|
||||
setPriceType('retail');
|
||||
}
|
||||
}, [stockType]);
|
||||
}, [stockType, isCashier]);
|
||||
|
||||
const [tiktokOrderId, setTiktokOrderId] = useState(transaction.tiktok_order_id ?? '');
|
||||
const [shopeeOrderId, setShopeeOrderId] = useState(transaction.shopee_order_id ?? '');
|
||||
@ -165,8 +173,12 @@ export default function TransactionEdit({
|
||||
return priceTypeOptions.filter((o) => o.value === 'reject_selling');
|
||||
}
|
||||
|
||||
return priceTypeOptions.filter((o) => SELLING_PRICE_TYPES.includes(o.value));
|
||||
}, [stockType, priceTypeOptions]);
|
||||
if (isCashier) {
|
||||
return priceTypeOptions.filter((o) => o.value === 'retail');
|
||||
}
|
||||
|
||||
return priceTypeOptions.filter((o) => SELLING_PRICE_TYPES.includes(o.value) && o.value !== 'retail');
|
||||
}, [stockType, priceTypeOptions, isCashier]);
|
||||
|
||||
const getUnitPrice = useCallback(
|
||||
(variantId: number) => {
|
||||
@ -180,9 +192,13 @@ export default function TransactionEdit({
|
||||
return variant.prices?.reject_selling ?? 0;
|
||||
}
|
||||
|
||||
if (isCashier) {
|
||||
return variant.prices?.retail ?? 0;
|
||||
}
|
||||
|
||||
return variant.prices?.[priceType] ?? 0;
|
||||
},
|
||||
[variantById, stockType, priceType],
|
||||
[variantById, stockType, priceType, isCashier],
|
||||
);
|
||||
|
||||
const subtotal = Object.entries(quantities).reduce(
|
||||
@ -207,8 +223,10 @@ export default function TransactionEdit({
|
||||
(variantId: number, amount: number) => {
|
||||
if (amount > 0 && getUnitPrice(variantId) <= 0) {
|
||||
toast.error('Harga produk ini belum diatur.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setQuantities((prev) => ({
|
||||
...prev,
|
||||
[variantId]: Math.max(0, (prev[variantId] ?? 0) + amount),
|
||||
@ -253,9 +271,9 @@ export default function TransactionEdit({
|
||||
|
||||
function getPayload() {
|
||||
return {
|
||||
stock_type: stockType,
|
||||
stock_type: stockType === 'good' && isCashier ? 'retail' : stockType,
|
||||
channel,
|
||||
price_type: stockType === 'reject' ? 'reject_selling' : priceType,
|
||||
price_type: stockType === 'reject' ? 'reject_selling' : (isCashier ? 'retail' : priceType),
|
||||
payment_type: paymentType,
|
||||
customer_id: customerId,
|
||||
marketing_id: marketingId,
|
||||
@ -371,9 +389,9 @@ export default function TransactionEdit({
|
||||
{selectedProduct.product_variants.map(
|
||||
(variant) => {
|
||||
const currentStock =
|
||||
stockType === 'good'
|
||||
? variant.stock
|
||||
: variant.reject_stock;
|
||||
stockType === 'reject'
|
||||
? variant.reject_stock
|
||||
: (isCashier ? variant.retail_stock : variant.stock);
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -424,7 +442,7 @@ export default function TransactionEdit({
|
||||
formatCurrency(
|
||||
stockType === 'reject'
|
||||
? (variant.prices?.reject_selling ?? 0)
|
||||
: (variant.prices?.[priceType] ?? 0),
|
||||
: (isCashier ? (variant.prices?.retail ?? 0) : (variant.prices?.[priceType] ?? 0)),
|
||||
)
|
||||
)}
|
||||
</p>
|
||||
@ -609,33 +627,35 @@ export default function TransactionEdit({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Label>Tipe Harga <span className="text-destructive">*</span></Label>
|
||||
<Select
|
||||
value={priceType}
|
||||
onValueChange={setPriceType}
|
||||
disabled={channel === 'tiktok' || channel === 'shopee'}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Pilih tipe harga" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{availablePriceTypes.map(
|
||||
(opt) => (
|
||||
<SelectItem
|
||||
key={opt.value}
|
||||
value={opt.value}
|
||||
>
|
||||
{opt.label}
|
||||
</SelectItem>
|
||||
),
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<InputError
|
||||
message={errors.price_type}
|
||||
/>
|
||||
</div>
|
||||
{!isCashier && (
|
||||
<div className="grid gap-2">
|
||||
<Label>Tipe Harga <span className="text-destructive">*</span></Label>
|
||||
<Select
|
||||
value={priceType}
|
||||
onValueChange={setPriceType}
|
||||
disabled={channel === 'tiktok' || channel === 'shopee'}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Pilih tipe harga" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{availablePriceTypes.map(
|
||||
(opt) => (
|
||||
<SelectItem
|
||||
key={opt.value}
|
||||
value={opt.value}
|
||||
>
|
||||
{opt.label}
|
||||
</SelectItem>
|
||||
),
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<InputError
|
||||
message={errors.price_type}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid gap-2">
|
||||
<Label>Tipe Pembayaran <span className="text-destructive">*</span></Label>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user