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:
@@ -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(); }}
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
Reference in New Issue
Block a user