ui chnages

This commit is contained in:
m7338ohd-debug
2026-07-20 11:40:44 +05:30
parent b13019c403
commit 4bf8ad6c4f
4 changed files with 39 additions and 81 deletions
-20
View File
@@ -15,18 +15,11 @@ export interface CustomAction {
className?: string;
}
export interface ActionMenuItem {
label: string;
icon?: React.ReactNode;
onClick: () => void;
}
export interface ActionMenuProps {
onView?: () => void;
onEdit?: () => void;
onDelete?: () => void;
customActions?: CustomAction[];
customActions?: ActionMenuItem[];
}
export function ActionMenu({ onView, onEdit, onDelete, customActions }: ActionMenuProps) {
@@ -88,19 +81,6 @@ export function ActionMenu({ onView, onEdit, onDelete, customActions }: ActionMe
<span className="font-medium text-sm">{action.label}</span>
</DropdownMenuItem>
))}
{customActions && customActions.map((action, idx) => (
<DropdownMenuItem
key={idx}
onClick={(e) => { e.stopPropagation(); action.onClick(); }}
className="cursor-pointer rounded-lg px-3 py-2 flex items-center outline-none mt-1 transition-colors"
style={{ color: "var(--color-table-header-text)" }}
onMouseEnter={e => (e.currentTarget.style.backgroundColor = "var(--color-table-header-bg)")}
onMouseLeave={e => (e.currentTarget.style.backgroundColor = "transparent")}
>
{action.icon && <span className="mr-2 flex items-center">{action.icon}</span>}
<span className="font-medium text-sm">{action.label}</span>
</DropdownMenuItem>
))}
{onDelete && (
<DropdownMenuItem
onClick={(e) => { e.stopPropagation(); onDelete(); }}
+1 -4
View File
@@ -3,7 +3,7 @@ import {
Search, X, ChevronsUpDown, ChevronUp, ChevronDown,
ChevronLeft, ChevronRight, Settings2, Check, Filter
} from "lucide-react";
import { ActionMenu, type CustomAction, type ActionMenuItem } from "./ActionMenu";
import { ActionMenu, type CustomAction } from "./ActionMenu";
export interface DataTableColumn<T = any> {
key: string;
@@ -33,13 +33,11 @@ export interface DataTableProps<T = any> {
onReorder?: (newData: T[]) => void;
searchPlaceholder?: string;
statusKey?: string;
loading?: boolean;
actionConfig?: {
onView?: (row: T) => void;
onEdit?: (row: T) => void;
onDelete?: (row: T) => void;
customActions?: (row: T) => CustomAction[];
customActions?: (row: T) => ActionMenuItem[];
};
}
@@ -59,7 +57,6 @@ export function DataTable<T extends Record<string, any> = any>({
draggable = false,
searchPlaceholder = "Search...",
statusKey = "status",
loading,
actionConfig,
loading = false,
}: DataTableProps<T>) {
+38 -56
View File
@@ -4,15 +4,12 @@ import { useFamily } from '../hook/useFamily';
import { useCategory } from '../../categories/hook/useCategory';
import { useAttribute } from '../../attributes/hook/useAttribute';
import { useChannel } from '../../channels/hook/useChannel';
import { useAssetType } from '../../asset-types/hook/useAssetType';
import { useWorkflow } from '../../workflow/hook/useWorkflow';
import { StageTimeline } from '../../workflow/components/StageTimeline';
import { Search, X, Layers, Activity, Activity } from 'lucide-react';
import { Button } from "../../../components/customs/Button";
import { Search, X, Layers, Activity } from 'lucide-react';
import { useAttributeSet } from '../../attribute-sets/hook/useAttributeSet';
import { useAssetFamily } from '../../asset-families/hook/useAssetFamily';
import { useBrand } from '../../brands/hook/useBrand';
import { useUnit } from '../../units/hook/useUnit';
import {
FileText, LayoutGrid, Tags, Globe, Eye, Settings2, Save,
CheckCircle2, AlertCircle, CheckSquare, Image as ImageIcon, Check,
@@ -34,7 +31,7 @@ const TABS = [
{ id: 'attributes', label: 'Attribute Groups', icon: LayoutGrid, step: 2 },
{ id: 'variants', label: 'Variant Strategy', icon: Tags, step: 3 },
{ id: 'channels', label: 'Allowed Channels', icon: Globe, step: 4 },
{ id: 'assets', label: 'Default Asset Types', icon: ImageIcon, step: 5 },
{ id: 'assets', label: 'Asset Families', icon: ImageIcon, step: 5 },
{ id: 'workflow', label: 'Workflow Assignment', icon: Settings2, step: 6 },
{ id: 'rules', label: 'Completeness Rules', icon: CheckSquare, step: 7 },
{ id: 'summary', label: 'Inheritance Summary', icon: Eye, step: 8 },
@@ -233,14 +230,12 @@ export default function NewFamily() {
const { categories, fetchCategories, loading: categoriesLoading } = useCategory();
const { attributes, fetchAttributes, loading: attributesLoading } = useAttribute();
const { items: channelsList, fetchItems: fetchChannels, loading: channelsLoading } = useChannel();
const { items: assetTypesList, fetchItems: fetchAssetTypes, loading: assetTypesLoading } = useAssetType();
const { fetchItems: fetchAssetFamilies, loading: assetFamiliesLoading } = useAssetFamily();
const { items: assetFamiliesList, fetchItems: fetchAssetFamilies, loading: assetFamiliesLoading } = useAssetFamily();
const { items: workflowsList, fetchItems: fetchWorkflows, loading: workflowsLoading } = useWorkflow();
const { items: attributeSetsList, fetchItems: fetchAttributeSets, loading: setsLoading } = useAttributeSet();
const { brands, fetchBrands, loading: brandsLoading } = useBrand();
const { fetchUnits, loading: unitsLoading } = useUnit();
const [familyLoading, setFamilyLoading] = useState(false);
const [activeTab, setActiveTab] = useState('basic');
@@ -294,7 +289,6 @@ export default function NewFamily() {
attributes: [] as string[],
variantAxes: [] as string[],
allowedBrands: [] as string[],
allowedUnits: [] as string[],
channels: [] as string[],
assetRequirements: [] as string[],
completenessRules: {
@@ -333,13 +327,11 @@ export default function NewFamily() {
fetchCategories();
fetchAttributes();
fetchChannels();
fetchAssetTypes();
fetchAssetFamilies();
fetchWorkflows();
fetchAttributeSets();
fetchBrands();
fetchUnits();
}, [fetchCategories, fetchAttributes, fetchChannels, fetchAssetTypes, fetchAssetFamilies, fetchWorkflows, fetchAttributeSets, fetchBrands, fetchUnits]);
}, [fetchCategories, fetchAttributes, fetchChannels, fetchAssetFamilies, fetchWorkflows, fetchAttributeSets, fetchBrands]);
// Load family details in Edit mode
useEffect(() => {
@@ -362,7 +354,6 @@ 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,
@@ -395,7 +386,7 @@ export default function NewFamily() {
}, [formik.values.attributeSetId, loadBlueprintPreview]);
const activeIndex = TABS.findIndex(t => t.id === activeTab);
const isLoading = familyLoading || categoriesLoading || attributesLoading || channelsLoading || assetTypesLoading || assetFamiliesLoading || workflowsLoading || setsLoading || brandsLoading || unitsLoading;
const isLoading = familyLoading || categoriesLoading || attributesLoading || channelsLoading || assetFamiliesLoading || workflowsLoading || setsLoading || brandsLoading;
// Selected attributes list for Step 3 Axis Filtering
const selectedAttributesList = attributes.filter(attr =>
@@ -687,22 +678,24 @@ export default function NewFamily() {
/>
</div>
{/* Allowed Units */}
<div className="col-span-2">
<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) => ({ 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>
{/* Row: Attribute Set Select */}
{/* 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' }} />
</div>
</div>
</Card>
)}
{/* ── Attribute Assignment ── */}
{activeTab === 'attributes' && (
<Card>
<CardHeader title="Attribute Set Blueprint Groups" subtitle="Attributes are defined by the Attribute Set. Expand groups to enable/disable optional items." />
<div className="p-6 space-y-6">
{/* Row: Attribute Set Select */}
<div className="border-b border-gray-100 pb-5">
<label className={labelClass}>Attribute Set <span className="text-red-400">*</span></label>
<Select
name="attributeSetId"
@@ -735,21 +728,6 @@ export default function NewFamily() {
<div className="text-xs text-red-500 mt-1 font-medium">{formik.errors.attributeSetId}</div>
)}
</div>
{/* 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' }} />
</div>
</div>
</Card>
)}
{/* ── Attribute Assignment ── */}
{activeTab === 'attributes' && (
<Card>
<CardHeader title="Attribute Set Blueprint Groups" subtitle="Attributes are defined by the Attribute Set. Expand groups to enable/disable optional items." />
<div className="p-6">
{formik.values.attributeSetId ? (
<div className="space-y-4 max-h-[420px] overflow-y-auto pr-2">
{(() => {
@@ -913,37 +891,41 @@ export default function NewFamily() {
</Card>
)}
{/* ── Default Asset Types ── */}
{/* ── Asset Families ── */}
{activeTab === 'assets' && (
<Card>
<CardHeader title="Default Asset Types" subtitle="Define which asset types are expected for products in this family" />
<CardHeader title="Asset Families" subtitle="Select which asset families are assigned to this product family" />
<div className="p-6 space-y-2">
{assetTypesList.map((asset) => {
const isChecked = formik.values.assetRequirements.includes(asset.id);
{assetFamiliesList.map((assetFamily) => {
const isChecked = formik.values.assetRequirements.includes(assetFamily.id);
const handleToggle = () => {
const current = [...formik.values.assetRequirements];
if (isChecked) {
formik.setFieldValue('assetRequirements', current.filter(id => id !== asset.id));
formik.setFieldValue('assetRequirements', current.filter(id => id !== assetFamily.id));
} else {
formik.setFieldValue('assetRequirements', [...current, asset.id]);
formik.setFieldValue('assetRequirements', [...current, assetFamily.id]);
}
};
return (
<label key={asset.id} onClick={handleToggle} className={`flex items-center justify-between px-4 py-3.5 rounded-lg border cursor-pointer transition-all ${isChecked ? 'border-primary/20 bg-primary/5/40 shadow-sm' : 'border-gray-100 hover:border-primary/10 hover:bg-primary/5/20'
<label key={assetFamily.id} onClick={handleToggle} className={`flex items-center justify-between px-4 py-3.5 rounded-lg border cursor-pointer transition-all ${isChecked ? 'border-primary/20 bg-primary/5/40 shadow-sm' : 'border-gray-100 hover:border-primary/10 hover:bg-primary/5/20'
}`}>
<div className="flex items-center gap-4">
<input type="checkbox" checked={isChecked} readOnly className="w-4 h-4 text-primary rounded border-gray-300 focus:ring-primary-light" />
<div>
<div className="font-medium text-sm text-gray-900">{asset.name}</div>
{asset.isRequired && <div className="text-xs text-red-400 mt-0.5">Required Asset</div>}
<div className="font-medium text-sm text-gray-900">{assetFamily.name}</div>
{assetFamily.assetTypes && assetFamily.assetTypes.length > 0 && (
<div className="text-xs text-gray-400 mt-0.5">
Expected Types: {assetFamily.assetTypes.map((t: any) => t.name).join(', ')}
</div>
)}
</div>
</div>
{isChecked && <CheckCircle2 className="w-4 h-4 text-primary" />}
</label>
);
})}
{assetTypesList.length === 0 && (
<div className="text-center py-8 text-gray-400 text-sm">No asset types available.</div>
{assetFamiliesList.length === 0 && (
<div className="text-center py-8 text-gray-400 text-sm">No asset families available.</div>
)}
</div>
</Card>
@@ -1163,7 +1145,7 @@ export default function NewFamily() {
</div>
<span className="text-gray-400 font-bold"></span>
<div className="px-3 py-1.5 bg-rose-50 border border-rose-200 rounded-lg shadow-xs">
🖼 {formik.values.assetRequirements.length} Asset Types
🖼 {formik.values.assetRequirements.length} Asset Families
</div>
<span className="text-gray-400 font-bold"></span>
<div className="px-3 py-1.5 bg-teal-50 border border-teal-200 rounded-lg shadow-xs">
@@ -1187,7 +1169,7 @@ export default function NewFamily() {
{ icon: FileText, title: 'Total Attributes', value: `${formik.values.attributes.length} Fields`, desc: 'Inherited EAV schema fields' },
{ icon: Tags, title: 'Variant Strategy', value: `${selectedAttributesList.length} Axes`, desc: selectedAttributesList.map(a => a.name).join(', ') || 'No variants' },
{ icon: Globe, title: 'Allowed Channels', value: `${formik.values.channels.length} Mappings`, desc: formik.values.channels.join(', ') || 'No channels selected' },
{ icon: ImageIcon, title: 'Media Requirements', value: `${formik.values.assetRequirements.length} Asset Types`, desc: 'Enforced for completeness' },
{ icon: ImageIcon, title: 'Media Requirements', value: `${formik.values.assetRequirements.length} Asset Families`, desc: 'Enforced for completeness' },
{ icon: Settings2, title: 'Lifecycle Workflow', value: workflowsList.find(w => w.code === selectedWorkflow)?.name || selectedWorkflow, desc: 'Products lifecycle flow' },
{ icon: Info, title: 'Completeness Rules', value: `${Object.keys(formik.values.completenessRules).length} Rules`, desc: 'Auto-calculated criteria' }
].map(({ icon: Icon, title, value, desc }, i) => (
@@ -13,7 +13,6 @@ import { Breadcrumb } from "../../../components/layouts/Breadcrumb";
import { StageTimeline } from "../components/StageTimeline";
import { toast } from "react-toastify";
import { Plus, Trash2, Edit2, ArrowUp, ArrowDown, Check, Layers, AlertCircle, Save } from "lucide-react";
import { Plus, Trash2, Edit2, ArrowUp, ArrowDown, Check, Layers, AlertCircle, Save } from "lucide-react";
import { workflowSchema } from "../validation/workflow.schema";
import type { WorkflowStage } from "../types/workflow.types";
import { notify } from "../../../services/toast";