solved merge conflicts

This commit is contained in:
m7338ohd-debug
2026-07-20 11:47:31 +05:30
6 changed files with 32 additions and 28 deletions
-3
View File
@@ -7,7 +7,6 @@ export type BadgeVariant =
| "draft"
| "inactive"
| "disabled"
| "inactive"
| "published"
| "pending"
| "archived"
@@ -25,7 +24,6 @@ const BADGE_STYLES: Record<BadgeVariant, string> = {
draft: "bg-slate-100 text-slate-700 border border-slate-200",
inactive: "bg-zinc-100 text-zinc-500 border border-zinc-200",
disabled: "bg-gray-100 text-gray-500 border border-gray-200",
inactive: "bg-gray-100 text-gray-500 border border-gray-200",
published: "bg-primary-light text-primary-dark border border-primary/20",
pending: "bg-amber-100 text-amber-700 border border-amber-200",
archived: "bg-slate-100 text-slate-500 border border-slate-200",
@@ -44,7 +42,6 @@ const DOT_COLORS: Record<BadgeVariant, string> = {
draft: "bg-slate-400",
inactive: "bg-zinc-400",
disabled: "bg-gray-400",
inactive: "bg-gray-400",
published: "bg-primary",
pending: "bg-amber-500",
archived: "bg-slate-400",
@@ -9,8 +9,6 @@ export interface AssetFamily {
createdAt: string;
assetTypes?: AssetType[];
assetTypeIds?: string[];
assetTypes?: any[];
assetTypeIds?: string[];
}
export type AssetFamilyCreateRequest = Omit<AssetFamily, 'id' | 'createdAt'>;
export type AssetFamilyUpdateRequest = Partial<AssetFamilyCreateRequest>;
+17 -4
View File
@@ -7,6 +7,7 @@ import { useChannel } from '../../channels/hook/useChannel';
import { useWorkflow } from '../../workflow/hook/useWorkflow';
import { StageTimeline } from '../../workflow/components/StageTimeline';
import { Search, X, Layers, Activity } from 'lucide-react';
import { useUnit } from '../../units/hook/useUnit';
import { useAttributeSet } from '../../attribute-sets/hook/useAttributeSet';
import { useAssetFamily } from '../../asset-families/hook/useAssetFamily';
import { useBrand } from '../../brands/hook/useBrand';
@@ -236,6 +237,7 @@ export default function NewFamily() {
const { items: attributeSetsList, fetchItems: fetchAttributeSets, loading: setsLoading } = useAttributeSet();
const { brands, fetchBrands, loading: brandsLoading } = useBrand();
const { units, fetchUnits, loading: unitsLoading } = useUnit();
const [familyLoading, setFamilyLoading] = useState(false);
const [activeTab, setActiveTab] = useState('basic');
@@ -289,6 +291,7 @@ export default function NewFamily() {
attributes: [] as string[],
variantAxes: [] as string[],
allowedBrands: [] as string[],
allowedUnits: [] as string[],
channels: [] as string[],
assetRequirements: [] as string[],
completenessRules: {
@@ -331,7 +334,8 @@ export default function NewFamily() {
fetchWorkflows();
fetchAttributeSets();
fetchBrands();
}, [fetchCategories, fetchAttributes, fetchChannels, fetchAssetFamilies, fetchWorkflows, fetchAttributeSets, fetchBrands]);
fetchUnits();
}, [fetchCategories, fetchAttributes, fetchChannels, fetchAssetFamilies, fetchWorkflows, fetchAttributeSets, fetchBrands, fetchUnits]);
// Load family details in Edit mode
useEffect(() => {
@@ -354,6 +358,7 @@ export default function NewFamily() {
channels: Array.isArray(data.channels) ? data.channels.map((c: any) => c.channelCode || c.channel_code || c) : [],
assetRequirements: Array.isArray(data.assetRequirements) ? data.assetRequirements.map((r: any) => r.id || r) : [],
allowedBrands: Array.isArray(data.allowedBrands) ? data.allowedBrands.map((b: any) => b.id || b) : [],
allowedUnits: Array.isArray(data.allowedUnits) ? data.allowedUnits.map((u: any) => u.id || u) : [],
completenessRules: data.completenessRules || {
required_attributes: 40,
at_least_one_image: 20,
@@ -386,7 +391,7 @@ export default function NewFamily() {
}, [formik.values.attributeSetId, loadBlueprintPreview]);
const activeIndex = TABS.findIndex(t => t.id === activeTab);
const isLoading = familyLoading || categoriesLoading || attributesLoading || channelsLoading || assetFamiliesLoading || workflowsLoading || setsLoading || brandsLoading;
const isLoading = familyLoading || categoriesLoading || attributesLoading || channelsLoading || assetFamiliesLoading || workflowsLoading || setsLoading || brandsLoading || unitsLoading;
// Selected attributes list for Step 3 Axis Filtering
const selectedAttributesList = attributes.filter(attr =>
@@ -682,8 +687,16 @@ export default function NewFamily() {
{/* Row 3 — Description last */}
<div className="col-span-2">
<label className={labelClass}>Description</label>
<textarea name="description" value={formik.values.description} onChange={formik.handleChange} placeholder="Describe this product family..." rows={4} className={`${inputClass} resize-y`} style={{ minHeight: '120px' }} />
<label className={labelClass}>Allowed Units <span className="text-red-400">*</span></label>
<SearchableMultiSelect
title="Allowed Units"
items={units}
selectedIds={formik.values.allowedUnits}
onChange={(ids) => formik.setFieldValue('allowedUnits', ids)}
searchPlaceholder="Search units by name, code or symbol..."
getDisplayInfo={(u: any) => ({ id: u.id, name: u.name, code: u.code, badgeText: u.symbol, status: u.status })}
error={formik.touched.allowedUnits && formik.errors.allowedUnits ? String(formik.errors.allowedUnits) : undefined}
/>
</div>
</div>
</Card>
+12 -16
View File
@@ -9,17 +9,16 @@ import { useBrand } from '../../brands/hook/useBrand';
import { useUnit } from '../../units/hook/useUnit';
import { useProductFamilyConfiguration } from '../hook/useProductFamilyConfiguration';
import { VariantsTab } from '../components/variants/VariantsTab';
import { ProductGeneralSection } from '../components/ProductGeneralSection';
import { ProductCategorySection } from '../components/ProductCategorySection';
import { ProductBrandSection } from '../components/ProductBrandSection';
import { DynamicAttributesSection } from '../components/DynamicAttributesSection';
import { StageTimeline } from '../../workflow/components/StageTimeline';
import { toast } from 'react-toastify';
import { Box, LayoutGrid, Tags, Globe, Eye, Save, Send, Copy, Image as ImageIcon, Info, FolderTree, AlertCircle, RefreshCw, Check, Plus } from 'lucide-react';
import { notify } from '../../../services/toast';
import { Box, LayoutGrid, Tags, Globe, Eye, Save, Send, Copy, Image as ImageIcon, Info, FolderTree, Check, Plus, CheckCircle2 } from 'lucide-react';
import { PageWrapper } from '../../../components/layouts/PageWrapper';
import { ProtectedRoute } from '../../../components/layouts/ProtectedRoute';
import { Breadcrumb } from '../../../components/layouts/Breadcrumb';
import { FamilyCard } from '../../../components/customs/FamilyCard';
import { Radio, RadioGroup } from '../../../components/customs/Radio';
import { Select } from '../../../components/customs/Select';
const TABS = [
{ id: 'general', label: 'General', icon: Box, step: 1 },
@@ -77,9 +76,6 @@ export default function NewProduct() {
groups: attributeGroups,
attributes: attributesList,
workflow: inheritedWorkflow,
loading: configLoading,
error: configError,
retry: configRetry
} = useProductFamilyConfiguration(selectedFamily || undefined);
const selectedFamilyObj = useMemo(() => {
@@ -185,15 +181,15 @@ export default function NewProduct() {
try {
if (isEdit && id) {
await productService.update(id, values as any);
toast.success("Product updated successfully!");
notify.success("Product updated successfully!");
navigate('/products');
} else {
const created = await productService.create({ ...values, family_id: selectedFamily } as any);
toast.success("Product created successfully!");
notify.success("Product created successfully!");
navigate(`/products/${created.id}/edit`);
}
} catch (err: any) {
toast.error(err.message || 'Failed to save product');
notify.error(err);
} finally {
setSubmitting(false);
}
@@ -247,7 +243,7 @@ export default function NewProduct() {
setSelectedFamily(productData.family_id || (productData.family ? productData.family.id : null));
}
} catch (err: any) {
toast.error(err.message || 'Failed to load product details');
notify.error(err);
} finally {
}
};
@@ -551,9 +547,9 @@ export default function NewProduct() {
metadata: updatedMetadata
} as any);
formik.setFieldValue('metadata', updatedMetadata);
toast.success(`Product advanced to stage: ${nextStage.name}`);
notify.success(`Product advanced to stage: ${nextStage.name}`);
} catch (err: any) {
toast.error(err.message || 'Failed to advance stage');
notify.error(err);
}
}}
className="px-3 py-1.5 bg-primary hover:bg-primary-hover text-white text-[11px] font-bold rounded-lg transition-all shadow-xs"
@@ -671,7 +667,7 @@ export default function NewProduct() {
onChange={formik.handleChange}
>
<option value="">Select brand</option>
{selectedFamilyObj?.allowedBrands?.map((b: any) => (
{activeAllowedBrandsList?.map((b: any) => (
<option key={b.id} value={b.id}>{b.name}</option>
))}
</Select>
@@ -684,7 +680,7 @@ export default function NewProduct() {
onChange={formik.handleChange}
>
<option value="">Select unit</option>
{selectedFamilyObj?.allowedUnits?.map((u: any) => (
{activeAllowedUnitsList?.map((u: any) => (
<option key={u.id} value={u.id}>
{u.name} {u.symbol ? `(${u.symbol})` : `(${u.code})`}
</option>
@@ -13,8 +13,8 @@ export const SystemMessages = {
timeout: (): BusinessNotification =>
createError("Request Timed Out", "The server took too long to respond. Please try again."),
internalServerError: (httpStatus?: number): BusinessNotification =>
createError("Server Error", "An internal server error occurred. Please try again later.", { httpStatus }),
internalServerError: (msg?: string, httpStatus?: number): BusinessNotification =>
createError("Server Error", msg ?? "An internal server error occurred. Please try again later.", { httpStatus }),
unauthorized: (httpStatus = 401): BusinessNotification =>
createWarning("Unauthorized", "Your session has expired. Please log in again.", { httpStatus }),
+1 -1
View File
@@ -77,7 +77,7 @@ export function normalizeError(error: unknown): BusinessNotification {
case 502:
case 503:
case 504:
return SystemMessages.internalServerError(status);
return SystemMessages.internalServerError(backendMessage, status);
default:
return SystemMessages.unknownError(backendMessage, status);
}