+ {/* Header */}
+
-
-
- متون رابط کاربری
-
-
ویرایش تمامی متون سایت از هدر تا فوتر
+
+
+ مدیریت محتوا و بنرهای صفحات (Page Builder)
+
+
+ ویرایش زنده متون، تصاویر بنر، لینک دکمهها و عناوین تمام صفحات سایت کانینا
+
- {unsavedCount > 0 && (
-
- {unsavedCount} تغییر ذخیره نشده
-
- )}
-
-
+ {/* Hidden File Input for Image Upload */}
+
{
+ if (e.target.files?.[0] && currentKeyForUpload.current) {
+ handleImageUpload(currentKeyForUpload.current, e.target.files[0]);
+ }
+ }}
+ />
+
+ {/* Section Tabs */}
+
+ {PAGE_TABS.map(tab => {
+ const Icon = tab.icon;
+ const isActive = activeTab === tab.id;
+ return (
+
+ );
+ })}
+
+
+ {/* Search Input */}
+
+
setSearch(e.target.value)}
- className="w-full bg-white border border-gray-200 rounded-xl py-3 pr-11 pl-4 text-sm focus:outline-none focus:ring-2 focus:ring-purple-500/20"
+ className="w-full bg-transparent border-none text-sm focus:outline-none font-bold"
/>
- {loading ? (
-
- ) : (
-
- {filteredGroups.map(({ group, keys }) => {
- const isExpanded = expandedGroups.has(group);
- return (
-
-
- {isExpanded && (
-
- {keys.map(key => {
- const current = edits[key] !== undefined ? edits[key] : texts[key] || "";
- const isDirty = edits[key] !== undefined;
- const isSaved = savedKey === key;
- const isSavingThis = saving === key;
+ {/* Content Groups */}
+
+ {Object.entries(GROUPS).map(([groupTitle, keys]) => {
+ const mappedTab = GROUP_PAGE_MAP[groupTitle] || 'other';
+ if (activeTab !== 'all' && mappedTab !== activeTab) return null;
- return (
-
-
-
- {key}
-
-
-
-
+ const filteredKeys = keys.filter(key => {
+ if (!search) return true;
+ const currentVal = edits[key] ?? texts[key] ?? '';
+ return key.toLowerCase().includes(search.toLowerCase()) || currentVal.includes(search);
+ });
+
+ if (filteredKeys.length === 0) return null;
+
+ return (
+
+
+
+ {groupTitle}
+
+
+
+ {filteredKeys.map(key => {
+ const val = edits[key] ?? texts[key] ?? '';
+ const isSaved = savedKey === key;
+ const isSaving = saving === key;
+ const isUploading = uploadingKey === key;
+ const isImg = isImageKey(key);
+
+ return (
+
+
+
+ {isSaved && (
+
+ ذخیره شد
+
+ )}
+
+
+
+ {val.length > 60 && !isImg ? (
+
- );
- })}
-
- )}
+ )}
+
+
+
+
+ );
+ })}
- );
- })}
-
- )}
+
+ );
+ })}
+
);
}
diff --git a/frontend/admin-panel/src/pages/Videos.tsx b/frontend/admin-panel/src/pages/Videos.tsx
index 601ba79..49c7da9 100644
--- a/frontend/admin-panel/src/pages/Videos.tsx
+++ b/frontend/admin-panel/src/pages/Videos.tsx
@@ -1,5 +1,5 @@
-import React, { useState, useEffect } from 'react';
-import { Plus, Search, Edit2, Trash2, Video as VideoIcon, Star, CheckCircle, X } from 'lucide-react';
+import React, { useState, useEffect, useRef } from 'react';
+import { Plus, Search, Edit2, Trash2, Video as VideoIcon, Star, CheckCircle, X, Upload, Image as ImageIcon, Film, Code } from 'lucide-react';
import api from '../services/api';
interface Video {
@@ -21,6 +21,11 @@ export default function Videos() {
const [searchTerm, setSearchTerm] = useState('');
const [isModalOpen, setIsModalOpen] = useState(false);
const [editingVideo, setEditingVideo] = useState
+ {/* Cover Thumbnail with Upload */}
+ {/* Video URL or Embed Tag */}
diff --git a/frontend/application/app/catalog/CatalogClient.tsx b/frontend/application/app/catalog/CatalogClient.tsx
index a5bae8b..d353557 100644
--- a/frontend/application/app/catalog/CatalogClient.tsx
+++ b/frontend/application/app/catalog/CatalogClient.tsx
@@ -97,11 +97,11 @@ export default function CatalogClient() {
/>
-
+