Files
edr-platform/apps/edr-freight-web/backoffice/src/user-management/TemplateSample/components/SettingsPanel.tsx
2026-07-10 10:41:48 +03:00

253 lines
8.0 KiB
TypeScript

import React, { useState, useMemo } from "react";
import { Search, ChevronDown, Loader } from "lucide-react";
import { TemplateSampleSetting } from "@/user-management/services/TemplateConfiguration/types/templateTypes";
import { useTranslation } from "react-i18next";
interface SectionSettings {
items: TemplateSampleSetting[];
page: number;
hasMore: boolean;
loading: boolean;
}
interface SettingsPanelProps {
settings: TemplateSampleSetting[];
onSettingChange: (index: number, field: string, value: string) => void;
onLoadMore?: (sectionKey: string, nextPage: number) => void;
}
interface StyleInputsProps {
setting: TemplateSampleSetting;
index: number;
onChange: (index: number, field: string, value: string) => void;
}
const StyleInputs: React.FC<StyleInputsProps> = ({
setting,
index,
onChange,
}) => {
return (
<div className="p-4 bg-gray-50 dark:bg-gray-700 rounded-lg border border-gray-200 dark:border-gray-600 space-y-3">
<h4 className="font-medium text-sm text-gray-900 dark:text-gray-100 capitalize">
{setting.code.replace("-", " ")}
</h4>
<div className="grid grid-cols-3 gap-3">
{Object.entries(setting.value).map(([key, value]) => (
<div key={key}>
<label className="block text-xs font-medium text-gray-600 dark:text-gray-300 mb-1 capitalize">
{key.replace("-", " ")}
</label>
<input
type="text"
value={typeof value === "string" ? value : String(value ?? "")}
onChange={(e) => onChange(index, key, e.target.value)}
className="w-full px-2 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded focus:outline-none focus:ring-2 focus:ring-purple-500 dark:bg-gray-600 dark:text-gray-200"
placeholder={key}
/>
</div>
))}
</div>
</div>
);
};
interface CollapsibleSectionProps {
title: string;
sectionKey: string;
sectionData: SectionSettings;
settings: TemplateSampleSetting[];
onSettingChange: (index: number, field: string, value: string) => void;
onLoadMore: (sectionKey: string, nextPage: number) => void;
}
const CollapsibleSection: React.FC<CollapsibleSectionProps> = ({
title,
sectionKey,
sectionData,
settings,
onSettingChange,
onLoadMore,
}) => {
const [isOpen, setIsOpen] = useState(true);
return (
<div className="border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
<button
onClick={() => setIsOpen(!isOpen)}
className="w-full px-4 py-3 bg-gray-50 dark:bg-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 flex items-center justify-between transition-colors"
>
<h3 className="font-semibold text-gray-900 dark:text-gray-100">{title}</h3>
<ChevronDown
className={`h-5 w-5 text-gray-600 dark:text-gray-300 transition-transform ${
isOpen ? "rotate-180" : ""
}`}
/>
</button>
{isOpen && (
<div className="p-4 space-y-4">
{sectionData.items.length === 0 ? (
<p className="text-sm text-gray-500 dark:text-gray-400">No settings available</p>
) : (
<>
{sectionData.items.map((setting) => {
const originalIndex = settings.findIndex(
(s) => s.code === setting.code,
);
return (
<StyleInputs
key={setting.code}
setting={setting}
index={originalIndex}
onChange={onSettingChange}
/>
);
})}
{sectionData.hasMore && (
<button
onClick={() =>
onLoadMore(sectionKey, sectionData.page + 1)
}
disabled={sectionData.loading}
className="w-full px-3 py-2 text-sm text-purple-600 hover:text-purple-800 hover:bg-purple-50 rounded transition-colors flex items-center justify-center gap-2 disabled:opacity-50"
>
{sectionData.loading ? (
<>
<Loader className="h-4 w-4 animate-spin" />
Loading...
</>
) : (
"Load more"
)}
</button>
)}
</>
)}
</div>
)}
</div>
);
};
export const SettingsPanel: React.FC<SettingsPanelProps> = ({
settings,
onSettingChange,
onLoadMore = () => {},
}) => {
const { t } = useTranslation();
const [searchTerm, setSearchTerm] = useState("");
// Flatten all settings for search
const allSettings = useMemo(() => settings, [settings]);
// Filter settings based on search term
const filteredSettings = useMemo(() => {
if (!searchTerm.trim()) return null;
return allSettings.filter(
(s) =>
s.code.toLowerCase().includes(searchTerm.toLowerCase()) ||
Object.values(s.value).some((v) =>
String(v).toLowerCase().includes(searchTerm.toLowerCase()),
),
);
}, [searchTerm, allSettings]);
// Group settings by section (for non-search view)
const sectionSettings: Record<string, SectionSettings> = useMemo(() => {
return {
subject: {
items: allSettings.filter((s) => s.code.includes("subject")),
page: 0,
hasMore: false,
loading: false,
},
body: {
items: allSettings.filter((s) => s.code.includes("body")),
page: 0,
hasMore: false,
loading: false,
},
header: {
items: allSettings.filter((s) => s.code.includes("header")),
page: 0,
hasMore: false,
loading: false,
},
footer: {
items: allSettings.filter((s) => s.code.includes("footer")),
page: 0,
hasMore: false,
loading: false,
},
};
}, [allSettings]);
return (
<div className="space-y-4">
{/* Search Input */}
<div className="relative">
<Search className="absolute left-3 top-3 h-5 w-5 text-gray-400" />
<input
type="text"
placeholder={t("common.search", "Search settings...")}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500"
/>
</div>
{/* Filtered Results View */}
{filteredSettings !== null ? (
<div className="space-y-4">
<p className="text-sm text-gray-600">
Found {filteredSettings.length} setting
{filteredSettings.length !== 1 ? "s" : ""}
</p>
{filteredSettings.length === 0 ? (
<div className="text-center py-8">
<p className="text-gray-500">
{t("common.noResults", "No settings found")}
</p>
</div>
) : (
<div className="space-y-4">
{filteredSettings.map((setting) => {
const originalIndex = allSettings.findIndex(
(s) => s.code === setting.code,
);
return (
<StyleInputs
key={setting.code}
setting={setting}
index={originalIndex}
onChange={onSettingChange}
/>
);
})}
</div>
)}
</div>
) : (
/* Collapsible Sections View */
<div className="space-y-4">
{Object.entries(sectionSettings).map(([key, section]) => (
<CollapsibleSection
key={key}
title={key.charAt(0).toUpperCase() + key.slice(1)}
sectionKey={key}
sectionData={section}
settings={allSettings}
onSettingChange={onSettingChange}
onLoadMore={onLoadMore}
/>
))}
</div>
)}
</div>
);
};