923 lines
24 KiB
Vue
923 lines
24 KiB
Vue
<script setup>
|
|
import { usePageTitle } from '../composables/Core/usePageTitle';
|
|
usePageTitle('Assign Product To Store');
|
|
|
|
import { ref, computed, onMounted, watch } from 'vue'
|
|
import axios from 'axios'
|
|
import { useNavigate } from '../composables/Core/useNavigate'
|
|
import { useModal } from '../composables/Core/useModal'
|
|
import LoadingSpinner from '../Components/LoadingSpinner.vue'
|
|
import CardSimple from '../Components/Core/CardSimple.vue'
|
|
|
|
const props = defineProps({
|
|
target: { type: String, default: null },
|
|
store_hash: { type: String, default: null },
|
|
payload: { type: Object, default: null },
|
|
user: { type: Object, default: null },
|
|
})
|
|
|
|
const { navigate } = useNavigate()
|
|
const modal = useModal()
|
|
|
|
// Form state
|
|
const productHash = ref(null)
|
|
const selectedStoreHash = ref('')
|
|
const productData = ref({})
|
|
const customPrice = ref(0)
|
|
const customStock = ref(0)
|
|
|
|
// Reset custom fields when product data loaded
|
|
watch(productData, (newData) => {
|
|
if (newData) {
|
|
customPrice.value = newData.price || 0
|
|
customStock.value = newData.available || 0
|
|
}
|
|
})
|
|
|
|
// Data
|
|
const storeList = ref([])
|
|
const isAdmin = ref(false)
|
|
|
|
// Loading state
|
|
const isLoading = ref(false)
|
|
const isSubmitting = ref(false)
|
|
const storesLoading = ref(false)
|
|
const successMessage = ref('')
|
|
const errorMessage = ref('')
|
|
|
|
// Computed
|
|
const currentUserType = computed(() => {
|
|
return props.user?.acct_type?.value || props.user?.acct_type || ''
|
|
})
|
|
|
|
const isUltimate = computed(() => {
|
|
return currentUserType.value === 'ult'
|
|
})
|
|
|
|
const selectedStore = computed(() => {
|
|
return storeList.value.find(s => s.hashkey === selectedStoreHash.value)
|
|
})
|
|
|
|
const isButtonDisabled = computed(() => {
|
|
return !!(isSubmitting.value || successMessage.value || !selectedStoreHash.value || !productHash.value)
|
|
})
|
|
|
|
// Initialize
|
|
onMounted(() => {
|
|
document.title = 'Assign Product to Store'
|
|
|
|
// Get product hash from props (passed via URL) or from query params
|
|
const urlParams = new URLSearchParams(window.location.search)
|
|
productHash.value = props.payload?.product_hashkey || props.payload?.product_hash || props.target || urlParams.get('target') || urlParams.get('product_id') || urlParams.get('id')
|
|
|
|
// Set store hash if provided
|
|
if (props.payload?.store_hashkey || props.payload?.store_hash || props.store_hash) {
|
|
selectedStoreHash.value = props.payload?.store_hashkey || props.payload?.store_hash || props.store_hash
|
|
}
|
|
|
|
if (!productHash.value) {
|
|
errorMessage.value = 'No product specified. Please select a product first.'
|
|
return
|
|
}
|
|
|
|
loadStores()
|
|
loadProductData()
|
|
})
|
|
|
|
// Load stores for current user (filtered by ownership/management)
|
|
const loadStores = async () => {
|
|
storesLoading.value = true
|
|
try {
|
|
const response = await axios.post('/ListStores/MyStores/data')
|
|
if (response.data && Array.isArray(response.data)) {
|
|
storeList.value = response.data
|
|
isAdmin.value = isUltimate.value || props.user?.acct_type === 'super operator' || props.user?.acct_type === 'operator'
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading stores:', error)
|
|
errorMessage.value = 'Failed to load your stores. Please try again.'
|
|
} finally {
|
|
storesLoading.value = false
|
|
}
|
|
}
|
|
|
|
// Load product data
|
|
const loadProductData = async () => {
|
|
isLoading.value = true
|
|
try {
|
|
const response = await axios.post('/View/Product/Details/data', {
|
|
target: productHash.value,
|
|
})
|
|
|
|
if (response.data && response.data.success && response.data.data) {
|
|
productData.value = response.data.data
|
|
} else {
|
|
errorMessage.value = 'Product not found or data unavailable.'
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading product data:', error)
|
|
errorMessage.value = 'Failed to load product details.'
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
// Get role badge
|
|
const getRoleBadge = (role) => {
|
|
switch (role) {
|
|
case 'owner': return { text: 'Owner', class: 'badge-owner' }
|
|
case 'manager': return { text: 'Manager', class: 'badge-manager' }
|
|
case 'admin': return { text: 'Admin', class: 'badge-admin' }
|
|
default: return { text: role, class: 'badge-default' }
|
|
}
|
|
}
|
|
|
|
// Show confirmation modal
|
|
const showConfirmation = () => {
|
|
if (!selectedStoreHash.value) {
|
|
modal.open({ title: 'Missing Selection', body: 'Please select a store to assign this product to.', footer: null })
|
|
return
|
|
}
|
|
|
|
const storeName = selectedStore.value?.name || 'Selected Store'
|
|
const productName = productData.value?.name || 'This product'
|
|
|
|
modal.yesNoModal({
|
|
title: 'Assign Product?',
|
|
body: `Are you sure you want to assign <strong>${productName}</strong> to <strong>${storeName}</strong>?`,
|
|
onYes: submitAssignment,
|
|
yesText: 'Assign',
|
|
noText: 'Cancel'
|
|
})
|
|
}
|
|
|
|
// Submit assignment
|
|
const submitAssignment = async () => {
|
|
isSubmitting.value = true
|
|
errorMessage.value = ''
|
|
successMessage.value = ''
|
|
|
|
try {
|
|
const response = await axios.post('/Products/AssignToStore/', {
|
|
TargetStore: selectedStoreHash.value,
|
|
target: productHash.value,
|
|
price: customPrice.value,
|
|
available: customStock.value,
|
|
})
|
|
|
|
if (response.data && response.data.success) {
|
|
successMessage.value = 'Product assigned to store successfully!'
|
|
|
|
// Navigate to the store view after a short delay
|
|
setTimeout(() => {
|
|
navigate({
|
|
page: 'ViewStoreMarket',
|
|
props: { target: selectedStoreHash.value }
|
|
})
|
|
}, 1800)
|
|
} else {
|
|
errorMessage.value = response.data?.message || 'Failed to assign product to store.'
|
|
}
|
|
} catch (error) {
|
|
console.error('Error assigning product:', error)
|
|
errorMessage.value = error.response?.data?.message || 'Failed to assign product. Please try again.'
|
|
} finally {
|
|
isSubmitting.value = false
|
|
}
|
|
}
|
|
|
|
// Cancel and go back
|
|
const goBack = () => {
|
|
navigate({ page: 'ListProductsMarket' })
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="assign-product-page pb-5">
|
|
<!-- Header -->
|
|
<div class="tf-container mt-5 mb-4 text-center">
|
|
<div class="page-icon-wrapper">
|
|
<i class="fas fa-store"></i>
|
|
<i class="fas fa-plus icon-overlay"></i>
|
|
</div>
|
|
<h1 class="fw_8 premium-title">Assign Product to Store</h1>
|
|
<p class="text-muted subtitle">Link a product to one of your stores for marketplace visibility</p>
|
|
</div>
|
|
|
|
<!-- Alerts -->
|
|
<div v-if="successMessage" class="tf-container mb-4">
|
|
<div class="glass-alert alert-success animate-fade-in">
|
|
<i class="fas fa-check-circle me-2"></i>
|
|
{{ successMessage }}
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="errorMessage" class="tf-container mb-4">
|
|
<div class="glass-alert alert-danger animate-shake">
|
|
<i class="fas fa-exclamation-triangle me-2"></i>
|
|
{{ errorMessage }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Loading State -->
|
|
<div v-if="isLoading" class="tf-container text-center py-5">
|
|
<LoadingSpinner size="large" />
|
|
<p class="text-muted mt-3">Loading product details...</p>
|
|
</div>
|
|
|
|
<!-- Main Form -->
|
|
<div v-else class="tf-container">
|
|
<div class="form-grid">
|
|
<!-- Left: Store Selection -->
|
|
<div class="form-section">
|
|
<CardSimple title="Select Store">
|
|
<div class="premium-input-group mb-4">
|
|
<label for="targetStore" class="form-label">
|
|
<i class="fas fa-store me-2"></i>Target Store <span class="required">*</span>
|
|
</label>
|
|
|
|
<div v-if="storesLoading" class="store-loading">
|
|
<LoadingSpinner size="small" />
|
|
<span class="ms-2 text-muted">Loading stores...</span>
|
|
</div>
|
|
|
|
<select
|
|
v-else
|
|
id="targetStore"
|
|
v-model="selectedStoreHash"
|
|
class="premium-select"
|
|
:disabled="storeList.length === 0"
|
|
>
|
|
<option value="" disabled>
|
|
{{ storeList.length === 0 ? 'No stores available' : 'Choose a store...' }}
|
|
</option>
|
|
<option v-for="store in storeList" :key="store.hashkey" :value="store.hashkey">
|
|
{{ store.name }} {{ store.category ? `(${store.category})` : '' }}
|
|
</option>
|
|
</select>
|
|
|
|
<p v-if="storeList.length === 0 && !storesLoading" class="input-hint text-warning mt-2">
|
|
<i class="fas fa-info-circle me-1"></i>
|
|
You don't own or manage any stores yet.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Store role indicator -->
|
|
<div v-if="selectedStore" class="selected-store-info animate-fade-in">
|
|
<div class="store-info-card">
|
|
<div class="store-info-header">
|
|
<i class="fas fa-store-alt"></i>
|
|
<span>{{ selectedStore.name }}</span>
|
|
</div>
|
|
<div class="store-info-details">
|
|
<span :class="['role-badge', getRoleBadge(selectedStore.role).class]">
|
|
<i class="fas fa-shield-alt me-1"></i>
|
|
{{ getRoleBadge(selectedStore.role).text }}
|
|
</span>
|
|
<span v-if="selectedStore.category" class="category-tag">
|
|
{{ selectedStore.category }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Access Level Info -->
|
|
<div class="access-info mt-4">
|
|
<div class="access-info-header">
|
|
<i class="fas fa-lock me-2"></i>
|
|
<span class="fw_6">Your Access Level</span>
|
|
</div>
|
|
<div class="access-info-body">
|
|
<div class="access-item" :class="{ 'active': isUltimate || isAdmin }">
|
|
<i class="fas fa-crown"></i>
|
|
<span>Admin Access</span>
|
|
<i v-if="isUltimate || isAdmin" class="fas fa-check-circle text-success ms-auto"></i>
|
|
</div>
|
|
<div class="access-item" :class="{ 'active': !isAdmin }">
|
|
<i class="fas fa-user-shield"></i>
|
|
<span>Owner/Manager Only</span>
|
|
<i v-if="!isAdmin && !isUltimate" class="fas fa-check-circle text-success ms-auto"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</CardSimple>
|
|
</div>
|
|
|
|
<!-- Right: Product Preview -->
|
|
<div class="form-section">
|
|
<CardSimple title="Product Details">
|
|
<!-- Product Photo -->
|
|
<div v-if="productData.photourl && productData.photourl.length > 0" class="product-photo-preview mb-4">
|
|
<img
|
|
:src="'/RequestData/File/' + productData.photourl[0]"
|
|
alt="Product Photo"
|
|
class="product-photo"
|
|
@error="$event.target.style.display = 'none'"
|
|
>
|
|
</div>
|
|
|
|
<div class="premium-input-group mb-3">
|
|
<label class="form-label">
|
|
<i class="fas fa-tag me-2"></i>Product Name
|
|
</label>
|
|
<input
|
|
type="text"
|
|
class="premium-input"
|
|
:value="productData.name || '—'"
|
|
disabled
|
|
>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-6">
|
|
<div class="premium-input-group mb-3">
|
|
<label class="form-label">
|
|
<i class="fas fa-peso-sign me-2"></i>Price
|
|
</label>
|
|
<input
|
|
type="text"
|
|
class="premium-input"
|
|
:value="productData.price ? `₱${productData.price}` : '—'"
|
|
disabled
|
|
>
|
|
</div>
|
|
</div>
|
|
<div class="col-6">
|
|
<div class="premium-input-group mb-3">
|
|
<label class="form-label">
|
|
<i class="fas fa-weight-hanging me-2"></i>Unit
|
|
</label>
|
|
<input
|
|
type="text"
|
|
class="premium-input"
|
|
:value="productData.unitname || '—'"
|
|
disabled
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-6">
|
|
<div class="premium-input-group mb-3">
|
|
<label class="form-label">
|
|
<i class="fas fa-layer-group me-2"></i>Category
|
|
</label>
|
|
<input
|
|
type="text"
|
|
class="premium-input"
|
|
:value="productData.category || '—'"
|
|
disabled
|
|
>
|
|
</div>
|
|
</div>
|
|
<div class="col-6">
|
|
<div class="premium-input-group mb-3">
|
|
<label class="form-label">
|
|
<i class="fas fa-barcode me-2"></i>Barcode
|
|
</label>
|
|
<input
|
|
type="text"
|
|
class="premium-input"
|
|
:value="productData.barcode || '—'"
|
|
disabled
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="productData.description" class="premium-input-group mb-3">
|
|
<label class="form-label">
|
|
<i class="fas fa-align-left me-2"></i>Description
|
|
</label>
|
|
<div class="description-preview">
|
|
{{ productData.description }}
|
|
</div>
|
|
</div>
|
|
|
|
<hr class="my-4 opacity-25">
|
|
|
|
<div class="pivot-custom-fields animate-fade-in">
|
|
<h5 class="fw_7 mb-3 text-primary">
|
|
<i class="fas fa-edit me-2"></i>Store Specific Settings
|
|
</h5>
|
|
|
|
<div class="row">
|
|
<div class="col-6">
|
|
<div class="premium-input-group mb-3">
|
|
<label for="customPrice" class="form-label">
|
|
<i class="fas fa-coins me-2"></i>Custom Price
|
|
</label>
|
|
<div class="input-with-icon">
|
|
<span class="prefix">₱</span>
|
|
<input
|
|
id="customPrice"
|
|
v-model.number="customPrice"
|
|
type="number"
|
|
step="0.01"
|
|
class="premium-input ps-5"
|
|
placeholder="0.00"
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-6">
|
|
<div class="premium-input-group mb-3">
|
|
<label for="customStock" class="form-label">
|
|
<i class="fas fa-cubes me-2"></i>Initial Stock
|
|
</label>
|
|
<input
|
|
id="customStock"
|
|
v-model.number="customStock"
|
|
type="number"
|
|
class="premium-input"
|
|
placeholder="0"
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p class="text-muted small mt-1">
|
|
<i class="fas fa-info-circle me-1"></i> These values will only apply to this store.
|
|
</p>
|
|
</div>
|
|
</CardSimple>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Action Buttons -->
|
|
<div class="action-bar mt-5 text-center">
|
|
<button
|
|
id="assign-product-btn"
|
|
@click="showConfirmation"
|
|
:disabled="isButtonDisabled"
|
|
class="btn-premium-assign"
|
|
:class="{ 'btn-loading': isSubmitting }"
|
|
>
|
|
<span v-if="!isSubmitting">
|
|
<i class="fas fa-link me-2"></i>Assign Product to Store
|
|
</span>
|
|
<LoadingSpinner v-else size="small" color="white" />
|
|
</button>
|
|
|
|
<div class="mt-4">
|
|
<button
|
|
@click="goBack"
|
|
class="btn-text"
|
|
>
|
|
<i class="fas fa-chevron-left me-2"></i>Cancel and Return
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.premium-title {
|
|
font-family: 'Outfit', sans-serif;
|
|
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
letter-spacing: -0.02em;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.subtitle {
|
|
font-size: 0.95rem;
|
|
max-width: 460px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.page-icon-wrapper {
|
|
position: relative;
|
|
display: inline-block;
|
|
font-size: 2.5rem;
|
|
color: #3b82f6;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.icon-overlay {
|
|
position: absolute;
|
|
font-size: 0.9rem;
|
|
background: #22c55e;
|
|
color: white;
|
|
border-radius: 50%;
|
|
width: 22px;
|
|
height: 22px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
bottom: 0;
|
|
right: -8px;
|
|
box-shadow: 0 2px 6px rgba(34, 197, 94, 0.4);
|
|
}
|
|
|
|
.form-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 24px;
|
|
}
|
|
|
|
@media (max-width: 992px) {
|
|
.form-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
.premium-input-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.form-label {
|
|
font-weight: 600;
|
|
font-size: 0.85rem;
|
|
color: #475569;
|
|
margin-bottom: 8px;
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.required {
|
|
color: #ef4444;
|
|
margin-left: 4px;
|
|
}
|
|
|
|
.premium-input, .premium-select {
|
|
padding: 12px 16px;
|
|
border-radius: 12px;
|
|
border: 1px solid #e2e8f0;
|
|
background: #fff;
|
|
font-size: 0.95rem;
|
|
transition: all 0.2s;
|
|
outline: none;
|
|
}
|
|
|
|
.premium-input:focus, .premium-select:focus {
|
|
border-color: #3b82f6;
|
|
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1);
|
|
}
|
|
|
|
.premium-input:disabled {
|
|
background: #f8fafc;
|
|
color: #64748b;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.premium-select {
|
|
appearance: none;
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%2364748b'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E");
|
|
background-repeat: no-repeat;
|
|
background-position: right 12px center;
|
|
background-size: 16px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.premium-select:disabled {
|
|
cursor: not-allowed;
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.input-hint {
|
|
font-size: 0.8rem;
|
|
color: #94a3b8;
|
|
}
|
|
|
|
/* Store Loading */
|
|
.store-loading {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 12px 16px;
|
|
border: 1px dashed #e2e8f0;
|
|
border-radius: 12px;
|
|
background: #f8fafc;
|
|
}
|
|
|
|
/* Selected Store Info Card */
|
|
.selected-store-info {
|
|
margin-top: 16px;
|
|
}
|
|
|
|
.store-info-card {
|
|
padding: 16px;
|
|
border-radius: 12px;
|
|
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
|
|
border: 1px solid #bae6fd;
|
|
}
|
|
|
|
.store-info-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
font-weight: 700;
|
|
color: #0369a1;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.store-info-details {
|
|
display: flex;
|
|
gap: 8px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.role-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 4px 12px;
|
|
border-radius: 20px;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
letter-spacing: 0.02em;
|
|
}
|
|
|
|
.badge-owner {
|
|
background: rgba(34, 197, 94, 0.15);
|
|
color: #15803d;
|
|
border: 1px solid rgba(34, 197, 94, 0.3);
|
|
}
|
|
|
|
.badge-manager {
|
|
background: rgba(59, 130, 246, 0.15);
|
|
color: #1d4ed8;
|
|
border: 1px solid rgba(59, 130, 246, 0.3);
|
|
}
|
|
|
|
.badge-admin {
|
|
background: rgba(168, 85, 247, 0.15);
|
|
color: #7e22ce;
|
|
border: 1px solid rgba(168, 85, 247, 0.3);
|
|
}
|
|
|
|
.badge-default {
|
|
background: rgba(100, 116, 139, 0.15);
|
|
color: #475569;
|
|
border: 1px solid rgba(100, 116, 139, 0.3);
|
|
}
|
|
|
|
.category-tag {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 4px 12px;
|
|
border-radius: 20px;
|
|
font-size: 0.75rem;
|
|
font-weight: 500;
|
|
background: rgba(100, 116, 139, 0.1);
|
|
color: #475569;
|
|
border: 1px solid rgba(100, 116, 139, 0.2);
|
|
}
|
|
|
|
/* Access Info */
|
|
.access-info {
|
|
border-radius: 12px;
|
|
border: 1px solid #e2e8f0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.access-info-header {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 12px 16px;
|
|
background: #f8fafc;
|
|
border-bottom: 1px solid #e2e8f0;
|
|
font-size: 0.85rem;
|
|
color: #475569;
|
|
}
|
|
|
|
.access-info-body {
|
|
padding: 8px;
|
|
}
|
|
|
|
.access-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 10px 12px;
|
|
border-radius: 8px;
|
|
font-size: 0.85rem;
|
|
color: #94a3b8;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.access-item.active {
|
|
background: rgba(34, 197, 94, 0.08);
|
|
color: #1e293b;
|
|
}
|
|
|
|
.access-item i:first-child {
|
|
font-size: 0.9rem;
|
|
width: 20px;
|
|
text-align: center;
|
|
}
|
|
|
|
/* Product Photo Preview */
|
|
.product-photo-preview {
|
|
display: flex;
|
|
justify-content: center;
|
|
padding: 12px;
|
|
background: #f8fafc;
|
|
border-radius: 12px;
|
|
border: 1px solid #e2e8f0;
|
|
}
|
|
|
|
.product-photo {
|
|
max-width: 100%;
|
|
max-height: 200px;
|
|
border-radius: 8px;
|
|
object-fit: contain;
|
|
}
|
|
|
|
.description-preview {
|
|
padding: 12px 16px;
|
|
border-radius: 12px;
|
|
border: 1px solid #e2e8f0;
|
|
background: #f8fafc;
|
|
font-size: 0.9rem;
|
|
color: #64748b;
|
|
line-height: 1.6;
|
|
max-height: 120px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
/* Alerts */
|
|
.glass-alert {
|
|
padding: 16px 20px;
|
|
border-radius: 16px;
|
|
backdrop-filter: blur(8px);
|
|
font-weight: 500;
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.alert-success {
|
|
background: rgba(34, 197, 94, 0.1);
|
|
border: 1px solid rgba(34, 197, 94, 0.2);
|
|
color: #15803d;
|
|
}
|
|
|
|
.alert-danger {
|
|
background: rgba(239, 68, 68, 0.1);
|
|
border: 1px solid rgba(239, 68, 68, 0.2);
|
|
color: #b91c1c;
|
|
}
|
|
|
|
/* Buttons */
|
|
.btn-premium-assign {
|
|
background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
|
|
color: white;
|
|
border: none;
|
|
padding: 16px 48px;
|
|
border-radius: 14px;
|
|
font-weight: 700;
|
|
font-size: 1.1rem;
|
|
cursor: pointer;
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
box-shadow: 0 10px 15px -3px rgba(34, 197, 94, 0.3);
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-width: 280px;
|
|
}
|
|
|
|
.btn-premium-assign:hover:not(:disabled) {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 20px 25px -5px rgba(34, 197, 94, 0.4);
|
|
filter: brightness(1.08);
|
|
}
|
|
|
|
.btn-premium-assign:disabled {
|
|
background: #cbd5e1;
|
|
cursor: not-allowed;
|
|
box-shadow: none;
|
|
}
|
|
|
|
.btn-loading {
|
|
padding: 12px 48px;
|
|
background: #16a34a;
|
|
}
|
|
|
|
.btn-text {
|
|
background: transparent;
|
|
border: none;
|
|
color: #64748b;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: color 0.2s;
|
|
}
|
|
|
|
.btn-text:hover {
|
|
color: #1e293b;
|
|
}
|
|
|
|
/* Animations */
|
|
.animate-fade-in {
|
|
animation: fadeIn 0.5s ease-out;
|
|
}
|
|
|
|
.animate-shake {
|
|
animation: shake 0.5s cubic-bezier(.36,.07,.19,.97) both;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from { opacity: 0; transform: translateY(-10px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
@keyframes shake {
|
|
10%, 90% { transform: translate3d(-1px, 0, 0); }
|
|
20%, 80% { transform: translate3d(2px, 0, 0); }
|
|
30%, 50%, 70% { transform: translate3d(-4px, 0, 0); }
|
|
40%, 60% { transform: translate3d(4px, 0, 0); }
|
|
}
|
|
|
|
/* Dark mode */
|
|
:global(.dark-mode) .premium-input, :global(.dark-mode) .premium-select {
|
|
background: #1e293b;
|
|
border-color: #334155;
|
|
color: #f8fafc;
|
|
}
|
|
|
|
:global(.dark-mode) .premium-input:disabled {
|
|
background: #0f172a;
|
|
color: #94a3b8;
|
|
}
|
|
|
|
:global(.dark-mode) .premium-title {
|
|
background: linear-gradient(135deg, #f8fafc 0%, #cbd5e1 100%);
|
|
-webkit-background-clip: text;
|
|
}
|
|
|
|
:global(.dark-mode) .form-label {
|
|
color: #94a3b8;
|
|
}
|
|
|
|
:global(.dark-mode) .store-info-card {
|
|
background: linear-gradient(135deg, #0c4a6e 0%, #164e63 100%);
|
|
border-color: #0e7490;
|
|
}
|
|
|
|
:global(.dark-mode) .store-info-header {
|
|
color: #67e8f9;
|
|
}
|
|
|
|
:global(.dark-mode) .access-info {
|
|
border-color: #334155;
|
|
background: #1e293b;
|
|
}
|
|
|
|
.input-with-icon {
|
|
position: relative;
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.input-with-icon .prefix {
|
|
position: absolute;
|
|
left: 16px;
|
|
color: #64748b;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.ps-5 {
|
|
padding-left: 38px !important;
|
|
}
|
|
|
|
.pivot-custom-fields {
|
|
background: rgba(59, 130, 246, 0.03);
|
|
padding: 16px;
|
|
border-radius: 12px;
|
|
border: 1px solid rgba(59, 130, 246, 0.1);
|
|
}
|
|
|
|
:global(.dark-mode) .pivot-custom-fields {
|
|
background: rgba(59, 130, 246, 0.1);
|
|
border-color: rgba(59, 130, 246, 0.2);
|
|
}
|
|
|
|
:global(.dark-mode) .access-info-header {
|
|
background: #1e293b;
|
|
border-color: #334155;
|
|
color: #94a3b8;
|
|
}
|
|
|
|
:global(.dark-mode) .access-item.active {
|
|
background: rgba(34, 197, 94, 0.15);
|
|
color: #f8fafc;
|
|
}
|
|
|
|
:global(.dark-mode) .description-preview {
|
|
background: #1e293b;
|
|
border-color: #334155;
|
|
color: #94a3b8;
|
|
}
|
|
|
|
:global(.dark-mode) .product-photo-preview {
|
|
background: #1e293b;
|
|
border-color: #334155;
|
|
}
|
|
|
|
:global(.dark-mode) .store-loading {
|
|
background: #1e293b;
|
|
border-color: #334155;
|
|
}
|
|
</style>
|