Merge pull request 'ui chnages' (#12) from ali into dev

Reviewed-on: https://gitea.maskantech.in/gitea_admin/productcatalogue_frontend/pulls/12
This commit is contained in:
liyaqath
2026-07-20 06:17:54 +00:00
2 changed files with 34 additions and 57 deletions
-19
View File
@@ -15,12 +15,6 @@ export interface CustomAction {
className?: string;
}
export interface ActionMenuItem {
label: string;
icon?: React.ReactNode;
onClick: () => void;
}
export interface ActionMenuProps {
onView?: () => void;
onEdit?: () => void;
@@ -87,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(); }}
+34 -38
View File
@@ -4,14 +4,13 @@ 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 } 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';
import { useUnit } from '../../units/hook/useUnit';
import {
FileText, LayoutGrid, Tags, Globe, Eye, Settings2, Save,
CheckCircle2, AlertCircle, CheckSquare, Image as ImageIcon, Check,
@@ -33,7 +32,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 },
@@ -232,8 +231,7 @@ 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();
@@ -332,13 +330,12 @@ 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, fetchUnits]);
// Load family details in Edit mode
useEffect(() => {
@@ -394,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 || assetTypesLoading || assetFamiliesLoading || workflowsLoading || setsLoading || brandsLoading || unitsLoading;
const isLoading = familyLoading || categoriesLoading || attributesLoading || channelsLoading || assetFamiliesLoading || workflowsLoading || setsLoading || brandsLoading || unitsLoading;
// Selected attributes list for Step 3 Axis Filtering
const selectedAttributesList = attributes.filter(attr =>
@@ -686,7 +683,9 @@ export default function NewFamily() {
/>
</div>
{/* Allowed Units */}
{/* Row 3 — Description last */}
<div className="col-span-2">
<label className={labelClass}>Allowed Units <span className="text-red-400">*</span></label>
<SearchableMultiSelect
@@ -699,9 +698,17 @@ export default function NewFamily() {
error={formik.touched.allowedUnits && formik.errors.allowedUnits ? String(formik.errors.allowedUnits) : undefined}
/>
</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="col-span-2">
<div className="border-b border-gray-100 pb-5">
<label className={labelClass}>Attribute Set <span className="text-red-400">*</span></label>
<Select
name="attributeSetId"
@@ -734,21 +741,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">
{(() => {
@@ -912,37 +904,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>
@@ -1162,7 +1158,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">
@@ -1186,7 +1182,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) => (